Эксплуатация баз данных
Эксплуатационные вопросы баз данных для DevOps — рост хранилища, распухание и реагирование на алерт о нехватке места на проде.
9 вопросов
SeniorДизайнОчень частоВы эксплуатируете нагруженный прод-PostgreSQL с данными, которые бизнес не может потерять. Спроектируйте стратегию бэкапа и восстановления так, чтобы пережить и потерю всего хоста, и логическую катастрофу — например плохую миграцию, повредившую таблицу в 14:32, — с минимальной потерей данных. Опишите выбор между логическими бэкапами (дамп вроде pg_dump) и физическими (base backup файлов данных), полными против инкрементальных, и как непрерывное архивирование write-ahead-log (WAL) даёт point-in-time recovery (PITR), чтобы восстановиться прямо до 14:32. Объясните, где живут бэкапы и WAL, чтобы один отказ не уничтожил и данные, и их бэкапы, почему реплика — не бэкап и как вы доказываете, что стратегия работает — ведь бэкап, который вы ни разу не восстанавливали, не бэкап. Назовите целевые RPO/RTO вашего дизайна.
Вы эксплуатируете нагруженный прод-PostgreSQL с данными, которые бизнес не может потерять. Спроектируйте стратегию бэкапа и восстановления так, чтобы пережить и потерю всего хоста, и логическую катастрофу — например плохую миграцию, повредившую таблицу в 14:32, — с минимальной потерей данных. Опишите выбор между логическими бэкапами (дамп вроде pg_dump) и физическими (base backup файлов данных), полными против инкрементальных, и как непрерывное архивирование write-ahead-log (WAL) даёт point-in-time recovery (PITR), чтобы восстановиться прямо до 14:32. Объясните, где живут бэкапы и WAL, чтобы один отказ не уничтожил и данные, и их бэкапы, почему реплика — не бэкап и как вы доказываете, что стратегия работает — ведь бэкап, который вы ни разу не восстанавливали, не бэкап. Назовите целевые RPO/RTO вашего дизайна.
Сочетайте физические и логические бэкапы. Периодически снимайте физический base backup и непрерывно архивируйте write-ahead log (WAL); проигрывание base backup плюс WAL до выбранного момента — point-in-time recovery (PITR) — восстанавливает состояние до плохой миграции, давая низкий RPO. Держите логические дампы для восстановления по таблицам. Храните бэкапы и WAL вне хоста, чтобы один отказ не уничтожил оба, и тестируйте restore, ведь непроверенный бэкап — не бэкап. Реплика — не бэкап: она копирует и повреждение.
Типичные ошибки
- ✗Считать живую реплику заменой настоящих бэкапов
- ✗Держать только ночные дампы, из-за чего PITR к точному моменту невозможен
- ✗Хранить бэкапы на том же хосте, чтобы один отказ уничтожил оба
Уточняющие вопросы
- →Как непрерывное архивирование WAL позволяет восстановиться на 14:31, а не на прошлую ночь?
- →Почему бэкапы и WAL должны жить на отдельном от primary хранилище?
JuniorТеорияЧастоЧто такое пул соединений и зачем базе данных нужен pooler вроде PgBouncer?
Что такое пул соединений и зачем базе данных нужен pooler вроде PgBouncer?
Открыть соединение с БД дорого — TCP-рукопожатие, аутентификация и backend-процесс на соединение, — и каждое соединение занимает память, поэтому сервер ограничивает их через max_connections. Пул соединений держит набор соединений и выдаёт их многим короткоживущим клиентам, а не открывает новое каждый раз. Pooler вроде PgBouncer стоит перед базой, чтобы сотни инстансов делили ограниченное число реальных соединений, избегая исчерпания max_connections.
Типичные ошибки
- ✗Ставить
max_connectionsочень высоко вместо добавления pooler - ✗Считать, что открывать соединение на каждый запрос дёшево
- ✗Думать, что pooler кэширует результаты запросов, а не переиспользует соединения
Уточняющие вопросы
- →Чем режимы transaction-mode и session-mode пула различаются в том, что можно делить?
- →Какие симптомы показывают, что базе не хватает
max_connections?
JuniorТеорияЧастоЧто такое репликация базы данных (leader/follower) и зачем команды реплицируют БД?
Что такое репликация базы данных (leader/follower) и зачем команды реплицируют БД?
Репликация держит копии (followers/реплики) базы синхронными с primary (leader), потоково передавая изменения leader на них. Leader принимает записи; каждый follower применяет тот же поток изменений и может обслуживать чтения. Реплицируют ради масштабирования чтений (разгрузить чтения на реплики), высокой доступности (повысить follower, если leader упал) и тёплой копии для аварийного восстановления. Реплика — не бэкап: она честно копирует и ошибки.
Типичные ошибки
- ✗Думать, что живая реплика заменяет бэкапы
- ✗Считать, что followers принимают клиентские записи, как leader
- ✗Путать репликацию (копии для чтений/HA) с шардингом (дробление данных)
Уточняющие вопросы
- →Почему реплика, честно скопировавшая ошибочный DELETE, не заменяет бэкап?
- →В чём разница между репликацией и шардингом базы данных?
MiddleТеорияЧастоКак работает автоматический failover базы данных и что такое split-brain?
Как работает автоматический failover базы данных и что такое split-brain?
Для высокой доступности БД держит primary с репликами и менеджер кластера (Patroni), который проверяет primary и при отказе повышает реплику, переводя клиентов, — автоматический failover. Опасность — split-brain: если старый primary лишь изолирован, а не мёртв, два узла оба пишут и расходятся. Против этого нужен кворум: большинство согласует, кто primary, и изолирует старый узел. Failover безопасен настолько, насколько свежа реплика.
Типичные ошибки
- ✗Думать, что два primary могут безопасно оба принимать записи при разделении
- ✗Опускать кворум, из-за чего сетевое разделение вызывает split-brain
- ✗Считать холодный, поднимаемый руками резерв высокой доступностью
Уточняющие вопросы
- →Почему кластеру на кворуме предпочтительно нечётное число участников?
- →Как клиенты находят новый primary после failover?
MiddleТеорияЧастоСинхронная против асинхронной репликации — какой компромисс вы принимаете в каждом случае?
Синхронная против асинхронной репликации — какой компромисс вы принимаете в каждом случае?
При асинхронной репликации leader фиксирует запись и сразу отвечает, реплицируя позже — быстро, но failover может потерять транзакции, не ушедшие на реплику, поэтому recovery-point objective (RPO) больше нуля. Синхронная репликация ждёт подтверждения от реплики, поэтому failover не теряет ничего (RPO около нуля), но каждая запись платит round-trip и застревает при медленной реплике. Берут async ради скорости, sync — для данных, которые нельзя терять.
Типичные ошибки
- ✗Верить, что реплика всегда держит каждый зафиксированный коммит после failover
- ✗Игнорировать, что синхронная репликация добавляет латентность записи
- ✗Считать, что у асинхронной репликации RPO равен нулю
Уточняющие вопросы
- →Как падение синхронной реплики застопоривает записи на leader?
- →Что означает RPO в несколько секунд для банка против хранилища метрик?
SeniorДизайнЧастоСпроектируйте высокодоступную топологию для одной реляционной БД, которая сегодня — один primary-сервер, единая точка отказа, чья потеря кладёт продукт. БД должна пережить потерю primary с автоматическим восстановлением и минимальной потерей данных, а клиенты — попадать на новый primary без ручной переконфигурации. Опишите раскладку реплик (синхронный standby против асинхронных реплик чтения и что даёт каждая), как обнаруживается отказ и реплика повышается автоматически, как предотвратить split-brain, когда старый primary лишь изолирован сетью, а не мёртв (кворум и fencing), и как соединения приложения переводятся на повышенный узел (virtual IP, proxy или pooler соединений). Назовите целевые RPO/RTO вашей топологии и устранённую точку отказа.
Спроектируйте высокодоступную топологию для одной реляционной БД, которая сегодня — один primary-сервер, единая точка отказа, чья потеря кладёт продукт. БД должна пережить потерю primary с автоматическим восстановлением и минимальной потерей данных, а клиенты — попадать на новый primary без ручной переконфигурации. Опишите раскладку реплик (синхронный standby против асинхронных реплик чтения и что даёт каждая), как обнаруживается отказ и реплика повышается автоматически, как предотвратить split-brain, когда старый primary лишь изолирован сетью, а не мёртв (кворум и fencing), и как соединения приложения переводятся на повышенный узел (virtual IP, proxy или pooler соединений). Назовите целевые RPO/RTO вашей топологии и устранённую точку отказа.
Держите primary с синхронным standby (RPO около нуля) плюс асинхронные реплики для чтений. Менеджер кластера проверяет primary и при отказе повышает свежий standby. Требуйте кворум: большинство согласует, кто primary, и изолирует старый узел, чтобы разделение не оставило два primary, пишущих разом (split-brain). Переводите клиентов через proxy, virtual IP или pooler, чтобы они попадали на новый primary. Синхронный standby ограничивает потерю данных; автоматика — простой.
Типичные ошибки
- ✗Называть один primary высокодоступным, потому что сервер мощный
- ✗Полагаться на ручное повышение, из-за чего простой зависит от человека
- ✗Опускать кворум и fencing, допуская split-brain при разделении
Уточняющие вопросы
- →Почему синхронный standby даёт лучший RPO, чем одни асинхронные реплики?
- →Как proxy или virtual IP избавляет клиентов от необходимости знать, какой узел primary?
JuniorТеорияИногдаПочему файлы данных БД продолжают расти, даже когда число строк примерно постоянно?
Почему файлы данных БД продолжают расти, даже когда число строк примерно постоянно?
Большинство БД не освобождают место сразу при UPDATE/DELETE. MVCC в PostgreSQL пишет новую версию строки, а старую помечает мёртвой; пока не пройдёт очистка, мёртвые кортежи занимают место — это и есть bloat (распухание). Индексы и журналы записи добавляют к этому. Место освобождает фоновая очистка (autovacuum/VACUUM), так что файлы растут, если очистка отстаёт.
Типичные ошибки
- ✗Думать, что
DELETEсразу освобождает занятое место на диске - ✗Не знать, что MVCC хранит мёртвые версии строк до очистки
- ✗Забывать, что индексы и WAL тоже занимают растущее место
Уточняющие вопросы
- →Как autovacuum решает, что в таблице достаточно мёртвых кортежей для очистки?
- →Почему
VACUUMосвобождает место для повторного использования, но часто не сжимает файл на диске?
MiddleДебаггингИногдаНа хосте продакшен-БД срабатывает алерт о нехватке места на диске — диагностируйте и среагируйте
На хосте продакшен-БД срабатывает алерт о нехватке места на диске — диагностируйте и среагируйте
Сначала подтвердите через df/du, что съедает место — данные, bloat из мёртвых кортежей, WAL или логи. Немедленно: освободите безопасное место (ротация логов, архив старого WAL), чтобы БД осталась записываемой. Устойчивый фикс bloat: VACUUM (ANALYZE) освобождает место кортежей и обновляет статистику; разберитесь с отставшим autovacuum и увеличьте диск. Не удаляйте файлы каталога данных вручную.
Типичные ошибки
- ✗Удалять файлы в каталоге данных руками, повреждая БД
- ✗Пропускать триаж и действовать, не зная, что занимает место
- ✗Считать
VACUUMтюнером запросов, а не освобождением места
Уточняющие вопросы
- →Почему
VACUUMосвобождает место для повторного использования, а для сжатия файла нуженVACUUM FULL? - →Что может заставить autovacuum отставать на таблице с высокой нагрузкой изменений?
SeniorДизайнИногдаВам нужно изменить схему таблицы на 500 миллионов строк в нагруженной прод-БД — добавить колонку, заполнить её из существующих данных, затем переключить приложение на неё — без простоя и без блокировки живых чтений и записей. Спроектируйте миграцию. Объясните, почему наивный одиночный ALTER (добавление колонки NOT NULL с default или смена типа колонки) может взять долгую табличную блокировку, которая встаёт в очередь за работающими запросами и застопоривает всю таблицу, и как паттерн expand-contract этого избегает: добавить новую колонку как nullable, заполнять маленькими батчами, а не одной гигантской транзакцией, писать из приложения в обе, переключить чтения на новую колонку и лишь затем удалить старую. Опишите, как батчевый backfill ограничивает время блокировки, bloat и отставание реплик, как короткий lock_timeout с повторами предотвращает затор очереди блокировок и как безопасно откатиться на каждом шаге.
Вам нужно изменить схему таблицы на 500 миллионов строк в нагруженной прод-БД — добавить колонку, заполнить её из существующих данных, затем переключить приложение на неё — без простоя и без блокировки живых чтений и записей. Спроектируйте миграцию. Объясните, почему наивный одиночный ALTER (добавление колонки NOT NULL с default или смена типа колонки) может взять долгую табличную блокировку, которая встаёт в очередь за работающими запросами и застопоривает всю таблицу, и как паттерн expand-contract этого избегает: добавить новую колонку как nullable, заполнять маленькими батчами, а не одной гигантской транзакцией, писать из приложения в обе, переключить чтения на новую колонку и лишь затем удалить старую. Опишите, как батчевый backfill ограничивает время блокировки, bloat и отставание реплик, как короткий lock_timeout с повторами предотвращает затор очереди блокировок и как безопасно откатиться на каждом шаге.
Не катите один блокирующий ALTER на горячей таблице — колонка NOT NULL с default или смена типа берёт табличную блокировку, которая блокирует все чтения и записи. Применяйте expand-contract: добавьте колонку как nullable (изменение метаданных), заполняйте батчами, чтобы избежать долгой блокировки и лага реплик, пишите в обе, переключите чтения на новую и удалите старую, когда её никто не читает. Короткий lock_timeout с повторами не даёт DDL выстроить трафик за собой; каждый шаг обратим.
Типичные ошибки
- ✗Катить один блокирующий ALTER, который блокирует горячую таблицу
- ✗Заполнять все строки в одной долгой транзакции
- ✗Удалять старую колонку до того, как все чтения перешли на новую
Уточняющие вопросы
- →Почему добавление nullable-колонки избегает блокировки, которую берёт NOT NULL default?
- →Как батчевый backfill удерживает отставание реплик и bloat под контролем?