Oracle RAC: как обеспечить целостность данных в кластере и избежать краха?

Обеспечит ли Oracle RAC целостность и согласованность данных?Все, о чем я рассказывал до сих пор, относилось к единственному экземпляру и единственной базе данных. Но Oracle позволяет нескольким экземплярам совместно использовать единственную базу данных, а это означает наличие нескольких независимых кэшей данных, нескольких независимых буферов журналов и нескольких независимых SGA, использующих один и тот же набор физических файлов и один и тот же словарь данных. Без непрекращающихся взаимодействий между экземплярами было бы невозможно гарантировать непротиворечивость их поведения, и мы могли бы сталкиваться с ситуациями, когда изменения, выполняемые одним экземпляром, утрачиваются в результате вмешательства другого экземпляра. Такая потребность постоянных взаимодействий является единственной новой для нас концепцией, на которой нужно сосредоточиться при обсуждении Oracle RACReal Application Cluster.

В этой статье мы рассмотрим следующие идеи:



Существует всего две функциональные особенности, которые нужно рассмотреть, чтобы ответить на все эти вопросы – глобальные блокировки и поддержка непротиворечивости кэша (cache coherency) – и эти две особенности будут главными темами в данной статье. Их исследование даст возможность проверить и закрепить наши знания о блокировках и защелках, сосредоточившись при этом на важнейших изменениях, которые могут вызывать проблемы в Oracle RAC.

Но, прежде чем углубиться в исследования, мы потратим некоторое время на общее знакомство с технологией RAC, с причинами, которые могут побудить вас использовать ее, и с проблемами, с которыми столкнулись разработчики Oracle при создании продукта. Попутно мы посмотрим, насколько хорошо укладываются в RAC механизмы восстановления, позволяющие одному экземпляру перехватывать управление, когда другой терпит аварию. Так как обсуждение RAC по большей части будет вестись на абстрактном уровне, я взял на вооружение широко используемый прием, заключающийся в выдаче конкретных рекомендаций, касающихся разработки приложений, которые учитывают особенности работы с Oracle RAC.


Примечание. Я выбрал для этой статьи не совсем обычный заголовок, чтобы подчеркнуть, насколько легко можно все разрушить, не понимая, хотя бы чуть-чуть, как действует RAC; но использование RAC не обязательно ведет к краху.


Общая картина

На мой взгляд понять, как действует RAC, совсем несложно, когда все настроено и работает. Установить и запустить этот продукт – вот главная сложность. В процессе установки придется настроить множество разных уровней. Неважно, сколько сил приложили разработчики Oracle, чтобы упростить работу со своими продуктами – некоторые из них в принципе являются сложнейшими агрегатами, в которых каждый отдельный компонент должен быть настроен безупречно, чтобы весь комплекс действовал слаженно. И каждый раз, когда вы делаете это (или каждый раз, когда это делаю я), появляются какие-то новые тонкости, о которых нужно позаботиться.

К счастью, я избавлен от тяжкого труда рассказывать, как установить Oracle RAC, благодаря множеству статей в блогах (таких как серия статей Тима Холла (Tim Hall) на сайте http://www.oracle-base.com), объемным инструкциям по установке на Oracle Technet (OTN) и целым книгам (таким как «Pro Oracle Database 11g RAC on Linux» Мартина Баха (Martin Bach) и Стива Шоу (Steve Shaw) (Apress, 2010)), детально описывающим эту процедуру. Я расскажу лишь о некоторых особенностях времени выполнения, чтобы показать, почему нужно проявлять осторожность при использовании этого продукта.

Итак, взгляните на рис. 1, где изображена общая схема RAC.

Схема RAC

Рис. 1. Схема RAC

Ниже приводятся некоторые ключевые замечания, касающиеся схемы на рис. 1:

Ниже перечислено, что не показано на схеме:


Виртуальные IP-адреса и SCAN


При настройке системы RAC, одной из ваших задач на уровне операционной системы будет присвоить каждой машине «виртуальный» IP-адрес («Virtual» IP address, VIP). Сразу после запуска, служба организации кластера вставляет свой код уровнем выше фактического IP-адреса и машины переходят на взаимодействия с использованием адресов VIP.

Самое большое отличие между действительным и виртуальным IP- адресами состоит в том, что действительный IP-адрес логически связан с конкретным аппаратным элементом (технически – с MAC-адресом сетевой карты), тогда как VIP-адрес управляется программно и его логическая связь с конкретной аппаратурой может динамически меняться.

Если машина с базой данных прекратит работу, когда клиентская машина подключена к ней через действительный IP, клиентская программа будет очень долго ждать (от десятков до сотен секунд) ответа от сервера, пока поймет, что сервер недоступен по каким-то причинам. Так как Oracle использует виртуальные IP-адреса, в случае выхода машины из строя один из других экземпляров очень быстро обнаружит этот факт и присвоит виртуальный IP прекратившей работу машины себе, чтобы обработать текущие запросы. Система будет автоматически перенастроена так, что она прекратит принимать новые запросы по проблемному VIP (пока машина и экземпляр не будут запущены вновь). Это означает, что клиентам не придется долго ждать в случае появления каких-то проблем.

Как еще одно средство уменьшить длительность ожиданий, вызываемых проблемами в сети, в Oracle 11.2 был реализован новый механизм SCAN (Single Client Access Name – единый адрес для доступа со стороны клиентов), как часть инфраструктуры Grid Infrastructure. Прежде чем вы сможете использовать механизм SCAN, вам потребуется попросить своего системного администратора настроить несколько новых записей DNS (Domain Name Service) или помочь вам настроить подраздел DNS для использования Oracle GNS (Grid Naming Service). После этого любая клиентская программа сможет ссылаться на систему по единому адресу SCAN и вам не придется перенастраивать клиентов, если вдруг потребуется перенести систему на другую машину или добавить (удалить) узлы.


 

Бесперебойная работа

Взглянув на рис. 1, кто-то из вас может задаться вопросом: что случится, если работа сети будет нарушена и машины не смогут взаимодействовать друг с другом? В этом случае система может «развалиться» на два множества экземпляров, уверенных, что они имеют монопольный доступ к базе данных и, как результат, постоянно затирающих изменения друг друга.

Есть еще два уровня, подверженных этой угрозе: экземпляр Oracle может потерять контакт с остальными экземплярами, или машина может потерять контакт с остальными машинами. Мы рассмотрим эти проблемы, двигаясь снизу вверх, и начнем с уровня машины.

 

Защита на уровне машины

Служба поддержки кластера на каждой машине постоянно поддерживает контакт с другими машинами в сети, но она также поддерживает контакт с дисками кворума (voting disc), изображенными на рис. 1, что дает дополнительные возможности в преодолении проблем, связанных с нарушениями в работе сети.

Все машины каждую секунду выполняют запись на диск «кворума» – в файл на диске, каждый блок которого соответствует конкретной машине, и каждая машина просто наращивает счетчик в блоке. В простейшем случае машина может обнаружить, что не в состоянии выполнить запись в файл – тогда она сама удалит себя из кластера и может отправиться на перезагрузку. (Разумеется, в этот момент соответствующий экземпляр внезапно завершит работу и один из других экземпляров выполнит процедуру восстановления за него.)

Записывая новое значение счетчика в свой блок, каждая машина проверяет также блоки, принадлежащие другим машинам в кластере, чтобы выявить ситуации, когда другим машинам не удалось обновить счетчики в недавнем прошлом. (Возможно, что какая-то машина находится в неустойчивом состоянии из-за некорректной работы ПО поддержки кластера, но при этом действует и на ней выполняется экземпляр – подобные ситуации угрожают целостности данных в базе.) В этом случае машина, обнаружившая неполадки, может завершить работу подозрительной машины.

Может так случиться, что все машины способны выполнять запись в файл, но испытывают проблемы из-за нарушений работы сети, мешающих нормальному взаимодействию. В этой ситуации одна из машин попытается взять диск кворума под наблюдение и подсчитать, со сколькими машинами она в состоянии взаимодействовать. Если она обнаружит, что принадлежит к сетевой группе, содержащей более половины машин, или, если число машин четное, содержащей половину машин и включающей машину с наименьшим идентификационным номером в кластере – она принудительно исключит остальные машины из кластера и отправит их на перезагрузку. В отсутствие необходимого кворума (именно поэтому диск кворума получил такое название), она исключит из кластера саму себя.

В действительности алгоритм работы намного сложнее, чем я описал, но и такого описания достаточно, чтобы понять, насколько интеллектуальным (и пессимистичным) должно быть ПО поддержки кластера, чтобы не подвергнуть опасности целостность базы данных при управлении другими машинами в кластере, попавшими под подозрение.

 

Защита на уровне Oracle

Аналогичная стратегия применяется на уровне Oracle. Запускаясь, каждый экземпляр сообщает о своем присутствии другим экземплярам, поэтому каждый экземпляр всегда знает, сколько других экземпляров должно существовать. Точно так же, завершаясь (корректно), экземпляр говорит всем «до свидания». В короткий период, во время запуска и завершения, последнее состояние экземпляра распространяется между членами группы и выполняется «перераспределение» нагрузки с учетом изменившегося числа экземпляров. С момента запуска экземпляры непрерывно взаимодействуют друг с другом по сети (в частности, обмениваются сообщениями типа «я живой» с помощью процесса LMON).

Поддерживается также проверка работоспособности экземпляров на основе файлов. Как было показано в ранее в блогах нашего портала, процесс выполнения контрольных точек (CKPT) обновляет управляющий файл примерно каждые три секунды. В конфигурации RAC каждый экземпляр имеет собственный процесс CKPT, поэтому управляющий файл может действовать как признак работоспособности экземпляра Oracle. Если вывести дамп файла с уровнем 3 (см. приложение), можно увидеть в нем раздел для записей обработки контрольных точек (Checkpoint Progress Records) – этот критически важный раздел управляющего файла содержит по одной записи для каждого экземпляра, которая должна постоянно обновляться соответствующим экземпляром.

Поэтому, если экземпляр обнаруживает, что больше не в состоянии осуществлять запись в управляющий файл, он может завершить себя, чтобы обезопасить систему. Также может сложиться ситуация, когда какой-то другой экземпляр заметит, что данный экземпляр «завис» (и, как будет показано ниже, если один экземпляр завис, тогда любой другой экземпляр, заметивший это, должен прервать его ожидание). То есть, можно надеяться, что в Oracle имеется механизм, позволяющий одному экземпляру (если он в кворуме, конечно) завершить другой экземпляр. К сожалению, я не уверен, что подобное возможно в версиях до 11.2, где появился новый процесс мониторинга LMHB (Lock Manager Heart Beat Monitor), очень быстро определяющий, что какой-то процесс приостановился, ожидая пока будет обслужен его запрос на приобретение блокировки.

Итак, у нас есть возможность собрать воедино имеющееся оборудование и создать нечто, что будет работать как единая, большая система; и у нас есть возможность защитить всю систему от проблем, возникающих в отдельных ее частях. Но это сложно и почему у нас должно появиться желание возиться с чем-то сложным?

 

Для чего это надо?

Всякий раз, знакомясь с новой технологией или с какой-то ее функцией, мы должны задавать себе вопрос: зачем нам это надо – какие преимущества или выгоды мы получим? Если у вас не получится объяснить, что вы надеетесь получить, тогда вы не сможете решить, как лучше использовать технологию и как измерить степень успеха.

В пользу продвижения RAC есть два основных аргумента – масштабируемость и отказоустойчивость, причем аргумент масштабируемости делится на два более мелких подаргумента: высокая пропускная способность и короткое время отклика. То есть, прежде чем начать прикручивать RAC, нужно решить, какой из двух аргументов наиболее важен и как лучше реализовать RAC в своих условиях, чтобы сказку сделать былью.

 

Отказоустойчивость

Согласно теории отказоустойчивости систем, если на одном из узлов RAC произойдет сбой, один из оставшихся узлов должен мгновенно выполнить восстановление транзакций, подтвержденных на узле, где случился сбой, и произвести перераспределение нагрузки между узлами. Восстановление должно выполняться прозрачно для приложений (однако, такая прозрачность невозможна в отношении незавершенных транзакций без переделки приложений). Обычно этот аргумент влечет за собой четыре вопроса:

К числу альтернативных технологий можно отнести горячее резервирование на уровне операционной системы, которое может предусматриваться ее поставщиком, или создание резервной базы данных, что может устроить тех, кто желает ограничиться исключительно технологиями Oracle – оба варианта поддерживают возможность включения в работу резервной машины в роли сервера баз данных. Недостатки этих альтернатив заключается в более длинном периоде времени, необходимом для перехода с основной машины на резервную; резервная машина будет в основном простаивать и может даже ухудшать время отклика системы в целом. Настроить резервный сервер можно очень быстро, только нужно предусмотреть, чтобы приложения могли быстро находить его.

Теперь о слабых звеньях – отказы каких компонентов вы собираетесь учитывать? Мы легко можем поставить две сетевых карты в машину и продублировать сетевые коммутаторы, но продублировать хранилище или кабель сети намного сложнее – и какая стратегия реагирования на пропадание электропитания используется в вашем вычислительном центре? Можно также принять во внимание сложность всего стека RAC и задаться вопросом: сколько времени вы готовы выделить на обслуживание и обновление ПО. В Oracle Corp. постоянно выпускают обновления и кто-то, когда-то должен эти обновления накладывать.

 

Масштабируемость

Существует два преобладающих представления о масштабируемости:

О первом варианте лучше размышлять в терминах отдельных больших заданий, а о втором – в терминах большого числа маленьких заданий. Если, к примеру, пакетная обработка информации занимает 40 минут, ее распределение по двум экземплярам может уменьшить время обработки до 20 минут, распределение по четырем узлам – до 10 минут. Такое представление несет на себе отпечаток представлений о параллельном выполнении, и такая ассоциация вполне оправданна. Если вы надеетесь уменьшить время обработки, не переписывая алгоритм, вы, вероятно, сможете получить выгоды от добавления узлов и за счет параллельной обработки. Часто параллельная обработка позволяет решить проблему производительности, но она влечет увеличение накладных расходов на обмен служебными сообщениями между параллельно действующими подчиненными процессами, и эти накладные расходы становятся особенно заметными, когда подчиненные процессы выполняются в разных экземплярах. Если требуется ускорить выполнение больших заданий (не изменяя код реализации), возможно стоит подумать об увеличении объема памяти, числа процессоров или более быстрых процессоров, прежде чем увеличивать сложность организации системы.

Если ваша цель состоит в том, чтобы выполнить больше заданий за единицу времени, например, ваше предприятие расширяется и требуется дать возможность работать с системой большему числу сотрудников, тогда добавление новых экземпляров позволит увеличившемуся числу сотрудников работать одновременно. Если, скажем, в вашей организации 50 сотрудников успешно работают с одним экземпляром, тогда, возможно, вы сможете организовать плодотворную работу 100 сотрудникам на двух экземплярах, а 200 сотрудникам – на четырех. Вам достаточно будет просто добавлять новые экземпляры с увеличением штата.

В пользу этой стратегии говорит тот факт, что каждый экземпляр имеет свой процесс записи в журнал (lgwr) и свой набор файлов журнала (redo log files) – как известно, скорость создания и обработки записей повторения является наиболее узким местом в Oracle. К недостаткам можно отнести увеличение числа процессов (разбросанных по разным экземплярам), выполняющих похожие задания, из-за чего могут возникать чрезмерно активные области в данных. В RAC появление таких областей означает увеличение трафика между экземплярами, а это влечет специфические проблемы производительности.

И снова вы можете спросить: почему бы с ростом числа сотрудников просто не увеличить мощность единственной машины. Дело в том, что «увеличить мощность» машины не так-то просто, особенно в сравнении с покупкой другой машины и включением ее в сеть. На самом деле, одно из рекламируемых преимуществ RAC заключается в простоте планирования наращивания возможностей – вам не нужно сразу покупать большую машину и использовать ее не на полную мощность пару лет. Вы можете начать с недорогой машины и постепенно наращивать кластер по мере необходимости.


Примечание. Один из неприятных побочных эффектов стратегии «мощного начала» , который я наблюдал пару раз, состоит в том, что наличие большой и мощной машины на начальном этапе, когда она нагружается не полностью, эффективно скрывает проблемы с производительностью в течение довольно длительного времени, а потом исправление проблем превращается в катастрофу.


 

Oracle Grid

Существует еще один аспект, говорящий в пользу создания кластера вместо использования отдельных мощных машин, и этот аспект – инфраструктура Grid и некоторые ее улучшения в Oracle 11.2. Если у вас имеется кластер с большим числом маленьких машин (на которых пока нет действующих экземпляров Oracle), вы сможете разместить множество баз данных на своей дисковой ферме, определить число экземпляров на каждой машине для каждого приложения и распределять рабочую нагрузку динамически. На рис. 2 показана схема организации подобного кластера с двумя конфигурациями.

Одна – для использования в обычные рабочие дни, когда на каждой машине действует отдельный экземпляр базы данных (исключая экземпляры управления хранилищем, ASM). И другая, с двумя машинами GL, начинающими действовать как WEB-экземпляры в выходные дни, когда нагрузка на систему учета, скорее всего, снизится, а на веб-приложение увеличится.

Динамическое перераспределение нагрузки в кластере Oracle RAC 

Рис. 2. Динамическое перераспределение нагрузки

Если внимательно рассмотреть эту схему, можно заметить, что я предпочитаю следовать стратегии (предложенной Джеймсом Морли (James Morle)) хранения множества баз данных в единой сети хранения данных.

Этот сценарий можно также расширить возможностью запуска дополнительного экземпляра GL на машине HR в конце месяца; а для большей отказоустойчивости можно даже запустить два экземпляра HR (второй – на одной из машин GL) в специальном режиме активный/пассивный (см. примечание) на случай, если один экземпляр HR выйдет из строя по каким-то причинам.


Примечание. Я упоминал, что взаимодействия между экземплярами являются важнейшим аспектом работы RAC. Существует реализация особого случая для RAC с двумя узлами, избавляющая от лишних накладных расходов, когда один экземпляр работает, а другой ничего не делает, ожидая пока первый выйдет из строя. Этот вариант известен как режим активный/пассивный и может быть интересен тем, кому требуется держать две или более базы данных на небольшом кластере с высокой скоростью восстановления после отказов.


Мне приходилось видеть пару систем, где использовались преимущества такой организации кластеров, но мне кажется вам будет сложно найти хотя бы один пример действующей системы, где действительно требовалось бы манипулировать целыми экземплярами при изменении рабочей нагрузки. Тем не менее, разработчики Oracle стараются упростить именно такой способ перераспределения, и даже автоматизировать его. (Я должен сказать, что один из моих клиентов использовал как раз такую конфигурацию и был очень счастлив достигнутым, но у него был кластер из восьми машин, плюс резервный кластер из восьми машин и ему пришлось потратить несколько недель на посещение консультаций, чтобы настроить рабочую конфигурацию.)

 

Как это работает?

При рассмотрении схемы кластера из восьми машин, где выполняются экземпляры Oracle, работающие с одной и той же базой данных, невольно возникает вопрос: как один экземпляр, желающий изменить блок, может быть уверенным, что этот же блок не будет одновременно изменен другими семью экземплярами, и данная проблема не будет разрастаться с увеличением числа экземпляров. Такую уверенность дает гений реализации – в кластерах, с числом экземпляров три или больше, уровень накладных расходов перестает увеличиваться. Чтобы убедиться в этом, познакомимся сначала с глобальным каталогом ресурсов (Global Resource Directory, GRD) – распределенным механизмом управления доступом к «вещам» и технологией общего кэша в кластере (Cache Fusion), позволяющей своевременно перемещать по кластеру наиболее важные «вещи» (блоки данных).

Примечание. Я часто говорю своим клиентам, что если они хотят исследовать RAC в направлении возможности постепенного расширения, они должны начинать минимум с четырех экземпляров. Структуру с двумя экземплярами мы стали рассматривать только потому, что такая конфигурация представляет собой весьма специфический случай. Конфигурация с тремя экземплярами – это уже первый, более или менее, обобщенный случай. Соответственно, исследования лучше всего начинать с конфигурации, насчитывающей не меньше четырех экземпляров, потому что если вы захотите исследовать обобщенный случай обеспечения отказоустойчивости, вы должны иметь базу в три экземпляра после отказа.

 

GRD

В RAC предусмотрено два основных механизма для решения проблем координации работы нескольких экземпляров.


Примечание. Начиная обсуждение таких механизмов, как GCS и Cache Fusion, стоит упомянуть, что экземпляры не имеют прямого доступа к SGA друг друга – они обмениваются сообщениями, запрашивая информацию посредством LMSn и LMD.


Вот в этой мы исследовали создание в Oracle ресурсов для представления объектов. В RAC требуется создавать еще больше ресурсов для представления еще большего числа объектов – и у нас должен быть механизм блокировки (постановки в очередь) этих ресурсов и обеспечения доступа к ним всем экземплярам. Это ведет нас к двум представлениям – v$dlm_ress (распределенный диспетчер блокировки ресурсов) и v$ges_enqueue (блокировки, известные глобальной службе очередей, ранее носило имя v$dlm_locks) – и стратегии GRD (Global Resource Directory – глобальный каталог ресурсов).

Существует две основные проблемы, которые Oracle решает с применением GRD. Первая: хранение информации об очень большом числе объектов – каждом элементе в кэше словаря, каждом блоке данных в памяти и т. д. – поэтому данная проблема намного масштабнее и имеет значительно больше аспектов, чем проблема, обсуждавшаяся нами в связи с блокировкой таблиц, файлов и других объектов. Вторая: наличие множества экземпляров – не может не вызывать беспокойства необходимость постоянного копирования информации о блокировках в каждый узел, что влечет за собой рост объема передаваемой информации в геометрической прогрессии с увеличением числа экземпляров в кластере. К счастью, вторая проблема просто не существует – нет необходимости клонировать тот же самый каталог GRD во все экземпляры, потому что они могут совместно использовать единственную копию, хотя способ совместного использования выглядит весьма нетривиальным, как мы вскоре убедимся в этом.


Примечание. В ранних версиях RAC (и в предшествующей технологии Oracle Parallel Server, OPS) использовалась единственная блокировка, охватывающая либо диапазон, либо группу блоков, соответствующих некоторому «шаблону», а механизм «избирательного блокирования» применялся в особых случаях. В новейших версиях избирательное блокирование (одна блокировка = один блок) используется по умолчанию, возможность блокирования группы блоков единственной блокировкой превратилась в особый случай. Принципиальным исключением являются табличные пространства, доступные только для чтения, которые требуют единственной блокировки для каждого файла.


Начнем со структур в памяти. У нас имеется представление v$dlm_ress, являющееся аналогом v$resources, – это список всех объектов, доступных для блокирования. И представление v$ges_enqueue, являющееся аналогом v$lock – это список всех заблокированных объектов.

Помимо обзора самих представлений, масштаб этих структур можно оценить еще двумя способами – в v$sgastat и в v$resource_limit.

Запросив эти два представления, можно увидеть, какой объем памяти в SGA занимают глобальные ресурсы и блокировки, а также их число. Ниже приводятся некоторые ключевые результаты (вывод был отредактирован, чтобы уменьшить его объем): 

select
        resource_name, current_utilization, max_utilization
from
        v$resource_limit
where
        resource_name like ‘%g_s%’
order by
        resource_name
;
RESOURCE_NAME   CURRENT_UTILIZATION MAX_UTILIZATION
--------------- ------------------- ---------------
gcs_resources                  6615            6615
gcs_shadows                    9716            9716
ges_cache_ress                 1218            1364
ges_locks                      4911            5071
ges_ress                       7772            7872
select
        *
from
        v$sgastat
where
        name like ‘%gcs%’
or      name like ‘%ges%’
order by
        name
;
POOL         NAME                            BYTES
------------ -------------------------- ----------
shared pool  gcs resources                 4927120
shared pool  gcs shadows                   3630512
shared pool  ges resource                  2602392
shared pool  ges enqueues                  5171824

В представлении v$sgastat намного больше строк, чем я показал, но я оставил только те результаты, которые могут послужить отправной точкой в экскурс по GRD. Обратите внимание, что представление v$sgastat отражает только очереди GES, а v$resource_limit – блокировки GES. В них отсутствует информация об очередях и или блокировках GCS. Механизм очередей/блокировок, реализованный в службе GES, применяет блокировки к кэшированным и к традиционным ресурсам.

Также большой интерес представляет информация о теневых ресурсах GCS (GCS shadows). Oracle отделяет обслуживание обычных очередей от обслуживания кэша, так как при работе с кэшем частота операций и размеры пакетов, которые должны передаваться через соединения, намного больше, соответственно для глобального кэша ресурсов была создана отдельная структура. Но существует еще одна причина создания отдельной структуры. Когда экземпляр завершается в результате аварии, можно особо не беспокоиться о том, что какие-либо блокировки не будут освобождены. Большее беспокойство должны вызывать блоки данных, оставшиеся в его кэше, и их состояние – чтобы отследить местоположение и состояние блоков, служба GCS реализует очень сложный алгоритм, требующий дополнительной инфраструктуры. Поэтому в Oracle имеется отдельная структура для поддержки глобального кэша ресурсов, даже при том, что некоторые объекты v$ скрывают это разделение.

Но кроме отдельного списка ресурсов существует еще кое-что – представление отображает значения gcs RESOURCES и gcs SHADOWS. И это именно те значения, которые подсказывают нам, как Oracle обслуживает глобальный каталог ресурсов. Oracle разделяет ответственность за управление ресурсами между всеми экземплярами в кластере. Но каждый экземпляр, использующий ресурс в настоящий момент, хранит теневой ресурс, содержащий критически важное подмножество информации из ведущего (master) ресурса – и в случае с кэшем ресурсов мы имеем явное представление занимаемой им памяти.

 

Ведущие и теневые ресурсы

Прежде чем переходить к обсуждению тонкостей управления глобальным кэшем, я хочу сделать несколько замечаний о том, как Oracle поддерживает владение ресурсами. По сути этот механизм основан на случайном распределении с применением функции хэширования. Если экземпляр хочет получить информацию о глобальном состоянии ресурса, он вычисляет хэш-значение идентичности ресурса и затем определяет, какой экземпляр является владельцем этого ресурса. Так, например, если экземпляру потребуется применить блокировку к таблице XYZ, он должен произвести некоторые арифметические вычисления с идентификатором (id) таблицы и узнать, какой узел владеет ресурсом TM для этой таблицы. Затем он должен обратиться к этому экземпляру и узнать, не заблокировал ли эту таблицу какой-то другой экземпляр в несовместимом режиме.


Переподчинение


Я уже рассказал, как Oracle находит ведущий ресурс объекта, выполняя арифметические операции. В разных версиях используются разные алгоритмы, а кроме того, для блоков данных имеются специальные случаи.

Если Oracle обнаруживает, что объект данных используется в некотором экземпляре намного чаще, чем в других, он может игнорировать обычный способ определения владельца ресурса на основе арифметических вычислений и сделать экземпляр владельцем ресурса для всех блоков этого объекта. (Реализация этого процесса претерпела три стадии: (а) вообще отсутствовала, (б) поддерживала переподчинение на уровне файла, (в) поддерживала переподчинение на уровне объекта (сегмента данных)).

Так как число объектов данных в базе данных относительно невелико, в сравнении с числом блоков, каждый экземпляр может хранить карту с информацией обо всех объектах данных и соответствующих им владельцах, и всегда иметь возможность быстро найти владельца объекта данных, прежде чем выполнять обычные вычисления для отдельного блока. Основное влияние на алгоритм заключается в возможности динамического переподчинения объектов – при этом на короткое время может блокироваться каталог GRD (что означает блокировку всех экземпляров), чтобы ведущие ресурсы для объекта могли быть перемещены в нужный экземпляр. Это может занять несколько секунд – и если вам не повезет, вы обнаружите, что природа обработки результатов в нескольких объектах будет приводить к
постоянному их переподчинению. Последние версии Statspack и AWR предусматривают возможность получения статистики с числом событий переподчинения и списка переподчиняемых объектов, которые могут помочь в поисках потенциальных проблем.


В моей небольшой системе RAC имеется объект с идентификатором 6334 (я выбрал этот пример наугад), соответствующий таблице WRH$_LATCH. Число 6334 в шестнадцатеричном формате имеет вид: 0x18be. По этому значению я могу узнать, что творится с ресурсом TM этой таблицы, обратившись к представлениям gv$dlm_ress и gv$ges_enqueue. Обратите внимание, что я использовал глобальные (gv$) версии представлений: 

select
*
from gv$dlm_ress
where resource_name like ‘%0x18be%TM%’
;
select
*
from gv$ges_enqueue
where resource_name1 like ‘%0x18be%TM%’
;

В данном случае результаты показывают, что на всех трех узлах был ресурс (v$dlm_ress) с соответствующим именем; но только узел 1 содержит очереди (gv$ges_enqueue). Иными словами, узел 1 является владельцем – он знает все, что происходит с ресурсом TM – но два других узла тоже имеют некоторую информацию о состоянии ресурса, потому что в настоящий момент они являются заинтересованными сторонами. Ниже приводятся значения нескольких столбцов (расположенных по вертикали с помощью замечательной процедуры print_table Тома Кайта (Tom Kyte)) из представления gv$ges_enqueue

INST_ID        : 1
HANDLE         : 000000008D2537C0
GRANT_LEVEL    : KJUSERCW
REQUEST_LEVEL  : KJUSERCW
RESOURCE_NAME1 : [0x18be][0x0],[TM][ext 0x0,0x0
RESOURCE_NAME2 : 6334,0,TM
STATE : GRANTED
OWNER_NODE     : 2
-----------------
INST_ID        : 1
HANDLE         : 000000008D256E60
GRANT_LEVEL    : KJUSERCW
REQUEST_LEVEL  : KJUSERCW
RESOURCE_NAME1 : [0x18be][0x0],[TM][ext 0x0,0x0
RESOURCE_NAME2 : 6334,0,TM
STATE          : GRANTED
OWNER_NODE     : 1
-----------------

 


Примечание. Динамические представления из Oracle с единственным экземпляром часто упоминаются как «представления v$», потому что их имена начинаются с префикса v$. Эти представления часто определяются внутри, как подмножества глобальных представлений, имена которых начинаются с префикса gv$, и ограничиваются единственным экземпляром. Если в действующей системе RAC выполнить запрос к одному из представлений gv$, экземпляр, которому передан запрос для выполнения, вызовет подчиненные процессы параллельного запроса в других экземплярах, обычно с именами, таким как PZ99 (вместо более привычного Pnnn). И даже если попытаться запретить параллельные запросы установкой параметра parallel_max_servers в нулевое значение, подчиненные процессы все равно останутся доступными в RAC.


Результаты показывают, что эти блокировки обслуживаются экземпляром с номером 1 – то есть, он является владельцем ресурса TM – а два других экземпляра (owner_node) в настоящий момент проявляют интерес. Из представленных выше результатов неочевидно, но нумерация inst_id начинается с единицы, а owner_node
с нуля! В данном конкретном примере узел-владелец не имеет блокировки для ресурса, даже при том, что он является владельцем ресурса (о чем можно судить по номерам).

Обратите также внимание, что блокировки имеют параметры grant_level и request_level – в данном случае оба параметра содержат значение KJUSERCW (concurrent write (одновременная запись) – эквивалент блокировки в режиме монопольного владения строкой (row-exclusive lock), которые часто можно наблюдать в v$lock при изменении записей в таблице). Хотя ни одна из структур поддержки не видна ни в представлении, ни даже в базовой структуре x$, глобальные очереди (блокировки) поддерживают те же самые шаблоны хэш-таблиц, указателей и связанных списков, что и очереди в системах с единственным экземпляром.

Возможно, что ресурсы разных типов ведут себя немного по-разному (а ресурсы для кэша буферов – это вообще отдельный случай), и возможно, что разные уровни блокировок приводят к разным результатам, но как бы то ни было, вы увидите теневые ресурсы с присоединенными блокировками. В этом случае блокировки присоединяются к теневым ресурсам только в теневых узлах. Ниже, например, приводится список глобальных блокировок для конкретного ресурса (ресурс QQ – это один из типов ресурсов кэша словаря, имеющий отношение к гистограммам): 

select
       inst_id, owner_node, grant_level, resource_name1
from
       gv$ges_enqueue
where
       resource_name1 like ‘%[0xf13e8525][0xa660035f],[QQ]%’
order by
       inst_id, owner_node
;
INST_ID    OWNER_NODE GRANT_LEV RESOURCE_NAME1
---------- ---------- --------- ------------------------------
         1          0 KJUSERPR [0xf13e8525][0xa660035f],[QQ][
         2          0 KJUSERPR [0xf13e8525][0xa660035f],[QQ][
                    1 KJUSERPR [0xf13e8525][0xa660035f],[QQ][
                    2 KJUSERPR [0xf13e8525][0xa660035f],[QQ][
         3          2 KJUSERPR [0xf13e8525][0xa660035f],[QQ][

Обратите внимание, на уровень привилегий (GRANT_LEV) KJUSERPR – режим защищенного чтения (protected read), эквивалентный режиму 4 (разделяемый режим) в v$lock. Этот более высокий уровень может объяснить, почему блокировки присоединяются и к ведущим, и к теневым ресурсам. По значениям inst_id и owner_node можно заключить, что владельцем этого ресурса является узел с inst_id = 2 (узел 1!), а узлы со значениями 1 и 3 в inst_id являются теневыми. На рис. 3 приводится графическое представление этой ситуации. Здесь я хотел показать данные о ресурсе BL (блоке) без использования причудливых числовых обозначений ссылок на разные экземпляры, заменив их символами A, B и C.

Рис. 3. Графическое представление ведущих и теневых ресурсов в кластере с тремя узлами

Как будет показано чуть ниже, теневые ресурсы с их теневыми блокировками приобретают особую важность для ресурсов типа BL – ресурсов кэша буферов (или, если говорить точнее, блоков – BL).

 

GCS и GES

Еще одно важное замечание – Oracle обслуживает огромное число ресурсов. Ниже приводятся результаты выполнения пары запросов, которые я выполнил на одном из узлов в кластере с тремя узлами, спустя всего несколько минут после запуска экземпляров. Функции instr() и substr() я использовал, только чтобы получить типы ресурсов (двухсимвольные коды из v$locktype), а кроме того я сократил вывод, оставив только ресурсы с наибольшими значениями счетчика:

select -- resources
    substr(resource_name, instr(resource_name,’,’)+2,2) , count(*)
from
       v$dlm_ress
group by
       substr(resource_name, instr(resource_name,’,’)+2,2)
order by
       count(*)
;
SUBSTR(R   COUNT(*)
-------- ----------
...
TM              558
QC             1061 -- кэш словаря, сегменты
BL             2802
QQ             3899 -- кэш словаря, гистограммы
QI             3959 -- кэш словаря, объекты
select -- enqueues
     substr(resource_name2,-2) , grant_level, request_level, count(*)
from
       v$ges_enqueue
group by
       substr(resource_name2,-2) , grant_level, request_level
order by
       count(*)
;
SUBSTR(R GRANT_LEV REQUEST_L   COUNT(*)
-------- --------- --------- ----------
...
QC       KJUSERPR  KJUSERPR        1060
BL       KJUSEREX  KJUSERNL        2552
BL       KJUSERPR  KJUSERNL        3891
QI       KJUSERPR  KJUSERPR        4888
QQ       KJUSERPR  KJUSERPR        6140

Как видите, ресурсы Qx – представляющие кэш словаря – весьма многочисленны, и их число увеличивается еще больше при наличии таблиц секционированных таблиц (partitioned tables) с большим числом секций и большого числа гистограмм. Еще одной многочисленной разновидностью ресурсов являются ресурсы типа BL type – блоки. В данном примере число ресурсов BL значительно меньше числа ресурсов Qx, но в процессе работы базы данных их число будет расти.

Фактически каждый ресурс BL связан с единственным блоком. Исключение составляют глобальные временные таблицы и табличные пространства, доступные только для чтения. И пока конкретный блок находится в кэше буферов одного из экземпляров, с ним будет связан ресурс BL. Теоретически, общее число ведущих ресурсов BL во всей системе должно было бы совпадать с общим числом буферов – но тогда каждый буфер в системе мог бы хранить только один блок.

На практике базы данных обычно имеют некоторые «популярные» области с несколькими копиями каждого блока в каждом экземпляре – то есть, число ведущих ресурсов BL часто оказывается значительно меньше общего числа буферов. С другой стороны, значительная доля буферов данных в экземпляре хранит блоки, которые должны быть защищены, а экземпляр, не владеющий ведущим ресурсом блока, будет хранить его теневой ресурс, поэтому общее число ресурсов BL (ведущих и теневых) будет примерно равно общему числу буферов. К счастью, для теневого ресурса выделяется значительно меньше памяти, чем для ведущего.


Примечание. Следя за обсуждением кэша буферов, старайтесь разделять блоки и буферы. Буферы используются для временного хранения копий (текущих, устаревших или согласованных) блоков, и операции обычно выполняются со связанными списками буферов. Однако, когда выполняется поиск данных, используются адреса блоков на диске, которые никак не связаны с адресами буферов в памяти. Ресурсы типа BL используются в RAC для защиты блоков – значения id1/id2, которые поставляются вместе с ресурсом BL, определяют адрес блока – но не для защиты буферов.


Несмотря на то, что число ресурсов Qx может быть очень большим и даже сопоставимо с числом ресурсов BL – между этими двумя типами ресурсов существуют два важных отличия. Во-первых, кэш словаря (Qx) остается достаточно стабильным; спустя несколько минут после запуска в кэш словаря будет загружена почти вся необходимая информация и будет изменяться, например, только при попытках получить новые статистики для объектов. Такое постоянство означает, что размер кэша никак не угрожает производительности. Исключение составляют только гистограммы, чрезвычайно большое число существенно сказывается на размере кэша словаря и может вызывать проблемы.

Содержимое кэша буферов, напротив, может очень быстро меняться с течением времени. Блоки могут читаться с диска и выталкиваться на диск, вследствие чего список ресурсов, соответствующих блокам в кэше, постоянно изменяется. Блоки могут изменяться разными экземплярами, из-за чего режимы блокировок будут постоянно меняться, чтобы гарантировать монопольный доступ к блоку только для одного экземпляра.

И еще одно, по-настоящему важное отличие – ресурсы BL связаны с объектами (блоками), которые фактически передаются между экземплярами. Большинство ресурсов всего лишь играют роль защитного механизма, но если какому-то экземпляру потребуется установить блокировку BL, он должен получить сам блок. Именно для этого был создан механизм GCS с процессами LMS – служба GES выдает право на доступ к блоку, а GCS – позволяет получить фактический блок.

 

Cache Fusion

Вообще говоря, большая часть сообщений, курсирующих в кластере RAC, связана с блоками – у нас имеется служба GES, выдающая право на доступ к блокам, и служба GCS, перемещающая копии блоков между экземплярами. В этом разделе мы посмотрим, как происходит перемещение блоков (или, как действует общий кэш кластера), и какие побочные эффекты при этом возникают.

Каждый раз, когда экземпляр в RAC читает блок с диска в память, он запускает процедуру создания ресурса, который будет представлять состояние блока. Фактически, перед тем, как экземпляр прочитает блок с диска, он определит, где должен находиться соответствующий ресурс, и пошлет запрос экземпляру-владельцу, чтобы узнать – не находится ли нужный блок в кэше, потому что в этом случае он сможет запросить у текущего владельца переслать ему требуемый блок. Экземпляр-читатель должен выполнить одну из двух последовательностей шагов. Первая последовательность:

  1. Вычислить местоположение ведущего ресурса по адресу блока.
  2. Послать сообщение экземпляру-владельцу, чтобы создать блокировку на ресурсе.
  3. Если искомый ресурс не существует, экземпляр-владелец создаст его, присоединит к нему блокировку и сообщит экземпляру-читателю, что тот может двигаться дальше.
  4. Создать теневой ресурс и блокировку, и прочитать блок с диска в память.

Вторая последовательность (см. рис. 4):

  1. Вычислить местоположение ведущего ресурса по адресу блока.
  2. Послать сообщение экземпляру-владельцу, чтобы создать блокировку на ресурсе.
  3. Экземпляр-владелец обнаруживает, что ресурс существует и, соответственно находится где-то в памяти. По текущей присоединенной блокировке определяется, какой экземпляр выбрать для получения копии блока. Экземпляр-владелец добавляет блокировку в ресурс, изменяет остальные блокировки (для чего может потребоваться послать сообщения другим экземплярам) и дает команду выбранному экземпляру передать блок экземпляру-читателю.

Когда экземпляр-читатель получает блок, он отправляет сообщение экземпляру-владельцу, извещая, что блок получен, и создает теневой ресурс с блокировкой.

Трехсторонний способ передачи блока в кластере с четырьмя узлами

Рис. 4. Трехсторонний способ передачи блока в кластере с четырьмя узлами

На рис. 4, где изображен кластер RAC с четырьмя узлами, можно увидеть наиболее важные особенности реализации RAC – совершенно неважно, сколько узлов имеется в кластере, в худшем случае в процедуру перемещения блока будет вовлечено не более трех узлов:

Именно поэтому можно увидеть факты ожидания таких событий, как gc current block 2-way и gc current block 3-way, и это же объясняет, почему кластер RAC с двумя узлами является особым случаем. Кластер с двумя узлами является единственной версией RAC, где отсутствует трехсторонний обмен сообщениями и этот факт используется для оптимизации некоторых операций.

Несмотря на то, что последовательность работы единого кэша (Cache Fusion) никогда не превышает трех шагов, влияние на производительность увеличивается с увеличением числа узлов, потому что увеличивается вероятность выполнения трехшаговой последовательности.

Рассмотрим следующую ситуацию: В системе RAC с тремя узлами вероятность, что данный узел будет владельцем требуемого ему ресурса, равна одной трети – то есть, один шанс из трех, что сеанс сможет прочитать его по своему требованию или по требованию другого узла (двусторонний сценарий). Если данный узел не является владельцем, он должен послать сообщение экземпляру-владельцу. Если блок уже находится где-то в памяти, есть вероятность 50/50, что владелец хранит блок и может передать его запрашивающему узлу (двусторонний сценарий), иначе ему придется в свою очередь обратиться к другому (третьему) узлу с просьбой переслать блок данному узлу (трехсторонний сценарий).

Теперь представьте, как изменятся вероятности в кластере с четырьмя узлами. Вероятность, что данный узел будет владельцем требуемого ему ресурса, равна одной четверти. Если данный узел не является владельцем, есть только один шанс из трех, что владелец будет держать требуемый ресурс. Поэтому увеличение числа узлов
подразумевает увеличение трафика между ними. Но, когда число узлов превысит 10, увеличение трафика перестает быть столь же существенными и его можно игнорировать.

Возможно, лучше понять вышесказанное вам помогут некоторые числа (полученные в ходе выполнения строго контролируемого сценария). Я настроил кластер RAC с тремя узлами и запустил по одному сеансу на каждом узле, в рамках которых выполнялось по 100 запросов на выборку и изменение (с подтверждением) единственного блока в быстром цикле. Ниже приводится три набора статистик для событий ожидания (я оставил только наиболее интересные), полученные в результате: 

Event                Waits Time_outs Csec Avg Csec Max Csec
-----                ----- --------- ---- -------- --------
gc cr block 2-way       98         0    4     .043        0
gc current block 2-way  98         0    4     .044        0
gc cr block 2-way       49         0    2     .036        2
gc cr block 3-way       47         0    3     .063        0
gc current block 2-way  48         0    2     .033        0
gc current block 3-way  46         0    2     .053        0
gc cr block 2-way       50         0    2     .049        0
gc cr block 3-way       47         0    3     .064        1
gc current block 2-way  50         0    2     .035        0
gc current block 3-way  46         0    2     .051        5

Нетрудно заметить, что результаты получились несимметричными, и немного подумав, нетрудно понять, что первый набор значений получен из узла, который хранит ведущий ресурс для запрашиваемого блока. Всякий раз, когда ему требуется получить последнюю версию блока обратно, в свой кэш, он знает, кто хранит эту копию, поэтому всегда использует двусторонний сценарий. (Статистика gc cr block 2-way соответствует операции выборки (SELECT), а статистика gc current block 2-way – операции изменения (UPDATE).) Другие два узла, напротив, вынуждены обращаться к первому узлу, чтобы получить нужный блок. В половине случаев первый узел отвечает: «я храню последнюю копию», – и мы наблюдаем двусторонний сценарий; а в другой половине случаев первый узел отвечает: «последнюю копию хранит другой узел», – и мы наблюдаем трехсторонний сценарий.

Если аналогичный тест выполнить в кластере с четырьмя узлами, вы все еще будете видеть, что узел-владелец в худшем случае выполняет двусторонний сценарий, но другие три узла в 33 случаях из 100 будут выполнять двусторонний сценарий, и в 66 случаях из 100 – трехсторонний. Вы также можете заметить, что трехсторонние операции выполняются медленнее двусторонних.


Примечание. Рассматривая механизм обмена данными в кластере RAC, в частности механизм передачи блоков данных между экземплярами, его работу можно оценивать с двух точек зрения. Кто-то может сказать, что дает определенные выгоды, потому что блоки передаются по сети, а это быстрее, чем читать их с диска. Кто-то, напротив, может сказать что он дает лишнюю нагрузку, и почему бы не отказаться от идеи создания кластера и просто не увеличить объем ОЗУ? Если вы считаете, что создание кластера RAC влечет дополнительные накладные расходы, тогда спросите себя: зачем вам вообще использовать его? Если ваш выбор сделан на основании веских причин, тогда нет смысла рассматривать трафик между узлами как накладные расходы. (Я не призываю вас полностью игнорировать эту проблему – всегда есть смысл избавиться от лишней работы, если есть такая возможность – и в RAC есть множество ее решений, одним из которых является минимизация числа блоков, нужных всем узлам.)


 

Следствия

Передача блоков между узлами кластера влечет за собой массу мелких побочных эффектов. В общем случае она остается почти незаметной, но в некоторых граничных случаях незначительные эффекты превращаются в большие проблемы производительности.

 

CUR или CR

Когда один узел запрашивает у другого переслать ему блок, влияние этого запроса во многом зависит от того, требуется ли получить согласованную копию блока или текущий блок. Если нужен текущий блок, тогда другой узел закрепляет соответствующий буфер в монопольном режиме и перед отправкой блока выталкивает текущее содержимое буфер журнала на диск, что отражается на статистиках gc current block pin time и gc current block flush time (оба значения измеряются в сантисекундах (сотые доли секунды)). Однако, после отправки блока, его буфер перестает быть текущим – текущим становится буфер в узле, получившем блок, – но несколько лучше, чем создание согласованной копии, потому что эта версия блока действительно была текущей какое-то время, поэтому Oracle дает ей статус PI (Past Image – прошлый образ).

Прошлые образы блоков играют важную роль и могут стать источником неприятностей. Если экземпляр, получивший блок, потерпит аварию после изменения текущей копии, процесс восстановления сможет воспользоваться прошлым образом, чтобы выполнить восстановление быстрее, а это определенно плюс.

С другой стороны, если экземпляр, получивший блок, запишет измененную копию на диск, как тогда следует поступить с прошлым образом в другом экземпляре? Он стал ненужным, что с ним следует сделать? Посмотрим на ситуацию с другого конца. Если другому экземпляру потребуется удалить прошлый образ (PI) из памяти, чтобы использовать буфер для другого блока, должен ли он предпринять какие-то особые шаги, учитывая, что имеет дело с прошлым образом (PI) – и имеет ли значение тот факт, что прошлый образ может быть «грязным» или «чистым»? Интересно было бы также узнать, может ли еще какой-то узел создать согласованную версию блока на основе прошлого образа (PI), не требуя от другого сконструировать эту версию из текущей версии блока.


Примечание. Существует множество мелких деталей, которые могут вызывать у нас вопросы, не имеющие ответа в официальной документации, или вопросы, ответ на которые изменился со временем. Далее я дам несколько ответов на вопросы, касающиеся прошлых образов. Если узел запишет текущий (XCUR) блок на диск, он должен сообщить экземпляру, хранящему прошлый образ этого блока, о необходимости понизить его статус до CR. Если узел хранит прошлый образ блока и хочет вытолкнуть его на диск, чтобы освободить буфер, он должен перед этим послать сообщение узлу, хранящему блок в статусе XCUR, чтобы тот записал текущую версию на диск (это верно, только если прошлый образ в настоящий момент «грязный»). Наконец, я не нашел подтверждений, что Oracle использует прошлый образ в локальном экземпляре для создания согласованной копии блока, похоже что создание такой копии всегда начинается с отправки сообщения узлу, хранящему блок в статусе XCUR.


Ситуация еще лучше, если экземпляру нужна только согласованная копия блока. Он отправляет запрос узлу, хранящему текущую версию, который создает и возвращает согласованную копию. Оценить этот эффект позволяют две другие статистики: gc cr block build time и gc cr block flush time. Но самое интересное, что перед созданием согласованной копии Oracle снова выталкивает буфер журнала. Я не могу найти веских причин для этого, разве что во избежание аномалии, которая может возникнуть из-за нового параметра commit write nowait, обсуждавшегося в этом блоге.

И снова возникают вопросы, касающиеся деталей процесса, ответы на которые могли измениться со временем. Вот интересный вопрос – случается ли так, что экземпляр, хранящий текущую копию блока, отказывается создавать согласованную версию и возвращает текущий блок, предоставляя вызывающему сеансу возможность самому создать копию?

Я думаю, что (по крайней мере, в прошлом) да, такое возможно. Если экземпляр определит, что не в состоянии сконструировать согласованную копию из текущей версии, которая уже хранится в его памяти, или обнаружит, что для этого ему потребуется обратиться к третьему экземпляру за получением блоков отмены, он может отказаться от создания согласованной копии и переложит проблемы на вызывающий экземпляр. (К сожалению, я знаю обо всем этом понаслышке, поэтому, пока я не соберусь протестировать данную ситуацию, я не буду делать никаких предположений о том, что происходит в действительности.)

 

Чтение текущих версий

Через соединения между узлами передаются не только сообщения, касающиеся блоков данных. Мы уже видели, что наличие прошлых образов может вызывать передачу дополнительных сообщений между экземплярами. Необходимо также посмотреть, что происходит с состоянием (в x$bh.state) блока, когда выполняется его чтение в буфер.

Когда сеанс читает блок с диска в память в версии Oracle с единственным экземпляром, этому блоку присваивается статус XCUR (exclusive current – исключительная текущая версия), потому что (а) это текущая версия блока и (б) существует только один экземпляр и один буфер, хранящий эту версию. В RAC блоку обычно присваивается статус SCUR (shared current – разделяемая текущая версия), потому что ту же самую копию блока могут иметь сразу несколько экземпляров и все эти копии являются текущими. (Однако, если блок читается с целью его изменения, он получает статус XCUR.)


Примечание. Имея привилегии SYS, вы можете выполнить следующий запрос, чтобы узнать, сколько блоков имеет такой статус: select state, count(*) from x$bh group by state;. Чаще всего встречаются состояния: 0 – свободный, 1 – XCUR (исключительная текущая версия), 2 – SCUR (разделяемая текущая версия), 3 – CR (доступен только для согласованного чтения), 8 – PI (прошлый образ). Это довольно тяжелый запрос, поэтому я не рекомендую выполнять его на промышленной системе с большим кэшем буферов.


Представьте теперь, что у вас имеется кластер с восемью узлами, все восемь имеют блок со статусом SCUR и один из них изменяет содержимое блока. Что случится с остальными семью копиями? Они не должны сохранять статус SCUR, потому что перестали быть текущими копиями. Поэтому экземпляр, изменивший блок, должен послать сообщения всем остальным экземплярам (посредством владельца ресурса) и сообщить им, что они должны изменить статус блока на CR. Однако я не знаю, должен ли экземпляр дождаться какого-либо подтверждения, что статусы были изменены. Возможно такие сообщения действуют по принципу «запустил и забыл», возможно ожидается подтверждение только от владельца ресурса.

На данный момент вы знаете, что многие сведения о работе RAC отсутствуют, а многие не опубликованы. Но, имея некоторые представления о том, как все должно работать, вы сможете поставить простые эксперименты, которые помогут понять причины аномалий производительности за счет исследования некоторых статистик, связанных с RAC, показывающих, казалось бы, бессмысленные значения.

Только один блок может быть текущим, но может существовать масса прошлых образов и согласованными копиями, появление которых может быть вызвано самыми разными действиями.


Примечание. Небольшой пример, помогающий увидеть странные детали в RAC: если выполнить инструкцию alter system flush buffer_cache, она вытолкнет кэши во всех экземплярах. Но в 11.2, в отсутствие других версий, блоки со статусом PI останутся в кэше и получат статус CR. Не спрашивайте – почему. Возможно это какой-то побочный эффект, а не предусмотренный результат.


Я не знаю, что еще можно рассказать о поддержке непротиворечивости кэша и о службе глобального кэша, но прежде чем перейти к более конкретным примерам использования RAC, я хотел бы сказать несколько слов о процессе восстановления в RAC и подчеркнуть важность прошлых образов.

 

Вас заинтересует / Intresting for you:

История развития СУБД Oracle
История развития СУБД Oracle 4476 просмотров Stas Belkov Tue, 21 Nov 2017, 13:19:55
Экстенты
Экстенты 5765 просмотров Ольга Потемкина Tue, 21 Nov 2017, 13:18:46
Индексы Oracle
Индексы Oracle 27370 просмотров Игорь Воронов Tue, 21 Nov 2017, 13:18:46
Как устроен поиск блоков данны...
Как устроен поиск блоков данны... 4494 просмотров Дэн Wed, 03 Jan 2018, 17:39:13
Печать
Войдите чтобы комментировать