Вторая PostgreSQL-встреча: производительность PostgreSQL в web-приложениях
Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский...
-
Upload
ontico -
Category
Engineering
-
view
192 -
download
8
Transcript of Девять кругов ада или PostgreSQL Vacuum / Алексей Лесовский...
Девять кругов ада или PostgreSQL Vacuum
Лесовский Алексей, 2016.11PostgreSQL-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 millisecondsvacuum_cost_page_hit = 1 # 0-10000 creditsvacuum_cost_page_miss = 10 # 0-10000 creditsvacuum_cost_page_dirty = 20 # 0-10000 creditsvacuum_cost_limit = 200 # 1-10000 creditsautovacuum_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 – [email protected]
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).
Пишем в журнал сообщение о проделанной работа.