26.02.2026

Буферы PostgreSQL: shared_buffers, кэш и performance tuning | Serverspace.uz

Введение

Тема буферов в PostgreSQL обычно всплывает только в одном месте — когда кто-то советует «подкрутить shared_buffers (например, до 1/4 RAM)».

Но буферы — это не просто настройка. Это фундаментальный слой, через который проходят чтения и записи: какие данные попадут в память, что будет вытеснено, когда и как «грязные» страницы окажутся на диске, почему последовательный скан не должен уничтожать прогретый кэш, и зачем вообще PostgreSQL держит собственный buffer cache при наличии кэша ОС.

Оригинал: https://boringsql.com/posts/introduction-to-buffers/

1) База основ: 8KB страница — атомарная единица I/O

Прежде чем говорить о буферах, важно понять ключевую вещь: PostgreSQL читает и пишет данные блоками фиксированного размера — страницами (pages).

По умолчанию размер страницы почти всегда 8192 байта (8KB). Это означает:

Проверить размер страницы можно так:

SHOW block_size;

2) Почему PostgreSQL держит свой кэш, если есть кэш ОС

Логичный вопрос: зачем PostgreSQL вообще нужен собственный буферный кэш, если операционная система уже умеет кэшировать файлы в RAM (page cache)?

Причины две:

2.1 Семантика данных

ОС видит «байты в файлах». PostgreSQL видит «таблицы, индексы, планы запросов» — и может принимать более умные решения. Например, при разовом последовательном скане большой таблицы PostgreSQL старается не «вымести» из кэша горячие страницы, используя специальные стратегии (ring buffers).

2.2 Долговечность и WAL (ACID)

PostgreSQL обязан соблюдать правило: сначала WAL должен попасть на стабильное хранилище, и только потом можно считать изменения данных надёжно зафиксированными. ОС не понимает, какие страницы относятся к данным, какие к WAL, и не может так же эффективно гарантировать этот порядок без сильного удара по производительности.

3) shared_buffers: главный буферный пул PostgreSQL

Параметр shared_buffers задаёт размер общей памяти, доступной всем backend-процессам.

Модель простая:

Посмотреть текущий размер:

SHOW shared_buffers;

Важно: shared_buffers — это не только «страницы данных». Внутри общего пула есть ещё служебные структуры, без которых быстрый кэш невозможен:

4) Pin count и usage count: почему «горячие» страницы живут дольше

Поведение буфера во многом определяется двумя счётчиками:

4.1 Pin count (закрепление)

Если страница прямо сейчас читается или модифицируется активным запросом, она «пинится» (pin). Пока pin > 0, страницу нельзя вытеснить из буферного пула — иначе запрос потеряет данные из-под ног.

4.2 Usage count (частота/давность использования)

PostgreSQL хранит небольшую оценку «насколько страница была востребована». При обращениях usage count растёт (с ограничением), а при попытке вытеснения — уменьшается.

Зачем это нужно? Чтобы один большой sequential scan не уничтожил весь прогретый кэш.

5) Как вытесняются страницы: clock sweep вместо LRU

Когда shared_buffers заполнен и нужно загрузить новую страницу, PostgreSQL должен найти место. Полноценный LRU был бы дорогим (поддерживать структуру данных на каждое обращение).

Вместо этого применяется алгоритм clock sweep (по аналогии со стрелкой часов, которая «обходит» круг буферов):

Итог: «холодные» страницы вылетают быстро, «горячие» держатся дольше.

6) Dirty buffers, background writer и checkpoint: когда запись реально уходит на диск

Когда PostgreSQL модифицирует страницу в shared_buffers, она становится dirty (грязной). Это означает: в памяти есть версия, которую нужно сбросить на диск, но делать это «немедленно» часто невыгодно (страница может измениться ещё много раз).

Как же грязные страницы попадают на диск:

6.1 Checkpoint

Во время checkpoint PostgreSQL сбрасывает грязные страницы, фиксируя согласованное состояние на диске. После успешного checkpoint при аварии нужно будет воспроизвести WAL только «с этого момента».

6.2 Background writer

Фоновый writer старается заранее «подчищать» грязные страницы, чтобы при вытеснении (eviction) backend не упирался в синхронную запись.

6.3 Худший случай: синхронная запись на вытеснении

Если clock sweep упёрся в dirty страницу, которую нужно вытеснить прямо сейчас — придётся синхронно записать её на диск, и запрос может ждать I/O. Это часто воспринимается как «внезапные лаги», особенно под нагрузкой.

7) Ring buffers: защита кэша от «одноразовых» операций

Есть операции, которые по природе «одноразовые» и трогают много страниц подряд: большой sequential scan, VACUUM, bulk операции. Если пустить их в общий shared_buffers, они могут выдавить горячие страницы и «охладить» систему на минуты.

Поэтому PostgreSQL использует ring buffers — маленькие отдельные буферные кольца для bulk-процессов. Смысл: прокрутить поток страниц внутри маленького буфера, не загрязняя основной кэш.

Типовые сценарии:

8) Local buffers: временные таблицы и temp_buffers

Временные таблицы (temporary tables) обслуживаются иначе: поскольку они «сессионные», PostgreSQL может использовать локальные буферы backend-процесса (local buffers) вместо общих shared_buffers.

Параметр temp_buffers задаёт размер памяти под временные таблицы на соединение. Плюсы:

Но есть важный момент: temp_buffers — это память на соединение, то есть при большом количестве коннектов суммарное потребление может вырасти очень сильно.

9) OS Page Cache: «второй уровень» кэша и почему правило 25% не из воздуха

PostgreSQL не обходит ОС: чтения и записи проходят через ядро, а ядро держит свой page cache. В итоге возможна двойная буферизация: одна и та же 8KB страница может находиться и в shared_buffers, и в кэше ОС.

Звучит как «трата памяти», но на практике это часто помогает:

Отсюда и классический компромисс: выделить часть RAM PostgreSQL под shared_buffers и оставить пространство ОС, чтобы page cache работал как «L2 кэш».

effective_cache_size: подсказка планировщику

Параметр effective_cache_size не «выделяет память». Он сообщает планировщику оценку доступного кэша (shared_buffers + OS cache), влияя на выбор планов (например, склоняя к index scan, если ожидается, что данные в кэше).

SHOW effective_cache_size;

Практические советы: как использовать знания о буферах в оптимизации

1) Смотрите BUFFERS в плане выполнения

Если вы анализируете производительность, одного EXPLAIN ANALYZE часто мало — важно видеть, сколько было:

EXPLAIN (ANALYZE, BUFFERS)
SELECT ...;

2) Избегайте «убийственных» sequential scan, которые охлаждают систему

Большие сканы могут быть допустимы, но важно понимать их последствия и почему PostgreSQL пытается изолировать их ring buffers. Иногда лучше оптимизировать отчёты индексами/партиционированием, чем регулярно прогонять гигантские сканы.

3) Следите за «грязными» страницами и пиками записи

Если система периодически «подвисает» на записи, это может быть:

4) Временные таблицы иногда быстрее, чем сложные CTE

Если у вас тяжёлые промежуточные вычисления, временная таблица + увеличение temp_buffers (в разумных пределах) может дать выигрыш и уменьшить давление на shared_buffers.

FAQ

Правда ли, что shared_buffers нужно ставить в 25% RAM?

Это популярная отправная точка, потому что важно оставить место OS page cache. Но идеальное значение зависит от нагрузки, размера данных, типа диска, числа соединений и того, насколько система «cache-friendly». Начинайте с разумной базы, затем измеряйте (BUFFERS, latency, I/O).

Если shared_buffers большой — будет быстрее?

Не всегда. Слишком большой shared_buffers может:

Почему иногда запрос “читает с диска”, хотя данных много в памяти?

Потому что «память» бывает двух уровней: shared_buffers и OS page cache. Miss в shared_buffers не означает физический диск — данные могут быть в кэше ОС, и тогда чтение будет быстрым.

Что такое pinned buffers и почему это важно?

Pinned буфер нельзя вытеснить, пока он используется активным запросом. Это защита от неконсистентности, но при высокой конкуренции и давлении на буферный пул pinned страницы могут усложнять eviction и влиять на latency.

Вывод

Буферы — это не «чёрный ящик» и не только shared_buffers. Это система управления страницами, которая включает:

Понимание этих механизмов помогает точнее диагностировать проблемы производительности и настраивать систему на реальные сценарии нагрузки.