PostgreSQL
Troubleshooting: диагностика production проблем
3 часа ночи, production не отвечает. CPU 100%, приложение зависло. Как за 5 минут найти причину и восстановить работу?
- GitLab публично разбирал инцидент с deadlock при деплое миграции - lock contention на 30 минут из-за одного ALTER TABLE
- Discord потерял $140K дохода за время инцидента с connection exhaustion до внедрения PgBouncer
- Notion расследовал CPU spike от 1% до 95% через pg_stat_statements - оказался один запрос с неправильным индексом
CPU spike: диагностика и устранение
CPU spike в PostgreSQL - это резкий рост потребления процессора, часто до 100%. Причины: внезапно деградировавший план запроса, запрос без индекса на большой таблице, autovacuum на перегретой таблице, или шторм параллельных аналитических запросов.
Типичный сценарий: статистика устарела после массовой загрузки данных, планировщик переоценил селективность индекса и начал делать Seq Scan на таблице в 100 млн строк. Решение: ANALYZE таблицы для обновления статистики, затем EXPLAIN ANALYZE для проверки нового плана.
SET enable_seqscan = off использовать только для диагностики в сессии, никогда на уровне конфигурации сервера. Это ломает плановщик для запросов, где Seq Scan действительно оптимален.
PostgreSQL внезапно загрузил CPU до 95%. Первый шаг диагностики?
Disk full: PostgreSQL кончилось место
Disk full в PostgreSQL - критичная ситуация: база переходит в read-only, WAL архивирование останавливается, replication lag растёт. Место заканчивается по трём причинам: bloat (мёртвые tuple без vacuum), WAL накопление из-за незакрытого replication slot, или рост pg_wal при интенсивной записи.
Наиболее коварная причина: неактивный replication slot. PostgreSQL не удаляет WAL-файлы, пока слот не подтвердит их применение. Если subscriber отключился (но слот остался), WAL накапливается и съедает весь диск. За несколько часов pg_wal может вырасти с 1 GB до 100+ GB.
Превентивная мера: alerting на retained_wal > 10 GB по pg_replication_slots. Также полезен параметр max_slot_wal_keep_size (PG 13+) - ограничивает объём WAL, удерживаемого слотом.
PostgreSQL pg_wal вырос до 80 GB за ночь. Наиболее вероятная причина?
Replication lag: replica отстаёт
Replication lag - это отставание standby от primary по WAL. При асинхронной репликации допустимый лаг зависит от RPO. При лаге > нескольких секунд приложение рискует читать устаревшие данные со standby. При лаге > 30 секунд автоматический failover может привести к потере данных.
Причины replication lag: 1) Долгий запрос на standby в hot_standby блокирует применение WAL-конфликтов - настройка max_standby_streaming_delay позволяет стандбаю отменить конфликтующий запрос; 2) Сетевая пропускная способность между primary и standby; 3) Тяжёлые запросы на standby нагружают IO, замедляя применение WAL.
hot_standby_feedback=on снижает lag, но увеличивает bloat на primary: primary не может vacuum строки, видимые транзакцией на standby. Компромисс: включать только если standby активно используется для чтения и lag критичен.
Replication lag на standby вырос до 5 минут. Как диагностировать причину?
Connection exhaustion: too many connections
Ошибка "FATAL: sorry, too many clients already" означает достижение max_connections. Каждое соединение в PostgreSQL - это отдельный процесс (~5-10 MB RSS). При 1000 соединениях сервер тратит 5-10 GB RAM только на процессы, независимо от нагрузки.
Решение: PgBouncer в transaction pooling mode. При transaction pooling клиент занимает соединение только на время транзакции. 10,000 клиентов → 50 реальных соединений к PostgreSQL. Discord обслуживает 1M+ одновременных пользователей через PgBouncer с pool_size=25 на каждую базу.
Transaction pooling несовместим с: SET/RESET session parameters, LISTEN/NOTIFY, prepared statements (если не включён pool_mode=session для конкретных приложений), advisory locks. Для приложений, использующих эти фичи, применять session mode или отдельный pool.
10,000 клиентов подключаются к PostgreSQL с max_connections=200. Что произойдёт?
Lock contention: deadlocks и долгие блокировки
Lock contention - ситуация, когда транзакции блокируют друг друга, ожидая освобождения строк или таблиц. Признаки: транзакции зависают на lock wait, дерево блокировок растёт, latency API растёт нелинейно. Deadlock - когда два процесса блокируют друг друга циклически: PostgreSQL автоматически детектирует и отменяет одну из транзакций.
Типичные паттерны lock contention: 1) UPDATE без WHERE на большой таблице блокирует все остальные UPDATE; 2) Долгая транзакция держит row lock, накапливая очередь ожидающих; 3) ALTER TABLE берёт AccessExclusiveLock и застревает за долгой транзакцией; 4) Deadlock при обновлении двух строк в разном порядке.
Для мониторинга lock contention в реальном времени: pg_stat_activity с фильтром wait_event_type = 'Lock' + alert при count > 5. Grafana dashboard с lock wait time помогает детектировать паттерны до появления инцидентов.
Перезапуск PostgreSQL при production-инциденте - быстрое решение проблемы
Перезапуск уничтожает shared_buffers (кеш прогревается часами), прерывает все соединения и не устраняет первопричину. Любую проблему нужно диагностировать и решать точечно.
После перезапуска: cold start без кеша даёт в 3-10 раз больше disk IO, все активные транзакции откатываются, приложения получают connection reset. Если проблема в плохом запросе или deadlock - он вернётся сразу. Правильный подход: pg_cancel_backend для конкретного запроса, pg_terminate_backend для зависшей транзакции.
Что такое deadlock в PostgreSQL и как он разрешается?
Troubleshooting PostgreSQL
- CPU spike: pg_stat_activity → найти долгий запрос → EXPLAIN ANALYZE → ANALYZE для обновления статистики
- Disk full: проверить replication slots (retained WAL) → pg_drop_replication_slot → VACUUM на bloated таблицах
- Replication lag: pg_stat_replication на primary + pg_stat_database_conflicts на standby
- Connection exhaustion: PgBouncer transaction pooling - 10,000 клиентов → 25 реальных соединений
- Lock contention: pg_blocking_pids для дерева блокировок → pg_cancel_backend корня → lock_timeout превентивно
Связанные темы
Troubleshooting опирается на знание всей системы
- Мониторинг — Проактивный мониторинг pg_stat_activity и pg_stat_statements предотвращает инциденты
- Autovacuum — Настроенный autovacuum предотвращает bloat и disk full от dead tuples
- Connection Pooling — PgBouncer - решение connection exhaustion и снижение overhead на process creation
Вопросы для размышления
- Как pg_stat_activity помогает отличить проблему с CPU от проблемы с lock contention?
- Почему незакрытый replication slot опаснее просто медленного роста таблиц для диска?
- В каких случаях перезапуск PostgreSQL оправдан, а в каких - только ухудшит ситуацию?