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скоро
  • Уроки
  • База знаний
  • Собеседование
home/postgres/kb/Хранение и формат на диске/page-layout

kb/storage ── Хранение и формат на диске ── beginner

Раскладка страницы 8 КБ

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

view as markdownaka: page-structure, heap-page, page-8kb

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

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

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

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

Прочитать заголовок можно расширением pageinspect:

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 - смещение, где заканчивается массив указателей. Всё до него - заголовок и указатели. На странице с 50 строками это (224 - 24) / 4 = 50 указателей по 4 байта.
  • upper - смещение, где начинаются кортежи. Всё после - данные строк.
  • Между lower и upper лежит свободное место. Здесь его 5464 - 224 = 5240 байт.
  • special указывает на спецзону в конце. У heap-таблицы она пустая, поэтому special = pagesize = 8192. У индексов там живут служебные данные метода доступа.

Если lower > upper - страница повреждена: зоны наложились. Это первый признак битого блока.

Указатели и кортежи - почему врозь

Почему PostgreSQL не кладёт строки подряд, а заводит отдельный массив указателей? Потому что строка должна иметь стабильный адрес, даже когда физически двигается внутри страницы. Адрес строки - это номер указателя, а не смещение в байтах. Указатель хранит реальное смещение и может поменяться при уплотнении страницы, а внешний адрес строки остаётся тем же. Детали - в line-pointers.

Сами строки лежат как кортежи. У каждого свой заголовок с версионными полями - его разбирает tuple-header.

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

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

§ команды

bash
CREATE EXTENSION IF NOT EXISTS pageinspect;

Подключить линзы для чтения сырых страниц

bash
SELECT * FROM page_header(get_raw_page('flights', 0));

Заголовок страницы 0: lsn, lower, upper, special, prune_xid

bash
SELECT lp, lp_off, lp_len FROM heap_page_items(get_raw_page('flights', 0));

Указатели и смещения всех строк на странице 0

bash
SELECT relpages FROM pg_class WHERE relname = 'flights';

Сколько страниц занимает таблица по оценке планировщика

§ см. также

  • line-pointersLine pointers и lp_flagsУказатель строки (line pointer) - это 4 байта в начале страницы: смещение до кортежа, его длина и флаг состояния. Флаг lp_flags бывает четырёх видов: unused, normal, redirect, dead. Внешний адрес строки (ctid) - это номер указателя, а не позиция в байтах, поэтому строка может двигаться внутри страницы, не меняя адреса.
  • tuple-headerЗаголовок кортежаПеред данными каждой строки лежит служебный заголовок в 23 байта, выровненный до 24. В нём t_xmin и t_xmax (кто вставил и кто удалил версию), t_ctid (ссылка на себя или на новую версию), t_infomask с флагами и t_hoff - смещение до пользовательских данных. Это поля, на которых стоит весь MVCC.
  • toastTOAST: вынос и сжатие длинных значенийСтрока не может пересекать границу страницы 8 КБ, поэтому длинные значения PostgreSQL сначала сжимает, а если всё равно не влезают - выносит во вторичную TOAST-таблицу чанками примерно по 2 КБ. Порог срабатывает около 2000 байт на строку. Управляется стратегией хранения: PLAIN, MAIN, EXTERNAL, EXTENDED.
  • relfilenode-forksrelfilenode и форки отношенияКаждая таблица и индекс лежит на диске как набор файлов - форков. Главный форк хранит сами данные, _fsm - карту свободного места, _vm - карту видимости, _init - шаблон для нежурналируемых таблиц. Имя файла - это relfilenode, и оно не равно OID после перезаписи таблицы.
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки