Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

2,582 views

Published on

Если вы сталкивались с PostgreSQL и зашли дальше, чем инструкция по установке, то, скорей всего, коротко познакомились с вакуумом, ну или, как минимум, что-то слышали про него.

Вакуум или по-русски очистка - это важная задача в жизненном цикле постгреса, которая заключается в регулярном освобождении базы данных от, так скажем, "мусора". Вакуум очень важен, его нельзя игнорировать и тем более отключать; более того, ему следует уделять должное внимание. А за кажущейся простотой скрывается довольно сложный и интересный механизм, к работе которого очень часто возникает много вопросов, на которые не всегда можно найти однозначный ответ.

В этом докладе я буду рассказывать про внутреннее устройство вакуума и раскрою следующие вопросы:
1) Что такое автовакуум (вакуум) и заморозка, и как они устроены изнутри.
2) Какие решения принимаются в процессе обработки таблиц и индексов.
3) Какие существуют возможности для управления вакуумом и как эти возможности влияют на работу вакуума.
4) Вакуум и вопрос производительности.

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

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

  1. 1. Девять кругов ада или PostgreSQL Vacuum Лесовский Алексей, 2016.11 PostgreSQL-Consulting
  2. 2. Как быстро сломать Postgres Часто и много обновлять таблицу. Отключить вакуум. До: 3565.5 tps, 0.839 ms. После: 172.8 tps, 17.373 ms. Как воспроизвести: https://goo.gl/Tql87l
  3. 3. Take home messages Вакуум это важно, его не стоит игнорировать. Если вакуум ненастроен производительность деградирует. Вакуум не страшен, настраивать его не сложно.
  4. 4. Вакуум и как это работает MVCC, Postmaster, Autovacuum Launcher & Workers. Что там внутри Worker'а. Подготовка к вакууму, costs, wraparound. Вакуум индексов, таблиц и их страниц. Слайды: ХХ-ХХ-ХХ
  5. 5. MVCC MVCC – Multiversion Concurrency Control: ● предлагает хорошую конкурентность; ● в условиях значительной read/write активности; ● читатели не блокируют писателей и наоборот.
  6. 6. MVCC MVCC – Multiversion Concurrency Control: ● предлагает хорошую конкурентность; ● в условиях значительной read/write активности; ● читатели не блокируют писателей и наоборот. ● Почти ;)
  7. 7. MVCC
  8. 8. MVCC
  9. 9. MVCC
  10. 10. MVCC
  11. 11. MVCC
  12. 12. MVCC
  13. 13. Postmaster Postmaster работает в бесконечном цикле. ● запуск фоновых процесов (checkpointer, bgwriter, walwriter, ...); ● и в т.ч. autovacuum launcher; ● вообще там много всего… AV Launcher будет перезапущен если что-то пойдет не так.
  14. 14. Autovacuum Launcher Инициализация Запуск воркера в случае emergency. Создание списка БД. Запуск бесконечного цикла (SIGTERM ?):
  15. 15. Autovacuum Launcher Инициализация Запуск воркера в случае emergency. Создание списка БД. Запуск бесконечного цикла (SIGTERM ?): ● обработка SIGTERM, SIGHUP, SIGUSR2; ● запуск воркера для баз в списке (autovacuum_naptime).
  16. 16. Как выбирается база для обработки? Определяем xidForceLimit = recentXid – autovacuum_freeze_max_age. Риск wraparound с самым старым datfrozenxid/datminmxid. Базы которые давно не посещал вакуум. Пропускаем базы обработанные недавно.
  17. 17. Кандидат выбран Отметка в shared памяти (имя БД, время запуска). Отправка сигнала Postmaster“у (флажок + SIGUSR1). Postmaster принимает сигнал и делает fork (connection limit?). Воркер запущен.
  18. 18. Postmaster & Co
  19. 19. 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
  20. 20. Worker Получение имени БД из av_startingWorker. Регистрация в runningWorkers и сброс av_startingWorker. Отправка SIGUSR2 процессу AV Launcher. Инициализация в качестве postgres backend.
  21. 21. pg_class Составляем список таблиц для обработки ● таблицы и мат. представления; ● TOAST таблицы.
  22. 22. pg_class Выбираются только таблицы и мат. представления (pg_class.relkind): ● чтение статы и параметров таблиц (pg_class.reloptions); ● запуск relation_needs_vacanalyze() – vaccum, analyze или wraparound? ● таблица является временной (pg_class.relpersistence)? Для TOAST запоминаем ассоциацию с родительской таблицей.
  23. 23. Wraparound
  24. 24. Wraparound recentXid – текущая транзакция. vacuum_freeze_min_age – строки с возрастом старше должны быть заморожены. vacuum_freeze_table_age – полное сканирование если достигнут возраст. autovacuum_freeze_max_age – возраст принудительного запуска wraparound вакуума.
  25. 25. А нужен ли вакуум? Проверка необходимости вакуума или сбора статистики (или все вместе). Определение пороговых параметров: ● параметры reloptions (от основной или TOAST таблицы); ● параметры конфигурации (postgresql.conf); ● для freeze_max_age выбираем минимум (reloptions vs. postgresql.conf);
  26. 26. А нужен ли вакуум? Принудительный вакуум если есть риск wraparound: ● xidForceLimit = recentXid – freeze_max_age; ● multiForceLimit = recentMulti – multixact_freeze_max_age; ● вакуум обязателен если pgclass.relfrozenxid или relminmxid старше порогов; ● если нет риска wraparound и AV отключен – пропускаем таблицу.
  27. 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. 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. 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. 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. 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. 32. Вакуум, вакуум
  33. 33. Вакуум, вакуум autovacuum_do_vac_analyze() – автовакуум и/или autoanalyze. ExecVacuum() – точка входа ручных VACUUM и ANALYZE команд. vacuum() – точка входа для вакуума и сбора статистики.
  34. 34. Вакуум или Аналайз Cost-based вакуум в случае VacuumCostDelay > 0. Обработка таблицы в зависимости от потребности: ● vacuum_rel() и analyze_rel(); Завершение обработки: ● обновление pg_database.datfrozenxid и чистка pg_clog; ● завершение работы.
  35. 35. Блокировки Проверка отмены со стороны пользователя. Выбор блокировки: ExclusiveLock или ShareUpdateExclusiveLock Открываем таблицу и берем блокировку. Не удалось взять блокировку? ● autovacuum: пишем в лог "skipping vacuum of %s --- lock not available"; ● не удалось открыть (таблица удалена?), завершаем работу.
  36. 36. Проверка таблицы Проверка привилегий (superuser, владелец таблицы, владелец БД). Проверка что объект вообще vacuumable (таблицы, мат.вью, TOAST). Пропуск временных таблиц других бекендов. Запоминаем ассоциацию с TOAST (исключение автовакуум). Переключение userid на владельца таблицы.
  37. 37. 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().
  38. 38. Таблица обработана Вакуум завершен – таблица обработана. Закрытие таблицы. При наличии TOAST, переходим к ней (также vacuum_rel()).
  39. 39. lazy_vacuum_rel() Установка пороговых значений для заморозки: ● freeze_min_age, freeze_table_age; ● multixact_freeze_min_age, multixact_freeze_table_age;
  40. 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. 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. 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. 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. 44. Таблица обработана (напоминание) Вакуум завершен – таблица обработана. Закрытие таблицы. При наличии TOAST, переходим к ней (также vacuum_rel()).
  45. 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. 46. lazy_scan_heap() Начинаем цикл проверки с первого непропускаемого блока: ● и снова ищем следующий блок который нельзя пропускать; ● проверяем хранилище dead строк на предмет переполнения; ● читаем содержимое страницы, считаем costs; ● пытаемся взять блокировку для чистки буффера (для HOT). Блок будет пропущен если блокировка провалится (искл. full-scan).
  47. 47. lazy_scan_heap() Проверка страницы на наличие строк — кандидатов в заморозку: ● всегда чистим неинициализированные страницы; ● пропускаем пустые страницы; ● проверяем нормальные страницы; ● dead и redirect никогда не нужно морозить; ● проверяем что любое из XID полей (xmin,xmax,xvac) старше порога.
  48. 48. lazy_scan_heap() Продолжаем основной цикл проверки страниц… Новые страницы инициализируем ● помечаем как грязные, отмечаем в Free Space Map. Пустые страницы: ● ставим отметку ALL_VISIBLE и ALL_FROZEN; ● помечаем как грязные, делаем запись в WAL, обновляем VM и FSM.
  49. 49. Heap Only Tuples
  50. 50. Heap Only Tuples
  51. 51. Heap Only Tuples
  52. 52. Heap Only Tuples
  53. 53. Heap Only Tuples
  54. 54. Heap Only Tuples Чистка всех HOT цепочек в странице: ● проверяем указатели на предмет HOT цепочек. ● пропускаем redirects, unused и dead указатели. ● Чистим указатели и HOT цепочки (но не вносим никаких изменений в страницу): ● чистим dead и битые HOT цепочки; ● перестраиваем редиректы.
  55. 55. Heap Only Tuples Применяем изменения в критической секции: ● обновляем указатели; ● делаем дефрагментацию. Убираем отметку "page is full", помечаем страницу как грязную, пишем WAL. Завершаем критическую секцию. (Если доступных для чистки цепочек нет, то ничего не делаем)
  56. 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. 57. lazy_scan_heap() Проверяем неудаляемые строки на возможность заморозки. ● подготавливаем строку если можно морозить (составляем локальный infomask). Если есть строки для заморозки: ● открываем критическую секцию; ● отмечаем страницу как грязную; ● устанавливаем биты в infomask строки; ● пишем изменения в WAL; ● завершаем критическую секцию.
  58. 58. lazy_scan_heap() Если нет индексов сразу вакуумим страницу. Обновляем Visibility Map и Free Space Map. Переходим к следующему блоку или завершаем цикл, если все блоки просканированы.
  59. 59. lazy_scan_heap() Сохраняем статистику, считаем новое pg_class.reltuples. Если еще есть строки к удалению, выполняем завершающий цикл вакуума. ● удаляем указатели в индексах; ● удаляем строки из таблицы с lazy_vacuum_heap().
  60. 60. lazy_vacuum_heap() lazy_vacuum_heap() – второй проход по таблице. Цикл через собранные строки (vacrelstats) – идем только в те страницы где есть мертвые строки: ● перед началом делаем vacuum_delay_point(); ● читаем блок и считаем costs; ● пытаемся взять блокировку для очистки – пропускаем блок если не удалось; ● чистим страницу с lazy_vacuum_page(); ● обновляем Free Space Map.
  61. 61. lazy_vacuum_page() lazy_vacuum_page() – чистим dead строки в странице, убираем фрагментацию. Все изменения в критической секции. ● цикл по dead строкам (внутри страницы); ● отмечаем указатель ItemID как неиспользуемый (LP_UNUSED); ● убираем фрагментацию страницы; ● отмечаем страницу как грязную, пишем в WAL. Закрываем критическую секцию. Обновляем Visibility Map.
  62. 62. lazy_scan_heap() Таблица обработана, VM обновлена. Обновляем FreeSpaceMap. Обновление статистики индексов (pg_class). Пишем в журнал сообщение о проделанной работе.
  63. 63. Конец ?
  64. 64. Что в итоге? Вакуум всегда должен быть включен. Дефолтные настройки не оптимальны. Нагрузка регулируется через cost-based опции. Вакуум не всегда может вычистить таблицу. Избегайте длинных транзакций.
  65. 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
  66. 66. lazy_scan_heap() Теперь таблица уже обработана, VM обновлена. Обновляем FreeSpaceMap. Пишем в журнал: "%s: removed %d row versions in %d pages ". Обновление статистики индексов (pg_class). Пишем в журнал сообщение о проделанной работа.

×