Мне стало интересно разобраться, как PostgreSQL хранит данные на диске, и в процессе своего исследования я обнаружил несколько интересных фактов, которыми хочу с вами поделиться. Мы будем рассматривать только файлы кучи (heap). Heap-файл — это просто файл записей. Не путайте heap-файл с heap-памятью. Хотя их использование очень похоже: хранение динамических данных. PostgreSQL разбивает файлы данных на сегменты. Размер сегмента фиксирован и обычно составляет 1 ГБ, но его можно изменить при компиляции, используя параметр --with-segsize. Когда размер таблицы или индекса превышает 1 ГБ, они разбиваются на сегменты по гигабайту. Такой подход позволяет избежать проблем на платформах с ограничением размера файла, 1 ГБ — очень консервативный выбор для любой современной платформы. Данные хранятся на страницах фиксированного размера. Размер страницы обычно 8 КБ, который можно изменить при компиляции через параметр --with-blocksize. Хотя изменяют его редко. Страница в 8 КБ — компромисс между производительностью и надежностью. Если размер страницы слишком мал, строки не поместятся внутри страницы, а если слишком большой, существует риск сбоя записи, поскольку оборудование обычно гарантирует атомарность только для блоков фиксированного размера, который может отличаться от диска к диску (обычно в диапазоне от 512 до 4096 байт). Страницы выглядят следующим образом: lp(1…N) — массив указателей на строки (line pointer array). Указатель содержит логическое смещение на странице. Поскольку это массив, то все элементы имеют фиксированный размер, но количество элементов на странице может изменяться. row(1...N) — непосредственно данные. Обычно они переменной длины и для доступа к определенному кортежу используются указатели строк. Строки добавляются  от конца к началу страницы, а указатели строк — от начала к концу. Область special space обычно используется при хранении индексов, например, для ссылок на соседние узлы в B-Tree. Для табличных данных не используется. Указатели lower и upper указывают занятое пространство на странице. Таким образом, по разности (upper - lower) можно определить, используется страница или нет. Заголовок страницы (page header) сам по себе не так уж и интересен, он содержит информацию о странице, включая указатели lower и upper. Его структура выглядит следующим образом: Поле Размер Описание pd_lsn               8 байт LSN: следующий байт после последнего байта записи xlog для последнего изменения на этой странице pd_checksum         2 байта Контрольная сумма страницы pd_flags             2 байта Биты признаков pd_lower             2 байта Смещение до начала свободного пространства pd_upper             2 байта Смещение до конца свободного пространства pd_special           2 байта Смещение до начала специального пространства (special space) pd_pagesize_version 2 байта Информация о размере страницы и номере версии компоновки                   pd_prune_xid         4 байта Самый старый неочищенный идентификатор XMAX на странице или ноль при отсутствии такового У такого способа хранения данных есть несколько особенностей: Во-первых, кортежи не могут занимать несколько страниц. Невозможно напрямую хранить очень большие значения полей. Значения больших полей сжимаются и/или разбиваются на несколько физических строк. Во-вторых, максимальное количество столбцов ограничено от 250 до 1600 в зависимости от типа столбца. В-третьих, если вы полагаете, что указав в select только нужные столбцы для таблицы с несколькими столбцами, запрос будет выполняться быстрее из-за меньшего количества дисковых операций ввода-вывода, то это не так.  Такой тип хранилища не оптимизирован для аналитических рабочих нагрузок, потому что необходимо считывать с диска данные, не имеющие отношения к запросу. Поэтому большинство аналитических баз данных (OLAP), таких как Vertica, используют колоночное хранилище. Давайте попробуем заглянуть внутрь страницы и посмотрим, какие у PostgreSQL есть инструменты для этого. Начнем с создания базы данных и таблицы с дальнейшим заполнением ее некоторыми данными. Для каждой строки PostgreSQL создает внутренний уникальный идентификатор, который обычно не виден пользователям. Но его можно запросить явно. Первая цифра в ctid — это номер страницы, а вторая — номер кортежа. Использование физических строк на странице, мы можем наблюдать при запуске VACUUM для дефрагментации страницы. Давайте удалим одну запись и вставим новую. Здесь мы видим, что PostgreSQL оставил физическую строку с name равным  c703af5c89b1d0bc2e99f540f553f182, которая была удалена, и добавил данные на новую физическую строку. Давайте запустим VACCUM. VACCUM, как и ожидалось, дефрагментировал страницу, переместив кортежи. Выглядит это следующим образом: Мы также можем заглянуть внутрь страниц с помощью расширения pageinspect. Взглянем на последнюю страницу (416). Указатель lower равен 344, а upper 3088, то есть свободного места на этой странице 2744 байт. Указатель special указывает на 8192 — это конец страницы. Можно сделать вывод, что данных в special space нет. Посмотрим еще на предыдущую страницу — мы ожидаем, что на этой странице недостаточно свободного места. Как и ожидалось, 8 байт недостаточно для хранения id + name. Также можем заглянуть внутрь элементов страницы, используя следующий запрос: Database Physical Storage  Расширение PageInspect  Row storage in PostgreSQL, heap file page layout  Database Storage I (CMU Databases Systems / Fall 2019) Материал подготовлен в преддверии старта онлайн-курса «Базы данных».