Агрессивный автовакуум на PostgreSQL

42

Я пытаюсь заставить PostgreSQL активно пылесосить мою базу данных. В настоящее время я настроил автоматический вакуум следующим образом:

  • autovacuum_vacuum_cost_delay = 0 # Отключить вакуум на основе стоимости
  • autovacuum_vacuum_cost_limit = 10000 # Максимальное значение
  • autovacuum_vacuum_threshold = 50 # Значение по умолчанию
  • autovacuum_vacuum_scale_factor = 0.2 # Значение по умолчанию

Я заметил, что автоматический вакуум включается только тогда, когда база данных не загружена, поэтому я попадаю в ситуации, когда мертвых кортежей гораздо больше, чем живых кортежей. Смотрите прикрепленный скриншот для примера. Один из столов имеет 23 живых кортежа, но 16845 мертвых кортежей ожидают вакуума. Это безумие!

Много мертвых кортежей

Автоматический вакуум включается, когда завершается тестовый прогон и сервер базы данных находится в режиме ожидания, а это не то, чего я хочу, поскольку я хотел бы, чтобы автоматический вакуум включался всякий раз, когда число мертвых кортежей превышает 20% живых кортежей + 50, поскольку база данных была сконфигурировано. Автоматический вакуум, когда сервер бездействует, для меня бесполезен, поскольку ожидается, что рабочий сервер будет работать с 1000-кратными обновлениями в секунду в течение длительного периода времени, поэтому мне нужно, чтобы автоматический вакуум работал даже при нагрузке на сервер.

Есть ли что-то, что я пропускаю? Как заставить автоматический вакуум работать, когда сервер находится под большой нагрузкой?

Обновить

Может ли это быть проблемой блокировки? Рассматриваемые таблицы являются сводными таблицами, которые заполняются с помощью триггера после вставки. Эти таблицы заблокированы в режиме SHARE ROW EXCLUSIVE, чтобы предотвратить одновременную запись в одну и ту же строку.

CadentOrange
источник

Ответы:

40

Eelke почти наверняка прав, что ваша блокировка блокирует автовакуум. Autovacuum предназначен для преднамеренной уступки пользовательской активности. Если эти таблицы заблокированы, автоочистка не может их пылесосить.

Для потомков, однако, я хотел привести пример набора настроек для гиперагрессивного автовакуума, так как настройки, которые вы дали, не совсем это делают. Обратите внимание, что повышение агрессивности автовакуума вряд ли решит вашу проблему. Также обратите внимание, что настройки автоочистки по умолчанию основаны на выполнении более 200 тестовых прогонов с использованием DBT2 в поисках оптимальной комбинации настроек, поэтому значения по умолчанию следует считать хорошими, если у вас нет веских оснований думать иначе, или если ваша база данных значительно выходит за пределы основной поток для баз данных OLTP (например, крошечная база данных, которая получает обновления 10 КБ в секунду, или хранилище данных объемом 3 ТБ).

Во-первых, включите ведение журнала, чтобы вы могли проверить, делает ли автоочистка то, что вы думаете:

log_autovacuum_min_duration = 0

Тогда давайте сделаем больше работников autovac и заставим их чаще проверять таблицы:

autovacuum_max_workers = 6
autovacuum_naptime = 15s

Давайте снизим пороговые значения для автоматического вакуума и автоматического анализа для более быстрого запуска:

autovacuum_vacuum_threshold = 25
autovacuum_vacuum_scale_factor = 0.1

autovacuum_analyze_threshold = 10
autovacuum_analyze_scale_factor = 0.05 

Тогда давайте сделаем автовакуум менее прерывистым, чтобы он завершался быстрее, но за счет большего влияния на параллельную пользовательскую активность:

autovacuum_vacuum_cost_delay = 10ms
autovacuum_vacuum_cost_limit = 1000

Есть полная программа для агрессивного автоочистки, которая может быть подходящей для небольшой базы данных, получающей очень высокую частоту обновлений, но может оказать слишком большое влияние на параллельную пользовательскую активность.

Кроме того, обратите внимание, что параметры автоочистки можно регулировать для каждой таблицы , что почти всегда является лучшим ответом для необходимости корректировать поведение автоочистки.

Опять же, это вряд ли решит вашу настоящую проблему.

Джош Беркус
источник
36

Просто для того, чтобы увидеть, какие таблицы вообще подходят для автоматического вакуума, можно использовать следующий запрос (основанный на http://www.postgresql.org/docs/current/static/routine-vacuuming.html ). Однако обратите внимание, что запрос не ищет специфичные для таблицы настройки:

 SELECT psut.relname,
     to_char(psut.last_vacuum, 'YYYY-MM-DD HH24:MI') as last_vacuum,
     to_char(psut.last_autovacuum, 'YYYY-MM-DD HH24:MI') as last_autovacuum,
     to_char(pg_class.reltuples, '9G999G999G999') AS n_tup,
     to_char(psut.n_dead_tup, '9G999G999G999') AS dead_tup,
     to_char(CAST(current_setting('autovacuum_vacuum_threshold') AS bigint)
         + (CAST(current_setting('autovacuum_vacuum_scale_factor') AS numeric)
            * pg_class.reltuples), '9G999G999G999') AS av_threshold,
     CASE
         WHEN CAST(current_setting('autovacuum_vacuum_threshold') AS bigint)
             + (CAST(current_setting('autovacuum_vacuum_scale_factor') AS numeric)
                * pg_class.reltuples) < psut.n_dead_tup
         THEN '*'
         ELSE ''
     END AS expect_av
 FROM pg_stat_user_tables psut
     JOIN pg_class on psut.relid = pg_class.oid
 ORDER BY 1;
pygrac
источник
11

Да, это проблема блокировки. Согласно этой странице (не полная) VACUUM необходим доступ SHARE UPDATE EXCLUSIVE, который заблокирован используемым уровнем блокировки.

Вы уверены, что вам нужен этот замок? PostgreSQL совместим с ACID, поэтому одновременные записи в большинстве случаев не являются проблемой, поскольку PostgreSQL прервет одну из транзакций, если произойдет нарушение сериализации.

Также вы можете заблокировать строки, используя SELECT FOR UPDATE для блокировки строк вместо всей таблицы.

Другой альтернативой без блокировки будет использование сериализуемого уровня изоляции транзакции . Однако это может повлиять на производительность других транзакций, и вы должны быть готовы к новым ошибкам сериализации.

Eelke
источник
Это связано с блокировкой сводных таблиц, так как они блокируются с помощью SHARE ROW EXCLUSIVE MODE. Одновременные записи без блокировки могут быть успешными, но они наверняка получат неправильные значения. Представьте, что я поддерживаю количество N строк типа X. Если я одновременно вставлю 2 строки типа X, без блокировки я получу N + 1 в моей сводной таблице вместо N + 2. Решение, которое я принял это иметь работу cron, которая вручную пылесосит сводные таблицы в моей базе данных. Это работает хорошо и кажется рекомендуемым подходом, но для меня это слишком похоже на взлом.
CadentOrange
6

Вероятно, поможет увеличение числа процессов в вакууме и сокращение времени бездействия. Вот конфигурация для PostgreSQL 9.1, которую я использую на сервере, который хранит информацию о резервных копиях и в результате получает много операций вставки.

http://www.postgresql.org/docs/current/static/runtime-config-autovacuum.html

autovacuum_max_workers = 6              # max number of autovacuum subprocesses
autovacuum_naptime = 10         # time between autovacuum runs
autovacuum_vacuum_cost_delay = 20ms     # default vacuum cost delay for

Я также постараюсь понизить, cost_delayчтобы сделать пылесос более агрессивным.

Я также могу проверить автоочистку с помощью pgbench.

http://wiki.postgresql.org/wiki/Pgbenchtesting

Пример высокой конкуренции:

Создать базу данных bench_replication

pgbench -i -p 5433 bench_replication

Запустите pgbench

pgbench -U postgres -p 5432 -c 64 -j 4 -T 600 bench_replication

Проверьте состояние автоочистки

psql
>\connect bench_replicaiton
bench_replication=# select schemaname, relname, last_autovacuum from pg_stat_user_tables;
 schemaname |     relname      |        last_autovacuum        
------------+------------------+-------------------------------
 public     | pgbench_branches | 2012-07-18 18:15:34.494932+02
 public     | pgbench_history  | 
 public     | pgbench_tellers  | 2012-07-18 18:14:06.526437+02
 public     | pgbench_accounts | 
Крейг Эфрейн
источник
6

Существующий сценарий "претендовать на автоочистку" очень полезен, но (как правильно сказано) не было конкретных параметров таблицы. Вот модифицированная версия, которая учитывает эти параметры:

WITH rel_set AS
(
    SELECT
        oid,
        CASE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_threshold=', 2), ',', 1)
            WHEN '' THEN NULL
        ELSE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_threshold=', 2), ',', 1)::BIGINT
        END AS rel_av_vac_threshold,
        CASE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_scale_factor=', 2), ',', 1)
            WHEN '' THEN NULL
        ELSE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_scale_factor=', 2), ',', 1)::NUMERIC
        END AS rel_av_vac_scale_factor
    FROM pg_class
) 
SELECT
    PSUT.relname,
    to_char(PSUT.last_vacuum, 'YYYY-MM-DD HH24:MI')     AS last_vacuum,
    to_char(PSUT.last_autovacuum, 'YYYY-MM-DD HH24:MI') AS last_autovacuum,
    to_char(C.reltuples, '9G999G999G999')               AS n_tup,
    to_char(PSUT.n_dead_tup, '9G999G999G999')           AS dead_tup,
    to_char(coalesce(RS.rel_av_vac_threshold, current_setting('autovacuum_vacuum_threshold')::BIGINT) + coalesce(RS.rel_av_vac_scale_factor, current_setting('autovacuum_vacuum_scale_factor')::NUMERIC) * C.reltuples, '9G999G999G999') AS av_threshold,
    CASE
        WHEN (coalesce(RS.rel_av_vac_threshold, current_setting('autovacuum_vacuum_threshold')::BIGINT) + coalesce(RS.rel_av_vac_scale_factor, current_setting('autovacuum_vacuum_scale_factor')::NUMERIC) * C.reltuples) < PSUT.n_dead_tup
        THEN '*'
    ELSE ''
    END AS expect_av
FROM
    pg_stat_user_tables PSUT
    JOIN pg_class C
        ON PSUT.relid = C.oid
    JOIN rel_set RS
        ON PSUT.relid = RS.oid
ORDER BY C.reltuples DESC;
Вадим Цингерталь
источник