Хабрахабр

Последние изменения в IO-стеке Linux с точки зрения DBA

Главные вопросы работы с базой данных связаны с особенностями устройства операционной системы, на которой работает база. Сейчас Linux — основная операционная система для баз данных. Solaris, Microsoft и даже HPUX все еще применяются в энтерпрайзе, но первое место им больше никогда не занять, даже вместе взятым. Linux уверенно завоевывает позиции, потому что open source баз данных все больше. Поэтому вопрос взаимодействия БД с ОС, очевидно, о базах данных в Linux. На это накладывается вечная проблема БД — производительность IO. Хорошо, что в Linux последние годы идет капитальный ремонт IO-стека и есть надежда на просветление.

Илья Космодемьянский (hydrobiont) работает в компании Data Egret, которая занимается консалтингом и поддержкой PostgreSQL, и про взаимодействие ОС и баз данных знает многое. В докладе на HighLoad++ Илья рассказал о взаимодействии IO и БД на примере PostgreSQL, но и показал, как с IO работают другие БД. Рассмотрел стек Linux IO, что нового и хорошего в нем появилось и почему все не так, как было пару лет назад. В качестве полезной памятки — контрольный список настроек PostgreSQL и Linux для максимальной производительности подсистемы IO в новых ядрах.
В видео доклада много английского языка, большую часть которого в статье мы перевели.

Зачем говорить про IO?

Быстрый ввод/вывод — это самая критичная вещь для администраторов баз данных. Все знают, что можно изменить в работе с CPU, что память можно расширить, но ввод/вывод способен все испортить. Если плохо с дисками, и слишком много ввода/вывода, то БД будет стонать. IO станет бутылочным горлышком.

Чтобы все хорошо заработало, настроить нужно всё.

Не только БД или только железо — всё. Даже высокоуровневый Oracle, который сам себе местами операционная система, требует настройки. Читаем инструкции в «Installation guide» от Oracle: поменять такие параметры ядра, поменять другие — настроек много. Не считая того, что в Unbreakable Kernel уже многое по умолчанию зашито на Oracle’овых Linux’ах.

Все потому, что эти технологии опираются на механизмы ОС. Для PostgreSQL и MySQL изменений требуется еще больше. DBA, который работает с PostgreSQL, MySQL или современными NoSQL, должен быть «Linux operation engineer» и крутить разные гайки ОС.

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

Ни железо, ни разработчики со статьями за ним не поспевают. Все многократно усложняется тем, что изначально разработка ядра Linux и переработка его стека отставали, а в последние годы пошли очень быстро.

Типичная база данных

Начнем с примеров для PostgreSQL — здесь буферизованный ввод-вывод. У него шаренная память, которая распределяется в User space с точки зрения ОС, и имеет такой же кэша в кэше ядра в Kernel space.

Основная задача современной БД:

  • поднимать в память странички с диска;
  • когда происходит какое-то изменение, помечать странички, как грязные;
  • записывать в Write-Ahead Log;
  • после этого синхронизировать память, чтобы она была консистентна диску.

В ситуации PostgreSQL это постоянное путешествие туда и обратно: из шаренной памяти, которой управляет PostgreSQL в Page Cache ядра, и дальше на диск через весь стек Linux. Если вы используете БД на файловой системе, она будет работать по этому алгоритму с любой UNIX-подобной системой и с любой БД. Отличия есть, но незначительны.

Но принцип тот же: с Direct IO или с Page Cache, но задача — как можно быстрее провести странички через весь стек ввода/вывода, каким бы он ни был. При использовании Oracle ASM будет по-другому — Oracle сам взаимодействует с диском. И проблемы могут возникнуть на каждом этапе.

Две проблемы IO

Пока все read only, проблем нет. Прочитали и, если достаточно памяти, все данные, которые нужно считать, помещаются в оперативную память. То, что при этом в случае PostgreSQL в Buffer Cache лежит то же самое, нас не очень волнует.

В этом случае придется гонять туда и обратно гораздо больше памяти. Первая проблема с IO — синхронизация кэша. Возникает, когда требуется запись.

В случае с PostgreSQL — надо еще хорошо настроить фоновое списывание грязных страниц в Linux, чтобы отправить все на диск. Соответственно, необходимо настроить PostgreSQL или MySQL, чтобы из шаренной памяти это все попало на диск.

Проявляется, когда нагрузка настолько мощная, что даже последовательно записываемый журнал упирается в диск. Вторая частая проблема — сбой записи Write-Ahead Log. В этой ситуации его тоже надо записать быстро.

В PostgreSQL мы работаем с большим количеством шаренных буферов, в базе есть механизмы для эффективной записи Write-Ahead Log, он до предела оптимизирован. Ситуация мало отличается от синхронизации кэша. Единственное, что можно сделать, чтобы сам лог писался эффективнее — поменять настройки Linux.

Основные проблемы работы с БД

Сегмент шаренной памяти может быть очень большим. Я начинал об этом рассказывать на конференциях в 2012 году. Тогда я говорил, что память подешевела, даже встречаются серверы с 32 Гб оперативной памяти. В 2019 уже и в ноутбуках может быть больше, на серверах все чаще стоит 128, 256 и т.д.

Банальная запись занимает время и ресурсы, а технологии, которые мы для этого используем, консервативны. Памяти действительно много. Механизмы в базах не то чтобы прямо по последнему слову техники. БД старые, их давно разрабатывают, они медленно эволюционируют.

Когда мы синхронизируем кэши, возникает большой поток IO, и встает еще одна проблема — мы не можем что-то покрутить и посмотреть на эффект. В научном эксперименте исследователи меняют один параметр — получают эффект, второй — получают эффект, третий. Синхронизация страниц в памяти с диском приводит к огромным операциям IO. Мы крутим какие-то параметры в PostgreSQL, настраиваем checkpoints — эффекта не увидели. У нас так не получится. Покрутить один параметр не получится — мы вынуждены настраивать сразу всё. Дальше опять настраивать весь стек, чтобы поймать хоть какой-то результат.

Если вы работали с PostgreSQL, возможно, видели checkpoints spikes, когда на графиках периодически возникает «пила». Большинство IO в PostgreSQL генерирует синхронизация страниц: checkpoints и другие механизмы синхронизации. Раньше многие сталкивались с такой проблемой, но сейчас есть мануалы, как это чинить, стало проще.

У PostgreSQL редко что-то упирается непосредственно в запись value. SSD сегодня сильно спасают ситуацию. Слишком много IO. Все упирается в синхронизацию: когда происходит checkpoint, вызывается fsync и происходит как бы «наезд» одного checkpoint на другой. Один checkpoint еще не закончился, не выполнил все свои fsyncs, а уже заработал другой checkpoint, и началось!

Это многолетняя история костылей под архитектуру БД. У PostgreSQL есть уникальная фишка — autovacuum. Иначе будут проблемы с DDL и с блокировками. Если autovacuum не справляется, обычно его настраивают, чтобы он работал агрессивно и не мешал остальным: много воркеров autovacuum, частое срабатывание по чуть-чуть, обработка таблиц быстро.

Но когда Autovacuum настроен агрессивно, он начинает «жевать» IO.

Если работа autovacuum накладывается на checkpoints, то большую часть времени диски утилизованы почти на 100%, и это источник проблем.

Она обычно меньше известна DBA. Как ни странно, бывает проблема Cache refill. Поэтому даже если у вас много-много оперативки — докупайте хорошие диски, чтобы стек прогревал кэш. Типичный пример: БД стартовала, и какое-то время все печально тормозит.

Проблемы начинаются не сразу после рестарта БД, а позже. Все это серьезно влияет на производительность. Они скопированы на диск, потому что нужно их синхронизовать. Например, прошел checkpoint, и много страниц запачкано во всей базе. На графиках будет видно, как Cache refill после каждого checkpoint вносит определенный процент в нагрузку. Потом запросы спрашивают новую версию страниц с диска, и база проседает.

В Oracle с ним проще, а в PostgreSQL — проблема. Самая неприятное во вводе/выводе БД — Worker IO. Когда каждый воркер, к которому вы обращаетесь с запросом, начинает генерировать свой IO.

Например, бывает что все буферы шаренные, они все попачканы, checkpoints еще не было. Причин проблем с Worker IO много: не хватает кэша, чтобы «провести» новые страницы с диска. Для этого сначала нужно сохранить это все на диск. Чтобы воркер выполнил простейший select, нужно откуда-то взять кэш. У вас не специализированный процесс checkpointer, и воркер начинает выполнять fsync, чтобы освободить и заполнить его чем-то новым.

Можно на уровне Linux где-то оптимизировать, но в PostgreSQL это аварийная мера. Тут возникает еще большая проблема: воркер — неспециализированная вещь, и весь процесс вообще никак не оптимизирован.

Основная проблема IO для БД

Какую проблему мы решаем, когда что-то настраиваем? Мы хотим максимизировать путешествие грязных страниц между диском и памятью.

Типичный случай — вы видите очень большой load average. Но часто бывает так, что эти вещи напрямую не касаются диска. Потому что кто-то ждет диск, и все остальные процессы тоже ждут. Почему так? Вроде бы нет явной утилизации дисков по записи, просто что-то там заблокировало диск, а проблема все равно с вводом/выводом.

Проблемы ввода-вывода для баз данных не всегда касаются только дисков.

В этой проблеме задействовано все: диски, память, CPU, IO Schedulers, файловые системы и настройки самой БД. Сейчас пройдемся по стеку, посмотрим, что с этим делать, и что хорошего в Linux изобрели, чтобы все работало лучше.

Диски

Долгие годы диски были страшно медленные и никто не занимался latency или оптимизацией стадий перехода. Оптимизация fsyncs не имела смысла. Диск вращался, по нему как по грампластинке ездили головки, и fsyncs был настолько длинный, что проблемы не всплывали.

Память

Без настройки БД топ запросов смотреть бесполезно. Вы настроите достаточное количество шаренной памяти и т.д, и у вас будет новый топ запросов — настраивать придется заново. Здесь та же история. Весь стек Linux был сделан из этого расчета.

Пропускная способность и latency

Максимизировать производительность IO через максимизацию пропускной способности легко до определенного момента. Придуман вспомогательный процесс PageWriter в PostgreSQL, который разгружал checkpoint. Работа стала параллельной, но задел для добавки параллелизма еще есть. А минимизировать latency — это задача последней мили, для которой нужны супертехнологии.

Когда они появились, latency резко снизилась. Этими супертехнологиями стали SSD. Проблемы требуют решения. Зато на всех остальных этапах стека проявились проблемы: и со стороны производителей БД, и со стороны производителей Linux.

Многие методы оптимизации ввода-вывода эпохи вращающихся дисков не так хороши для SSD. Разработка БД была сосредоточена вокруг максимизации пропускной способности, также как и разработка ядра Linux.

Смотрели performance-тесты от производителя с большим количеством разных IOPS, а БД лучше не становилось, потому что БД не только и не столько про IOPS. В промежутке мы были вынуждены ставить подпорки для текущей инфраструктуры Linux, но уже с новыми дисками. Но если мы не знаем latency, не знаем ее распределение, то ничего не можем сказать о производительности. Часто бывает, что мы можем пропустить 50 000 IOPS в секунду, и это хорошо. В какой-то момент БД начнет делать checkpoint, и latency резко увеличится.

Virtual IO свойственна неравномерность latency, что, конечно, тоже влечет проблемы. Долгое время, как и сейчас, это было большой проблемой производительности на виртуалках для баз данных.

Стек IO. Как это было раньше

В БД настроили так, чтобы все работало как надо. Здесь есть User space — та память, которая управляется самой базой данных. Дальше все неизбежно идет через Page Cache либо через интерфейс Direct IO попадает в Block Input/Output-слой. Об этом можно сделать отдельный доклад, и даже не один.

Через него съезжают странички, которые были в Buffer Cache, как они изначально были в БД, то есть блоками. Представьте себе интерфейс файловой системы. Есть C-структура, которая описывает блок в ядре. Block IO-слой занимается следующим. Под BIO-слоем находится слой реквестов. Структура берет эти блоки и собирает из них векторы (массивы) запросов на ввод или вывод. На этом слое собираются векторы и отправятся дальше.

Без перехода было не обойтись. Долгое время эти два слоя в Linux были заточены на эффективную запись на магнитные диски. Нужно эти блоки собрать в векторы, которые удобно записать на диск, чтобы они где-то рядом лежали. Есть блоки, которыми удобно управлять из базы данных. Чтобы это эффективно работало, придумали Elevators, или Schedulers IO.

Elevators

Elevators преимущественно занимались тем, что объединяли и сортировали векторы. Все для того, чтобы в блочный драйвер SD — драйвер квазидиска — блоки для записи приехали в удобном для него порядке. Драйвер производил трансляцию из блоков в свои секторы и писал на диск.

Проблема была в том, что нужно было делать несколько transitions, и на каждом реализовывать свою логику оптимального процесса.

Elevators: до ядра 2.6

До ядра 2.6 существовал Linus Elevator – самый примитивный IO Scheduler, который написан сами догадаетесь кем. Долгое время он считался абсолютно незыблемым и хорошим, пока не разработали что-то новое.

Он объединял и сортировал в зависимости от того, как эффективнее записать. Linus Elevator имел много проблем. Если внезапно нужно одновременно эффективно прочитать, а он уже повернут не так — читается с такого диска плохо. В случае с вращающимися механическими дисками это приводило к тому, что возникал «starvation»: ситуация, когда эффективность записи зависит от поворота диска.

Поэтому начиная с ядра 2. Постепенно стало понятно, что это неэффективный путь. 6 стал появляться целый зоопарк schedulers, который предназначался для разных задач.

Elevators: между 2.6 и 3

Многие путают эти schedulers с schedulers операционной системы, потому что у них похожие названия. CFQ — Completely Fair Queuing — это не то же самое, что schedulers ОС. Просто названия похожи. Он был придуман как универсальный scheduler.

У баз данных очень плохо с универсальностью. Что такое универсальный scheduler? Как вы считаете, у вас усредненная нагрузка или, наоборот, уникальная? Там происходит все подряд: мы слушаем музыку, играем, набираем текст. Универсальную нагрузку можно представить себе как работу обычного ноутбука. Для этого как раз универсальные schedulers и писались.

Когда мы хотим послушать музыку в аудиопроигрывателе, IO для проигрывателя занимает очередь. Основная задача универсального scheduler: в случае Linux для каждого виртуального терминала и процесса создать свою очередь запросов. Если мы хотим забэкапить что-нибудь с помощью команды cp, этим занимается что-то еще.

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

С учетом устройства конкретной дисковой подсистемы мы собираем векторы блоков, чтобы записать их оптимальным способом. Постепенно появился deadline scheduler — работает хитрее, но базово это merge и сортировка для вращающихся дисков. У него было меньше проблем со «starvation», но они там были.

Включая scheduler noop, мы фактически отключаем scheduling: нет сортировок, мерджинга и подобных вещей, которыми занимались CFQ и deadline. Поэтому ближе к третьим ядрам Linux появился noop или none, который гораздо лучше работал с распространившимися SSD.

Чем больше этих элементов напихать на одной PCIe плате, тем эффективнее все будет работать. Это лучше работает с SSD, потому что SSD от природы параллельный: у него есть ячейки памяти.

Это все заканчивается воронкой. Scheduler из каких-то своих потусторонних, с точки зрения SSD, соображений, собирает какие-то векторы и куда-то их отправляет. Поэтому простое отключение, когда векторы едут как попало без всякой сортировки, срабатывало лучше с точки зрения производительности. Так мы убиваем параллелизм SSD, не используем их на полную катушку. Из-за этого считается, что на SSD лучше идут random read, random write.

Elevators: 3.13 и далее

Начиная с ядра 3.13 появился blk-mq. Немного раньше был прототип, но в 3.13 впервые появилась рабочая версия.

Это замена request layer в ядре. Начинался blk-mq как scheduler, но scheduler’ом его назвать сложно — архитектурно он стоит отдельно. Потихоньку разработка blk-mq привела к серьезной переработке всего стека ввода/вывода Linux.

В зависимости от того, сколько параллельных потоков ввода/вывода можно использовать, есть честные очереди, через которые мы просто пишем as is на SSD. Идея такая: давайте для ввода/вывода использовать нативную возможность SSD делать эффективный параллелизм. Для каждой CPU есть своя очередь для записи.

Нет причин его не использовать. В настоящий момент blk-mq активно развивается и работает. В современных ядрах, от 4 и выше, от blk-mq выигрыш ощутим — не 5-10%, а существенно больше.

blk-mq — наверное, лучшая опция для работы с SSD.

В нынешнем виде blk-mq напрямую завязан на NVMe driver Linux. Есть не только драйвер для Linux, но и драйвер для Microsoft. Но именно идея сделать blk-mq и NVMe driver — это та самая переработка стека Linux, от которой базы данных здорово выиграли.

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

Система нацелена на замену всего уровня запросов. Драйвер blk-mq и NVMe больше, чем планировщик.

Давайте в нее погрузимся, чтобы понять, что это такое. Спецификация NVMe большая, поэтому все подробности не рассмотрим, а только пробежимся по ним.

Старый подход к elevators

Простейший случай: есть CPU, есть его очередь, и мы как-то идем на диск.

Есть несколько CPU и несколько очередей. Более продвинутые Elevators работали по-другому. Каким-то образом, например, в зависимости от того, от какого родительского процесса отпочковались воркеры БД, IO попадает в очередь и на диски.

Новый подход к elevators

blk-mq — это абсолютно новый подход. Каждый CPU, каждая NUMA-зона добавляет свой ввод/вывод в свою очередь. Дальше данные попадают на диски, не важно, как подключенными, потому что драйвер новый. Там нет SD драйвера, который оперирует понятиями цилиндров, блоков.

В какой-то момент все вендоры RAID-массивов стали продавать аддоны, которые позволяли обходить кэш RAID. Был переходный период. Они отключали применение SD-драйвера для своих продуктов, как blq-mq. Если подключены SSD — напрямую писать туда.

Новый стек с blk-mq

Так выглядит стек в новом виде.

Например, БД сильно отстают. Сверху все осталось также. Там есть тот самый blk-mq, который заменяет слой запросов, а не scheduler. Ввод/вывод от БД точно также, как раньше, попадает в Block IO-слой.

13 примерно на этом заканчивалась вся оптимизация, но в современных ядрах используются новые технологии. В ядре 3. В современных четвертых ядрах Linux зрелыми считаются два schedulers IO — это Kyber и BFQ. Стали появляться специальные schedulers для blk-mq, которые рассчитаны на более сильный параллелизм. Они рассчитаны на работу с параллелизмом и с blk-mq.

Они абсолютно непохожи, хотя один вырос из другого. BFQ — Budget Fair Queueing — это аналог СFQ. Каждое приложение и процесс имеет некоторую квоту на IO. BFQ — это scheduler со сложной математикой. Согласно этому бюджету у нас есть полоса, в которую пишем. Квота зависит от объема IO, который процесс/приложение генерирует. Если интересуетесь BFQ, есть масса статей, которые разбирают математику процесса. Насколько хорошо это работает — вопрос сложный.

Это как BFQ, но без математики. Kyber — это альтернатива. Его основная задача — принимать от CPU и отправлять. У Kyber небольшой scheduler по количеству кода. Kyber легковеснее и работает лучше.

У него нет очередного слоя конверсии, на который я жаловался, когда показывал, как раньше выглядел IO-стек. Важный момент для всего стека — blk-mq не смотрит в SD-драйвер. Блоки в цилиндры не пересчитываются. Из blk-mq в NVMe driver все приходит сразу в готовом виде.

Сначала появились SSD, которые хорошо работают — появилась возможность перерабатывать этот слой. В новом подходе возникло несколько интересных моментов — резко упала latency, и этого слоя в том числе. Сейчас о них поговорим. Как только мы перестали конвертировать туда-сюда, выяснилось, что и в NVMe-слое, и в blk-mq есть свои узкие места, которые тоже надо оптимизировать.

Диаграмма стека Linux IO

У Томаса Кренна есть постоянно обновляемая и актуальная диаграмма стека ввода/вывода Linux.

На диаграмме видно, кто над кем стоял, взаимоотношения между драйверами, какие Elevators, часть какого слоя.

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

Спецификация NVM Express

NVM Express или NVMe — это спецификация, набор стандартов, которые помогают эффективнее использовать SSD. Спецификация хорошо реализована в Linux. Linux — одна из движущих сил стандарта.

Драйвер этой версии по спецификации может пропускать в районе 20 ГБ/с на один SSD блок, а NVMe пятой версии, которой еще нет, — до 32 ГБ/с. Сейчас в продакшне третья версия. У SD драйвера нет ни интерфейсов, ни механизмов внутри, чтобы обеспечить такую пропускную способность.

Эта спецификация существенно быстрее, чем все, что было раньше.

Когда-то БД писались под вращающиеся диски и ориентированы на них — у них есть индексы в виде B-дерева, например. Возникает вопрос: готовы ли к NVMe базы данных? Способны ли БД прожевать такую нагрузку?

Недавно в рассылке PostgreSQL была пара коммитов про pwrite() и подобные вещи. Пока нет, но они адаптируются. Конечно, хотелось бы, чтобы взаимодействия было больше. Разработчики PostgreSQL и MySQL взаимодействуют с разработчиками ядра.

Последние разработки

За последние полтора года в NVMe добавили IO polling.

Потом появились SSD, которые гораздо быстрее. Сначала были вращающиеся диски с высокой latency. Но возник косяк: идет fsync, начинается запись, и на очень низком уровне — глубоко в драйвере, отправляется запрос непосредственно в железку — запиши.

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

В первых версиях возрастание скорости ввода/вывода достигло 50% из-за того, что не ждем прерывания, а активно спрашиваем железку о записи. Поскольку SSD записывает очень быстро, вынужденно появился механизм опроса железки о записи. Этот механизм и называется IO polling.

В версии 4. Он был представлен в последних версиях. Они уже официально в ядре, их можно использовать. 12 появились IO schedulers, специально заточенные для работы с blk-mq и NVMe, про которые я сказал — Kyber и BFQ.

В основном производители облаков и виртуалок контрибьютят в эту разработку. Сейчас уже в пригодном для использования виде есть так называемый IO tagging. К этому пока не готовы БД, но следите за обновлениями. Грубо говоря, ввод от определенного приложения можно затэгать и дать ему приоритет. Думаю, скоро это будет мейнстримом.

Примечания по Direct IO

В PostgreSQL не поддерживается Direct IO и есть ряд проблем, которые мешают включить поддержку. Сейчас это поддерживается только для value, и только если не включена репликация. Требуется написать очень много ОС-специфичного кода, и пока что все воздерживаются от этого.

В Oracle и MySQL Direct IO активно используется. Несмотря на то, что Linux сильно ругается на идею Direct IO и на то, как он реализован, все БД туда идут. PostgreSQL — единственная БД, которая Direct IO не переносит.

Чек-лист

Как защититься в PostgreSQL от сюрпризов fsync:

  • Настроить checkpoint, чтобы они были реже и больше.
  • Настроить background writer, чтобы они помогал checkpoint.
  • Оттюнить Autovacuum, чтобы не было лишнего паразитного ввода/вывода.

Еще есть месяц, чтобы подать заявку на доклад, но первые доклады в программу мы уже приняли. Согласно традиции в ноябре ждем профессиональных разработчиков высоконагруженных сервисов в Сколково на HighLoad++. Подпишитесь на рассылку и будете узнавать о новых темах из первых рук.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть