Перейти к основному содержимому

Уровень 2.0

Предусловие:

  • Изучен модуль «Свойства транзакций» данного курса

В этом задании вы узнаете о:

  • Структуре страниц
  • Структуре версий строк
  • Журнале статусов транзакций (CLOG)
  • Выполнении операций с данными
  • Работе с точками сохранения

Структура страниц​

В Pangolin такие типы объектов, как таблицы, индексы, последовательности, материализованные представления и TOAST-таблицы, хранятся в виде страниц данных, имеющих общую структуру и размер 8 Кбайт. Такая реализация обеспечивает возможность использования общего кеша буферов для различных типов объектов.

Объекты указанных типов в силу схожести своего устройства объединяются общим термином отношение (relation).

Структура страницы включает:

  • Заголовок страницы
  • Идентификаторы элементов
  • Свободное пространство
  • Элементы (у табличных страниц — версии строк)
  • Специальное пространство

Заголовок страницы​

Заголовок страницы имеет размер 24 байта и содержит общую информацию о странице. В том числе в заголовке хранятся следующие данные:

  • Ссылка на последнюю запись в журнале WAL (номер LSN — Log Sequence Number), связанную с этой страницей
  • Контрольная сумма страницы (CRC), если включен режим контрольных сумм
  • Смещения от начала страницы до разделов со свободным пространством, элементами и специальным пространством
  • Признак видимости всех версий строк на странице во всех снимках
  • Самый старый идентификатор xmax неочищенных версий строк используется для оптимизации процесса очистки

Идентификаторы элементов​

Идентификаторы элементов представляют собой массив указателей, каждый из которых содержит следующую информацию:

  • Смещение элемента относительно начала страницы
  • Длина элемента в байтах
  • Статус элемента

Указатели необходимы для организации косвенной адресации индексных строк на версии строк в таблице. В каждой индексной строке хранится идентификатор версии строки (ctid — current tuple id), состоящий из номера страницы в файле основного слоя и номера указателя в странице данных. Косвенная адресация позволяет перемещать элементы (версии строк) при уплотнении свободного пространства внутри страницы без изменения ссылающихся на них индексных строк. При перемещении версии строки достаточно изменить ее указатель без изменения ctid в индексе. Помимо этого наличие указателей в начале страницы позволяет оптимизировать поиск версий строк в ней.

Свободное пространство​

Между идентификаторами элементов и самими элементами в странице содержится свободное пространство. Свободное пространство не фрагментировано — при очистке «мертвых» версий строк происходит уплотнение оставшихся версий строк и, соответственно, свободного пространства. Границы свободного пространства страницы отмечены в карте свободного пространства (Free Space Map — FSM).

Элементы (версии строк)​

Между свободным пространством и специальным пространством хранятся сами элементы страницы, в роли которых для табличных страниц выступают версии строк с некоторыми служебными параметрами.

Хранение в табличной странице именно версий строк продиктовано механизмом многоверсионности MVCC. Время жизни каждой версии строки определяется параметрами xmin и xmax — номерами транзакций, создавшей и удалившей версию строки соответственно. Однако в индексах нет информации о версионности строк, поэтому в индексных страницах для каждой табличной версии строки имеется соответствующая ссылка. Выделение места для хранения элементов осуществляется с конца свободного пространства.

Каждая версия строки должна целиком помещаться в одной странице. Если версия строки целиком не помещается в одной странице, то возможно два варианта: либо сжатие части полей, либо их перенос в отдельную TOAST-таблицу.

Специальное пространство​

Специальное пространство в индексных страницах содержит информацию, необходимую для реализации того или иного метода доступа. В частности, индексные страницы типа B-дерево в специальном пространстве содержат ссылки на страницы слева и справа от данной страницы.

В табличных страницах PostgreSQL специальное пространство не занято. Однако в Pangolin оно занимает 24 байта и используется для хранения информации, необходимой для увеличения размера счетчика транзакций до 64 бит. Более подробно этот вопрос рассматривается в лекции «Заморозка версий строк».

Структура версий строк​

Версия строки в странице данных содержит следующие разделы:

  • Заголовок
  • Карта пустых значений (не обязательна)
  • Идентификатор объекта (OID, не обязателен)
  • Данные

Заголовок имеет размер 24 байта и среди прочего содержит следующие поля:

  • xmin — номер транзакции, создавший версию строки
  • xmax — номер транзакции, удалившей версию строки
  • ctid — идентификатор следующей, более новой версии той же строки; используется при оптимизации HOT (Heap Only Tuple, см. лекцию «Внутристраничная очистка»)
  • infomask — набор информационных битов или битов-подсказок, определяющих свойства версии строки

Поле infomask содержит множество информационных битов, среди которых на данном этапе обучения можно выделить следующие:

  • xmin committed — флаг фиксации транзакции, создавшей версию строки
  • xmin aborted — флаг отката транзакции, создавшей версию строки
  • xmax committed — флаг фиксации транзакции, удалившей версию строки
  • xmax aborted — флаг отката транзакции, удалившей версию строки

Поля xmin, xmax совместно с перечисленными информационными битами используются при определении видимости версии строки в том или ином снимке. Более подробно это рассматривается в лекции «Снимки данных».

Журнал статусов транзакций (CLOG)​

Статус транзакции при завершении (фиксацией или откатом) необходимо каким-то образом сохранять. Можно было бы использовать соответствующие информационные биты в версиях строк. Однако это привело бы к значительным накладным расходам, поскольку потребовалось бы обращаться к множеству строк в множестве страниц данных.

По этой причине для хранения статусов транзакций используется отдельная структура — журнал статусов транзакций (Commit LOG — CLOG). CLOG хранится в подкаталоге pg_xact каталога данных и кешируется в общей памяти. Структура CLOG представляет собой всего два бита для каждой транзакции — флаги committed и aborted. Таким образом, для завершения транзакции необходимо только выставить соответствующий флаг, что происходит мгновенно.

Для определения видимости версии строки в том числе необходимо узнать статус транзакции, создавшей данную версию строки.

Чтобы понять, завершилась транзакция xmin или нет, читающая транзакция обращается к структуре ProcArray, размещенной в общей памяти и содержащей список всех активных обслуживающих процессов с указанием идентификатора текущей транзакции. В случае если транзакция завершилась, необходимо понять каким образом: фиксацией или откатом. Для этого уже используется структура CLOG.

Однако такую проверку накладно проводить каждый раз для каждой читаемой версии строки. Поэтому статусы завершенных транзакций xmin и xmax для версии строки определяются один раз, после чего сохраняются в соответствующие информационные биты заголовка строки. При этом необходимо учитывать, что даже читающие транзакции при такой реализации могут изменять страницы данных (путем изменения информационных битов со статусами транзакций).

Пример структуры страниц​

Пример табличной страницы и связанной с ней индексной страницы с соответствующим состоянием журнала CLOG представлен на следующем рисунке:

Пример табличной и индексной  страниц

В данном примере в табличной странице имеется две строки: первая (с ключом 100) имеет одну версию, вторая (с ключом 200) — две версии.

На каждую из версий строк в индексной странице имеется соответствующая запись, ссылающаяся на нее посредством идентификаторов элементов в табличной странице.

Транзакция с номером 750 зафиксирована, что видно в журнале CLOG и информационном бите xmin committed в соответствующей версии строки.

Транзакция с номером 770 также зафиксирована. Однако в информационных битах в версиях строк пока это не отражено. При следующем обращении к соответствующим версиям строк информационные биты указанной транзакции будут проставлены.

Также по странице данных и журналу CLOG можно определить, что транзакция с номером 770 выполнила обновление строки с ключом 200. Старая версия строки удалена (xmax = 770), и создана новая версия (xmin = 770).

В данном случае при обновлении в индексной странице была создана отдельная запись для новой версии строки. Однако во многих случаях этого не происходит благодаря оптимизации под названием HOT (Heap Only Tuple), которая будет рассмотрена в лекции «Внутристраничная очистка».

Более подробно реализация операций с данными будет рассмотрена ниже.

Реализация операций с данными​

Рассмотрим выполнение операций DML на примере простой таблицы с индексом:

some_db=> \d some_table
Таблица "public.some_table"
Столбец | Тип | Правило сортировки | Допустимость NULL | По умолчанию
---------+---------+--------------------+-------------------+--------------
id | integer | | |
value | integer | | |
Индексы:
"some_table_value_idx" btree (value)

Вставка строк​

Состояние табличной и индексной страниц, а также журнала CLOG после вставки первой строки в таблицу с фиксацией транзакции представлено на следующем рисунке:

Состояние табличной и индексной страниц

При вставке строки в табличной странице произошли следующие изменения:

  • Создан указатель на версию строки (идентификатор элемента)
  • Создана сама версия строки

При этом в поле xmin проставлен номер транзакции (880), создавшей данную версию строки. В поле xmax проставлен 0, поскольку версия строки еще не удалена. Также проставлен информационный бит xmax aborted, чтобы другие транзакции не принимали во внимание значение поля xmax.

В индексной странице произошли следующие изменения:

  • Создан указатель на индексную строку
  • Создана сама индексная строка

При этом в индексной строке указан ctid = (0, 1) созданной версии строки.

В журнале CLOG для транзакции, вставившей строку, проставлен бит committed, что говорит о завершении транзакции фиксацией. Однако в версии строки информационный бит xmin committed пока не проставлен. Это будет сделано позже, при следующем обращении к ней.

Обновление строк​

Результат обновления строки без фиксации транзакции представлен на следующем рисунке:

Результат обновления строки

При обновлении строки в табличной странице произошли следующие изменения:

  • Создан указатель на вторую версию строки
  • Добавлена вторая версия строки
  • В первой версии строки в поле xmax проставлен номер транзакции (881), обновившей строку, а также сброшен информационный бит xmax aborted
  • В первой версии строки проставлен информационный бит xmin committed на основе информации из CLOG
  • В первой версии строки в поле ctid указана ссылка на следующую версию строки (0,2)

Обновление строки происходит в два этапа. Сначала актуальная версия помечается удаленной посредством указания номера транзакции в поле xmax и сброса информационного бита xmax aborted. Затем создается новая версия строки так же, как и при вставке.

Значение xmax старой версии строки совпадает со значением xmin новой версии строки, в поле ctid старой версии строки указывается ссылка на новую версию строки.

Выставленное значение xmax, соответствующее незавершенной транзакции, является признаком блокировки строки. Другие транзакции, пытающиеся изменить или удалить такую строку, будут ждать завершения транзакции с номером xmax.

В индексной странице произошли следующие изменения:

  • Создан новый указатель на индексную строку
  • Создана новая индексная строка с ctid = (0, 2), соответствующим новой версии строки

Обратите внимание, что в индексной странице нет информации о версионности строк. На основе информации в индексной строке транзакция не может определить ее видимость. Даже если индекс содержит все запрашиваемые данные, то есть является покрывающим для запроса, для определения видимости транзакции приходится обращаться либо к табличной странице, либо к карте видимости.

В журнале CLOG добавился статус транзакции с номером 881, обновившей строку. Сброшенные биты committed и aborted говорят о том, что транзакция еще не завершена.

Удаление строк​

Результат удаления строки с откатом транзакции представлен на следующем рисунке:

Результат удаления строки

При удалении строки в поле xmax указывается номер транзакции и сбрасывается информационный бит xmax aborted.

Указатель на версию строки при этом не освобождается, как и не удаляется соответствующая индексная строка.

В данном случае транзакция, удалившая строку (xid = 882), была отменена, что отражено в журнале CLOG. Помимо этого транзакция, ранее обновившая строку (xid = 881), завершена. При этом соответствующие информационные биты в версиях строк пока не проставлены.

Реализация точек сохранения​

Возможность частичного отката транзакций с использованием команд SAVEPOINT и ROLLBACK TO не обеспечивается напрямую механизмом многоверсионности, описанным выше. В нем предполагается, что все изменения внутри транзакции имеют один и тот же статус транзакции в каждый момент времени.

Чтобы подстроиться под общий механизм многоверсионности при реализации функционала точек сохранения, было определено понятие вложенной транзакции. Границы вложенной транзакции могут определяться командами SAVEPOINT, ROLLBACK TO и COMMIT. Вложенная транзакция имеет свой собственный номер xid, больший, чем у основной транзакции. Статус вложенной транзакции, как и основной, хранится в журнале CLOG. Однако зафиксированные вложенные транзакции командой RELEASE SAVEPOINT отмечаются сразу двумя битами: committed и aborted.

Итоговый статус вложенной транзакции определяется статусом основной транзакции. При отмене основной транзакции все вложенные транзакции также помечаются отмененными. При фиксации основной транзакции происходит обращение к подкаталогу pg_subtrans в каталоге данных, в котором содержатся файлы с информацией о вложенных транзакциях. Страницы файлов каталога pg_subtrans кешируются в общей памяти.

Пример реализации в журнале CLOG вложенных транзакций приведен на следующем рисунке:

Журнал CLOG

Механизм вложенных транзакций применяется не только при использовании команд SAVEPOINT и ROLLBACK TO. Вложенные транзакции также неявно применяются при обработке исключения в PL/pgSQL и при использовании режима ON_ERROR_ROLLBACK = on/interactive в psql.

Итоги​

Подведем итоги:

  • Механизм MVCC реализуется за счет возможности хранения нескольких версий строк в табличных страницах, время жизни которых ограничено параметрами xmin и xmax
  • В индексных строках нет информации о версионности, поэтому для определения видимости необходимо обратиться либо к версиям строк в табличных страницах, либо к карте видимости
  • При удалении строки ее версия в табличной странице лишь помечается как удаленная путем заполнения поля xmax. Физического удаления не происходит
  • Завершение транзакций происходит моментально за счет использования журнала CLOG
  • Частичная отмена транзакций происходит с использованием вложенных транзакций

Самопроверка​

Вопрос 1

Что из перечисленного входит в состав идентификатора ctid? Выберите все верные варианты ответа.

Вопрос 2

С использованием каких данных может быть определена видимость индексной строки? Выберите все верные варианты ответа.

Вопрос 3

В заголовке версии строки значение xmax = 100. В журнале CLOG для транзакции с номером 100 биты committed и aborted не выставлены.

Что из перечисленного соответствует указанному состоянию? Выберите все верные варианты ответа:

Вопрос 4

Транзакции с номерами 101, 102 и 103 являются вложенными для транзакции с номером 100. Состояние журнала CLOG:

| xid | committed | aborted | | --- | --- | --- | | 100 | 1 | | | 101 | | 1 | | 102 | 1 | 1 | | 103 | 1 | |

Изменения каких из перечисленных транзакций в результате были зафиксированы в базе данных? Выберите все верные варианты ответа: