Мониторинг query interner
Query interner дедуплицирует тексты Parse в памяти процесса
pg_doorman. Хранилище разделено на две независимые хеш-таблицы —
для именованных prepared (NAMED) и анонимных (ANON); каждая
работает по своей политике. NAMED чистит пассивный сборщик по
Arc::strong_count. ANON вытесняет по бездействию через
query_interner_anon_idle_ttl_seconds. Обе половины публикуют
метрики Prometheus: текущие значения, счётчики вытеснений и
гистограмму длительности уборки. Плюс счётчик синтетических
ошибок SQLSTATE 26000 — этот код pg_doorman возвращает клиентам,
чей анонимный prepared statement выпал из всех кешей.
Эта страница помогает оператору пользоваться этими метриками: готовый дашборд, правила алертов и приёмы настройки.
Дашборд
Главные панели (на первом экране)
- Сводка — общий объём интернера.
sum(pg_doorman_query_interner_bytes)в разрезе инстансов. Красный порог 1.5 ГиБ, жёлтый — 500 МиБ. Главный сигнал по памяти. - График — записи по типу. Две линии:
pg_doorman_query_interner_entries{kind="named"}pg_doorman_query_interner_entries{kind="anonymous"}Окно шесть часов. Устойчивый рост любой из линий — повод открыть панели детализации.
- График — частота синтетических 26000.
rate(pg_doorman_query_interner_synthetic_misses_total[5m]). Норма — плоский ноль. Любой всплеск означает: либо TTL вытеснил запись, на которую сослался клиент, либо драйвер рассчитывает на безымянный prepared statement, который остаётся доступным после Sync.
Детализация
- Скорость вытеснений с разбивкой по причине:
sum by (kind, reason) (rate(pg_doorman_query_interner_evictions_total[5m])). - Тепловая карта длительности уборки:
histogram_quantile(0.5, rate(pg_doorman_query_interner_gc_duration_seconds_bucket[5m])), с P99 поверх. - Среднее число байт на запись по типу:
pg_doorman_query_interner_bytes / pg_doorman_query_interner_entries.
Корреляции
- Скорость вытеснений ANON в сравнении с общим темпом запросов. Линейная корреляция говорит о здоровом трафике; нелинейная — о взрыве динамического SQL от ORM.
- Частота синтетических 26000 в сравнении с P99 latency запросов. Корреляция означает, что TTL режет живой трафик; разбираться с медленной веткой.
Переменные дашборда
instance— сравнивать реплики.kind— отфильтровать значения и счётчик до одной из половин.
Лейблы pool, user и database к интернеру неприменимы — он один на процесс. На панелях интернера они только введут читателя в заблуждение.
Правила алертов
Готовый блок groups: лежит в
monitoring/prometheus-rules/query-interner.yaml. Пять
алертов.
PgDoormanAnonInternerMemoryHigh(critical) — байты ANON выше 1.5 ГиБ. Уменьшить TTL или проверить ORM на динамический SQL.PgDoormanAnonTTLTooShort(critical) — синтетические 26000 чаще 1/с в течение 10 минут. Сначала определить источник: клиентский LRU,RESET INTERNER, TTL-вытеснение anonymous-записей или поведение драйвера.PgDoormanAnonInternerNotShrinking(warning) — ANON растёт, а вытеснение по TTL не идёт. TTL слишком велик, либо поток уникальных запросов превышает скорость их истечения.PgDoormanInternerGCSlow(warning) — P99 уборки выше 50 мс на 15-минутном окне. Увеличитьquery_interner_gc_interval_seconds(этот параметр работает только при перезапуске: reload не изменит частоту проходов у работающего процесса) или сделатьRESET INTERNERи уменьшить размеры кешей.PgDoormanNamedInternerGrowsUnbounded(warning) — больше 100 000 записей в NAMED при почти нулевом вытеснении. Почти всегда баг: ссылка наArc<str>удерживается навсегда.
Защита от холодного старта: у всех алертов for: > 5m. Пустой
интернер сразу после запуска процесса их не зажигает.
Размеры
Стационарный объём ANON-интернера при условии, что 50% запросов идут через prepared, а средний SQL — 2 КиБ:
| RPS | TTL = 60 с | TTL = 300 с |
|---|---|---|
| 100 | ~12k записей / ~24 МиБ | ~60k / ~120 МиБ |
| 1 000 | ~120k / ~240 МиБ | ~600k / ~1.2 ГиБ |
| 10 000 | ~1.2M / ~2.4 ГиБ | размер не рекомендуется |
Интернер глобален на процесс, поэтому объём кластера растёт линейно
с числом реплик pg_doorman. Берите это как стартовую оценку для
query_interner_anon_idle_ttl_seconds и бюджета RAM на хост;
фактическое значение даёт pg_doorman_query_interner_bytes.
Реальный TTL
Сборщик ходит в два прохода. На первом помечает запись как
кандидата, на втором удаляет — но только если за это время к
записи никто не обратился. Проход выполняется каждые
gc_interval / 4 секунд. По умолчанию (gc_interval = 60 с,
anon_idle_ttl = 60 с) это раз в 15 секунд. Пометка ставится
через 60–75 секунд после последнего использования, удаление — на
следующем проходе. Итого запись простаивает 75–120 секунд, прежде
чем сборщик её выкинет. Снижать anon_idle_ttl ниже 60 секунд
смысла мало: чаще, чем раз в gc_interval / 4, сборщик не
запускается.
Приёмы настройки
Уменьшить TTL, когда давит память
Когда: горит PgDoormanAnonInternerNotShrinking, байты ANON
подходят к лимиту памяти хоста.
Действие: уменьшить query_interner_anon_idle_ttl_seconds в
general (например, с 60 до 30). Перечитать конфиг pg_doorman.
Скорость вытеснений догонит новый порог.
Разобрать синтетические 26000 перед увеличением TTL
Когда: горит PgDoormanAnonTTLTooShort.
Действие: понять, какой клиент и какой запрос. У synthetic_misses
нет меток, поэтому смотреть WARN-лог — он пишется на каждый
промах и содержит client, pool, connection_id. Перед изменением
конфига проверьте pg_doorman_clients_prepared_anonymous_evictions_total
и pg_doorman_query_interner_evictions_total{kind="anonymous"}. Если
промахи идут из клиентского Anonymous LRU, увеличьте
client_anonymous_prepared_cache_size. Если они идут из TTL anonymous
interner или от драйвера, который законно переиспользует безымянный
Bind в следующей пачке, поднимите TTL (с 60 до 300, например). Если
нет — переключите клиента на именованный prepared.
Сбросить интернер
Когда: разовая диагностика или жёсткое сжатие памяти под инцидентом.
Действие: psql "host=127.0.0.1 port=6432 user=admin dbname=pgdoorman" -c "RESET INTERNER". Возвращает
CommandComplete RESET. Активные клиенты заново разберут запрос
при следующем использовании. Короткоживущие клиенты эффекта не
заметят: их last_anonymous_hash всё ещё помнит хеш, который они
зарегистрировали до сброса, и следующий Bind увидит отсутствие
записи и один раз получит 26000. Драйвер на это отреагирует
повторным Parse.
Правила предвычислений (recording rules)
Кластерные агрегаты, которые имеет смысл предвычислять, чтобы дашборды были дешевле:
groups:
- name: pg_doorman_query_interner_recording
interval: 30s
rules:
- record: pg_doorman:query_interner_total_bytes:5m
expr: sum without (instance) (pg_doorman_query_interner_bytes)
- record: pg_doorman:query_interner_eviction_rate:5m
expr: |
sum without (instance) (rate(pg_doorman_query_interner_evictions_total[5m]))
Первое правило позволяет общей сводной панели читать одну серию.
Второе рисует темп вытеснений с разбивкой по причине без
пересчёта rate() на каждой перерисовке дашборда.