← все кластеры

Буферный кеш, WAL, контрольные точки

Как PostgreSQL держит горячие страницы в памяти и при этом переживает внезапный сбой: буферный кеш и его вытеснение, журнал предзаписи (WAL), full-page images, контрольные точки, уровни журнала. Тут проверяют, понимаешь ли ты разницу между «записано в память» и «гарантированно переживёт сбой».

8 вопросов · ~35 мин чтения

#buffer-cache-basics

juniorчасто

Как устроен буферный кеш и зачем он, если есть кеш файловой системы?

Что отвечать

Буферный кеш - общая для всех бэкендов область в разделяемой памяти, размером `shared_buffers`, нарезанная на слоты по 8 КБ. Любое чтение и запись страницы идёт через него: бэкенд не лезет в файл напрямую, а просит страницу у менеджера буферов. Если страница уже там - попадание, диск не трогается. Кеш ОС тоже есть и работает слоем ниже, но буферный кеш PostgreSQL знает про MVCC, грязные страницы и WAL, поэтому может гарантировать правило журнала предзаписи и не отдавать на диск то, что ещё не зафиксировано в WAL.

Что хотят услышать

Senior должен: - описать кеш как разделяемую память из 8-килобайтных буферов, через которую идут все обращения к страницам - объяснить, зачем свой кеш поверх кеша ОС: контроль грязных страниц и соблюдение правила WAL - понимать двойное кеширование: одна страница может лежать и в `shared_buffers`, и в page cache ОС - дать ориентир по размеру (около четверти ОЗУ как старт) и сказать, почему «больше» не всегда лучше

Подводные камни

  • Ставить `shared_buffers` в почти всю память - кеш ОС тоже нужен, и контрольные точки начнут писать лавиной
  • Думать, что бэкенд читает файл напрямую - всё идёт через менеджер буферов
  • Путать попадание в буферный кеш с попаданием в кеш ОС - это два разных слоя

Follow-up

  • ? Почему `shared_buffers` обычно не ставят в 90% памяти?
  • ? Как `pg_buffercache` показывает, что сейчас в кеше?
  • ? Что такое двойное кеширование и чем оно плохо?

Глубина в базе знаний

tags: buffers, cache, memorybook: postgresql_internals-17.pdf:ch9 buffer cache

#buffer-eviction-clocksweep

intermediateиногда

Как PostgreSQL выбирает, какую страницу вытеснить из буферного кеша?

Что отвечать

Вместо классического LRU используется clock sweep. У каждого буфера есть счётчик использования: при обращении он растёт (до небольшого потолка), а специальный указатель ходит по кругу и на каждом буфере уменьшает счётчик. Буфер с нулём и без закрепления (pin) становится жертвой. Если жертва грязная (менялась с момента чтения), её сначала записывают на диск - но только после того, как соответствующая запись WAL уже там (правило предзаписи). Горячие страницы успевают подрасти счётчиком и переживают круг, холодные вытесняются.

Что хотят услышать

Senior должен: - описать clock sweep: счётчик использования плюс указатель, который его гасит по кругу - объяснить, почему перед вытеснением грязной страницы пишут WAL (write- ahead правило) - связать с производительностью: при нехватке кеша бэкенды сами начинают вытеснять и писать, отсюда просадки - упомянуть bgwriter, который заранее выписывает грязные буферы, чтобы бэкендам реже приходилось это делать

Подводные камни

  • Назвать алгоритм LRU - в PostgreSQL это clock sweep со счётчиком использования
  • Забыть про правило WAL: грязный буфер нельзя сбросить раньше его записи журнала
  • Думать, что вытеснением занят только bgwriter - под давлением этим занимаются и обычные бэкенды

Follow-up

  • ? Почему грязный буфер нельзя записать раньше его WAL-записи?
  • ? Чем занимается фоновый процесс bgwriter?
  • ? Что значит «буфер закреплён» (pinned) и кто его закрепляет?

Глубина в базе знаний

tags: buffers, eviction, bgwriterbook: postgresql_internals-17.pdf:ch9 buffer cache

#why-wal

juniorчасто

Зачем нужен WAL и в чём состоит правило предзаписи?

Что отвечать

WAL (write-ahead log) - последовательный журнал всех изменений страниц. Правило простое: запись в журнал о том, что страница изменилась, попадает на диск раньше, чем сама изменённая страница. Поэтому при коммите достаточно гарантированно записать WAL (один последовательный fsync), а грязные страницы данных можно сбрасывать лениво потом. Если сервер упадёт, при старте он проиграет WAL от последней контрольной точки и восстановит все подтверждённые изменения. Так одна последовательная запись даёт и долговечность (D в ACID), и быстрый коммит без случайных записей по всей таблице.

Что хотят услышать

Senior должен: - сформулировать правило предзаписи: журнал на диск раньше страницы данных - объяснить, почему это быстро: последовательная запись WAL вместо случайных записей страниц при каждом коммите - связать с восстановлением: после сбоя проигрывается WAL от контрольной точки - различить fsync WAL при коммите (обязателен для durability) и отложенный сброс страниц данных

Подводные камни

  • Думать, что при коммите на диск пишутся страницы данных - пишется WAL, страницы сбрасываются позже
  • Путать WAL с логом запросов или с журналом репликации - это журнал физических изменений страниц
  • Считать, что `synchronous_commit=off` ничего не теряет - он рискует последними транзакциями ради скорости

Follow-up

  • ? Что произойдёт при старте после внезапного выключения питания?
  • ? Чем рискует `synchronous_commit=off`?
  • ? Почему последовательная запись WAL дешевле, чем сброс страниц при каждом коммите?

Глубина в базе знаний

tags: wal, durability, recoverybook: postgresql_internals-17.pdf:ch10 wal

#wal-lsn-recovery

intermediateчасто

Что такое LSN и как идёт восстановление после сбоя?

Что отвечать

LSN (log sequence number) - монотонный адрес позиции в WAL, по сути смещение в журнале. У каждой страницы в заголовке хранится LSN последней применённой к ней записи WAL. При восстановлении сервер берёт точку последней контрольной точки и проигрывает WAL вперёд: для каждой записи сравнивает её LSN с LSN страницы и применяет только то, что страница ещё не видела (идемпотентность по LSN). Дойдя до конца журнала, база оказывается в согласованном состоянии со всеми подтверждёнными транзакциями. Те же LSN служат позициями для потоковой репликации.

Что хотят услышать

Senior должен: - определить LSN как позицию в WAL и сказать, что он же пишется в заголовок страницы - описать redo от контрольной точки и сравнение LSN записи с LSN страницы как защиту от повторного применения - связать LSN с репликацией: standby тянет WAL по LSN и сообщает, до куда применил - понимать, что восстановление накатывает изменения, а откат незавершённых транзакций обеспечивает MVCC (их версии просто невидимы)

Подводные камни

  • Думать, что recovery откатывает незавершённые транзакции явно - их версии просто остаются невидимыми по MVCC
  • Считать, что redo применяет все записи подряд - запись с LSN ниже LSN страницы пропускается
  • Путать LSN с номером транзакции - это позиция в журнале, а не xid

Follow-up

  • ? Как LSN страницы защищает от повторного применения записи WAL?
  • ? С какой точки начинается проигрывание WAL при восстановлении?
  • ? Как LSN используется в потоковой репликации?

Глубина в базе знаний

tags: wal, lsn, recoverybook: postgresql_internals-17.pdf:ch10 wal

#full-page-images

seniorиногда

Что такое full-page image и зачем PostgreSQL пишет целую страницу в WAL?

Что отвечать

Страница 8 КБ не записывается на диск атомарно: при сбое во время записи можно получить полустраницу (torn page) - часть старая, часть новая. Чтобы такую страницу можно было восстановить, при первом изменении после контрольной точки PostgreSQL пишет в WAL её полную копию - full-page image (FPI). Дальше идут обычные дельты, пока следующая контрольная точка снова не обнулит счётчик. Управляет этим `full_page_writes` (по умолчанию включён). FPI - главная причина, почему WAL раздувается сразу после контрольной точки и почему частые контрольные точки увеличивают объём журнала.

Что хотят услышать

Senior должен: - объяснить проблему torn page: неатомарность записи 8 КБ при сбое - сказать, что FPI пишется при первом изменении страницы после контрольной точки и лечит частичную запись - связать частоту контрольных точек с объёмом WAL: чаще точки - больше FPI - больше журнала - знать, когда `full_page_writes` можно выключить (ФС/устройство с атомарной записью страницы) и чем это рискованно

Подводные камни

  • Выключать `full_page_writes` без гарантий атомарной записи от хранилища - прямой риск порчи данных при сбое
  • Удивляться всплеску объёма WAL сразу после контрольной точки - это FPI
  • Делать контрольные точки очень частыми ради скорости recovery, забывая, что они раздувают WAL через FPI

Follow-up

  • ? Что такое torn page и почему он возможен?
  • ? Почему сразу после контрольной точки объём WAL подскакивает?
  • ? При каких условиях `full_page_writes` безопасно выключить?

Глубина в базе знаний

tags: wal, fpi, durabilitybook: postgresql_internals-17.pdf:ch10 wal

#checkpoints

intermediateчасто

Что делает контрольная точка и как её настройка влияет на нагрузку?

Что отвечать

Контрольная точка сбрасывает на диск все грязные буферы, накопившиеся до некоторого LSN, и записывает в WAL отметку: «всё до этой позиции уже в файлах данных». Это сокращает объём журнала, который придётся проиграть при восстановлении. Запускается по времени (`checkpoint_timeout`) или по объёму журнала (`max_wal_size`). Чтобы не было всплеска записи, сброс размазан во времени параметром `checkpoint_completion_target`. Слишком частые точки раздувают WAL через FPI и грузят диск; слишком редкие удлиняют восстановление и копят грязные буферы. Балансируют между скоростью recovery и ровностью записи.

Что хотят услышать

Senior должен: - описать контрольную точку как сброс грязных буферов плюс отметку в WAL, задающую начало будущего recovery - назвать оба триггера: время и объём журнала - объяснить компромисс: частые точки - больше FPI и записи, редкие - дольше восстановление - знать про размазывание записи (`checkpoint_completion_target`) и диагностику через лог контрольных точек и `pg_stat_bgwriter`

Подводные камни

  • Ставить очень маленький `max_wal_size` - контрольные точки зачастят и зальют диск через FPI
  • Игнорировать `checkpoint_completion_target` - сброс соберётся в пик и даст просадку
  • Путать контрольную точку с vacuum - точка про durability и recovery, не про мусор

Follow-up

  • ? Почему слишком частые контрольные точки вредны?
  • ? Зачем размазывать сброс через `checkpoint_completion_target`?
  • ? Как по логам понять, что контрольные точки идут по объёму, а не по таймеру?

Глубина в базе знаний

tags: wal, checkpoint, tuningbook: postgresql_internals-17.pdf:ch11 wal modes

#wal-levels

intermediateиногда

Какие бывают уровни WAL и зачем их повышать?

Что отвечать

Уровень журнала задаёт, сколько информации пишется в WAL. `minimal` - только то, что нужно для восстановления после сбоя на этом же сервере; некоторые массовые операции при нём могут не писать полный журнал. `replica` (по умолчанию) добавляет данные для потоковой репликации и архивного восстановления (PITR) - этого хватает для физических реплик. `logical` пишет ещё больше: достаточно, чтобы декодировать изменения на уровне строк для логической репликации и CDC. Чем выше уровень, тем больше объём WAL, поэтому его поднимают ровно под нужный сценарий.

Что хотят услышать

Senior должен: - перечислить уровни minimal/replica/logical и что каждый разблокирует - связать `replica` с физическими репликами и PITR, `logical` - с логической репликацией и захватом изменений - понимать цену: рост объёма журнала с повышением уровня - знать, что смена уровня требует перезапуска и заранее закладывается под план репликации

Подводные камни

  • Ставить `logical` без необходимости - лишний объём WAL без пользы
  • Ждать потоковую реплику на `minimal` - её данных не хватит standby
  • Думать, что уровень меняется на лету - нужен рестарт сервера

Follow-up

  • ? Какой уровень нужен для потоковой реплики, а какой для логической?
  • ? Почему `minimal` нельзя использовать для standby?
  • ? Что именно добавляет уровень `logical` в журнал?

Глубина в базе знаний

tags: wal, wal-level, replicationbook: postgresql_internals-17.pdf:ch11 wal modes

#ring-buffer-seqscan

seniorредко

Что такое буферное кольцо и зачем оно для больших последовательных операций?

Что отвечать

Если позволить большому `SELECT` по таблице крупнее кеша забивать `shared_buffers`, он вытеснит весь полезный горячий набор. Чтобы этого не было, для больших последовательных сканов, для `COPY` и для vacuum выделяется небольшое кольцо буферов (ring buffer): операция крутится внутри нескольких сотен килобайт и не выбивает чужие горячие страницы. Близкий, но отдельный механизм - синхронизация сканов (`synchronize_seqscans`): параллельные seq scan одной таблицы согласуют стартовую позицию, чтобы читать соседние страницы, пока те ещё горячие в кеше, поэтому скан может стартовать не с начала файла.

Что хотят услышать

Senior должен: - объяснить цель кольца: не дать большой операции вымыть горячий набор из кеша - назвать операции, которым выдают кольцо: большие seq scan, COPY, vacuum - описать синхронизацию сканов как отдельный механизм: параллельные сканы согласуют стартовую позицию, чтобы переиспользовать горячие страницы - понимать, что кольцо это компромисс: меньше вреда кешу ценой возможных повторных чтений

Подводные камни

  • Думать, что любой запрос свободно заполняет весь кеш - большие последовательные операции ограничены кольцом
  • Удивляться, что параллельный скан стартует с середины таблицы - это синхронизация сканов
  • Путать кольцо с общим кешем - это маленькая выделенная область под одну операцию

Follow-up

  • ? Каким операциям PostgreSQL выдаёт буферное кольцо?
  • ? Почему два одновременных скана одной таблицы экономят чтения?
  • ? В чём минус кольца по сравнению со свободным использованием кеша?

Глубина в базе знаний

tags: buffers, ring-buffer, seqscanbook: postgresql_internals-17.pdf:ch9 buffer cache