Девять кругов ада или PostgreSQL Vacuum Лесовский Алексей, 2016.11 PostgreSQL-Consulting
Как быстро сломать Postgres Часто и много обновлять таблицу. Отключить вакуум. До: 3565.5 tps, 0.839 ms. После: 172.8 tps, 17.373 ms. Как воспроизвести: https://goo.gl/Tql87l
Take home messages Вакуум это важно, его не стоит игнорировать. Если вакуум ненастроен производительность деградирует. Вакуум не страшен, настраивать его не сложно.
Вакуум и как это работает MVCC, Postmaster, Autovacuum Launcher & Workers. Что там внутри Worker'а. Подготовка к вакууму, costs, wraparound. Вакуум индексов, таблиц и их страниц. Слайды: ХХ-ХХ-ХХ
MVCC MVCC – Multiversion Concurrency Control: ● предлагает хорошую конкурентность; ● в условиях значительной read/write активности; ● читатели не блокируют писателей и наоборот.
MVCC MVCC – Multiversion Concurrency Control: ● предлагает хорошую конкурентность; ● в условиях значительной read/write активности; ● читатели не блокируют писателей и наоборот. ● Почти ;)
MVCC
MVCC
MVCC
MVCC
MVCC
MVCC
Postmaster Postmaster работает в бесконечном цикле. ● запуск фоновых процесов (checkpointer, bgwriter, walwriter, ...); ● и в т.ч. autovacuum launcher; ● вообще там много всего… AV Launcher будет перезапущен если что-то пойдет не так.
Autovacuum Launcher Инициализация Запуск воркера в случае emergency. Создание списка БД. Запуск бесконечного цикла (SIGTERM ?):
Autovacuum Launcher Инициализация Запуск воркера в случае emergency. Создание списка БД. Запуск бесконечного цикла (SIGTERM ?): ● обработка SIGTERM, SIGHUP, SIGUSR2; ● запуск воркера для баз в списке (autovacuum_naptime).
Как выбирается база для обработки? Определяем xidForceLimit = recentXid – autovacuum_freeze_max_age. Риск wraparound с самым старым datfrozenxid/datminmxid. Базы которые давно не посещал вакуум. Пропускаем базы обработанные недавно.
Кандидат выбран Отметка в shared памяти (имя БД, время запуска). Отправка сигнала Postmaster“у (флажок + SIGUSR1). Postmaster принимает сигнал и делает fork (connection limit?). Воркер запущен.
Postmaster & Co
Worker Инициализация (signals, file descriptors, filemgr, bufmgr, smgr, shm, local struct). Установка параметров: ● zero_damaged_pages=false ● statement_timeout=0, lock_timeout=0 ● default_transaction_isolation="read commited" ● synchronous_commit=local
Worker Получение имени БД из av_startingWorker. Регистрация в runningWorkers и сброс av_startingWorker. Отправка SIGUSR2 процессу AV Launcher. Инициализация в качестве postgres backend.
pg_class Составляем список таблиц для обработки ● таблицы и мат. представления; ● TOAST таблицы.
pg_class Выбираются только таблицы и мат. представления (pg_class.relkind): ● чтение статы и параметров таблиц (pg_class.reloptions); ● запуск relation_needs_vacanalyze() – vaccum, analyze или wraparound? ● таблица является временной (pg_class.relpersistence)? Для TOAST запоминаем ассоциацию с родительской таблицей.
Wraparound
Wraparound recentXid – текущая транзакция. vacuum_freeze_min_age – строки с возрастом старше должны быть заморожены. vacuum_freeze_table_age – полное сканирование если достигнут возраст. autovacuum_freeze_max_age – возраст принудительного запуска wraparound вакуума.
А нужен ли вакуум? Проверка необходимости вакуума или сбора статистики (или все вместе). Определение пороговых параметров: ● параметры reloptions (от основной или TOAST таблицы); ● параметры конфигурации (postgresql.conf); ● для freeze_max_age выбираем минимум (reloptions vs. postgresql.conf);
А нужен ли вакуум? Принудительный вакуум если есть риск wraparound: ● xidForceLimit = recentXid – freeze_max_age; ● multiForceLimit = recentMulti – multixact_freeze_max_age; ● вакуум обязателен если pgclass.relfrozenxid или relminmxid старше порогов; ● если нет риска wraparound и AV отключен – пропускаем таблицу.
А нужен ли вакуум? pg_stat_all_tables.n_dead_tup, pg_stat_all_tables.n_mod_since_analyze reltuples = classForm->reltuples; vactuples = tabentry->n_dead_tuples; anltuples = tabentry->changes_since_analyze; vacthresh = (float4) vac_base_thresh + vac_scale_factor * reltuples; anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples; *dovacuum = force_vacuum || (vactuples > vacthresh); *doanalyze = (anltuples > anlthresh);
А нужен ли вакуум? autovacuum_vacuum_threshold = 50 # min number of row updates # before vacuum autovacuum_analyze_threshold = 50 # min number of row updates # before analyze autovacuum_vacuum_scale_factor = 0.2 # fraction of table size # before vacuum autovacuum_analyze_scale_factor = 0.1 # fraction of table size # before analyze
Подготовка к вакууму Все таблицы проверены – список составлен – закрываем pg_class. Выбор стратегии работы с shared памятью: ● BAS_BULKREAD: ring_size = 256 * 1024 / BLCKSZ; ● BAS_BULKWRITE: ring_size = 16 * 1024 * 1024 / BLCKSZ; ● BAS_VACUUM: ring_size = 256 * 1024 / BLCKSZ; (32kB). Выбор первой таблицы из списка.
Расчет cost параметров vacuum_cost_delay = 0 # 0-100 milliseconds vacuum_cost_page_hit = 1 # 0-10000 credits vacuum_cost_page_miss = 10 # 0-10000 credits vacuum_cost_page_dirty = 20 # 0-10000 credits vacuum_cost_limit = 200 # 1-10000 credits autovacuum_vacuum_cost_delay = 20ms # default vacuum cost delay for # autovacuum, in milliseconds; # -1 means use vacuum_cost_delay autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for # autovacuum, -1 means use # vacuum_cost_limit
Расчет cost параметров Разделение I/O поровну между всеми воркерами. Объем I/O определяется с помощью cost_limit, cost_delay. ● autovacuum_vacuum_cost_limit или vacuum_cost_limit; ● autovacuum_vacuum_cost_delay или vacuum_cost_delay; Ничего не делать если параметры не установлены (<= 0).
Вакуум, вакуум
Вакуум, вакуум autovacuum_do_vac_analyze() – автовакуум и/или autoanalyze. ExecVacuum() – точка входа ручных VACUUM и ANALYZE команд. vacuum() – точка входа для вакуума и сбора статистики.
Вакуум или Аналайз Cost-based вакуум в случае VacuumCostDelay > 0. Обработка таблицы в зависимости от потребности: ● vacuum_rel() и analyze_rel(); Завершение обработки: ● обновление pg_database.datfrozenxid и чистка pg_clog; ● завершение работы.
Блокировки Проверка отмены со стороны пользователя. Выбор блокировки: ExclusiveLock или ShareUpdateExclusiveLock Открываем таблицу и берем блокировку. Не удалось взять блокировку? ● autovacuum: пишем в лог "skipping vacuum of %s --- lock not available"; ● не удалось открыть (таблица удалена?), завершаем работу.
Проверка таблицы Проверка привилегий (superuser, владелец таблицы, владелец БД). Проверка что объект вообще vacuumable (таблицы, мат.вью, TOAST). Пропуск временных таблиц других бекендов. Запоминаем ассоциацию с TOAST (исключение автовакуум). Переключение userid на владельца таблицы.
Do the actual work /* * Do the actual work --- either FULL or "lazy" vacuum */ VACUUM FULL? ● закрываем таблицу, но продолжаем держать блокировку; ● cluster_rel() – VACUUM FULL является вариантом CLUSTER; см. cluster.c. В любом другом случае – lazy_vacuum_rel().
Таблица обработана Вакуум завершен – таблица обработана. Закрытие таблицы. При наличии TOAST, переходим к ней (также vacuum_rel()).
lazy_vacuum_rel() Установка пороговых значений для заморозки: ● freeze_min_age, freeze_table_age; ● multixact_freeze_min_age, multixact_freeze_table_age;
lazy_vacuum_rel() Установка пороговых значений для заморозки: ● freeze_min_age, freeze_table_age; ● multixact_freeze_min_age, multixact_freeze_table_age; ● oldestXmin – оценка когда строка считается DEAD или RECENTLY_DEAD; ● freezeLimit – старше этого порога все строки замораживаются; ● xidFullScanLimit – полное сканирование таблицы если relfrozenxid старше порога;
lazy_vacuum_rel() Установка пороговых значений для заморозки: ● freeze_min_age, freeze_table_age; ● multixact_freeze_min_age, multixact_freeze_table_age; ● oldestXmin – оценка когда строка считается DEAD или RECENTLY_DEAD; ● freezeLimit – старше этого порога все строки замораживаются; ● xidFullScanLimit – полное сканирование таблицы если relfrozenxid старше порога; ● multiXactCutoff – порог для удаления всех MultiXactIds из Xmax; ● mxactFullScanLimit – полное сканирование если relminmxid старше порога.
lazy_vacuum_rel() Установка пороговых значений для заморозки: ● freeze_min_age, freeze_table_age; ● multixact_freeze_min_age, multixact_freeze_table_age; ● oldestXmin – оценка когда строка считается DEAD или RECENTLY_DEAD; ● freezeLimit – старше этого порога все строки замораживаются; ● xidFullScanLimit – полное сканирование таблицы если relfrozenxid старше порога; ● multiXactCutoff – порог для удаления всех MultiXactIds из Xmax; ● mxactFullScanLimit – полное сканирование если relminmxid старше порога. Сравниваем relfrozenxid/relminmxid с пороговыми значениями.
lazy_vacuum_rel() Открываем индексы → вакуум с lazy_scan_heap() → Закрываем индексы. Считаем вся ли таблица была просканирована: scanned_pages + frozenskipped_pages = rel_pages Если возможно обрезаем таблицу. Обновляем Free Space Map, pg_class: ● relpages, reltuples, relallvisible, relhasindex, refrozenxid/relminmxid (full scan only). Сохраняем статистику в stats коллектор (n_live_tupe, n_dead_tuples). Пишем сообщение в журнал, при log_min_duration >= 0. Конец.
Таблица обработана (напоминание) Вакуум завершен – таблица обработана. Закрытие таблицы. При наличии TOAST, переходим к ней (также vacuum_rel()).
/* lazy_scan_heap() – scan an open heap relation */ Выделяем память для хранения dead строк (autovacuum_work_mem); Проверяем страницы которые можно пропустить: ● ALL_FROZEN и ALL_VISIBLE флаги (в соотв. с visibility map); ● в случае full scan, нельзя пропускать ALL_VISIBLE страницы; ● всегда пропускаем ALL_FROZEN страницы; ● всегда сканируем последний блок – вдруг таблицу можно обрезать. После каждого блока выполняем vacuum_delay_point().
lazy_scan_heap() Начинаем цикл проверки с первого непропускаемого блока: ● и снова ищем следующий блок который нельзя пропускать; ● проверяем хранилище dead строк на предмет переполнения; ● читаем содержимое страницы, считаем costs; ● пытаемся взять блокировку для чистки буффера (для HOT). Блок будет пропущен если блокировка провалится (искл. full-scan).
lazy_scan_heap() Проверка страницы на наличие строк — кандидатов в заморозку: ● всегда чистим неинициализированные страницы; ● пропускаем пустые страницы; ● проверяем нормальные страницы; ● dead и redirect никогда не нужно морозить; ● проверяем что любое из XID полей (xmin,xmax,xvac) старше порога.
lazy_scan_heap() Продолжаем основной цикл проверки страниц… Новые страницы инициализируем ● помечаем как грязные, отмечаем в Free Space Map. Пустые страницы: ● ставим отметку ALL_VISIBLE и ALL_FROZEN; ● помечаем как грязные, делаем запись в WAL, обновляем VM и FSM.
Heap Only Tuples
Heap Only Tuples
Heap Only Tuples
Heap Only Tuples
Heap Only Tuples
Heap Only Tuples Чистка всех HOT цепочек в странице: ● проверяем указатели на предмет HOT цепочек. ● пропускаем redirects, unused и dead указатели. ● Чистим указатели и HOT цепочки (но не вносим никаких изменений в страницу): ● чистим dead и битые HOT цепочки; ● перестраиваем редиректы.
Heap Only Tuples Применяем изменения в критической секции: ● обновляем указатели; ● делаем дефрагментацию. Убираем отметку "page is full", помечаем страницу как грязную, пишем WAL. Завершаем критическую секцию. (Если доступных для чистки цепочек нет, то ничего не делаем)
lazy_scan_heap() Проверка страницы, сбор vacuumable строк, проверка на возможность заморозки. Проверка указателей: ● пропускаем unused, dead, redirects; проверяем только нормальные. HeapTupleSatisfiesVacuum(): ● HEAPTUPLE_DEAD: vacuumable (пропускаем если, это HOT цепочка). ● HEAPTUPLE_LIVE: хорошая строка, вакуум не нужен. ● HEAPTUPLE_RECENTLY_DEAD: нельзя удалять строку. ● HEAPTUPLE_INSERT_IN_PROGRESS и HEAPTUPLE_DELETE_IN_PROGRESS: пропускаем, страница не является ALL_VISIBLE. Запоминаем vacuumable строки в хранилище (vacrelstats).
lazy_scan_heap() Проверяем неудаляемые строки на возможность заморозки. ● подготавливаем строку если можно морозить (составляем локальный infomask). Если есть строки для заморозки: ● открываем критическую секцию; ● отмечаем страницу как грязную; ● устанавливаем биты в infomask строки; ● пишем изменения в WAL; ● завершаем критическую секцию.
lazy_scan_heap() Если нет индексов сразу вакуумим страницу. Обновляем Visibility Map и Free Space Map. Переходим к следующему блоку или завершаем цикл, если все блоки просканированы.
lazy_scan_heap() Сохраняем статистику, считаем новое pg_class.reltuples. Если еще есть строки к удалению, выполняем завершающий цикл вакуума. ● удаляем указатели в индексах; ● удаляем строки из таблицы с lazy_vacuum_heap().
lazy_vacuum_heap() lazy_vacuum_heap() – второй проход по таблице. Цикл через собранные строки (vacrelstats) – идем только в те страницы где есть мертвые строки: ● перед началом делаем vacuum_delay_point(); ● читаем блок и считаем costs; ● пытаемся взять блокировку для очистки – пропускаем блок если не удалось; ● чистим страницу с lazy_vacuum_page(); ● обновляем Free Space Map.
lazy_vacuum_page() lazy_vacuum_page() – чистим dead строки в странице, убираем фрагментацию. Все изменения в критической секции. ● цикл по dead строкам (внутри страницы); ● отмечаем указатель ItemID как неиспользуемый (LP_UNUSED); ● убираем фрагментацию страницы; ● отмечаем страницу как грязную, пишем в WAL. Закрываем критическую секцию. Обновляем Visibility Map.
lazy_scan_heap() Таблица обработана, VM обновлена. Обновляем FreeSpaceMap. Обновление статистики индексов (pg_class). Пишем в журнал сообщение о проделанной работе.
Конец ?
Что в итоге? Вакуум всегда должен быть включен. Дефолтные настройки не оптимальны. Нагрузка регулируется через cost-based опции. Вакуум не всегда может вычистить таблицу. Избегайте длинных транзакций.
Ссылки Alexey Lesovsky – lesovsky@pgco.me See slides on SlideShare: http://www.slideshare.net/alexeylesovsky/ PostgreSQL official documentation: ● Vacuum: https://www.postgresql.org/docs/current/static/routine-vacuuming.html ● Autovacuum: ● https://www.postgresql.org/docs/current/static/routine-vacuuming.html#AUTOVACUUM ● https://www.postgresql.org/docs/current/static/runtime-config-autovacuum.html ● Progress Reporting: https://www.postgresql.org/docs/devel/static/progress-reporting.html ● PageInspect contrib module: https://www.postgresql.org/docs/current/static/pageinspect.html
lazy_scan_heap() Теперь таблица уже обработана, VM обновлена. Обновляем FreeSpaceMap. Пишем в журнал: "%s: removed %d row versions in %d pages ". Обновление статистики индексов (pg_class). Пишем в журнал сообщение о проделанной работа.

Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский (PostgreSQL-Consulting LLC)

  • 1.
    Девять кругов ада илиPostgreSQL Vacuum Лесовский Алексей, 2016.11 PostgreSQL-Consulting
  • 2.
    Как быстро сломатьPostgres Часто и много обновлять таблицу. Отключить вакуум. До: 3565.5 tps, 0.839 ms. После: 172.8 tps, 17.373 ms. Как воспроизвести: https://goo.gl/Tql87l
  • 3.
    Take home messages Вакуумэто важно, его не стоит игнорировать. Если вакуум ненастроен производительность деградирует. Вакуум не страшен, настраивать его не сложно.
  • 4.
    Вакуум и какэто работает MVCC, Postmaster, Autovacuum Launcher & Workers. Что там внутри Worker'а. Подготовка к вакууму, costs, wraparound. Вакуум индексов, таблиц и их страниц. Слайды: ХХ-ХХ-ХХ
  • 5.
    MVCC MVCC – MultiversionConcurrency Control: ● предлагает хорошую конкурентность; ● в условиях значительной read/write активности; ● читатели не блокируют писателей и наоборот.
  • 6.
    MVCC MVCC – MultiversionConcurrency Control: ● предлагает хорошую конкурентность; ● в условиях значительной read/write активности; ● читатели не блокируют писателей и наоборот. ● Почти ;)
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
    Postmaster Postmaster работает вбесконечном цикле. ● запуск фоновых процесов (checkpointer, bgwriter, walwriter, ...); ● и в т.ч. autovacuum launcher; ● вообще там много всего… AV Launcher будет перезапущен если что-то пойдет не так.
  • 14.
    Autovacuum Launcher Инициализация Запуск воркерав случае emergency. Создание списка БД. Запуск бесконечного цикла (SIGTERM ?):
  • 15.
    Autovacuum Launcher Инициализация Запуск воркерав случае emergency. Создание списка БД. Запуск бесконечного цикла (SIGTERM ?): ● обработка SIGTERM, SIGHUP, SIGUSR2; ● запуск воркера для баз в списке (autovacuum_naptime).
  • 16.
    Как выбирается базадля обработки? Определяем xidForceLimit = recentXid – autovacuum_freeze_max_age. Риск wraparound с самым старым datfrozenxid/datminmxid. Базы которые давно не посещал вакуум. Пропускаем базы обработанные недавно.
  • 17.
    Кандидат выбран Отметка вshared памяти (имя БД, время запуска). Отправка сигнала Postmaster“у (флажок + SIGUSR1). Postmaster принимает сигнал и делает fork (connection limit?). Воркер запущен.
  • 18.
  • 19.
    Worker Инициализация (signals, filedescriptors, filemgr, bufmgr, smgr, shm, local struct). Установка параметров: ● zero_damaged_pages=false ● statement_timeout=0, lock_timeout=0 ● default_transaction_isolation="read commited" ● synchronous_commit=local
  • 20.
    Worker Получение имени БДиз av_startingWorker. Регистрация в runningWorkers и сброс av_startingWorker. Отправка SIGUSR2 процессу AV Launcher. Инициализация в качестве postgres backend.
  • 21.
    pg_class Составляем список таблицдля обработки ● таблицы и мат. представления; ● TOAST таблицы.
  • 22.
    pg_class Выбираются только таблицыи мат. представления (pg_class.relkind): ● чтение статы и параметров таблиц (pg_class.reloptions); ● запуск relation_needs_vacanalyze() – vaccum, analyze или wraparound? ● таблица является временной (pg_class.relpersistence)? Для TOAST запоминаем ассоциацию с родительской таблицей.
  • 23.
  • 24.
    Wraparound recentXid – текущаятранзакция. vacuum_freeze_min_age – строки с возрастом старше должны быть заморожены. vacuum_freeze_table_age – полное сканирование если достигнут возраст. autovacuum_freeze_max_age – возраст принудительного запуска wraparound вакуума.
  • 25.
    А нужен ливакуум? Проверка необходимости вакуума или сбора статистики (или все вместе). Определение пороговых параметров: ● параметры reloptions (от основной или TOAST таблицы); ● параметры конфигурации (postgresql.conf); ● для freeze_max_age выбираем минимум (reloptions vs. postgresql.conf);
  • 26.
    А нужен ливакуум? Принудительный вакуум если есть риск wraparound: ● xidForceLimit = recentXid – freeze_max_age; ● multiForceLimit = recentMulti – multixact_freeze_max_age; ● вакуум обязателен если pgclass.relfrozenxid или relminmxid старше порогов; ● если нет риска wraparound и AV отключен – пропускаем таблицу.
  • 27.
    А нужен ливакуум? pg_stat_all_tables.n_dead_tup, pg_stat_all_tables.n_mod_since_analyze reltuples = classForm->reltuples; vactuples = tabentry->n_dead_tuples; anltuples = tabentry->changes_since_analyze; vacthresh = (float4) vac_base_thresh + vac_scale_factor * reltuples; anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples; *dovacuum = force_vacuum || (vactuples > vacthresh); *doanalyze = (anltuples > anlthresh);
  • 28.
    А нужен ливакуум? autovacuum_vacuum_threshold = 50 # min number of row updates # before vacuum autovacuum_analyze_threshold = 50 # min number of row updates # before analyze autovacuum_vacuum_scale_factor = 0.2 # fraction of table size # before vacuum autovacuum_analyze_scale_factor = 0.1 # fraction of table size # before analyze
  • 29.
    Подготовка к вакууму Всетаблицы проверены – список составлен – закрываем pg_class. Выбор стратегии работы с shared памятью: ● BAS_BULKREAD: ring_size = 256 * 1024 / BLCKSZ; ● BAS_BULKWRITE: ring_size = 16 * 1024 * 1024 / BLCKSZ; ● BAS_VACUUM: ring_size = 256 * 1024 / BLCKSZ; (32kB). Выбор первой таблицы из списка.
  • 30.
    Расчет cost параметров vacuum_cost_delay= 0 # 0-100 milliseconds vacuum_cost_page_hit = 1 # 0-10000 credits vacuum_cost_page_miss = 10 # 0-10000 credits vacuum_cost_page_dirty = 20 # 0-10000 credits vacuum_cost_limit = 200 # 1-10000 credits autovacuum_vacuum_cost_delay = 20ms # default vacuum cost delay for # autovacuum, in milliseconds; # -1 means use vacuum_cost_delay autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for # autovacuum, -1 means use # vacuum_cost_limit
  • 31.
    Расчет cost параметров РазделениеI/O поровну между всеми воркерами. Объем I/O определяется с помощью cost_limit, cost_delay. ● autovacuum_vacuum_cost_limit или vacuum_cost_limit; ● autovacuum_vacuum_cost_delay или vacuum_cost_delay; Ничего не делать если параметры не установлены (<= 0).
  • 32.
  • 33.
    Вакуум, вакуум autovacuum_do_vac_analyze() –автовакуум и/или autoanalyze. ExecVacuum() – точка входа ручных VACUUM и ANALYZE команд. vacuum() – точка входа для вакуума и сбора статистики.
  • 34.
    Вакуум или Аналайз Cost-basedвакуум в случае VacuumCostDelay > 0. Обработка таблицы в зависимости от потребности: ● vacuum_rel() и analyze_rel(); Завершение обработки: ● обновление pg_database.datfrozenxid и чистка pg_clog; ● завершение работы.
  • 35.
    Блокировки Проверка отмены состороны пользователя. Выбор блокировки: ExclusiveLock или ShareUpdateExclusiveLock Открываем таблицу и берем блокировку. Не удалось взять блокировку? ● autovacuum: пишем в лог "skipping vacuum of %s --- lock not available"; ● не удалось открыть (таблица удалена?), завершаем работу.
  • 36.
    Проверка таблицы Проверка привилегий(superuser, владелец таблицы, владелец БД). Проверка что объект вообще vacuumable (таблицы, мат.вью, TOAST). Пропуск временных таблиц других бекендов. Запоминаем ассоциацию с TOAST (исключение автовакуум). Переключение userid на владельца таблицы.
  • 37.
    Do the actualwork /* * Do the actual work --- either FULL or "lazy" vacuum */ VACUUM FULL? ● закрываем таблицу, но продолжаем держать блокировку; ● cluster_rel() – VACUUM FULL является вариантом CLUSTER; см. cluster.c. В любом другом случае – lazy_vacuum_rel().
  • 38.
    Таблица обработана Вакуум завершен– таблица обработана. Закрытие таблицы. При наличии TOAST, переходим к ней (также vacuum_rel()).
  • 39.
    lazy_vacuum_rel() Установка пороговых значенийдля заморозки: ● freeze_min_age, freeze_table_age; ● multixact_freeze_min_age, multixact_freeze_table_age;
  • 40.
    lazy_vacuum_rel() Установка пороговых значенийдля заморозки: ● freeze_min_age, freeze_table_age; ● multixact_freeze_min_age, multixact_freeze_table_age; ● oldestXmin – оценка когда строка считается DEAD или RECENTLY_DEAD; ● freezeLimit – старше этого порога все строки замораживаются; ● xidFullScanLimit – полное сканирование таблицы если relfrozenxid старше порога;
  • 41.
    lazy_vacuum_rel() Установка пороговых значенийдля заморозки: ● freeze_min_age, freeze_table_age; ● multixact_freeze_min_age, multixact_freeze_table_age; ● oldestXmin – оценка когда строка считается DEAD или RECENTLY_DEAD; ● freezeLimit – старше этого порога все строки замораживаются; ● xidFullScanLimit – полное сканирование таблицы если relfrozenxid старше порога; ● multiXactCutoff – порог для удаления всех MultiXactIds из Xmax; ● mxactFullScanLimit – полное сканирование если relminmxid старше порога.
  • 42.
    lazy_vacuum_rel() Установка пороговых значенийдля заморозки: ● freeze_min_age, freeze_table_age; ● multixact_freeze_min_age, multixact_freeze_table_age; ● oldestXmin – оценка когда строка считается DEAD или RECENTLY_DEAD; ● freezeLimit – старше этого порога все строки замораживаются; ● xidFullScanLimit – полное сканирование таблицы если relfrozenxid старше порога; ● multiXactCutoff – порог для удаления всех MultiXactIds из Xmax; ● mxactFullScanLimit – полное сканирование если relminmxid старше порога. Сравниваем relfrozenxid/relminmxid с пороговыми значениями.
  • 43.
    lazy_vacuum_rel() Открываем индексы →вакуум с lazy_scan_heap() → Закрываем индексы. Считаем вся ли таблица была просканирована: scanned_pages + frozenskipped_pages = rel_pages Если возможно обрезаем таблицу. Обновляем Free Space Map, pg_class: ● relpages, reltuples, relallvisible, relhasindex, refrozenxid/relminmxid (full scan only). Сохраняем статистику в stats коллектор (n_live_tupe, n_dead_tuples). Пишем сообщение в журнал, при log_min_duration >= 0. Конец.
  • 44.
    Таблица обработана (напоминание) Вакуумзавершен – таблица обработана. Закрытие таблицы. При наличии TOAST, переходим к ней (также vacuum_rel()).
  • 45.
    /* lazy_scan_heap() –scan an open heap relation */ Выделяем память для хранения dead строк (autovacuum_work_mem); Проверяем страницы которые можно пропустить: ● ALL_FROZEN и ALL_VISIBLE флаги (в соотв. с visibility map); ● в случае full scan, нельзя пропускать ALL_VISIBLE страницы; ● всегда пропускаем ALL_FROZEN страницы; ● всегда сканируем последний блок – вдруг таблицу можно обрезать. После каждого блока выполняем vacuum_delay_point().
  • 46.
    lazy_scan_heap() Начинаем цикл проверкис первого непропускаемого блока: ● и снова ищем следующий блок который нельзя пропускать; ● проверяем хранилище dead строк на предмет переполнения; ● читаем содержимое страницы, считаем costs; ● пытаемся взять блокировку для чистки буффера (для HOT). Блок будет пропущен если блокировка провалится (искл. full-scan).
  • 47.
    lazy_scan_heap() Проверка страницы наналичие строк — кандидатов в заморозку: ● всегда чистим неинициализированные страницы; ● пропускаем пустые страницы; ● проверяем нормальные страницы; ● dead и redirect никогда не нужно морозить; ● проверяем что любое из XID полей (xmin,xmax,xvac) старше порога.
  • 48.
    lazy_scan_heap() Продолжаем основной циклпроверки страниц… Новые страницы инициализируем ● помечаем как грязные, отмечаем в Free Space Map. Пустые страницы: ● ставим отметку ALL_VISIBLE и ALL_FROZEN; ● помечаем как грязные, делаем запись в WAL, обновляем VM и FSM.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
    Heap Only Tuples Чисткавсех HOT цепочек в странице: ● проверяем указатели на предмет HOT цепочек. ● пропускаем redirects, unused и dead указатели. ● Чистим указатели и HOT цепочки (но не вносим никаких изменений в страницу): ● чистим dead и битые HOT цепочки; ● перестраиваем редиректы.
  • 55.
    Heap Only Tuples Применяемизменения в критической секции: ● обновляем указатели; ● делаем дефрагментацию. Убираем отметку "page is full", помечаем страницу как грязную, пишем WAL. Завершаем критическую секцию. (Если доступных для чистки цепочек нет, то ничего не делаем)
  • 56.
    lazy_scan_heap() Проверка страницы, сборvacuumable строк, проверка на возможность заморозки. Проверка указателей: ● пропускаем unused, dead, redirects; проверяем только нормальные. HeapTupleSatisfiesVacuum(): ● HEAPTUPLE_DEAD: vacuumable (пропускаем если, это HOT цепочка). ● HEAPTUPLE_LIVE: хорошая строка, вакуум не нужен. ● HEAPTUPLE_RECENTLY_DEAD: нельзя удалять строку. ● HEAPTUPLE_INSERT_IN_PROGRESS и HEAPTUPLE_DELETE_IN_PROGRESS: пропускаем, страница не является ALL_VISIBLE. Запоминаем vacuumable строки в хранилище (vacrelstats).
  • 57.
    lazy_scan_heap() Проверяем неудаляемые строкина возможность заморозки. ● подготавливаем строку если можно морозить (составляем локальный infomask). Если есть строки для заморозки: ● открываем критическую секцию; ● отмечаем страницу как грязную; ● устанавливаем биты в infomask строки; ● пишем изменения в WAL; ● завершаем критическую секцию.
  • 58.
    lazy_scan_heap() Если нет индексовсразу вакуумим страницу. Обновляем Visibility Map и Free Space Map. Переходим к следующему блоку или завершаем цикл, если все блоки просканированы.
  • 59.
    lazy_scan_heap() Сохраняем статистику, считаемновое pg_class.reltuples. Если еще есть строки к удалению, выполняем завершающий цикл вакуума. ● удаляем указатели в индексах; ● удаляем строки из таблицы с lazy_vacuum_heap().
  • 60.
    lazy_vacuum_heap() lazy_vacuum_heap() – второйпроход по таблице. Цикл через собранные строки (vacrelstats) – идем только в те страницы где есть мертвые строки: ● перед началом делаем vacuum_delay_point(); ● читаем блок и считаем costs; ● пытаемся взять блокировку для очистки – пропускаем блок если не удалось; ● чистим страницу с lazy_vacuum_page(); ● обновляем Free Space Map.
  • 61.
    lazy_vacuum_page() lazy_vacuum_page() – чистимdead строки в странице, убираем фрагментацию. Все изменения в критической секции. ● цикл по dead строкам (внутри страницы); ● отмечаем указатель ItemID как неиспользуемый (LP_UNUSED); ● убираем фрагментацию страницы; ● отмечаем страницу как грязную, пишем в WAL. Закрываем критическую секцию. Обновляем Visibility Map.
  • 62.
    lazy_scan_heap() Таблица обработана, VMобновлена. Обновляем FreeSpaceMap. Обновление статистики индексов (pg_class). Пишем в журнал сообщение о проделанной работе.
  • 63.
  • 64.
    Что в итоге? Вакуумвсегда должен быть включен. Дефолтные настройки не оптимальны. Нагрузка регулируется через cost-based опции. Вакуум не всегда может вычистить таблицу. Избегайте длинных транзакций.
  • 65.
    Ссылки Alexey Lesovsky –lesovsky@pgco.me See slides on SlideShare: http://www.slideshare.net/alexeylesovsky/ PostgreSQL official documentation: ● Vacuum: https://www.postgresql.org/docs/current/static/routine-vacuuming.html ● Autovacuum: ● https://www.postgresql.org/docs/current/static/routine-vacuuming.html#AUTOVACUUM ● https://www.postgresql.org/docs/current/static/runtime-config-autovacuum.html ● Progress Reporting: https://www.postgresql.org/docs/devel/static/progress-reporting.html ● PageInspect contrib module: https://www.postgresql.org/docs/current/static/pageinspect.html
  • 67.
    lazy_scan_heap() Теперь таблица ужеобработана, VM обновлена. Обновляем FreeSpaceMap. Пишем в журнал: "%s: removed %d row versions in %d pages ". Обновление статистики индексов (pg_class). Пишем в журнал сообщение о проделанной работа.