TOAST

Обзор

В PostgreSQL используется фиксированный размер страницы (значение по умолчанию — 8 КБ), также кортежи не могут занимать несколько страниц, поэтому невозможно напрямую хранить большие значения полей. PostgreSQL сжимает большие значения и/или разбивает их на несколько физических строк. Такие строки сохраняются в специальное хранилище. Этот метод известен как TOAST (The Oversized-Attribute Storage Technique). PostgreSQL также использует механизм TOAST для улучшения обработки данных большого размера в памяти.

Только определенные типы данных поддерживают TOAST (например, text, json, jsonb и bytea). Чтобы поддерживать TOAST, тип данных должен представлять значение переменной длины (varlena), в котором первое четырёхбайтовое слово хранящегося значения содержит общую длину значения в байтах (включая само это слово). Содержание оставшейся части значения TOAST не ограничивает. Алгоритмы TOAST работают, интерпретируя первое слово. Таким образом, функции уровня C, работающие с типом данных, поддерживающим TOAST, должны аккуратно обращаться со входными значениями, которые могут быть в TOAST-формате. Входные данные могут не состоять из четырёхбайтового слова длины и содержимого после него, пока не будут распакованы. Обычно в таких случаях нужно использовать макрос PG_DETOAST_DATUM прежде чем что-либо делать с входным значением, но в некоторых ситуациях возможны и более эффективные подходы. За подробностями обратитесь к статье TOAST Considerations.

TOAST использует два бита слова длины varlena для дополнительных настроек, поэтому длина TOAST-значения ограничена 1 ГБ. Когда оба бита равны нулю, значение является обычным значением не в TOAST-формате и оставшиеся биты слова длины задают общий размер данных в байтах. Когда установлен старший (или младший, в зависимости от архитектуры) бит, значение имеет однобайтовый заголовок вместо четырехбайтового, а оставшиеся биты этого байта задают общий размер данных в байтах. Этот подход поддерживает компактное хранение значений короче 127 байт, но при этом позволяет при необходимости расширить значение этого типа данных до 1 ГБ. В особом случае, если все оставшиеся биты однобайтового заголовка равны нулю, значение представляет собой TOAST-указатель на отдельно размещённые данные, с несколькими возможными вариантами, описанными ниже. Код, хранящийся во втором байте, определяет тип и размер TOAST-указателя. Если старший (или младший, в зависимости от архитектуры) бит очищен, а соседний бит установлен, содержимое данных хранится в упакованном виде и должно быть распаковано перед использованием. В этом случае оставшиеся биты четырёхбайтового слова длины задают общий размер сжатых, а не исходных данных. Сжатие также возможно и для данных, хранимых отдельно, но заголовок varlena не говорит, имеет ли оно место — это определяется содержимым TOAST-указателя.

Вы можете выбрать метод сжатия для каждого столбца, который будет применяться при внутреннем и внешнем хранении. Для этого необходимо установить параметр COMPRESSION в команде CREATE TABLE или ALTER TABLE. Параметр default_toast_compression определяет значение по умолчанию, если COMPRESSION не установлен.

Существуют разные типы TOAST-указателей:

  • Самый старый и наиболее распространенный тип — это указатель на отделённые данные на диске, хранящиеся в отдельной TOAST-таблице, связанной с таблицей, содержащей TOAST-указатель. Такой указатель создаётся кодом обработки TOAST, когда кортеж, сохраняемый на диск, оказывается слишком большим.

  • TOAST-указатель может ссылаться на отделённые данные в памяти. Такие данные недолговечны и не могут быть сохранены на диск. Этот тип указателей очень полезен, чтобы избежать избыточного копирования и обработки данных большого размера.

Отдельное размещение TOAST на диске

Если какие-либо столбцы таблицы хранятся в TOAST-формате, с таблицей связана TOAST-таблица. OID TOAST-таблицы сохраняется в значении pg_class.reltoastrelid основной таблицы. Все TOAST-значения из главной таблицы помещаются в эту TOAST-таблицу.

PostgreSQL делит значения для внешнего хранения (после сжатия, если оно используется) на фрагменты размером не более TOAST_MAX_CHUNK_SIZE байт. Значение по умолчанию для этого параметра составляет около 2000 байт. Таким образом, четыре строки фрагментов помещаются на странице. Каждый фрагмент хранится в виде отдельной строки в TOAST-таблице. Каждая TOAST-таблица имеет следующие столбцы:

  • chunk_id — OID, идентифицирующий конкретное TOAST-значение;

  • chunk_seq — последовательный номер для фрагмента внутри значения;

  • chunk_data — непосредственно данные фрагмента.

Уникальный индекс по полям chunk_id и chunk_seq обеспечивает быстрый поиск значений. TOAST-указатель содержит OID связанной TOAST-таблицы и OID конкретного значения (его chunk_id). Указатель также хранит логический размер данных (исходную длину несжатых данных), физический сохраненный размер (отличается, если применяется сжатие) и метод сжатия, если он указан. Общий размер TOAST-указателя составляет 18 байт независимо от фактического размера представляемого значения.

Код обработки TOAST срабатывает, только когда значение строки, которое должно храниться в таблице, по размеру больше, чем TOAST_TUPLE_THRESHOLD байт (обычно это 2 КБ). Код TOAST будет сжимать и/или выносить значения поля за пределы таблицы до тех пор, пока значение строки не станет меньше TOAST_TUPLE_TARGET байт (переменная величина, так же обычно 2 КБ) или уменьшить объём станет невозможно. Во время операций UPDATE значения неизменённых полей обычно сохраняются как есть, поэтому модификация строки с отдельно хранимыми значениями не несёт издержек, связанных с TOAST, если все TOAST-значения остаются без изменений.

Код обработки TOAST распознаёт четыре различные стратегии хранения столбцов, совместимых с TOAST, на диске:

  • PLAIN — не допускает ни сжатия, ни отдельного хранения. Он также отключает использование однобайтовых заголовков для типов varlena. Это единственная возможная стратегия для столбцов типов данных, не поддерживающих TOAST.

  • EXTENDED — допускает как сжатие, так и внешнее хранение. Это значение по умолчанию для большинства типов данных, поддерживаемых TOAST. PostgreSQL сначала использует сжатие, а затем отдельное хранение, если строка все еще слишком велика.

  • EXTERNAL — допускает отдельное хранение, но не сжатие. Использование EXTERNAL ускоряет операции над частями строк в столбцах типа text и bytea (ценой увеличения объёма памяти для хранения), так как эти операции оптимизированы для извлечения только требуемых частей отделённого значения, когда оно не сжато.

  • MAIN — допускает сжатие, но не внешнее хранение. Для таких столбцов по-прежнему будет выполняться внешнее хранение, в том случае, если нет другого способа сделать строку достаточно маленькой, чтобы она поместилась на странице.

Каждый тип данных, совместимый с TOAST, определяет стандартную стратегию для столбцов этого типа данных, но стратегия для заданного столбца таблицы может быть изменена с помощью команды ALTER TABLE …​ SET STORAGE.

Вы также можете использовать ALTER TABLE …​ SET (toast_tuple_target = N), чтобы изменить TOAST_TUPLE_TARGET для каждой таблицы.

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

Отдельное размещение TOAST в памяти

Указатели TOAST также могут указывать на данные, расположенные в памяти текущего серверного процесса. В настоящее время есть два типа указателей:

  • Косвенный TOAST-указатель ссылается на значение varlena, хранящееся в памяти. В настоящее время применяется при логическом декодировании, чтобы не приходилось создавать физические кортежи больше одного 1 ГБ, что может потребоваться при консолидации всех отделённых значений полей в одном кортеже. Данный вариант имеет ограниченное применение, так как целевые данные будут существовать, только пока существует указатель, и никакой инфраструктуры для их сохранения нет.

  • Указатели на развёрнутые TOAST-данные полезны для сложных типов, представление которых на диске плохо приспособлено для вычислительных целей. Например, стандартное представление в виде varlena массива PostgreSQL включает информацию о размерности, битовую карту элементов NULL (если они в нём содержатся), а затем значения всех элементов по порядку. Когда элемент сам по себе имеет переменную длину, единственный способ найти элемент — это просканировать все предыдущие элементы. Это представление компактно и поэтому подходит для хранения на диске, но для вычислительной обработки массива гораздо удобнее иметь "развёрнутое" или "деконструированное" представление, в котором можно определить начальные адреса всех элементов. Механизм указателей TOAST способствует решению этой задачи, допуская передачу по ссылке элемента datum как указателя на стандартное значение varlena (представление на диске) или TOAST-указателя на развёрнутое представление где-то в памяти. Детали развёрнутого представление определяются самим типом данных, хотя оно может иметь стандартный заголовок и удовлетворять другим требованиям API, описанным в Declarations for access to "expanded" value representations.

TOAST-указатели на развёрнутые значения далее подразделяются на указатели для чтения/записи и указатели только для чтения. Представление, на которое они указывают, одинаковое, но функции, получающей указатель для чтения/записи, разрешается модифицировать целевые данные. Тогда как функция, получающая указатель только для чтения, должна сначала сделать копию, чтобы получить изменённую версию значения. Это отличие и связанные с ним соглашения позволяют избежать излишнего копирования развёрнутых значений при выполнении запросов.

Для всех типов указателей TOAST на данные в памяти, код обработки TOAST гарантирует, что такие данные не окажутся случайно сохранены на диске. Указатели TOAST в памяти автоматически сворачиваются в обычные значения varlena перед сохранением — а затем могут преобразоваться в указатели TOAST на диске, если без этого не смогут уместиться в содержащем их кортеже.

Нашли ошибку? Выделите текст и нажмите Ctrl+Enter чтобы сообщить о ней