Найти, какой именно запрос медленныйПеремещён
Перед любой правкой надо знать конкретный запрос. Включи лог медленных запросов:
1
2ALTER SYSTEM SET log_min_duration_statement = 1000;
3SELECT pg_reload_conf();
Либо ставь расширение pg_stat_statements и смотри топ по суммарному времени:
1SELECT calls, mean_exec_time, total_exec_time, query
2FROM pg_stat_statements
3ORDER BY total_exec_time DESC
4LIMIT 10;
Колонка total_exec_time важнее, чем mean_exec_time: запрос на 50 мс, выполняемый десять тысяч раз, съедает больше, чем один на четыре секунды.
SELECT calls, mean_exec_time, query FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;Посмотреть план выполненияПеремещён
1EXPLAIN (ANALYZE, BUFFERS) SELECT ...;
Что искать в выводе:
| Строка в плане | Что значит |
|---|
Seq Scan по большой таблице | нет нужного индекса |
rows=1 в плане, actual rows=50000 | статистика устарела, нужен ANALYZE |
тот же узел с loops=200 | классическая проблема N+1 |
Sort Method: external merge Disk | не хватает work_mem |
Вывод удобно разбирать в визуализаторе — ссылка ниже.
EXPLAIN (ANALYZE, BUFFERS) SELECT ...;Настройки PostgreSQL — и главная ловушкаПеремещён
Значения по умолчанию не зависят от объёма памяти машины: shared_buffers всегда 128 МБ, work_mem — 4 МБ. На сервере с 4 ГБ это мало.
Вот как НЕ работает — именно так было написано в исходном плане:
1environment:
2 POSTGRES_SHARED_BUFFERS: 1GB
3 POSTGRES_WORK_MEM: 16MB
Официальный образ postgres знает только POSTGRES_PASSWORD, POSTGRES_USER, POSTGRES_DB, POSTGRES_INITDB_ARGS, POSTGRES_HOST_AUTH_METHOD и PGDATA. Всё остальное он просто игнорирует — без единого предупреждения.
Вот как работает:
1postgres:
2 image: postgres:16-alpine
3 command: >
4 postgres
5 -c shared_buffers=1GB
6 -c effective_cache_size=3GB
7 -c work_mem=16MB
8 -c maintenance_work_mem=256MB
Отправная точка для расчёта: shared_buffers около 25% от памяти, effective_cache_size — 50–75%.
docker exec -it <контейнер> psql -U postgres -c "SHOW shared_buffers;"Кэш: поднять время жизни и сразу же продумать сбросПеремещён
Для редко меняющихся данных время жизни в 10 минут действительно мало:
1
2SEO_TTL = 3600
3ANALYTICS_TTL = 7200
4CONTACTS_TTL = 1800
Но вместе с этим обязательно сброс при записи:
1async def update_contacts(self, data):
2 result = await self.repo.update(data)
3 await self.cache.delete("site_content:contacts")
4 return result
5
Без сброса время жизни в два часа означает буквально следующее: администратор меняет телефон на сайте, обновляет страницу и видит старый. Два часа подряд.
Проверить, что кэш работает:
1docker exec -it <redis> redis-cli
2> KEYS site_content:*
3> TTL site_content:contacts
docker exec -it <redis> redis-cli TTL site_content:contactsПул подключений — когда он действительно нуженОпциональноПеремещён
Каждое подключение к PostgreSQL — это отдельный процесс на сервере базы. Сотня подключений — сотня процессов.
Но сначала проверь, есть ли проблема:
1SELECT count(*), state FROM pg_stat_activity GROUP BY state;
2SHOW max_connections;
Если подключений десяток из ста возможных — пул не нужен, это не узкое место.
Если нужен — есть два уровня:
- Пул в самом приложении. У SQLAlchemy он включён по умолчанию — проверь
pool_size и max_overflow прежде, чем ставить что-то ещё.
- Внешний пул (PgBouncer). Нужен, когда к базе ходят несколько разных приложений или много копий одного.
У PgBouncer есть важное ограничение: в режиме transaction не работают подготовленные выражения и сеансовые настройки. Для asyncpg это значит, что нужно явно отключать кэш выражений, иначе получишь плавающие ошибки под нагрузкой.
SELECT count(*), state FROM pg_stat_activity GROUP BY state;Проверить актуальные версии инструментовДобавлен
Убедитесь, что используете актуальные версии PostgreSQL и Redis, чтобы избежать проблем с совместимостью и использовать последние улучшения производительности.
Проверить настройки конфигурацииДобавлен
Убедитесь, что все изменения в конфигурации сохранены и применены. Проверьте, что настройки, такие как shared_buffers и work_mem, установлены правильно.
# ИсходнаяУдалён
# Исходная
Реальная ситуация с боевого сервера:
- запросы к базе — от 3 до 8 секунд;
- PostgreSQL занимает 20 МБ из 3.8 ГБ доступной памяти;
- `shared_buffers` — 128 МБ, то есть значение по умолчанию;
- кэш есть, но с коротким временем жизни;
- пула подключений нет.
Составленный тогда план выглядел так: поднять время жизни кэша, потом настройки PostgreSQL, потом пул подключений. Обещанный итог — улучшение на 99%.
План рабочий, но в нём пропущен первый шаг и есть одна правка, которая не работает вообще. Разберём по порядку.
## Почему порядок важенУдалён
## Почему порядок важен
Исходный план ранжировал работы по соотношению эффекта к затраченному времени — это правильный подход:
| Шаг | Время | Заявленный эффект |
|---|---|---|
| Поднять время жизни кэша | 5 мин | минус 99% нагрузки на базу |
| Настройки PostgreSQL | 15 мин | 20–30% |
| Пул подключений | 30 мин | 10–20% |
Но с двумя оговорками.
**Первая.** «Минус 99% нагрузки» верно только для повторных чтений редко меняющихся данных: настройки сайта, контакты, баннеры. На корзине, остатках товара или личном кабинете такой цифры не будет никогда. Кэш убирает повторные чтения, а не медленный запрос.
**Вторая, важнее.** Первый запрос после истечения кэша всё равно идёт четыре секунды. Кэш не починил базу — он **спрятал проблему** и отложил её до дня, когда кэш упадёт, и все запросы разом уйдут в медленную базу.
Поэтому правильный порядок: **сначала EXPLAIN и индексы, потом кэш.** Кэш ставят поверх быстрого запроса, а не вместо него.
## Правильный порядок целикомУдалён
## Правильный порядок целиком
1. **Измерить.** Какой запрос, сколько раз, сколько времени суммарно.
2. **EXPLAIN ANALYZE.** Индекс, N+1 или устаревшая статистика — самый дешёвый и самый большой выигрыш.
3. **Настройки базы.** Через `command: postgres -c ...`, с проверкой через `SHOW`.
4. **Кэш.** Поверх уже быстрого запроса, со сбросом при записи.
5. **Пул подключений.** Только если измерения показали, что он нужен.
**После каждого шага — измерить снова.** Иначе через неделю ты не сможешь сказать, какая из пяти правок сработала, а какая просто добавила сложности.
И отдельно про цифры в планах. Когда видишь «улучшение на 99.9%» — спроси, на каком сценарии. Обычно это сто одинаковых запросов к одному эндпоинту подряд — то есть идеальный для кэша и не очень похожий на живой трафик.