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/Хранение и формат на диске/column-alignment

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

Выравнивание и порядок колонок

Внутри строки колонки лежат с выравниванием: bigint и timestamp хотят начинаться с адреса, кратного 8, int - кратного 4. Между колонками PostgreSQL вставляет байты-заполнители (padding). Поэтому порядок колонок влияет на размер строки. Если расставить колонки от широких к узким, заполнителей становится меньше и строка занимает меньше места.

view as markdownaka: alignment, column-order, padding

Колонки в строке хранятся не вплотную. Процессор быстрее читает 8-байтовое число, когда оно начинается с адреса, кратного 8. PostgreSQL соблюдает это правило выравнивания (alignment) и вставляет между колонками байты-заполнители - padding. Заполнители места стоят, но пользы не несут.

Из-за этого две таблицы с одними и теми же колонками, но в разном порядке, занимают на диске разное место.

Один и тот же набор, два размера

Возьмём три колонки: два smallint (по 2 байта, выравнивание 2) и один bigint (8 байт, выравнивание 8). Разложим их двумя способами.

sql
CREATE TABLE align_bad  (a smallint, b bigint, c smallint);
CREATE TABLE align_good (b bigint, a smallint, c smallint);
INSERT INTO align_bad  VALUES (1, 2, 3);
INSERT INTO align_good VALUES (2, 1, 3);
SELECT 'bad'  AS t, lp_len FROM heap_page_items(get_raw_page('align_bad', 0))
UNION ALL
SELECT 'good' AS t, lp_len FROM heap_page_items(get_raw_page('align_good', 0));
--   t   | lp_len
-- ------+--------
--  bad  |     42
--  good |     36

Одни и те же данные - и 6 байт разницы на строку. Откуда они берутся.

Где прячутся 6 байт

Заголовок кортежа - 24 байта (см. tuple-header). Дальше идут данные.

Плохой порядок (smallint, bigint, smallint):

смещение:  0        2                8              16    18
данные:    [a: 2]   [6 байт padding] [b: 8 байт]    [c:2]

bigint обязан начаться с адреса, кратного 8. После первого smallint мы на смещении 2, поэтому PostgreSQL добивает 6 байт заполнителя, чтобы дотянуть до 8. Итого данных 18 байт, строка 24 + 18 = 42.

Хороший порядок (bigint, smallint, smallint):

смещение:  0            8       10    12
данные:    [b: 8 байт]  [a: 2]  [c:2]

bigint уже на нуле - выровнен. Два smallint ложатся следом без зазоров. Данных 12 байт, строка 24 + 12 = 36.

Правило: от широких к узким

Простая эвристика - перечислять колонки по убыванию выравнивания: сначала 8-байтовые (bigint, timestamp, double precision), потом 4-байтовые (int, date), потом 2-байтовые (smallint), потом 1-байтовые (bool, "char") и переменной длины в конце. Так заполнителей почти не остаётся.

Узнать выравнивание типа можно из каталога:

sql
SELECT typname, typalign FROM pg_type
WHERE typname IN ('int2','int4','int8','bool','timestamptz');
-- typalign: c = 1 байт, s = 2, i = 4, d = 8

Стоит ли переставлять колонки

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

§ команды

bash
SELECT lp_len FROM heap_page_items(get_raw_page('t', 0));

Реальная длина строки на диске в байтах, с учётом заполнителей

bash
SELECT pg_column_size(row(1::smallint, 2::bigint, 3::smallint));

Размер набора значений с выравниванием, без обращения к таблице

bash
SELECT typname, typalign, typlen FROM pg_type WHERE typname = 'int8';

Выравнивание и длина типа из системного каталога

§ см. также

  • tuple-headerЗаголовок кортежаПеред данными каждой строки лежит служебный заголовок в 23 байта, выровненный до 24. В нём t_xmin и t_xmax (кто вставил и кто удалил версию), t_ctid (ссылка на себя или на новую версию), t_infomask с флагами и t_hoff - смещение до пользовательских данных. Это поля, на которых стоит весь MVCC.
  • page-layoutРаскладка страницы 8 КБТаблица на диске - это массив страниц по 8 КБ. Внутри страницы четыре зоны: заголовок 24 байта, массив указателей строк (растёт сверху вниз), свободное место и сами кортежи (растут снизу вверх). У heap-страницы спецзона в конце пустая. Указатели и кортежи движутся навстречу.
  • toastTOAST: вынос и сжатие длинных значенийСтрока не может пересекать границу страницы 8 КБ, поэтому длинные значения PostgreSQL сначала сжимает, а если всё равно не влезают - выносит во вторичную TOAST-таблицу чанками примерно по 2 КБ. Порог срабатывает около 2000 байт на строку. Управляется стратегией хранения: PLAIN, MAIN, EXTERNAL, EXTENDED.
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки