From d4692a6ff15a74f1e7b04c5bb93f8331995ea4dc Mon Sep 17 00:00:00 2001 From: bot-backend Date: Thu, 20 Aug 2026 23:34:40 +0300 Subject: [PATCH] =?UTF-8?q?chore(db):=20Postgres=20tradein=20=D1=83=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=B8=D1=82=20=D1=81=D0=BE=20=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=D1=8B=D1=85=20=D0=BD=D0=B0=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=20=E2=80=94=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8?= =?UTF-8?q?=D0=B3=20=D0=B2=20compose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Блокер переезда (#2991). Контейнер шёл на ПОЛНОСТЬЮ стоковой конфигурации: ни command:, ни смонтированного postgresql.conf. Замер прода 2026-08-20 — 169 млрд blks_read за 91,75 сут ≈ 175 МБ/с мимо кеша при shared_buffers 128 МБ. Поправка к диагнозу из issue: 288 чекпойнтов в сутки — это ровно 24ч/288 = 5 минут, то есть дефолтный checkpoint_timeout, а НЕ max_wal_size. При WAL 7 ГБ/сут лимит в 1 ГБ дал бы 7 чекпойнтов, а не 288. Поэтому поднят именно timeout до 30min — правка одного max_wal_size не изменила бы ничего. Реже чекпойнты → реже full-page images, а они сейчас 55-86% объёма WAL. Значения подобраны под ТЕКУЩИЙ сервер и не выходят за mem_limit=3g: shared_buffers 768MB (6× от стоковых), work_mem 16MB, maintenance_work_mem 256MB (autovacuum по listings идёт 193 раза в сутки). max_wal_size 4GB, а не 8GB: на диске 38 ГБ свободно, а pg_wal растёт до этого значения. При timeout=30min между чекпойнтами копится ~0,15 ГБ — запас большой. wal_compression=zstd — самая дешёвая победа при такой доле FPI. random_page_cost 1.1 вместо стоковых 4: диск NVMe, а 4 — настройка под HDD, из-за неё планировщик недооценивал индексные сканы. pg_stat_statements подключён через shared_preload_libraries — расширение было в образе, но не активировано, и прошлый разбор пришлось вести по косвенным признакам. shm_size 512m: дефолтные 64 МБ /dev/shm ронял параллельные воркеры на тяжёлых PostGIS-сортировках. mem_limit оставлен 3g. В комментарии зафиксировано, что при переезде его надо поднимать ДО правки shared_buffers: лимит контейнера срабатывает раньше postgresql.conf, и shared_buffers=16GB при mem_limit=3g даёт OOM на старте. Refs #2991, #2989 --- tradein-mvp/docker-compose.prod.yml | 64 +++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tradein-mvp/docker-compose.prod.yml b/tradein-mvp/docker-compose.prod.yml index c68d74c2..2f9914bd 100644 --- a/tradein-mvp/docker-compose.prod.yml +++ b/tradein-mvp/docker-compose.prod.yml @@ -125,6 +125,70 @@ services: # тяжёлых PostGIS-сортировок бэктеста/эстиматора + autovacuum) mem_limit: 3g memswap_limit: 3g + # ⚠️ При переезде на выделенный сервер (#2989) mem_limit поднимать ДО правки + # shared_buffers, а не после: лимит контейнера срабатывает РАНЬШЕ postgresql.conf, + # и shared_buffers=16GB при mem_limit=3g даёт OOM-kill на старте. Целевое на + # 64 ГБ — 40-48g, а не 64g: нужен запас под page cache ВНЕ контейнера. + shm_size: 512m + # /dev/shm по умолчанию 64 МБ. Параллельные воркеры кладут туда shared memory + # segments; на тяжёлых PostGIS-сортировках это «could not resize shared memory». + # + # ── Конфигурация Postgres (#2991) ───────────────────────────────────────── + # До этого контейнер шёл на ПОЛНОСТЬЮ стоковых настройках. Замер прода 2026-08-20: + # 169 млрд blks_read за 91,75 сут ≈ 175 МБ/с мимо кеша при shared_buffers=128 МБ. + # + # Значения подобраны под ТЕКУЩИЙ сервер и НЕ выходят за mem_limit=3g. + # После переезда пересчитать под 64 ГБ. + command: + - postgres + # Память. 768MB shared_buffers — 6× от стоковых 128MB, с запасом внутри 3g + # (idle-замер контейнера был 292 МБ, work_mem-спайки и autovacuum сверху). + - -c + - shared_buffers=768MB + # effective_cache_size — подсказка планировщику, НЕ аллокация. На хосте + # MemAvailable 6,1 ГБ, поэтому 6GB честно отражает доступный page cache. + - -c + - effective_cache_size=6GB + - -c + - work_mem=16MB + # maintenance_work_mem: autovacuum по listings идёт 193 раза в сутки, + # с 64MB каждый проход перечитывает индексы лишними итерациями. + - -c + - maintenance_work_mem=256MB + # Чекпойнты. ГЛАВНОЕ: 288 чекпойнтов в сутки — это ровно 24ч/288 = 5 минут, + # то есть дефолтный checkpoint_timeout, а НЕ max_wal_size. При WAL 7 ГБ/сут + # лимит в 1 ГБ дал бы 7 чекпойнтов, а не 288. Поэтому поднимаем ИМЕННО + # timeout — иначе правка max_wal_size ничего бы не изменила. + # Реже чекпойнты → реже full-page images (сейчас 55-86% всего объёма WAL). + - -c + - checkpoint_timeout=30min + - -c + - checkpoint_completion_target=0.9 + # max_wal_size=4GB, а не 8GB: на диске сейчас всего 38 ГБ свободно, а pg_wal + # растёт до этого значения. При timeout=30min и 7 ГБ/сут между чекпойнтами + # накапливается ~0,15 ГБ, так что 4GB — потолок с большим запасом. + - -c + - max_wal_size=4GB + - -c + - min_wal_size=1GB + # Самая дешёвая победа при доле FPI 55-86%. zstd доступен с PG15, у нас 16. + - -c + - wal_compression=zstd + # Диск NVMe, а стоковый random_page_cost=4 — настройка под HDD: планировщик + # систематически недооценивает индексные сканы и уходит в Seq Scan. + - -c + - random_page_cost=1.1 + - -c + - effective_io_concurrency=200 + # pg_stat_statements: расширение есть в образе, но не подключено. Без него + # следующий разбор снова придётся вести по косвенным признакам. + # shared_preload_libraries требует рестарта — поэтому именно здесь. + - -c + - shared_preload_libraries=pg_stat_statements + - -c + - pg_stat_statements.max=5000 + - -c + - pg_stat_statements.track=top logging: *default-logging environment: POSTGRES_DB: tradein