Приглашаем всех специалистов принять участии в секции, посвященной коммерческим базам данных на PG Day'17 Russia! Александр Халухин из Deutsche Bank готовит интенсивный мастер-класс о диагностике производительности Oracle Database. Полный список выступлений смотрите в программе на сайте!
А мы продолжаем публиковать переводы статей от специалистов по базам данных из CERN. Сегодняшний материал является второй частью цикла статей, посвященных трассировке и отладке производительности Oracle DB с помощью SystemTap.
Эта статья посвящена отслеживанию логических и физических чтений в Oracle с помощью SystemTap. Здесь вы найдете несколько примеров, иллюстрирующих основные механизмы, используемые Oracle для выполнения физических и логических операций ввода-вывода, и узнаете, как создавать скрипты SystemTap для последующей диагностики и устранения неполадок ввода-вывода Oracle.
Введение
Выполнение ввода/вывода с носителя и на него, т.е. физического ввода/вывода, является одной из важнейших задач, лежащих в основе всех движков базы данных. При обращении к данным, хранящимся в кэше базы данных в оперативной памяти, мы можем говорить о логическом вводе/выводе. Физический ввод-вывод выполняется базой данных с помощью системных вызовов ядра OS. Тип используемых системных вызовов зависит от параметров базы данных, OS и типа хранилища. Логический ввод-вывод выполняется процессами базы данных с использованием функций ядра Oracle. Oracle обеспечивает измерение времени вызовов физического ввода-вывода с помощью интерфейса событий ожидания, а время логического ввода-вывода в общем случае учитывается как время CPU. Трассировка операций ввода-вывода, выполняемая процессами Oracle и дополняемая подробностями, предоставляемыми интерфейсом событий ожидания Oracle, является мощным методом для исследования внутреннего устройства ввода-вывода Oracle. С помощью нескольких примеров из реальной жизни мы выясним, какие механизмы используются Oracle для доступа к хранилищу, как операции ввода-вывода коррелируют с интерфейсом события ожидания, а также узнаем норму времени для наиболее распространенных событий ожидания, связанных с вводом-выводом.
Создание инструмента трассировки
Исследование операций, которые осуществляют Oracle и OS при выполнении операции ввода-вывода, требует специальных инструментов. SystemTap — это инструмент динамической трассировки, предназначенный специально для Linux и позволяющий отслеживать функции как OS, так и Oracle (функции ядра Linux, системные вызовы и функции ядра базы данных Oracle). Ниже приведено описание скрипта systemtap, который можно использовать для отслеживания логического и физического ввода-вывода Oracle: он состоит из probes, которые могут присоединяться к представляющим интерес вызовам функции и считывать параметры функций, память и регистры процессора.
Systemtap требует относительно недавних версий ядра для наличия возможности профилировать userspace: должна быть доступна функциональность utrace или uprobe_events. Подойдет, к примеру, RHEL/OL 6.5 и выше, но не RHEL 5. Кроме того, для системных вызовов трассировки необходимо установить пакет ядра debuginfo.
Подробнее об этом и о том, как создать тестовую среду, можно прочитать в этой статье. Скрипт, который мы используем в данном посте для трассировки логического и физического ввода-вывода Oracle, а также интерфейса событий ожидания, выглядит следующим образом: trace_oracle_logicalio_wait_events_physicalio_12102.stp (для Oracle 12.1.0.2). Также доступен вариант для версии 11.2.0.4: trace_oracle_logicalio_wait_events_physicalio_11204.stp. Прежде, чем перейти к примерам исследования ввода-вывода Oracle, я бы хотел рассмотреть три основные части скрипта: отслеживание физического ввода-вывода, логического ввода-вывода и событий ожидания.
Отслеживание физического ввода-вывода
В конечном итоге, Oracle осуществляет системные вызовы для выполнения операций ввода-вывода. Тип вызовов зависит от параметров базы данных, типа OS и типа хранилища. В этой статье вы можете найти примеры для Oracle на Linux, с использованием хранилища ASM на блочных устройствах. Обратите внимание, что direct NFS, локальное хранилище и ASM с asmlib в статью не попали и, возможно, будут рассмотрены в будущих исследованиях. Системные вызовы можно отследить в том числе с помощью утилиты strace(1) на Linux. Однако, в этой статье вы увидите, как могут быть созданы probes SystemTap для сбора информации об интересующих системных вызовах: pread, pwrite, io_submit и io_getevents. Следующие SystemTap probes могут быть полезны для “нижележащего” слоя блочного ввода-вывода: ioblock.request, ioblock_trace.request и ioblock.end (заметьте, это не системные вызовы, а точки трассировки для вызовов функций внутри ядра Linux).
Имя probe | Функция | Избранные параметры |
syscall.pread/pwrite | Синхронный ввод-вывод: чтение/запись из/в файловый дескриптор | fd (номер файлового дескриптора), смещение, количество. Возвращаемое значение: прочитано/записано байт |
syscall.io_submit | Асинхронный ввод-вывод: отправка блоков на обработку | nr (количество операций ввода-вывода). Для каждой операции: файловый дескриптор, смещение, байты, код операции (0 для чтения, 1 для записи) Возвращаемое значение: число выполненных операций ввода-вывода |
syscall.io_getevents | Асинхронный ввод-вывод: чтение событий из очереди выполнения | min_nr (минимальное количество операций ввода-вывода для чтения), тайм-аут ожидания события. Возвращаемое значение: количество собранных операций ввода-вывода, Для каждой операции: файловый дескриптор, смещение, байты. |
ioblock.request, ioblock_trace.request | Запросы ввода-вывода, отправляемые на уровень ядра интерфейса блочного устройства | devname, сектор, размер, rw, адрес структуры block_io |
ioblock.end | Возвращение с уровня ядра интерфейса блочного устройства | devname, сектор, rw, адрес структуры block_io |
Примечание, добавленное в августе 2015 года: probes Systemtap для kernel.trace('block_rq_issue') и kernel.trace('block_rq_complete') также могут использоваться для исследования интерфейса блочного ввода-вывода с дополнительным преимуществом: они не требуют kernel debuginfo. Примеры таких probes можно найти на странице с материалами для загрузки или на GitHub.
Отслеживание логического ввода-вывода
Речь идет об истории и деталях операций логического ввода-вывода, когда Oracle читает блок данных из памяти (буферный кэш). Операции логического ввода-вывода включают в себя подмножество операций физического ввода-вывода: если Oracle не находит данные в памяти (буферном кэше), он выполняет физическое чтение. Вы можете найти несколько примеров этого механизма в действии далее в этой статье.
Инструментарий Oracle предоставляет обширную информацию о логических чтениях в нескольких представлениях V$, таких как V$SYSSTAT, V$SESSTAT, V$SYSMETRIC. Также данные о логических чтениях доступны из sql*plus с параметром «set autotrace on» и с отчетами tkprof о трассировках событий 10046. Логические чтения бывают двух основных видов: consistent и current. Oracle будет использовать consistent reads при чтении данных с заданным system change number SCN, current reads используются при выполнении операций DML на блоке. Consistent reads могут быть оттрассированы в Oracle с использованием событий 10200 и 10201. Более подробную информацию можно найти в презентации Джулиана Дайка (Julian Dyke) о логическом вводе-выводе.
В этой статье мы покажем, как отслеживать логический ввод-вывод, подключая probes SystemTap к набору функций ядра Oracle. Роль функций kcbgtcr и kcbgcur для трассировки consistent и current reads уже обсуждалась Александром Анохиным в его превосходных статьях о Dtrace LIO. Совсем недавно я исследовал эту тему и счел полезным также оттрассировать функции kcbzib, kcbzgb, kcbzvb.
Резюме того, что я выяснил об этих функциях, приведено ниже в таблице. Примечание: выражения %rdi, %rsi, %r8 указывают значения соответствующих регистров процессора. С помощью SystemTap значение регистра может быть прочитано с помощью функции register, например: register('rdi'). Регистры CPU используются для извлечения аргументов вызова функции. Systemtap не предоставляет аргументы вызова функции, когда для исполняемого файла отсутствуют символы отладки. В частности, соглашения о вызовах для x86-64, как описано в www.x86-64.org/documentation/abi.pdf, указывают, что целочисленные параметры для вызова функции доступны в следующих регистрах (по порядку): %rdi, %rsi, %rdx, %rcx, %r8 and %r9.
Имя функции | Выполняемая задача | Избранные параметры |
kcbgtcr | Kernel Cache Buffers Get Consistent Read Используется для consistent reads |
tbs#=user_int32(%rdi) rel file n#= user_int32(%rdi+4) >> 22 & 0x003FFFFF block#=user_int32(%rdi+4) & 0x003FFFFF data_object_id#=user_int32(%rdi+8) object_id#=user_int32(%rdi+12) Примечание: для bigfile tablespaces: |
kcbgcur | Kernel Cache Buffers Current Используется для current reads |
tbs#=user_int32(%rdi) rel file n#= user_int32(%rdi+4) >> 22 & 0x003FFFFF block#=user_int32(%rdi+4) & 0x003FFFFF data_object_id#=user_int32(%rdi+8) object_id#=user_int32(%rdi+12) Примечание: для bigfile tablespaces: |
kcbzib | kcbZIB расшифровывается как: Z (kcbz.o — модуль для вспомогательных функций физического ввода-вывода), IB: Input Buffer Oracle выполнит физическое чтение(я) в буферный кэш |
|
kcbzgb | Суффикс GB в kcbZGB расшифровывается как: Get (space for) Buffer Oracle выделяет пространство в буферном кэше для заданного блока (обычно до операций ввода-вывода). |
tbs n#=%rsi, rel file n#=%rdx >> 22 & 0x003FFFFF block#=%rdx & 0x003FFFFF data_object_id#=%r8 object_id#=%r9 Примечание: для bigfile tablespaces: |
kcbzvb | Вызывается после того, как Oracle прочитал заданный блок Это часть цепочки вызовов для проверки блоков, управляемых параметром db_block_checking. Предполагаемая расшифровка: Kernel cache buffer verify block. Спасибо @FritsHoogland за это уточнение. Эта функция используется как для чтений в буферном кэше, так и для direct reads |
tbs n#=%rsi, rel file n#=%rdx >> 22 & 0x003FFFFF, block#=%rdx & 0x003FFFFF data_object_id=user_int32(%rdi+24) Примечание: для bigfile tablespaces: block#=%rdx |
Примечание: на момент написания статьи только uprobes для ядер версии 3.10 и выше поддерживают возвращаемые probes для функций userspace. Поэтому данная функциональность недоступна в RHEL или OL 6.x, но доступна в ядрах RHEL 7.x.
Отслеживание событий ожидания
Интерфейс событий ожидания, вероятно, является одной из лучших функций в настройке производительности Oracle, поскольку он предоставляет количественный метод выявления проблем производительности и узких мест. Кэри Миллсап (Cary Millsap) опубликовала вдохновляющую работу на эту тему.
Для наших целей достаточно указать, что события ожидания являются контрольными точками (instrumentation points), в которых измеряются избранные действия ядра Oracle. В частности, Oracle будет отмечать начальные и завершающие вызовы большинства операций физического ввода-вывода, позволяя тем самым хорошо понять, сколько времени тратится в процессе физического ввода-вывода (некоторые ограничения этого механизма обсуждаются далее в этом посте). Существуют ограничения на использование интерфейса событий ожидания для изучения задержки ввода-вывода, в частности, в разделе примеров вы можете найти интересные задачи, связанные с измерением длительности событий асинхронного ввода-вывода.
Трассировка событий ожидания обычно активируется в Oracle с помощью события 10046 или посредством вызова DBMS_MONITOR.SESSION_TRACE_ENABLE. В этой статье мы получаем подробные данные о событии ожидания, присоединяя probes SystemTap к соответствующим функциям ядра Oracle, а именно к kskthbwt и kskthewt, для сбора информации соответственно в начале и в конце каждого события ожидания. Включение трассировки событий ожидания с помощью события 10046 на уровне 8 и другими методами является способом извлечения наружу информации о времени события ожидания (а также других подробностей об обработке SQL), а не способом включения информации ожидания. Это, в свою очередь, делается с помощью параметра базы данных TIMED_STATISTICS, и в современных базах данных Oracle он должен быть установлен в TRUE. Ключевой частью информации, выдаваемой нам функциями kskthbwt и kskthewt ядра Oracle, является указатель на служебную таблицу X$KSUSE (или, вернее, на “нижележащую” структуру памяти в SGA), которая является “нижележащей” таблицей для V$SESSION и, следовательно, позволяет сопоставить события ожидания с множеством полезных сведений о сессии, выполняющей SQL. Разыменовывание указателя в регистре r13 в базовое значение X$KSUSE и вычисление смещения для интересующих полей требует дополнительной работы, подробно описанной в предыдущей статье (см. также скрипт: ksuse_find_offsets.sql).
Имя функции | Выполняемая задача | Избранные параметры |
kskthbwt | Kernel service Kompile thread begin wait. Эта функция вызывается в начале события ожидания Oracle. kslwtbctx является вызовом своей родительской функции и отмечает начало события ожидания. |
Полезная информация для профилирования функции: register r13 -> указывает на сегментированный массив X$KSUSE (V$SESSION) в SGA register rsi -> временная метка начала ожидания (в микросекундах) register rdx -> количество событий ожидания |
kskthewt | Kernel service Kompile thread end wait. Эта функция вызывается в конце события ожидания Oracle. kslwtectx — вызов родительской функции, отмечающий конец события ожидания. Суффикс «ewt», скорее всего, означает «end wait». |
Полезная информация для профилирования функции: register r13 -> указывает на сегментированный массив X$KSUSE (V$SESSION) в SGA register rdi -> временная метка начала ожидания (в микросекундах) register rsi -> количество событий ожидания |
Запуск скрипта
Ниже вы найдете несколько примеров трассировки для распространенных шаблонов доступа к Oracle I/O. Трассировки были получены командой stap -v trace_oracle_logicalio_wait_events_physicalio_12102.stp -x _pid_, где _pid_ — идентификатор процесса отслеживаемой сессии. Результат передается в sed -f eventsname.sed, где обеспечивается трансляция номера события в связанное с ним имя. Файл eventsname.sed создается путем запуска скрипта eventsname.sql. Важно перезапустить скрипт для генерации имен событий перед тестированием, так как номера событий ожидания могут изменяться без предупреждения, например, после миграции на более новые версии или после установки патча. Система, используемая для сбора трассировок в следующих примерах, — это Oracle 12.1.0.2, работающая на OEL 7.0 с UEK3 под Virtualbox.
Пример 1: single-block random reads
Данный пример относится к категории single block random reads. Этот способ доступа к данным очень важен для многих рабочих нагрузок OLTP, поскольку он используется для доступа к таблице через индексный ключ. На рисунке 1 ниже вы можете увидеть фрагмент файла трассировки для запроса типа select col_list from table tab where indexed_col=val.
Рис. 1: Oracle Single-block random read и событие ожидания sequential read из файла db
Ключевые моменты:
- Oracle осуществляет логическое чтение одного из блоков, необходимых для извлечения данных для SQL в этом примере:
- в вызове логического чтения содержится дополнительная информация, из которой видно, что Oracle хочет прочитать block#=2505675 из tbs#=7, принадлежащий obj#=32174 (который является сегментом таблицы в данном примере);
- блок не найден в кэше, поэтому в буферном кэше выделяется новый блок и инициируется физическое чтение;
- база данных помечает начало события ожидания для 'db file sequential read';
- запрос на физическое чтение для блока становится вызовом pread к OS:
- запрос IO поступает в ядро через системный вызов; любая функция, встречающаяся после системного вызова, является функцией ядра (например, ioblock.request) до тех пор, пока системный вызов не завершается и, таким образом, снова не оказывается в userland (в данном случае, исполняемый файл Oracle).
- Этот вызов функции чтения передается интерфейсу блочного ввода-вывода для чтения 8192 байт (1 блок базы данных) с блочного устройства (/dev/sdl в примере на рисунке 1);
- интерфейс блока и, затем, вызов ОС возвращают прочитанные данные ядру Oracle;
- интерфейс ожидания Oracle помечает завершение ожидания и вычисляет прошедшее время:
- событие ожидания — «db file sequential read», параметры: p1 — номер файла, p2 — номер блока, p3 — количество прочитанных блоков (1 блок);
- по значениям временной метки в трассировке можно увидеть, что длительность ожидания sequential read файла db во многом зависит от времени, затраченного на выполнение вызова pread, с небольшими издержками (обратите внимание, что накладные расходы могли быть более существенными, если бы у нас была высокая загрузка CPU на сервере БД, например, из-за рабочей нагрузки с других сессий Oracle).
- Мы можем заключить, что в этом примере измеренное время ожидания для sequential read файла db соответствует задержке, связанной с физическим чтением.
Пример 2: sequential I/O в буферный кэш с синхронным вводом-выводом
В этом примере речь идет о мультиблочных (multi-block) чтениях в кэш. Это способ доступа, который используется, например, когда Oracle выполняет full table scan. Существует несколько способов, с помощью которых Oracle может обращаться к хранилищу для выполнения full table scan, в этом примере вы видите случай, когда Oracle использует синхронный ввод-вывод (т.е. вызов pread) для выполнения операций чтения OS, примеры 3 и 4 охватывают sequential reads с асинхронным вводом-выводом. На рисунке 2 ниже вы можете увидеть фрагмент отслеживания рабочей нагрузки запроса типа select col_list form table tab, Oracle выполняет full scan таблицы tab.
Рис. 2: Sequential I/O Oracle в случае, когда чтение выполняется с использованием вызовов pread OS
Ключевые моменты трассировки на Рис.2:
- Oracle осуществляет sequential read для tbs#=7 block#=2621443 и находит его в кэше.
- Oracle выполняет consistent read для следующего блока, не находит его в кэше, поэтому готовится к чтению списка блоков из хранилища за одно физическое чтение. Этот список состоит из 124 блоков (он был сокращен в этом примере для простоты).
- База данных помечает начало события ожидания «db file scattered read».
- Запросы на физическое чтение становятся единичным вызовом pread к OS для получения 1015808 байт (124 блока).
- Этот запрос перемещается вниз по цепочке ввода-вывода к интерфейсам блочного ввода-вывода, которые делят его на 2 чтения с “нижележащего” блочного устройства, так как в системе, используемой для тестов, максимальный размер ввода-вывода составляет 524288 байт.
- Согласно исследованию Фрица Хугланда, представленный запрос на ввод-вывод помещается в список scattered/gather планировщика I/O, где он разбивается до размеров I/O, заявленных блочным устройством.
- Вводы-выводы выполнены интерфейсом блочного ввода-вывода.
- Возврат вызова pread.
- Интерфейс события ожидания отмечает время окончания ожидания
- Событие ожидания — db file scattered read, а параметры: p1 — номер файла, p2 — номер блока, p3 — количество блоков.
- Из значений временной метки в трассировке видно, что длительность ожидания scattered read файла db, в основном, объясняется временем, затраченным на выполнение вызова pread, с небольшими издержками.
- Из трассировки вызовов функции kcbzvb ядра Oracle мы получаем дополнительное подтверждение блоков, которые были прочитаны.
- Можно сделать вывод, что в этом примере время ожидания scattered read файла db соответствует задержке ввода-вывода.
Пример 3: Sequential I/O в буферный кэш с вызовами функций асинхронного ввода-вывода OS
На рисунке 3 вы видите фрагмент трассировки SQL select col_list from table tab, где Oracle выполняет full scan таблицы tab. Это тот же запрос, который использовался в примере 2, отличие в том, что трассировка на рисунке 3 была сделана на более поздней стадии выполнения запроса, когда процесс Oracle переключился от использования synchronous I/O с помощью вызовов pread к применению интерфейса асинхронного ввода-вывода. Асинхронный ввод-вывод реализован в Linux вызовами io_submit и io_getevents для отправки запросов на ввод-вывод и получения результатов. Не смотря на то, что в этом примере Oracle использует асинхронный интерфейс для выполнения операций чтения, конечный результат очень похож на результат, представленный в примере 2 (т.е., с использованием synchronous I/O). Такое же событие ожидания Oracle, «db file scattered read», чтения также выполняются в буферный кэш, а результаты операций ввода-вывода собираются с помощью блокирующих вызовов, что фактически делает процесс синхронным. Более «агрессивное использование» интерфейса для асинхронного ввода-вывода встречается в Oracle при выполнениии direct reads, которые обсуждаются в примере 4 ниже.
Рис. 3: Oracle, выполняющий sequential I/O reads в буферный кэш, используя асинхронные вызовы ввода-вывода
Ключевые моменты:
- Oracle выполняет consistent read для tbs#=7 block#=2522760, не находит его в кэше, поэтому готовится прочитать список блоков в буферный кэш. Этот список состоит из 128 блоков и сокращен в данном примере.
- База данных помечает начало события ожидания для «db file scattered read».
- Запросы на физическое чтение передаются в ОС как вызов io_submit с двумя запросами общим размером 1 МБ (то есть 128 блоков): первый запрос для чтения (код операции 0) из 65536 байт, второй — для операции чтения из 983040 байт (то есть 1 MБ — 64 КБ).
- Эти запросы передаются интерфейсу блочного ввода-вывода, который разделяет наибольший запрос на 2 чтения от “нижележащего” блочного устройства (/dev/sdi в этом примере). Чтение 64 КБ передается в /dev/sdr без изменений в размера.
- Oracle вызывает io_getevents, чтобы получить результаты только что отправленных асинхронных запросов ввода-вывода, и ждет. Поле timeout.tv_sec установлено равным 600, что указывает на то, что это блокирующее ожидание (это значит, что io_getevents ожидает 600 секунд (tv_sec) и 0 наносекунд (tv_nsec)) для завершения min_nr = 2 операций IO.
- Интерфейс блочного устройства возвращает данные вверх по цепочке вызовов.
- Вызов io_getevents возвращает два чтения.
- Интерфейс события ожидания помечает окончание ожидания. Событием ожидания в этом случае является scattered read файла db.
- Параметры события ожидания: p1 — номер файла, p2 — номер блока, p3 — количество блоков.
- Продолжительность события ожидания — это, в основном, время, затрачиваемое на вызовы io_submit и io_getevents плюс небольшие накладные расходы.
- Обратите внимание, не смотря на то что Oracle использовал асинхронный интерфейс, он также ожидал завершения всех операций ввода-вывода, поэтому операции ввода-вывода, фактически, являются синхронными.
- Из вызовов к kcbzvb мы получаем дополнительное подтверждение прочитанных блоков.
- Заключение: время ожидания scattered read файла db в этом примере не является точным показателем задержек ввода-вывода, поскольку два чтения на двух разных устройствах были запрошены параллельно, и общее время было отражено так, словно это было одно чтение.
Пример 4: full scans с применением direct reads и асинхронного ввода-вывода
В этом параграфе вы найдете два примера Oracle I/O в случае direct reads в блочном хранилище, настроенном с помощью ASM. Здесь используется тот же запрос, выполняющий full table scan, что и в примерах 2 и 3. В данном примере, в отличие от примеров 2 и 3, Oracle использует serial direct read для доступа к данным. Serial direct read — это функция, появившаяся в 11gR2 для оптимизации операций full segment scan и обхода слоя буферного кэша, подобно тому, что происходит при параллельных запросах. Oracle выбирает, когда использовать serial direct reads вместо cached reads, основываясь на нескольких параметрах. В первом приближении direct reads предпочтительнее, когда таблица «большая» по сравнению с размером буферного кэша, но фактический механизм сложнее и потенциально может изменяться в зависимости от версии Oracle. Вы можете больше узнать по этой теме в статье Танела Подера (Tanel Poder).
Для запуска этих тестов, мне пришлось принудительно установить использование serial direct reads перед запуском запроса, это было сделано с помощью alter session set "_serial_direct_read"=always;.
Обратите внимание, что запрос alter session set "_serial_direct_read" = never; может использоваться для отключения serial direct reads и принуждения Oracle использовать cached reads (то есть тип ввода-вывода, который вы видели в примерах 2 и 3 выше). Значением по умолчанию для параметра «_serial_direct_read» является «auto».
Для исследований в этом примере мы можем использовать немного другой SystemTap probe, чем в предыдущих. Я удалил трассировку логического ввода-вывода и блочного ввода-вывода для уменьшения беспорядка, а также добавил новый probe на функцию userspace io_getevents_0_4 из libaio (libaio — это библиотека Linux, которая реализует асинхронный ввод-вывод). Это связано с тем, что serial direct reads используют неблокирующие вызовы для сбора результатов операций ввода-вывода, и эти вызовы лучше всего отслеживаются в интерфейсе libaio по причинам, описанным Фрицем Хуландом (Frits Hoogland) в этой статье. Скрипт SystemTap, представленный на рис. 4a и 4б, выглядит следующим образом: trace_oracle_wait_events_asyncio_libaio_12102.stp (версия для 11.2.0.4 доступна по ссылке trace_oracle_wait_events_asyncio_libaio_11204.stp).
Рис. 4a: Трассировка события ожидания и вызовов Oracle I/O во время full table scan с serial direct read и асинхронным вводом-выводом. Это то, что происходит на первых этапах full scan
Ключевые моменты рисунка 4a:
- Oracle запрашивает ввод-вывод непрерывными кусками пространства хранилища размером 1 МБ. Это делается для сокращения затрат времени поиска и, потенциально, использования readahead, когда это возможно.
- Значение 1 МБ для запроса на чтение идет из параметра db_file_multiblock_read_count=128, умноженного на размер блока — 8 КБ.
- Oracle отправляет одну операцию ввода-вывода размером 1 МБ с вызовом io_submit, а затем вызывает io_getevents_0_4 в библиотеке libiao, используя неблокирующий режим, чтобы попытаться получить результаты. Это происходит дважды, без каких-либо успехов. Затем Oracle отправляет еще одну операцию размером 1 МБ и 4 раза пытается собрать результаты в неблокирующем режиме без каких-либо успехов (операции ввода-вывода еще не закончены).
- Отсчет времени события ожидания начинается непосредственно перед вызовами io_getevents, которые используются для получения результатов ввода-вывода, на этот раз в блокирующем режиме (с таймаутом в 600 секунд).
- В этом случае событием ожидания является «direct path read».
- Oracle собирает результаты двух операций ввода-вывода в двух последовательных вызовах io_getevents.
- После сбора данных ввода-вывода Oracle помечает окончание ожидания события.
- В результате было выполнено 2 чтения, по 1 МБ каждое.
- Интерфейс события ожидания не сообщает правильное количество выполненных операций ввода-вывода:
- Ожидание «direct path read» в этом примере сообщает об одном чтении 128 блоков (1 MB), а прочитано было 2 MB.
- Истекшее время для события ожидания «direct path read» не измеряет задержку ввода-вывода.
- Oracle засекает только блокирующие вызовы io_getevents, которые запускаются после запроса операций ввода-вывода.
- Oracle считает время 2 чтений вместе.
Использование serial direct reads в Oracle сложнее, чем простой пример, приведенный на рис. 4a. Фриц Хугланд более детально разобрал внутреннее устройство direct reads в Oracle в этой презентации. В частности, Oracle использует адаптивный механизм для direct reads с целью достижения высокой пропускной способности подсистемы хранения, когда это возможно. Это реализуется за счет увеличения количества невыполненных операций ввода-вывода при вызове асинхронного интерфейса ввода-вывода. Внутри этот подход использует I/O slots. Обратите внимание, что из комментария к диагностическому событию Oracle 10353 у нас есть дополнительная косвенная информация по этой теме: «Слоты (slots) — это единица ввода-вывода, и этот фактор контролирует количество невыполненных операций ввода-вывода».
Еще одно открытие Фрица заключается в том, что существуют операции ввода-вывода, выполняемые для direct reads, время которых не отслеживается интерфейсом событий ожидания. Это происходит потому, что Oracle может выполнять вызовы io_getevents с timeout=0, которая собирает данные ввода-вывода без блокировки. Вы можете увидеть эти механизмы в действии в трассировке на рис. 4б. Она выполнена для того же full scan, что и на рисунке 4а, но в более позднее время, когда механизм adaptive serial direct reads сконфигурировал запросы Oracle I/O, чтобы они были «более агрессивными» в использовании ресурсов хранилища.
Рис. 4б. Трассировка события ожидания и вызовов Oracle I/O во время выполнения full table scan с помощью serial direct read и асинхронным вводом-выводом. Это пример того, что происходит, когда адаптивный механизм увеличивает количество совершаемых операций ввода-вывода для оптимизации пропускной способности
Ключевые моменты рисунка 4б:
- Это тот же full scan, что изображен на рис. 4a. Разница в том, что трассировка на рисунке 4б была сделана на более поздней стадии выполнения операции full scan (таблица имеет 1 миллион блоков), когда adaptive serial direct reads увеличили количество слотов ввода-вывода (невыполненных асинхронных операций ввода-вывода).
- На рисунке видно, что несколько операций io_submit выполняются до начала отсчета времени события ожидания.
- Oracle пытается сохранить в очереди несколько невыполненных операций ввода-вывода: до девяти невыполненных асинхронных операций ввода-вывода можно увидеть на рисунке 4б, в то время как в более простом случае на рисунке 4a (т.е. в начале full scan) присутствовали только 2 операции.
- Несколько попыток собрать данные ввода-вывода с помощью операций io_getevents выполняются процессом Oracle до начала отсчета времени события ожидания:
- Результаты двух чтений, по 1 МБ каждое, собираются одним вызовом io_getevents до начала отсчета времени события ожидания, поэтому эти чтения не будут учтены в интерфейсе событий ожидания.
- Важно отметить, что процесс Oracle устанавливает тайм-аут для этого конкретного вызова io_getevents (а также для вызовов io_getevents_0_4 в libaio, которые не вернули каких-либо данных ввода-вывода) равным 0, а это значит, что Oracle не ждет, пока результаты ввода-вывода будут доступны, он соберет их только в том случае, если они доступны.
- Между моментом, когда Oracle начинает отсчитывать событие ожидания "direct path read", и завершением события ожидания были выполнены 3 операции io_getevents (с тайм-аутом, установленным равным 600, тем самым блокируя в ожидании чтений), в общей сложности было выполнено 9 чтений, по 1 МБ каждое.
- Интерфейс событий ожидания не сообщает обо всех выполненных операциях ввода-вывода:
- В параметрах события ожидания мы смогли найти только трассировку прочтения 1 МБ (128 блоков), хотя на самом деле было прочитано 9 МБ.
- Это в дополнение к чтению 2 МБ за пределами instrumentation событий ожидания.
- Истекшее время для «db file direct read» не отражает в точности задержку ввода-вывода, а, скорее, время ожидания для сбора данных ввода-вывода, как показано на рисунке 4б.
- Интерфейс событий ожидания не сообщает обо всех выполненных операциях ввода-вывода:
Пример 5: асинхронный ввод-вывод для random reads
Oracle оптимизирует single-block reads в ситуациях, когда он может выполнить предварительную выборку и/или сгруппировать операции ввода-вывода. Это оптимизация для random I/O с использованием интерфейса асинхронного ввода-вывода, поскольку Oracle может группировать несколько запросов ввода-вывода в одном вызове io_submit, а не посылать их один за другим, как в примере 1, где использовался вызов pread OS. Пример на рис. 5 появился после запуска инструментария SLOB Кевина Клоссона (Kevin Closson) в базе данных 12c. Обратите внимание на шаг 3 плана выполнения, где Oracle использует операцию «TABLE ACCESS BY INDEX ROWID BATCHED» для доступа к данным таблицы.
Рис. 5: План выполнения запроса SLOB, иллюстрирующий порционный доступ (batch access) к таблице
На рисунке 6 ниже вы можете увидеть фрагмент трассировки Oracle I/O при выполнении рабочей нагрузки SLOB. Как вы видите, интерфейс асинхронного ввода-вывода используется для выполнения random I/O. В случае, показанном на рис. 6, 126 запросов передаются в одной операции io_submit, а затем результаты собираются с помощью функции io_getevents, ожидающей завершения всех запросов. Событие ожидания в этом случае — «db file parallel read», а расчётное время ожидания — это время завершения партии из 126 запросов. Поэтому время ожидания db file parallel read не обеспечивает точного измерения задержки ввода-вывода для single block random reads.
Рис. 6: Oracle, выполняющий ввод-вывод для случайных чтений с асинхронным вводом-выводом, в ситуации с batched reads. Трассировка была отредактирована с заменой длинного списка похожих строк трассировки (обычно 126 строк, отличающихся только лишь block#) на "..."
Пример 6: трассировка DML
На рисунке 7 вы видите фрагмент трассировки Oracle logical I/O и события ожидания при вставке строки в таблицу, за которой следует коммит. В этой операции физические операции ввода-вывода не выполнялись (таблица ранее была закэширована). Вы можете видеть, что большая часть чтений блоков была выполнена в режиме current read, как и ожидалось для DML. Более того, оба блока принадлежат к таблице (вы можете идентифицировать их на рисунке 7 как принадлежащие tbs#=11), вставляемой в rollback segments (вы можете идентифицировать их на рисунке 7 как принадлежащие tbs#=2). Обратите внимание, что для простоты таблица в этом примере не имеет индексов.
Рис. 7: Oracle logical I/O, выполняющего операцию вставки и commit
Пример 7: трассировка log writer
На рисунке 8 вы можете увидеть трассировку log writer во время операции commit. Это та же операция commit, что и в примере 6 (см. также рис. 7 выше). Событие ожидания — «log file parallel write», и его продолжительность засекается Oracle от старта до завершения выполняемых операций асинхронного ввода-вывода. Следовательно, измеренное время события ожидания «log file parallel write» не является точным показателем задержек записи блока, поскольку его продолжительность зависит от количества выполненных операций. В случае, изображенном на рисунке 8, вы можете видеть, что log writer записывает данные на два разных блочных устройства, что согласуется с использованием 1 redo-лога из группы, размещенной на ASM-группе с уровнем избыточности normal: log writer отправляет 2 запроса записи ввода-вывода одновременно — для записи основного и зеркального экстентов лог-файла — и ожидает завершения их обоих.
Рис. 8: трассировка log writer во время операции commit
Заключение
Динамическая трассировка и, в частности, SystemTap предоставляют возможность исследовать физические и логические операции Oracle I/O, а также открывают путь для углубленного изучения операций Oracle I/O на различных уровнях движка Oracle вплоть до уровня ядра Linux. В этой статье описываются некоторые методы, инструменты и примеры того, как эти исследования могут быть выполнены.
Примеры из реальных рабочих нагрузок Oracle выявляют некоторые наиболее распространенные случаи логического и физического Oracle I/O и связи между интерфейсом событий ожидания и вызовами OS, осуществляемыми процессами Oracle. Интерфейс событий ожидания Oracle может предоставить полезные данные для измерения задержки ввода-вывода и в целом для устранения неполадок ввода-вывода. Это справедливо для random reads, измеряемых с помощью события ожидания «db file sequential read». Однако информация, предоставляемая событиями ожидания, связанными с асинхронным вводом-выводом, требует дополнительного внимания: такие события обычно не обеспечивают точного измерения задержки операций ввода-вывода. Кроме того, в этой статье приводятся примеры, где некоторые из асинхронных операций ввода-вывода для Oracle direct reads не обрабатываются интерфейсом событий ожидания.
Благодарности: Я хотел бы сказать спасибо Танелу Подеру и Фрицу Хугланду за оригинальные идеи, которые послужили вдохновением для этой работы. Особая благодарность Фрицу Хугланду, который щедро делился со мной своей глубокой экспертизой по данному вопросу.
Загрузка скриптов: обсуждавшиеся в этой статье скрипты можно взять нати на веб-странице с материалами для загрузки или на GitHub.
Автор: PG Day'17 Russia