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скоро
  • Уроки
  • База знаний
  • Собеседование
Часть 0 — Карта территории

$ глава 4 · 45 минут

Файл таблицы и форки отношения

В прошлой главе мы дошли до пути base/16384/16503 - файла таблицы flights. Сейчас разберём этот путь до конца. Окажется, что таблица - это не один файл, а несколько параллельных, и что имя файла живёт своей жизнью, отдельной от внутреннего идентификатора таблицы.

Это последняя глава «карты территории». После неё мы спустимся внутрь одного файла - на уровень страниц и байтов. А пока научимся находить файлы таблицы на диске и понимать, что в них.

4.1 Таблица - это несколько файлов

Таблица в PostgreSQL хранится не одним файлом, а набором параллельных файлов - форков (forks). Главный форк держит строки, остальные - служебные карты, которые ускоряют работу. Все они лежат рядом в подкаталоге базы и относятся к одной таблице. Полный разбор - в relfilenode-forks.

Зачем разносить по файлам? Чтобы служебные карты можно было читать и обновлять, не трогая основной массив данных. Карта свободного места крошечная по сравнению с таблицей, и держать её отдельным маленьким файлом дешевле, чем вплетать в данные.

4.2 Найти файл таблицы

Путь к главному файлу таблицы возвращает pg_relation_filepath:

sql
SELECT pg_relation_filepath('flights');
-- base/16384/16503

Читается так: base/ - каталог обычных баз, 16384 - OID базы (совпадает со строкой lab из pg_database), 16503 - имя файла таблицы. Это имя называют relfilenode.

Числа на твоей установке будут другими: идентификаторы выдаются по мере создания объектов. Важна структура пути, а не конкретные цифры.

4.3 Четыре форка

У отношения может быть до четырёх форков. Различают их по суффиксу имени файла:

ФоркСуффиксЧто хранит
mainнетсами строки таблицы (или записи индекса)
fsm_fsmкарта свободного места по страницам
vm_vmкарта видимости и заморозки
init_initпустой шаблон для нежурналируемых таблиц

Главный форк есть всегда. Карта свободного места (_fsm) помогает вставке быстро находить страницу с местом - подробно в free-space-map. Карта видимости (_vm) отмечает страницы, где всё видимо всем, и ускоряет чтение и очистку - о ней visibility-map. Форк _init нужен только нежурналируемым (UNLOGGED) таблицам как эталон для сброса после сбоя.

4.4 Форки появляются не сразу

Сразу после CREATE TABLE на диске только главный форк. Служебные карты создаются позже - когда в них появляется смысл. Карту видимости, например, заполняет VACUUM. Это легко увидеть на размере форков:

sql
CREATE TABLE forklab (id int, note text);
INSERT INTO forklab SELECT g, 'row'||g FROM generate_series(1, 300) g;
SELECT pg_relation_size('forklab', 'vm') AS vm_before;   -- 0: карты видимости ещё нет
VACUUM forklab;
SELECT pg_relation_size('forklab', 'vm') AS vm_after;    -- 8192: появился форк _vm

До VACUUM форк _vm имеет нулевой размер - его попросту нет. После - это 8192 байта: одна страница карты на маленькую таблицу. Так проявляется ленивость: PostgreSQL не заводит структуру, пока она не понадобилась.

4.5 relfilenode - это не OID

У отношения два числа: OID (внутренний идентификатор в системном каталоге) и relfilenode (имя файла на диске). Поначалу они равны. Но команды, переписывающие таблицу целиком, создают новый файл с новым relfilenode, а OID оставляют прежним.

sql
SELECT oid, relfilenode, oid = relfilenode AS same
FROM pg_class WHERE relname = 'forklab';
-- oid=16583, relfilenode=16583, same=t
VACUUM FULL forklab;          -- переписывает таблицу в новый файл
SELECT oid, relfilenode, oid = relfilenode AS same
FROM pg_class WHERE relname = 'forklab';
-- oid=16583, relfilenode=16588, same=f

После VACUUM FULL OID тот же, а relfilenode сменился. То же делают TRUNCATE, CLUSTER и ALTER TABLE, меняющий тип колонки. Поэтому в коде, который должен пережить перезапись таблицы, ссылаются на OID, а pg_relation_filepath всегда показывает актуальный relfilenode.

4.6 Сегменты по гигабайту

Один форк не растёт одним бесконечным файлом. Как только главный форк переваливает за 1 ГБ, PostgreSQL продолжает в файле relfilenode.1, потом relfilenode.2 и так далее. Это страховка от старых файловых систем с лимитом на размер файла и удобство для операций копирования.

На практике это значит: большая таблица на диске - это пачка файлов 16503, 16503.1, 16503.2, а не один гигантский. Логически - одна таблица, физически - набор сегментов по гигабайту.

4.7 Подводный камень: имя файла - не адрес навсегда

Соблазнительно записать relfilenode таблицы и потом обращаться к файлу напрямую - например, в скрипте мониторинга. Не делай так. Любая перезапись таблицы (VACUUM FULL, TRUNCATE, смена типа колонки) даст новый relfilenode, и твоя ссылка станет указывать на удалённый или чужой файл.

Стабилен только OID. Если нужен путь, спрашивай его заново через pg_relation_filepath - он переведёт OID в актуальное имя файла. Имя файла - это снимок на момент запроса, а не вечный адрес.

Уроки в sandbox

lab-4.1. Найди файл таблицы на диске

Пройди путь от имени таблицы до её файлов и понаблюдай, как форки появляются, а relfilenode меняется. Перед каждым шагом предсказывай: нулевой ли размер форка, совпадут ли OID и relfilenode.

  1. Узнай путь к файлу: SELECT pg_relation_filepath('flights'); - разбери его на OID базы и relfilenode.

  2. Создай таблицу forklab(id int, note text) и вставь 300 строк через generate_series.

  3. Предскажи и проверь размер карты видимости до очистки: SELECT pg_relation_size('forklab','vm'); (ожидай 0).

  4. Сравни OID и relfilenode: SELECT oid, relfilenode, oid=relfilenode AS same FROM pg_class WHERE relname='forklab'; - сейчас они равны.

  5. Выполни VACUUM forklab; и снова посмотри размер _vm - предскажи, станет ли он ненулевым.

  6. Выполни VACUUM FULL forklab; и снова сравни OID с relfilenode - объясни, почему OID тот же, а relfilenode другой.

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

Резюме

  • Таблица хранится набором форков: главный (данные), _fsm (свободное место), _vm (видимость), _init (для UNLOGGED).
  • pg_relation_filepath возвращает путь base/<oid базы>/<relfilenode> к главному форку.
  • Главный форк есть всегда, служебные карты создаются лениво: _vm заполняет VACUUM.
  • OID - постоянный идентификатор в каталоге; relfilenode - имя файла, меняется при перезаписи таблицы.
  • VACUUM FULL, TRUNCATE, CLUSTER и смена типа колонки дают новый relfilenode при том же OID.
  • Форк делится на сегменты по 1 ГБ: relfilenode, relfilenode.1, relfilenode.2 ...

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

  1. Из каких файлов состоит таблица и зачем их несколько?

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

    Таблица состоит из форков: главного (строки), _fsm (карта свободного места), _vm (карта видимости и заморозки) и _init (шаблон для нежурналируемых таблиц). Их разносят по файлам, чтобы служебные карты можно было читать и обновлять, не трогая основной массив данных: карты маленькие, и отдельный маленький файл дешевле, чем вплетать их в данные.

  2. Почему сразу после CREATE TABLE форка _vm на диске нет?

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

    Потому что форки создаются лениво - когда в них появляется смысл. Карту видимости заполняет VACUUM, поэтому до первого вакуума размер _vm равен нулю (файла нет). Это видно по pg_relation_size('t','vm'): 0 до вакуума и ненулевое значение после. PostgreSQL не заводит структуру, пока она не понадобилась.

  3. Чем отличаются OID и relfilenode и когда они расходятся?

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

    OID - постоянный внутренний идентификатор отношения в системном каталоге. relfilenode - имя файла данных на диске. Сначала они равны, но команды, переписывающие таблицу целиком (VACUUM FULL, TRUNCATE, CLUSTER, смена типа колонки), создают новый файл с новым relfilenode, оставляя OID прежним. После такой операции OID тот же, relfilenode - другой.

  4. Почему опасно сохранять relfilenode и обращаться к файлу по нему позже?

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

    Потому что relfilenode меняется при любой перезаписи таблицы. Сохранённое имя файла после VACUUM FULL или TRUNCATE станет указывать на удалённый или чужой файл. Стабилен только OID; чтобы получить актуальный путь, нужно заново спросить pg_relation_filepath. Имя файла - это снимок на момент запроса, а не постоянный адрес.

  5. Почему большая таблица на диске - это несколько файлов, а не один?

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

    Потому что форк делится на сегменты по 1 ГБ. Когда главный форк превышает гигабайт, данные продолжаются в файлах relfilenode.1, relfilenode.2 и так далее. Это защита от ограничений старых файловых систем на размер файла и удобство для операций с файлами. Логически это одна таблица, физически - набор сегментов.

← Предыдущая03-cluster-and-databasesСледующая →05-page-anatomy
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки