linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it worksскоро
  • Уроки
  • База знаний
  • Собеседование
Часть I — Хранение данных

$ глава 5 · 55 минут

Анатомия страницы (8 КБ)

Мы дошли до главного файла таблицы. Теперь откроем его и посмотрим, что внутри. А внутри - массив страниц по 8 КБ. Страница - это та единица, в которой PostgreSQL читает, пишет и кеширует данные. Пока не увидишь раскладку одной страницы своими глазами, MVCC, vacuum и индексы останутся магией.

В этой главе мы вскроем страницу инструментом pageinspect и назовём каждую её зону. Не из учебника - из вывода реальной функции, которая читает сырые байты. Сначала предскажем смещения, потом сверим.

5.1 Страница как единица обмена

PostgreSQL не читает с диска отдельные строки и не пишет отдельные байты. Он оперирует страницами фиксированного размера - по 8 КБ (8192 байта). Страницу ещё называют блоком. Это единица всего: чтения с диска, записи, кеширования в памяти, ссылок из журнала.

Фиксированный размер упрощает движку жизнь. Буферный кеш - это массив одинаковых слотов по 8 КБ. Журнал ссылается на страницу номером блока. Чтение идёт ровными порциями. За удобство платят внутренней фрагментацией: если строка чуть больше половины страницы, на страницу влезет только одна, и половина места пропадёт. Но для типичных строк это выгодный размен. Общая раскладка - в page-layout.

5.2 Четыре зоны страницы

Внутри страница разбита на четыре зоны:

Хитрость в том, что массив указателей растёт от начала вниз, а кортежи кладутся с конца вверх. Между ними - свободное место. Они движутся навстречу, и страница полна, когда встретились. Зачем так - разберём через пару разделов.

5.3 Заголовок: три числа, которые держат всё

Подключим линзу и прочитаем заголовок страницы 0 таблицы flights:

sql
CREATE EXTENSION IF NOT EXISTS pageinspect;
SELECT lower, upper, special, pagesize
FROM page_header(get_raw_page('flights', 0));
--  lower | upper | special | pagesize
-- -------+-------+---------+----------
--    224 |  5464 |    8192 |     8192

Три числа задают всю геометрию страницы:

  • lower - где заканчивается массив указателей. Всё до него - заголовок и указатели.
  • upper - где начинаются кортежи. Всё после - данные строк.
  • special - где начинается спецзона. У heap она пустая, поэтому special равен размеру страницы.

5.4 Откуда берётся lower

Число lower = 224 не случайно. Заголовок занимает 24 байта. Дальше идёт массив указателей по 4 байта на строку. У flights 50 строк, значит указателей 50:

lower = 24 (заголовок) + 50 × 4 (указатели) = 224

Предскажи это перед запросом - и pageinspect подтвердит. Сами указатели видны так:

sql
SELECT lp, lp_off, lp_len
FROM heap_page_items(get_raw_page('flights', 0)) ORDER BY lp LIMIT 3;
--  lp | lp_off | lp_len
-- ----+--------+--------
--   1 |   8144 |     48
--   2 |   8096 |     48
--   3 |   8048 |     48

Указатели нумеруются 1, 2, 3 по возрастанию, а смещения lp_off убывают: 8144, 8096, 8048. Кортежи заполняют страницу с конца. Подробно про указатели - в line-pointers.

5.5 Свободное место посередине

Между концом указателей (lower) и началом кортежей (upper) лежит свободное место. На нашей странице это 5464 - 224 = 5240 байт.

Когда вставляется новая строка, происходит две вещи навстречу: с верхнего края добавляется новый указатель (растёт lower), а с нижнего кладётся сам кортеж (убывает upper). Свободное место посередине сжимается с обеих сторон. Как только его перестаёт хватать на новый указатель и кортеж - страница считается полной, и строка идёт в другую страницу.

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

5.6 Подводный камень: pd_lower больше pd_upper

lower всегда должен быть меньше или равен upper: указатели сверху, кортежи снизу, между ними зазор. Если вдруг lower > upper - зоны наложились, и это верный признак повреждённой страницы.

Такое не случается в нормальной работе: за этим следят и сам движок, и контрольные суммы страниц, если они включены. Но если ты диагностируешь подозрительную таблицу и page_header показывает lower больше upper, дальше читать эту страницу как обычную нельзя - данные в ней битые. Это первое, что проверяют при подозрении на порчу блока.

5.7 Зачем странице фиксированный размер

Вернёмся к началу: почему именно фиксированные 8 КБ. Потому что одинаковый размер пронизывает всю систему. Буферный кеш держит страницы в слотах одного размера - не нужно управлять кусками разной длины. Журнал предзаписи ссылается на изменения номером блока. Карта свободного места хранит по байту на страницу. Всё это работает, потому что страница - предсказуемая единица.

Размер 8 КБ - компромисс. Меньше - больше накладных расходов на заголовки и больше обращений к диску на тот же объём. Больше - грубее гранулярность блокировок и кеша, дороже частичные изменения. 8 КБ исторически легли в середину и стали значением по умолчанию. Менять его можно только при сборке, и почти никто этого не делает.

Уроки в sandbox

lab-5.1. Вскрытие страницы

Прочитай сырую страницу таблицы и назови её зоны - не из учебника, а из вывода pageinspect. Перед каждым шагом предсказывай: сколько будет указателей, чему равен lower.

  1. Подключи линзу: CREATE EXTENSION IF NOT EXISTS pageinspect;.

  2. Предскажи число строк на странице 0 таблицы flights и проверь: SELECT count(*) FROM heap_page_items(get_raw_page('flights', 0));.

  3. Предскажи lower по формуле 24 + число_строк × 4, потом сверь: SELECT lower, upper, special FROM page_header(get_raw_page('flights', 0));.

  4. Посчитай свободное место как upper - lower и объясни, где оно лежит.

  5. Посмотри смещения первых строк: SELECT lp, lp_off, lp_len FROM heap_page_items(get_raw_page('flights',0)) ORDER BY lp LIMIT 5; - убедись, что lp_off убывает.

  6. Объясни, почему special равен pagesize: у heap-страницы спецзона пустая.

sandbox с автопроверкой - открыть в песочнице

Резюме

  • Страница - фиксированные 8 КБ, единица чтения, записи, кеширования и ссылок из журнала.
  • Четыре зоны: заголовок (24 байта), массив указателей, свободное место, кортежи; спецзона у heap пустая.
  • lower - конец указателей, upper - начало кортежей; свободное место между ними.
  • Указатели растут сверху вниз, кортежи - снизу вверх, навстречу друг другу.
  • lower = 24 + число_строк × 4: заголовок плюс по 4 байта на указатель.
  • lower > upper означает повреждённую страницу - зоны наложились.
  • Фиксированный размер упрощает буферный кеш, журнал и карты; 8 КБ - компромисс по умолчанию.

Контрольные вопросы

  1. Из каких четырёх зон состоит heap-страница и в каком порядке они идут?

    Показать ответ

    Сверху вниз: заголовок (24 байта с метаданными), массив указателей строк (растёт вниз), свободное место, кортежи (растут вверх от конца страницы). В самом конце - спецзона, у heap-таблицы пустая (у индексов она занята служебными данными метода доступа).

  2. Почему lower равен 224 на странице с 50 строками?

    Показать ответ

    Потому что lower указывает на конец массива указателей. Заголовок занимает 24 байта, затем идёт по 4 байта на каждый указатель строки. На 50 строк это 24 + 50 × 4 = 224. То есть всё до смещения 224 - это заголовок и указатели, а свободное место начинается после.

  3. Зачем указатели и кортежи разнесены по краям страницы и растут навстречу?

    Показать ответ

    Чтобы свободное место всегда было единым куском посередине. Указатели растут сверху вниз (растёт lower), кортежи - снизу вверх (убывает upper), а между ними зазор. При вставке обе границы сдвигаются навстречу. Так не нужно искать дырки между записями: есть один непрерывный свободный участок, и проверка «влезет ли» сводится к сравнению upper - lower с нужным размером.

  4. О чём говорит ситуация lower > upper?

    Показать ответ

    О повреждении страницы. В норме lower (конец указателей) всегда меньше или равен upper (началу кортежей) - между ними свободный зазор. Если lower больше upper, зоны наложились, и данные в странице битые. В обычной работе такого не бывает; при диагностике подозрительной таблицы это первый признак порчи блока.

  5. Почему PostgreSQL использует страницы фиксированного размера?

    Показать ответ

    Потому что одинаковый размер упрощает всю систему: буферный кеш держит страницы в слотах одной длины, журнал ссылается на изменения номером блока, карта свободного места хранит по байту на страницу. 8 КБ - компромисс: меньше дало бы больше накладных расходов и обращений к диску, больше - грубее гранулярность кеша и блокировок и дороже частичные изменения. Менять размер можно только при сборке.

← Предыдущая04-relfilenode-forksСледующая →06-tuple-header
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки