[Перевод] Роковой каскад: JIT, и как обновление Postgres привело к 70% отказов на национальном сервисе критической важности

image-loader.svg

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

Сайт мониторинга ситуации по коронавирусу Соединенного Королевства — основной сервис отчетности во время пандемии COVID-19 для всей страны. Он испытывает нагрузку порядка 45–50 миллионов запросов в день и относится к национальным сервисам критической важности.

Мы работаем в соответствии с архитектурой active-active, что значит, у нас есть минимум две, часто — три экземпляра каждого сервиса, которые запущены в разных географических локациях.

Есть только одно исключение — наша база данных. Сервис работает с использованием специальной версии PostgreSQL: Hyperscale Citus. Тот факт, что наша база данных не соответствует архитектуре active-active — это не следствие того, что мы не знаем, как делать реплики для чтения, скорее — результат логистических проблем, обсуждение которых выходит за рамки этой статьи.

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

Замечательное обновление

СУБД PostgreSQL 14 была официально выпущена 30 сентября 2021 года. Помимо всего остального, новая версия предлагала расширенную модель безопасности и прирост производительности запросов к сегментированным таблицам.

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

Но благодаря отличной работе команды Azure Postgres, наши ощущения оказались ложными и новое обновление стало доступным для облачных СУБД в Azure в течение 24 часов.

Это было огромное достижение команды Azure Postgres — новая веха в поддержке облачных сервисов баз данных. И это обновление оказалось даже лучше, чем мы ожидали, потому что оно также предлагало новую версию модуля Citus 10. В этой версии было исправлено несколько багов, которые мы нашли в предыдущем релизе.

Вместе с командой Azure Postgres (Citus), мы развернули новую версию нашего окружения для разработки вечером в среду, 13 октября 2021. Все прошло очень гладко, мы наблюдали за всем в течение 48 часов и сделали несколько нагрузочных тестов перед тем, как запустить развертывание на промышленное окружение в полночь субботы, 16 октября, 2021. Процесс развертывания закончился около 11 утра в субботу без проблем.

Все было прекрасно и пахло розами.

Что ещё произошло?  

На следующей после обновления неделе мы сделали несколько изменений в сервисе:

Второе обновление было нацелено на:

  • уменьшение зависимости от БД;

  • уменьшение времени ответа во время пиковой нагрузки;

  • использование Redis для дорогостоящих запросов;

  • использование решения из нашего ETL для предварительного заполнения кэша

Неделя перед судной ночью

До конца недели мне в Твиттере написали несколько человек и сообщили, о росте количества ошибок в запросах ко второй версии API (APIv2).

Самое странное, что они говорили, это то, что было невозможно скачать новые данные через API после релиза, т. е. в 4 вечера.

Первое, что я делаю в таких случаях — проверяю сервис API и наши экземпляры [PGBouncer](https://www.pgbouncer.org/), чтобы убедится, что с ними все в порядке. Затем я обычно очищаю кэш данных для APIv2, и это обычно решает проблему.

Количество жалоб ко мне в Твиттере увеличилось. Люди теперь жаловались на увеличивающиеся задержки при использовании нашего [GenericAPI](https://coronavirus.data.gov.uk/details/developers-guide/generic-api). Это беспокоило меня, потому что, в отличие от APIv2, который мы активно замедляем и который предназначен для загрузок очень больших объемов данных, Generic API был [написан на Go](https://github.com/publichealthengland/coronavirus-dashboard-generic-apis) и использует сильно оптимизированные запросы для быстрого ответа. В то время, как загрузка данных через APIv2 может занять 3 или 4 минуты, средняя задержка ответа для GenericAPI — 30–50 миллисекунд.

Я ещё раз проверил инфраструктуру и даже откатил несколько изменений Generic API, чтобы убедиться, что это ошибка не из-за этих недавних, хотя и небольших, изменений. Но нет.

Катастрофический выходной 

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

Я подчеркивал ранее, что все соединения с БД происходят через пул соединений (PGBouncer), который развернут на двух мощных виртуальных машинах с 64 Гб памяти и 16 процессорными ядрами. (Standard D16 v4), которые управлялись с использованием Azure VM Scale Set.

Почему мы используем PGBouncer?

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

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

PGBouncer, да и в целом пулы соединений, смягчают ситуацию тем, что они постоянно поддерживают фиксированное число соединений к БД (986 в нашем случае) и предоставляют короткоживущие соединения их клиентским приложениям, а затем возвращают результат запроса клиенту. Короче говоря, 986 соединений превращаются в 5000.

Как долго поддерживается соединение между клиентом и PGBouncer? Это зависит от настроек, но в нашем случае оно поддерживается для одной транзакции.

Я только недавно включил ускоренное сетевое соединение для экземпляров PGBouncer, так что у них не было причины падать, но, тем не менее они показывались как «сбойные»

Проверка состояния для PGBouncer сделана в виде пинга Postgres. Это означает, что (помимо исчерпания ресурсов процессора или памяти), есть только одна причина для того, чтобы они сбоили: проблема с СУБД.

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

image-loader.svg

Все было в порядке

© Habrahabr.ru