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

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

TOAST: вынос и сжатие длинных значений

Строка не может пересекать границу страницы 8 КБ, поэтому длинные значения PostgreSQL сначала сжимает, а если всё равно не влезают - выносит во вторичную TOAST-таблицу чанками примерно по 2 КБ. Порог срабатывает около 2000 байт на строку. Управляется стратегией хранения: PLAIN, MAIN, EXTERNAL, EXTENDED.

view as markdownaka: oversized-attribute, out-of-line

Одно жёсткое правило: строка целиком должна помещаться в одну страницу 8 КБ, пересекать границу страницы она не может. Но в колонку text, jsonb или bytea легко положить мегабайт. Как уживаются эти два факта - через TOAST (The Oversized-Attribute Storage Technique), механизм сжатия и выноса длинных значений.

Порог и два приёма

Когда строка после упаковки оказывается длиннее порога (около 2000 байт, примерно четверть страницы), PostgreSQL берётся за самые длинные значения переменной длины и применяет к ним два приёма по очереди:

Сначала сжатие. Если сжатого значения хватает, чтобы строка влезла, на этом всё - значение остаётся в строке, просто компактнее.

sql
CREATE TABLE toast_demo (id int, big text);
INSERT INTO toast_demo VALUES (1, repeat('A', 5000));
SELECT pg_column_size(big) AS stored, octet_length(big) AS logical FROM toast_demo;
--  stored | logical
-- --------+---------
--      69 |    5000

5000 одинаковых букв сжались до 69 байт и спокойно остались в строке. octet_length - логическая длина значения, pg_column_size - сколько оно реально занимает на диске.

Когда сжатия мало: вынос наружу

Если значение плохо сжимается (случайные данные, уже сжатый JPEG), сжатие не спасает, и его выносят в отдельную TOAST-таблицу. У каждой таблицы с длинными колонками она своя:

sql
INSERT INTO toast_demo
SELECT 2, string_agg(md5(g::text), '') FROM generate_series(1, 200) g;  -- ~6400 несжимаемых байт
SELECT reltoastrelid::regclass FROM pg_class WHERE relname = 'toast_demo';
-- pg_toast.pg_toast_16566   (число в имени - oid таблицы, у тебя своё)

Внутри строки остаётся короткий указатель, а само значение лежит в TOAST-таблице, нарезанное на чанки примерно по 1996 байт. 6400 байт превращаются в 4 чанка. Сервер собирает значение обратно из чанков, только когда оно реально понадобится в запросе - короткий SELECT id длинное поле даже не трогает.

Четыре стратегии хранения

Поведение колонки задаёт её стратегия (ALTER TABLE ... ALTER COLUMN ... SET STORAGE):

СтратегияСжиматьВыносить
PLAINнетнет (для типов фиксированной длины)
MAINдав последнюю очередь
EXTERNALнетда
EXTENDEDдада (по умолчанию для длинных типов)

EXTERNAL иногда ставят осознанно: без сжатия чтение куска большого значения (подстроки, среза bytea) быстрее, потому что не надо распаковывать всё целиком.

Подводный камень: длинное значение и btree

Значение длиннее примерно трети страницы нельзя положить в btree-индекс целиком - запись индекса не помещается. Поэтому индексировать большой текст «как есть» не выйдет: для поиска по длинным значениям берут другие методы (например, индекс по выражению-хешу или полнотекстовый). Сам факт, что значение ушло в TOAST, на это не влияет - ограничение именно у btree.

§ команды

bash
SELECT pg_column_size(big), octet_length(big) FROM toast_demo;

Физический размер на диске против логической длины значения

bash
SELECT reltoastrelid::regclass FROM pg_class WHERE relname = 'toast_demo';

Имя TOAST-таблицы, обслуживающей длинные колонки

bash
ALTER TABLE toast_demo ALTER COLUMN big SET STORAGE EXTERNAL;

Сменить стратегию: выносить без сжатия

bash
SHOW default_toast_compression;

Алгоритм сжатия по умолчанию: pglz или lz4

§ см. также

  • page-layoutРаскладка страницы 8 КБТаблица на диске - это массив страниц по 8 КБ. Внутри страницы четыре зоны: заголовок 24 байта, массив указателей строк (растёт сверху вниз), свободное место и сами кортежи (растут снизу вверх). У heap-страницы спецзона в конце пустая. Указатели и кортежи движутся навстречу.
  • column-alignmentВыравнивание и порядок колонокВнутри строки колонки лежат с выравниванием: bigint и timestamp хотят начинаться с адреса, кратного 8, int - кратного 4. Между колонками PostgreSQL вставляет байты-заполнители (padding). Поэтому порядок колонок влияет на размер строки. Если расставить колонки от широких к узким, заполнителей становится меньше и строка занимает меньше места.
  • tuple-headerЗаголовок кортежаПеред данными каждой строки лежит служебный заголовок в 23 байта, выровненный до 24. В нём t_xmin и t_xmax (кто вставил и кто удалил версию), t_ctid (ссылка на себя или на новую версию), t_infomask с флагами и t_hoff - смещение до пользовательских данных. Это поля, на которых стоит весь MVCC.
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки