Расшифровка доклада «Noisia — генератор аварийных и нештатных ситуаций в PostgreSQL» с конференции PGConf.Online 2021.

В докладе рассказывается про утилиту Noisia которая используется для намеренного создания аварийных ситуаций в СУБД PostgreSQL. Докладчик (то есть я) рассказывает о функциональности и назначении утилиты и о разных способах сломать Postgres.

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

Сам доклад и видео здесь.

image

Добрый день! Меня зовут Алексей Лесовский и ближайшие 20 минут я буду рассказывать про инструмент, который позволяет намеренно создавать аварийные ситуации в PostgreSQL.

image

Стандартный слайд любого доклада – рассказ о себе.

  • Я PostgreSQL DBA, работаю в компании Data Egret. Компания занимается PostgreSQL-консалтингом, технической и практической поддержкой PostgreSQL и всего что вокруг него.
  • Кроме того, я системный администратор и делаю много разных вещей, связанных с системным администрированием в Linux и всего того что может на ней работать.
  • Люблю мониторинги, люблю разбираться со статистикой, поиском и устранением проблем. Люблю разного рода визуализации.

image

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

image

Если совсем коротко, о чем будет доклад, то он будет о том, как контролируемо создавать аварийные ситуации в Postgres и как их контролируемо завершать.

image

Зачем это могло понадобиться?

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

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

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

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

image

В итоге появилась Noisia. Она написана на языке Go, умеет работать только в Linux (другие ОС просто не пробовал). Она представляет собой консольный инструмент — её можно запускать без графических оболочек, указываете ей Postgres куда следует подключиться — она подключается туда и создает там аварийные ситуации по вашему запросу. Всё просто.

Написана утилита на языке Go и также дополнительно её функциональность представлена в виде пакета (package). Вы можете встраивать ее в свои собственные приложения и использовать функциональность, которая доступна из этого пакета уже в своих собственных утилитах и инструментах.

Очень важно отметить, что утилита позволяет создавать потенциально опасные и аварийные ситуации, которые могут нарушить работу БД, поэтому инструмент не предоставляет никаких гарантий с сохранением работоспособности БД. Используйте на свой страх и риск. Инструмент распространяется в свободной BSD-3 лицензии. Вы можете копировать код, модифицировать, предлагать свои изменения.

image

Давайте рассмотрим функциональность Noisia. На данный момент её относительно немного, но тем не менее любой DBA, наверняка сталкивался с этими проблемами и занимался их решением.

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

Давайте пройдемся по этим сценариям и я коротко расскажу, что это всё означает.

image

Самый простой сценарий – это воспроизведение транзакций, которые ничего не делают. В Postgres они называются idle транзакции. Суть в том, что приложение подключается к базе, открывает транзакцию, создает какие-то запросы и далее ничего не делает, не закрыв транзакцию. В зависимости от рабочей нагрузки, такие idle транзакции отрицательно сказываются на производительности БД. Если транзакции не завершается в течение долгого времени это может привести к аварийной ситуации.

Соответственно, Noisia позволяет создавать такие транзакции и позволяет указывать продолжительность такой транзакции: минимальную и максимальную. Плюс есть возможность указывать конкурентность и продолжительности теста.

Конкурентность и продолжительность – это общие характеристики любого сценария в Noisia.

image

Следующий сценарий – это транзакции, которые завершаются откатом (ROLLBACK). Postgres так устроен, что все запросы, которые заканчиваются с ошибкой, увеличивают счетчик откатов (см. pg_stat_database.xact_rollback). По сути, этот счетчик можно рассматривать как счетчик ошибок. Таким образом, можно с помощью Noisia генерировать сценарий, где будет создаваться большое количество ошибок (и стриггерить алерты в мониторинге).

Так же можно регулировать минимальную и максимальную частоту откатов за период времени, конкурентность и продолжительность теста.

image

Можно пойти еще дальше и попробовать более сложные кейсы, например сгенерировать заблокированную транзакцию. Здесь сценарий чуть сложнее и охватывает взаимодействие нескольких транзакций между собой. Noisia создает несколько транзакций, которые мешают друг другу — они начинают бороться за один и тот же ресурс (как правило это строки). Одна транзакция пытается обновить строку или несколько строк и уходит в idle; другая транзакция пытается также обновить эти же строки. Но так как первая транзакция еще не завершилась, вторая транзакция встает в ожидание и ждет завершения первой транзакции.

В этом сценарии также можно регулировать минимальное и максимальное время блокировки, конкурентность и продолжительность теста.

image

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

Здесь также можно указывать конкурентность, длительность. И можно регулировать количество взаимо-блокировок, которые нужно создать.

image

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

Если Postgres оценивает что памяти не хватит, то создается временный файл на диске. Все вычисления и результаты этой операции уже делаются с использованием этого файла. За счет этого запрос становится медленней и появляется дополнительная нагрузка на диск. Другие запросы которым требуется доступ к диску могут начать работать медленнее.

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

Сценарий позволяет указывать количество временных создаваемых файлов и их размер.

И также конкурентность и длительность.

image

Другая функциональность уже связана с завершением процессов. Эта функция мне нравится больше, её я делал с воодушевлением.

Сценарий сводится к тому, что Noisia с определенной случайностью завершает работу Postgres’овых процессов.

Обязательно можно указывать масштаб разрушений частоту и интервал между завершениями процессов.

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

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

Есть еще более жестокий режим – это не жалеть системные процессы СУБД. У Postgres есть различные системные процессы типа autovacuum, checkpointer, процессы связанные с репликацией. И по умолчанию эти процессы исключаются из выборки, но можно сказать Noisia, что эти процессы тоже можно завершать. И можно будет посмотреть, как ведет себя Postgres.

image

Последний доступный сценарий – это переполнение лимита подключений. Известно, что у Postgres есть стандартный лимит на количество доступных подключений. По-умолчанию 100. Соответственно, к базе может подключиться только 100 пользователей. И если переполнить этот лимит, то к базе никто не сможет подключиться и она будет не доступна для работы приложений.

Соответственно, Noisia тоже позволяет произвести этот сценарий и переполнить этот лимит и вызвать отказ в обслуживании.

Это достаточно простой сценарий, его было легко сделать. И у него единственный параметр – это длительность самого сценария.

image

Пока это вся имеющаяся функциональность. Использовать можно на свой страх и риск. Возникают вопросы: «Зачем это может понадобиться?» и «Где это можно использовать?».

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

Другой пример использования – это тестирование и chaos engineering. Можно проводить испытания тестовых и staging стендов, и наблюдать как ведут себя приложения, когда в базах данных возникают проблемы.

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

image

Про планы на будущее. Планов довольно много.

Начну с простого. Хочется эмулировать различные рабочие нагрузки. Как вариант – можно симулировать последовательные проходы (sequential scan). Это паттерн доступа к таблице, когда запрос читает таблицу с самого начала пока не прочитает необходимое количество строк. Такая нагрузка плоха тем, что вызывает нагрузку на диск. Либо если таких запросов много и они читают много небольших таблиц, то это вызывает расходование процессорных ресурсов. Такая нагрузка довольно ресурсоемкая, её полезно обнаруживать и устранять, например, через постройку соответствующих индексов.

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

image

Другой план – это сделать сценарий, который позволяет исчерпать память на сервере. Сценарий заключается в том, что если у нас, например, есть swap-область на сервере, мы с помощью специального запроса или, может быть, с помощью постоянного выделения памяти стараемся забрать всю свободную память в системе, в том числе и из кэша ОС. Таким образом пытаемся загнать систему либо в swap, либо вызвать OOM Killer. В результате хочется протестировать, как Postgres будет работать в этих условиях.

Если у нас система будет активно использовать swap, то система и у приложений работающих с БД будет падать производительность — запросы будут работать медленно.

Если придет OOM Killer, то он, скорее всего, сложит Noisia, либо базу, либо какие-то другие системные процессы.

В общем тут мы смотрим, насколько база устойчива к сбоям.

image

Другой сценарий, который тоже можно взять из предыдущего, когда приходит OOM Killer и завершает процесс принадлежащий Postgres, то БД переходит в аварийный режим. СУБД нужно отключить всех клиентов, запустить аварийное восстановление, прочитать WAL журналы и после этого он уже можно принимать подключения.

В этот момент БД недоступна и не может обслуживать подключения. Это тоже аварийная ситуация и будет интересно посмотреть, как приложения работают в случае, если Postgres находится в таком режим восстановления.

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

image

Также есть план сделать сценарий, который связан с работой репликации, т. е. можно приостановить репликацию, чтобы накопился какой-то лаг репликации. Можно отстреливать системные процессы, которые связаны с работой этой репликации. На мастере – это wal sender, на реплике – это wal receiver.

С помощью такого сценария можно тестировать кластеры, также тестировать работу сценариев связанных с переключением ролей (switchover, failover). Можно посмотреть, как они будут отрабатывать при таких аварийных ситуациях.

image

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

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

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

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

Это самая сложная штука, о которой я думал и на которую есть планы. Я надеюсь, что я ее в итоге сделаю.

image

Это весь мой доклад. Привожу ссылку на проект. Называется он Noisia. Это дань уважения нидерландской группе, которая играет Drum & Bass. Заходите на страницу, ставьте звезды. В дискуссиях оставляйте свои комментарии. Если есть какие-то идеи, предлагайте, будет время я обязательно их рассмотрю, оценю и как-то, может быть, пообщаемся на эту тему.

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

image

На этом все. Большое спасибо! Задавайте вопросы.

Вопросы



Вопрос: Спасибо, Алексей, за интересный доклад про Noisia – генератора аварийных и нештатных ситуаций в Postgres. Алексей готов отвечать на ваши вопросы, которые вы можете задавать в чате. И первый вопрос: «В Go не очень качественные драйверы Postgres. Не используя стандартные Libpq, были проблемы с работой через PgBouncer. Почему был выбран Go, а не Python?».

Ответ: Начну отвечать с конца, почему был выбран Go, а не Python. Я являюсь разработчиком утилиты pgCenter. Она была написана на C. И я столкнулся с тем, что мне довольно сложно писать на C, потому что я не профессиональный разработчик. Мне нужно было выбрать какой-то более понятный мне язык. И Go показался мне гораздо более приятным, чем Python. Так что это исторически сложилось.

Потом, когда я уже делал следующие свои инструменты, и в том числе Noisia, выбора уже не было. Я сразу начал писать на Go.

Про драйверы. Сам по себе Libpq написан на C. Это нативный драйвер. И все остальные драйвера в других языках – это, по сути, какая-то оболочка поверх Libpq. И зритель верно подметил, что драйверы там не очень хорошего качества. Изначально я использовал драйвер lib/pq, но он ушел в стагнацию и сейчас, по-моему, не поддерживается.

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

Я недавно столкнулся с проблемой. Pgx тоже не реализует всю доступную функциональность, которая есть в Libpq. Например, я столкнулся с тем, что в обоих драйверах есть ограниченная поддержка переменных окружения. Libpq environment variables. В go'шных драйверах реализован лишь ограниченный список, может быть, из 15-20 переменных, а остальные просто не поддерживаются — нужно писать свои обработчики. Вот такая проблема.

Работаю с тем, что есть. Драйвера для Postgres я еще не научился писать. Может быть, когда-нибудь напишу.

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

Ответ: Можно мониторить эти проблемы с помощью классических систем мониторинга, которая у вас есть. Например, Zabbix, Prometheus, SaaS-мониторинги. И всегда можно нажать Ctrl+C и отменить запущенный сценарий.

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

Вопрос: Следующий вопрос: «Не боитесь, что проблемы, генерируемые Noisia, будут вызваны некорректной работой собственной реализации Postgres-протокола в Go-драйвере?».

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

Вопрос: Следующий вопрос от Сергея Новикова: «Планов на wraparound случайно нет? Или цель инструмента все-таки проверка работы приложения и автоматики при аварии, а не тренировка DBA?».

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

Вопрос: Максим Юрьевич Шерстюк: «Noisia можно скачать и попробовать?».

Ответ: Да, можно скачать. На GitHub-странице проекта есть релизы, там есть targz-архивы, rpm и deb пакеты для установки пакетными менеджерами.

Вопрос: Роман Павлович Фролов спрашивает: «Будет ли реализованы сценарии, связанные с настройками ядра Linux, Sysctl –a, например. И хочу знать, как специфические настройки ядра влияют на работу PostgreSQL?».

Ответ: С влиянием sysctl на работу системы все не так просто. Тут все зависит от рабочей нагрузки. И это скорей уже отдельная тема, которая не относится напрямую к докладу. То, как будет себя вести производительность Postgres, очень сильно зависит от того, какой workload, какие запросы и от ресурсов самого сервера. По опыту, Sysctl-настройки в большинстве рабочих нагрузок практически не влияют на нее. Они имеют значения только в краевых ситуация при очень специфичных и пиковых нагрузках.

Самый простой пример, который приходит в голову, это объем грязных страниц виртуальной памяти (dirty pages и vm.dirty_* параметры). В нормальной ситуации их менять практически не приходится — достаточно значений по-умолчанию. И эти настройки нужно менять только, если в Postgres в короткий промежуток времени приходит большой объем данных на запись и нужно справиться с этим объем так чтобы не просадить производительность запросов.

Но на практике, большинство встречающихся нагрузок – это 99 % чтение и 1 % запись. Здесь достаточно тех значений sysctl что выставлены по-умолчанию. Однако, есть 5-10 ключей, которые мы меняем от дефолтных. И этого хватает когда наступают эти краевые ситуации.