MAATRIX / Блог / Контрольная точка: почему база ровно и предсказуемо замирает раз в несколько минут

Контрольная точка: почему база ровно и предсказуемо замирает раз в несколько минут

MAATRIX

Если посмотреть на график нагрузки диска под базой достаточно долго, почти всегда видна одна и та же картина: ровный фон — и на нём периодические короткие всплески, похожие один на другой, как под линейку. Многие администраторы принимают это за проблему — «что-то раз в несколько минут грузит диск» — и начинают искать несуществующий баг. На самом деле это контрольная точка, checkpoint, и она делает именно то, для чего создана. В этой статье разберём, что накапливается в памяти базы между такими всплесками, зачем нужен сброс на диск разом, а не по одной странице, и как читать этот паттерн, чтобы не тратить время на поиск проблемы там, где её нет.

Страница данных живёт в памяти дольше, чем кажется

Когда транзакция меняет строку в таблице, база не бежит тут же переписывать файл данных на диске. Строка живёт внутри страницы (обычно 8 КБ в PostgreSQL, похожий принцип у MySQL/InnoDB с его страницами по 16 КБ) — и именно страница, а не отдельная строка, является единицей обмена с диском. Страница, с которой сейчас работает движок, загружена в буферный кэш — область разделяемой памяти (shared_buffers в PostgreSQL, buffer pool в InnoDB). Если её содержимое изменилось, а на диск это изменение ещё не записано, страницу называют «грязной» (dirty page).

Дальше с этой грязной страницей может произойти что угодно: её могут читать и менять ещё десяток раз в течение следующих секунд или минут. Если бы база физически переписывала страницу файла данных на каждое изменение, диск бы захлёбывался — одна и та же страница пишется по кругу ради одной строки, которая меняется чаще соседних. Поэтому грязная страница остаётся в памяти и накапливается там вместе с другими такими же, пока не наступит момент её сброса.

Гарантию сохранности при этом даёт не страница данных, а журнал упреждающей записи — WAL. Именно в него база записывает факт изменения синхронно, при коммите, и именно на этом держится обещание «данные не потеряются при сбое». Если вы ещё не разбирались, как устроена эта запись «дважды» — сначала в журнал, потом когда-нибудь в файл данных — есть отдельный разбор: что такое WAL и зачем писать дважды. Здесь важно другое следствие: раз страница данных откладывается, а журнал растёт с каждой транзакцией, кто-то должен периодически разгребать это накопление. Этим и занимается checkpoint.

Что делает контрольная точка и зачем она нужна

Контрольная точка — это операция, которая берёт все грязные страницы, накопившиеся в буферном кэше с прошлого checkpoint, и сбрасывает их на диск. Не одну страницу, а разом весь набор, который успел накопиться. После того как все они физически записаны и подтверждены (fsync), база отмечает в журнале: «до этой позиции WAL всё, что было изменено, уже гарантированно лежит в файлах данных».

Смысл в двух вещах.

Во-первых, ограничение размера журнала. WAL растёт, пока изменения не «осели» в файлах данных. До момента checkpoint журнал нельзя переиспользовать или удалить старые сегменты — база должна быть готова проиграть его целиком с последней контрольной точки в случае сбоя. Если checkpoint не наступает подолгу, журнал растёт неограниченно, и это отдельная больная тема — ей посвящён разбор почему растёт размер WAL. Checkpoint — это именно тот механизм, который позволяет журналу не расти бесконечно: как только грязные страницы сброшены, старые сегменты WAL становятся не нужны для восстановления и могут быть переработаны или удалены.

Во-вторых — и это часто недооценивают — контрольная точка ограничивает время восстановления после сбоя. Если сервер упал (отключилось питание, убило процесс, перезагрузили хост), при старте база не читает файлы данных «как есть» — она проигрывает WAL начиная с последней контрольной точки, применяя все изменения, которые не успели попасть в файлы данных до сбоя. Чем дальше в прошлом последняя контрольная точка, тем больше журнала нужно проиграть и тем дольше база будет недоступна после рестарта. Без checkpoint восстановление после аварии на нагруженной базе могло бы занимать часы вместо секунд-минут. Контрольная точка — это буквально контракт «мы не уйдём в проигрывание журнала дальше, чем на N минут (или N мегабайт) назад».

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Почему это не просто фоновая уборка, а разовая операция

Важно понимать разницу между checkpoint и обычной фоновой записью. У базы есть отдельный процесс (background writer в PostgreSQL), который и без всякого checkpoint потихоньку выталкивает часть грязных страниц на диск в спокойном режиме — просто чтобы буферный кэш не был забит целиком грязными страницами и чтобы серверу было проще найти свободный буфер под новые данные. Эта фоновая запись размазана по времени и обычно не создаёт заметного всплеска.

Checkpoint — другое дело. Это не поддержание порядка, а закрытие эпохи: «зафиксировать состояние на текущий момент». Он должен гарантированно сбросить на диск все страницы, изменённые с прошлой контрольной точки, — не часть из них, а все, потому что именно на этом основана гарантия «WAL до этой позиции больше не нужен для восстановления». Частично сброшенный набор такую гарантию дать не может. Поэтому checkpoint — это операция с чётким началом и концом, а не постоянный процесс, и именно поэтому она видна на графиках как всплеск, а не как ровный фон.

Почему всплеск дисковой нагрузки заметен, а не бесплатен

Даже если между двумя контрольными точками прошло немного времени, за это время под нагрузкой успевает накопиться заметное количество грязных страниц — во многих СУБД одна и та же горячая страница (например, индекс на первичном ключе активно растущей таблицы) переписывается в памяти десятки раз, но сбросить её на диск нужно один раз при checkpoint. Когда наступает момент сброса, движку нужно за ограниченное время записать все эти страницы физически на диск и дождаться подтверждения (fsync), что они там действительно лежат, а не просто уехали в кэш операционной системы.

Отсюда и всплеск: на короткое время параллельно с обычными операциями чтения-записи диск получает пачку дополнительных операций записи. На вращающихся дисках это ощущается сильнее всего — головкам приходится метаться между записью грязных страниц и обслуживанием обычных запросов. На SSD и NVMe эффект мягче, но не исчезает совсем: очередь запросов на запись растёт, и у операций, которые обычно укладываются в доли миллисекунды, задержка на секунды-другие подрастает — иногда заметно для клиента, который ждёт ответа именно в этот момент.

Важно: это не деградация и не признак того, что диск не справляется с базовой нагрузкой. Это разовая операция, которая должна выполниться, чтобы система оставалась в безопасном состоянии — с ограниченным журналом и предсказуемым временем восстановления. Плата за отложенную запись — это временный всплеск при её реализации; так это устроено, альтернативы без последствий здесь нет.

Как отличить checkpoint на графике от настоящей проблемы

Признаки, что вы видите именно checkpoint, а не деградацию:

  • Периодичность. Всплески повторяются с примерно одинаковым интервалом — это интервал по времени (checkpoint_timeout в PostgreSQL, по умолчанию 5 минут) либо по объёму накопленного WAL (max_wal_size, по умолчанию 1 ГБ), смотря что наступит раньше.
  • Форма всплеска. Заметный рост записи на диск (iostat -x 1, столбцы w/s, wMB/s, %util), который длится секунды или первые минуты, а затем возвращается к фоновому уровню.
  • Корреляция с процессом. В PostgreSQL всплеск совпадает по времени с активностью процесса checkpointer — это видно в pg_stat_activity и в логе, если включено log_checkpoints. Запись в лог покажет длительность checkpoint и объём записанных буферов.
  • Отсутствие роста задержек «навсегда». После всплеска задержки возвращаются к норме. Если рост задержек не проходит, а только накапливается — это уже не checkpoint, а, например, диск, который не успевает за постоянной нагрузкой, или медленно растущий bloat в таблицах.

Проверить гипотезу просто: включите логирование контрольных точек и посмотрите, совпадают ли моменты замедления по мониторингу с записями в логе.

-- postgresql.conf
log_checkpoints = on

После перезагрузки конфигурации (SELECT pg_reload_conf(); или systemctl reload postgresql, перезапуск не нужен) в логе появятся строки вида:

LOG:  checkpoint starting: time
LOG:  checkpoint complete: wrote 4213 buffers (25.7%); 0 WAL file(s) added,
      0 removed, 2 recycled; write=42.881 s, sync=1.204 s, total=44.912 s

Если время всплеска на графике диска совпадает со временем между checkpoint starting и checkpoint complete — вопрос закрыт, это плановая операция, а не инцидент.

Как сгладить всплеск, если он мешает

Полностью убрать checkpoint нельзя — без него журнал будет расти бесконечно, а время восстановления станет непредсказуемым. Но всплеск можно растянуть во времени и сделать менее концентрированным.

  • Растянуть запись самого checkpoint. Параметр checkpoint_completion_target в PostgreSQL задаёт долю интервала между checkpoint, за которую нужно успеть записать все грязные страницы. Значение ближе к 0.9 означает «пиши постепенно почти весь интервал», а не «выгрузи всё в первые секунды». Это не уменьшает объём записи, но размазывает её тоньше — пиковая нагрузка на диск в моменте становится ниже.
  • Реже, но крупнее — или чаще, но мельче. Увеличение checkpoint_timeout и max_wal_size снижает частоту checkpoint, но каждый отдельный checkpoint становится тяжелее (накопится больше грязных страниц) и время восстановления после сбоя увеличится. Уменьшение — обратный компромисс: чаще, но каждый раз легче. Здесь нет универсально правильного значения — это баланс между заметностью всплесков и допустимым временем восстановления после аварии.
  • Развести журнал и данные физически. Если WAL и файлы данных пишутся на один и тот же диск, всплеск checkpoint конкурирует за пропускную способность с синхронной записью WAL при каждом коммите — а она чувствительна к задержкам сильнее всего. Вынос WAL на отдельный диск снижает шанс, что checkpoint «заденет» латентность коммитов.
  • Проверить и настроить планировщик ввода-вывода на уровне ОС. То, как ядро распределяет приоритеты между потоками записи, тоже влияет на то, насколько сильно checkpoint «пробивает» задержки обычных запросов — подробнее в разборе настройки планировщика ввода-вывода.
  • Использовать более быстрый диск, если фон и так близок к пределу. Если базовая нагрузка на диск уже держит утилизацию на 60-70%, любой дополнительный всплеск будет заметен сильнее, чем на диске с запасом. Здесь помогает не столько тонкая настройка checkpoint, сколько апгрейд хранилища — NVMe вместо сетевого блочного тома, например.

Ни одна из этих настроек не убирает всплеск полностью — цель в том, чтобы он укладывался в допустимую задержку для ваших клиентов, а не был незаметен вовсе. Если задержки в моменты checkpoint критичны для приложения (например, это платёжный шлюз с жёстким SLA по времени ответа), обычно комбинируют сразу несколько мер: растянутый checkpoint_completion_target, отдельный диск под WAL и запас по IOPS на хранилище.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Checkpoint — это то же самое, что резервная копия?

Нет. Контрольная точка про внутреннюю согласованность буферного кэша и файлов данных, а не про резервное копирование. Некоторые инструменты бэкапа (например, pg_basebackup в специальном режиме) действительно запускают checkpoint перед стартом копирования, чтобы зафиксировать согласованную точку, но это частный случай использования механизма, а не его основное назначение.

Можно ли отключить checkpoint полностью?

Нет, штатной возможности отключить его нет — и не должно быть: без периодического сброса грязных страниц журнал вырастет до предела диска, а восстановление после любого сбоя займёт непредсказуемо долгое время. Можно только менять частоту и то, насколько растянуто во времени идёт запись.

Почему checkpoint иногда почти не заметен, а иногда даёт ощутимый провал?

Заметность зависит от того, сколько страниц успело стать грязными с прошлой контрольной точки, и от того, насколько диск уже загружен фоновой нагрузкой в этот момент. На тихой базе ночью checkpoint может пройти почти незаметно; на пиковой нагрузке в рабочие часы тот же checkpoint по объёму данных даст более заметный провал, потому что накладывается на и так загруженный диск.

Что делать, если всплески происходят слишком часто?

Проверьте, что именно триггерит checkpoint — время (checkpoint_timeout) или объём WAL (max_wal_size). Если каждый раз в логе видно, что checkpoint наступает раньше таймаута из-за достижения max_wal_size, увеличение этого параметра снизит частоту ценой более тяжёлых отдельных сбросов.

Влияет ли это на MySQL/InnoDB так же, как на PostgreSQL?

Идея та же — InnoDB тоже накапливает грязные страницы в buffer pool и периодически сбрасывает их (fuzzy checkpoint), — но детали параметров и алгоритмов свои: innodb_io_capacity, adaptive flushing и так далее. Общий принцип «страницы копятся в памяти, потом сбрасываются пачкой ради ограничения журнала и времени восстановления» одинаков для большинства СУБД с журналированием.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →