|
к.ф.-м.н. Ю.Пудовченко,
ypudovchenko@ot.ru
“Открытые технологии”
Oracle latches – механизм последовательного доступа
Статья II
Статья I под тем же названием опубликована в предыдущем выпуске журнала.
Для того, чтобы с меньшими ожиданиями обрабатывать нагрузку, которая приходится на защелку, имеет смысл завести несколько защелок. Эта здравая мысль при принципиальном отсутствии альтернативных вариантов во многом определила развитие СУБД Oracle.
В целях решения этой задачи развитие СУБД Oracle происходило в нескольких направлениях.
- увеличение количества структур памяти, управляемых различными защелками (специализация структур памяти), что позволило распределить нагрузку на бОльшее количество защелок;
- усложнение архитектуры блокировок (введение родительских и подчиненных защелок, shred latch), что также позволило увеличить количество защелок и снизить ожидания за каждую из них;
- взаимодействие с разработчиками ЭВМ и ОС с целью оптимизации механизмов планирования процессов в ОС.
Усложнение структур памяти
Начиная с версии Oracle8i появились множественные блочные кеши (buffer pool). Один большой блочный кеш разделился вначале на три части (default pool, keep pool, recycle pool), а затем появились еще пять (2k,4k,8k,16k,32k). Поскольку каждый из кешей защищается своим набором защелок, то нагрузка распределяется на бОльшее количество защелок, в результате чего конкуренция за каждую из них уменьшается.
В Oracle8i был введен параметр управления количеством защелок db_block_lru_latches. Однако, этот параметр вызывал некоторые проблемы у администраторов, ибо создать новый кеш они создавали, но обычно забывали рассчитать для него количество дополнительных защелок. Поэтому в версии Oracle9i задача вычисления необходимого количества защелок для буферных кешей была переложена на СУБД, а к версии Oracle10g параметр db_block_lru_latches исчез из числа документированных параметров.
В Oracle 8i из одного монолитного shared pool выделился вначале large pool, приняв на себя нагрузку RMAN, параллельных процессов, UGA Shared servers и dbwr_io_slawes, а в Oracle10g обрел независимость и streams pool, взяв на себя нагрузку AQ. Поскольку каждый из этих кешей находится под управлением своего множества защелок, то администраторы получили в свои руки инструмент позволяющий уменьшать конкуренцию за защелки shared pool.
Начиная с Oracle9i, log buffer получил возможность делился в значение, указанное параметром log_parallelism от 2 до 255 раз. Этот механизм отменен, начиная с Oracle10.1, поскольку он включается автоматически, когда количество процессоров в сервере становится более 16. Тем не менее, эту опцию можно включить вручную, используя параметры _log_parallelism_max и _log_parallelism_dynamic=true|false.
Начиная с Oracle10.2, возможность делиться получил и сам shared pool, который автоматически разбивается на области размером по 256 Мб, каждая со своим набором защелок, в случае если shared_pool_size>256 Мб и cpu_count>=4 (но не более 7 shared pool областей).
Технология OPS -> RAC -> Grid тоже является решением, которое позволяет снижать конкуренцию за защелки, распределяя вычисления между узлами кластера.
Усложнение архитектуры защелок
Направление усложнения архитектуры защелок выразилось в следующем:
- введение механизма единичных и групповых защелок;
- появление уровней защелок;
- механизм wait posting;
- механизм Shared latches.
Механизм групповых защелок
В СУБД Oracle защелки бывают единичные и групповые. Единичные защелки существуют в единственном числе. Групповые состоят из родительской защелки и множества дочерних, которые являются подчиненными к родительской. Единичные и родительские защелки фиксированы в коде. Примерами единичных защелок являются process allocation, session allocation, trace latch, alert log latch.
Родительских - cache buffers lru chain, cache buffers chains, shared pool, redo allocation.
Идея групповых защелок в том, что родительская защелка захватывается на более короткий промежуток времени, чем раньше, и ее задача просто назначить процессу одну из дочерних защелок, которая будет удерживаться уже на более длительный период и обеспечивать защиту структур данных.
Рассмотрим пример. Для того, чтобы обновить блок, находящийся в кеше, сервер Oracle захватывает родительскую защелку cache buffer chains. Буферный кеш состоит из цепочек блоков, объединенных одним хеш-значением, поэтому родительская защелка cache buffer chains порождает для пользовательского процесса дочернюю защелку, связанную с конкретной хеш-цепочкой, после чего процесс проходит по хеш-цепочке в поисках нужного блока. Затем процесс “пришпиливает” (“pins”) блок в памяти и освобождает дочернюю защелку. После выполнения операцию над блоком пользовательский процесс освобождает блок (unpins).
Не на каждую цепочку буферов создается одна дочерняя защелка. В моей системе количество хеш-цепочек 489733 (параметр _db_block_hash_buckets), а количество дочерних защелок - 2048 (параметр _db_block_hash_latches). Т.е. одна защелка на 489733/2048 = 239,127 цепочек.
Дочерние защелки создаются и удаляются динамически, в процессе работы сервера. Параметры, влияющими на количество динамически создаваемых защелок, является размер кеша и число процессоров в сервере - CPU_COUNT .
Родительская и дочерняя защелки имеют одно и то же имя, чтобы под этим именем собирать статистику. Для корректного сбора статистики имена родительской и дочерних защелок совпадают, а значения суммируются. Родительская защелка используется для создания отчетов о производительности.
Вначале процесс получает родительскую, а затем дочернюю защелки. Следовательно, в сильно нагруженных системах родительская защелка все-таки может оказаться узким местом.
Если узким местом является одна из дочерних защелок, то это свидетельствует о наличии "hot blocks" – интенсивно используемых/обновляемых блоков.
Уровни защелок
Использование механизма блокировок чревато появлением дополнительных проблем, таких, как взаимоблокировки и проблема инверсии приоритета.
Проблема взаимоблокировок возникает в тех случаях, кода процессу во время работы может потребоваться несколько блокировок и если запросы на блокировки со стороны процессов возникают в случайном порядке. Например, первый процесс успешно получил и удерживает защелку А и запрашивает защелку Б. А в это время второй процесс уже захватил защелку Б и запрашивает защелку А.
В компьютерной науке выработаны средства и методы избежания взаимоблокировок. Основной метод здесь – это общий для всех участников системы алгоритм (общий порядок), в котором будут запрошены и предоставлены критические ресурсы. Это означает, что если, например, имеется три критических ресурса А, Б и В, то процессам разрешено блокировать эти ресурсы только в одном направлении, например от А к В, но никак не наоборот и не в случайном порядке. В системе с такими правилами исключается возможность взаимоблокировки.
В итоге, алгоритм получения защелки с проверкой на взаимоблокировку выглядит следующим образом:
- Если защелка свободна, то запрос на нее удовлетворяется сразу. Конец.
- Если защелка занята, то процесс, запрашивающий ее, делает spin: считает от 0 до некоторого большого числа, такая своеобразная задержка, а затем пытается получить ее еще раз командой TSL. Если опять получить защелку не удалось, то процесс повторяет запросы на защелку в цикле _spin_count раз. Если запрос удовлетворен, то Конец. Если в наличии имеется только 1 процессор, то _SPIN_COUNT устанавливается в 1 при старте БД. Т.е. как бы нет причины ожидать освобождения защелки на процессоре, если в системе всего один процессор (т.е. отсутствует второй процессор способный занимать ее). В случае, если процессоров в сервере более одного, то _SPIN_COUNT устанавливается в 2000.
- Если защелка все еще недоступна, то процесс увеличивает статистику latch free , освобождает процессор и отправляется спать. Т.е. одному засыпанию соответствует единичка статистики latch free. В первый раз процесс спит 0.01 секунды., после чего переходит к п.2. Если запрос опять не будет удовлетворен, то в каждом следующем цикле длительность этого интервала удваивается, после чего следует переход к п.2.
Итак, промежуток сна удваивается в каждом следующем цикле, пока не дойдет до значения, определяемого параметром _MAX_EXPONENTIAL_SLEEP, которое по умолчанию равно 2 секунды.
- После четырехкратного засыпания и убедившись, что защелка занята в пятый раз, процесс обращается к PMON’y проверить, жив ли еще процесс-держатель защелки и не возникла ли взаимоблокировка (в этом случае процесс ожидает события latch activity). Если удерживающий процесс сбойнул, то PMON очищает соответствующие структуры данных и после отката данных освобождает защелку. Если держатель еще "жив" и PID удерживающего процесса не изменился между засыпаниями запрашивающего процесса, то такая ситуация означает либо конкуренцию за блокировки (latch contention) либо означает проблемы с планировщиком заданий в ОС (т.е. процесс-держатель защелки не может получить процессорное время, чтобы закончить свою работу и освободить защелку).
В СУБД Oracle модуль предоставления блокировок (уровень KSL) устанавливает свой собственный порядок для предотвращения взаимоблокировок. Для этого каждой защелке присваивается некоторый уровень от 0 до15, у большинства защелок уровень от 0 до 8. Более высокие уровни также существуют, но оставлены для специальных случаев, таких как DB scheduler. Уровень назначается при разработке и не может измениться. Уровень представляется в виде 16-битового массива (2 байта), отображающего уровни защелок, которые процесс держит в настоящий момент. Эта переменная ассоциирована с каждым процессом.
Главное правило предоставления блокировки довольно простое: если процесс удерживает блокировку некоторого уровня, то он не может запросить-получить исключительную блокировку такого же или более низкого уровня (либо может запросить и получить ее только в no-wait режиме). Т.е. процесс всегда должен запрашивать блокировки в порядке возрастания уровней.
Дочерние защелки при создании наследуют уровень родителей, При этом только первая защелка может быть запрошена в режиме willing-to-wait. Вторая должна быть запрошена в режиме no-wait.
Для запросов типа no-wait позволен любой уровень запросов, но не более, чем две защелки могут быть захвачены на одном уровне.
Если же процесс удерживает одну защелку, и ему необходимо получить вторую блокировку более низкого уровня и в режиме willing-to-wait, то он должен освободить свою защелку и запросить защелки повторно в надлежащем порядке.
На мой взгляд, необходимо подчеркнуть, что угроза взаимоблокировок возможна только тогда, когда две и более защелок запрашиваются одним процессом одновременно и в случайном порядке. Если относительно некоторой защелки можно иметь уверенность, что она запрашивается процессами только в единственном числе и никогда не запрашивается процессами, удерживающими другие защелки, то взаимоблокировки с участием такой защелки не может быть в принципе.
Механизм Wait Posting
Wit posting - механизм пробуждения процессов, ожидающих защелки. Он означает, что процесс, удерживающий защелку, не только освободит ее, но и пошлет сигнал проснуться процессу, ожидающему защелку. В результате, "пробуждается от сна" только один процесс, а не множество процессов, ожидающих данную защелку и "всплеска" бесполезной активности не происходит.
Перед тем как отправиться в "сон" ожидающий процесс должен записать свой PID в wait list - список процессов, которые должны быть разбужены. Запись в этот список производится под защитой защелки wait list.
Процесс, освободивший защелку проверяет этот список и если в этом списке есть процесс, ожидающий защелку, то он разбудит семафор ждущего процесса, который работает как сигнал операционной системе поставить ожидающий процесс в очередь к процессору.
Преимущества этого механизма в том, что ждущий процесс получает защелку почти сразу же после ее освобождения.
Также как и любая другая структура данных, wait list защищается защелкой. Если механизм wait posting используется интенсивно, то список становится большим и в результате защелка wait list удерживается все дольше и все более часто, чем раньше, и в результате возникает вторичная конкуренция за защелку wait list, в тех случаях, когда высока конкуренция за другие защелки.
По-умолчанию, wait posting применяется только к защелкам library cache и shared pool. Он может быть отменен полностью установкой _LATCH_ WAIT_POSTING=0 (по-умолчанию = 1) или может быть разрешен для всех защелок установкой значения 2. Отмена этого механизма может принести пользу там, где высокая конкуренция за защелку library cache, и применение этого механизма ко всем защелкам может повысить производительность в случаях умеренной конкуренции за остальные защелки. Даже когда он включен для всех защелок, latch wait posting не всегда будет использоваться для засыпания на защелках cache buffers chains.
Столбец WAITERS_WOKEN в представлениях V$LATCH% показывает сколько раз ожидающий процесс был разбужен через механизм wait posting. Эта статистика бывает больше, чем количество промахов MISSES, потому что процесс может быть разбужен, но не получил защелку потому что другой процесс уже получил.
Поведение защелок в версии Oracle 8i определял параметр _latch_wait_posting. Значению 1 соответствовали защелки short wait, значению 2 соответствовали long wait. В версиях 9i и 10g параметр _latch_wait_posting отсутствует среди скрытых и документированных параметров. Тем не менее, в Oracle 9i и 10g этот механизм еще жив. Убедиться в этом можно взглянув на столбец WAITERS_WOKEN, который показывает ненулевые значения. В документации к представлению V$LATCH столбец WAITERS_WOKEN описан так: “Для некоторых защелок, сессия освобождающая защелку пробуждает сессию ожидающую эту же защелку. Данный параметр означает количество раз, которое ожидающие сессии были "разбужены", а также является списком защелок, относящихся к типу "wait posting"
SQL> select name, waiters_woken from v$latch where waiters_woken>0;
NAME WAITERS_WOKEN
---------------------- -------------
session allocation 6
cache buffers lru chain 25
simulator lru latch 3
object queue header operation 1
redo allocation 4
undo global data 2
parallel txn reco latch 5
intra txn parallel recovery 1
In memory undo latch 8
row cache objects 41
shared pool 7
library cache 13
Есть предположение, что перечисленные выше защелки используют механизм wait posting, и этот механизм жестко зашит в сервере.
Shared latches – защелки с множественным доступом
В Oracle9i появились shared (разделяемые) latches. Это такие защелки, доступ к которым возможен, когда они заняты другим процессом. Идея разрешить двум и более процессам одновременно захватывать одну и ту же защелку улучшает масштабируемость системы и снижает конкуренцию за эту защелку. Пока таких защелок известно две: undo global data и cache buffer chains. Данные защелки могут быть запрошены в двух режимах: shared и exclusive.
Совершенствование ОС
Операционная система планирует выполнение процессов в соответствии со своим внутренним алгоритмом. Однако, при планировании выполнения процессов Oracle в ряде случаев полезно дать возможность процессу, удерживающему защелку, подольше поработать на процессоре с тем, чтобы побыстрее освободить заблокированные данные.
По причине отсутствие у ОС информации о синхронизации между пользовательскими процессами, операционная система не может сделать выбор в пользу того или иного процесса. Совместное развитие операционных систем и СУБД Oracle привело к адаптации алгоритмов ОС под процессы Oracle:
Влияние на планировщик ОС,
Быстрое освобождение процессора,
Предпочтение к одному процессору
Влияние на планировщик ОС
Эта функциональность позволяет пользовательским процессам повлиять на планировщик заданий ОС с целью задержаться на процессоре даже при появлении в очереди более высокоприоритетного процесса. Когда процесс Oracle устанавливает флаг “не прерывать меня”, то процессы удерживающие защелку будут продолжать свое выполнение на CPU и не будут прерваны более высокоприоритетным процессом (информация по эту вопросу - man schedctl_init в Solaris).
Обычно в ОС существует некоторое пороговое значение, для того, чтобы защитить порядок в очереди от нового более высокоприоритетного процесса. Причина здесь в том, что вставка нового процесса в очередь на выполнение требует процессорного времени и переключения на поток диспетчера. А выполнять пересортировку всей очереди при каждом появлении нового процесса накладно. Поэтому разработчиками ОС принято решение производить пересортировку очереди только тогда, когда приоритет нового процесса превышает некоторое пороговое значение (например, kpreeptpri=100 для ОС Solaris), либо когда новый процесс принадлежит более высокоприоритетному классу.
Применительно к процессам Oracle этот механизм позволяет иметь дополнительное процессорное время на время выполнения критически важных операций. Таким образом, операции, защищенные защелками, завершаются так быстро, как только возможно, и длительность удерживания защелки снижается.
Причинами принудительного сброса процесса удерживающего защелку с выполнения (с процессора) могут быть:
- процессу явно назначен другой процессор (например, системным администратором);
- блокировка процесса системным вызовом, например I/O;
- операция процесса над семафором;
- page fault.
Управление (включение/отключение) этой возможности осуществляется параметром _NO_PREEMPT.
Быстрое освобождение процессора
Быстрое освобождение процессора (CPU yielding) – означает освобождение процессора одним процессом Oracle в пользу другого в процессе spin, если в очереди к запуску есть процессы более высокого приоритета. В этом случае более приоритетный процесс ставится на выполнение, а уступающий процесс снимается с ЦПУ и помещается в конец очереди на выполнение.
Если в очереди к запуску нет другого более высокоприоритетного процесса, которому нужен CPU, то процесс будет продолжать spin на этой защелке. Частота с которой Oracle будет предлагать уступать CPU в процессе spinning управляется параметром _SPIN_YIELD_CPU_ FREQ (отсутствует начиная с Oracle9i), который по умолчанию равен значению _SPIN_COUNT. Если механизм освобождения включен, и если эти два параметра имеют одинаковое значение, то процесс начнет новый spin без ухода в сон, если в очереди на выполнение отсутствуют другие процессы. Таким образом, освобождение процессора позволяет процессам Oracle получать защелки быстро, как только возможно, без избыточного потребления CPU time.
Предпочтение к процессору
Эта функциональность (в англоязычной литературе affinity) основана на двухуровневом планировании, используемом в современных операционных системах. Так, в многопроцессорной системе первый уровень (ядро ОС) создает по одному потоку планирования на каждый процессор, после чего каждый процессор планирует свое время самостоятельно. Первый уровень периодически балансирует нагрузку среди всех процессоров, а второй уровень осуществляет локальное планирование на уровне процессора. Одним из принципов планирования второго уровня является удержание процесса на том же процессоре, на котором он выполнялся в последний раз.
Эффективность этой технологии основана на том, что если процесс выполняется на том же процессоре, что и прежде, то многие адреса памяти и cache lines, оставшиеся после последнего запуска, могут сохраниться в кеше этого CPU. А это позволит резко увеличить коэффициент попадания в кеш, что уменьшит количество обращений к основной памяти и следовательно, ускорит выполнение процесса. Однако, не рекомендуется использовать явное назначение процессоров процессам Oracle, ибо в противном случае они не смогут мигрировать на более свободные процессоры в системе.
Литература
- Настройка систем баз данных с помощью анализа событий ожидания
http://www.oracle.com/global/ru/oramag/dec2004/admin_wait_events5.html
- Стив Адамс, “Отпечатки пальцев” http://www.oracle.com/global/ru/oramag/july2001/fingerprints.html
- http://www.sql.ru/forum/actualtopics.aspx?bid=3
- Steve Adams, Oracle8i Internal Services for Waits, Latches, Locks, and Memory, O'Reilly, 1999.
- Jim Mauro and Richard McDougall, Solaris Internals,
Core Kernel Components.
- http://metalink.oracle.com, Note 22908.1, “What are Latches and What Causes Latch Contention”, 2004.
- Tanel Põder, "Memory Management and Latching Improvements on Oracle 9i and 10g", UKOUG Conference 2004, http://integrid.info
- Tanel Põder, "Advanced Research Techniques in Oracle", NoCOUG 2006, http://integrid.info
|