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

TOAST

В этом разделе представлен обзор TOAST (The Oversized-Attribute Storage Technique, Методика хранения атрибутов сверхбольшого размера).

PostgreSQL использует фиксированный размер страницы (обычно 8 КБ) и не позволяет кортежам занимать несколько страниц. Поэтому невозможно напрямую хранить очень большие значения полей. Чтобы преодолеть это ограничение, большие значения полей сжимаются и/или разбиваются на несколько физических строк. Это происходит прозрачно для пользователя с небольшим влиянием на большинство серверного кода. Эта методика известна как TOAST (или «лучшее изобретение со времен нарезанного хлеба»). Инфраструктура TOAST также используется для улучшения обработки больших значений данных в памяти.

Только определенные типы данных поддерживают TOAST - нет необходимости накладывать накладные расходы на типы данных, которые не могут производить большие значения полей. Для поддержки TOAST тип данных должен иметь представление переменной длины (varlena), при котором обычно первое четырехбайтовое слово любого сохраненного значения содержит общую длину значения в байтах (включая само себя). TOAST не ограничивает оставшуюся часть представления типа данных. Специальные представления, коллективно называемые значениями TOASTed, работают путем изменения или переинтерпретации этого начального слова длины. Поэтому функции уровня C, поддерживающие тип данных, который может быть TOAST, должны быть осторожны в отношении того, как они обрабатывают потенциально TOASTированные входные значения: ввод может фактически состоять из четырехбайтового слова длины и содержимого только после его размораживания. (Обычно это делается путем вызова PG_DETOAST_DATUM перед выполнением каких-либо действий с входным значением, но в некоторых случаях возможны более эффективные подходы.

TOAST захватывает два бита слова длины varlena (старшие разряды на машинах с прямым порядком байтов, младшие разряды на машинах с обратным порядком байтов), тем самым ограничивая логический размер любого значения типа данных, поддерживаемого TOAST, до 1 ГБ (230 - 1 байт). Когда оба бита равны нулю, значение является обычным неповрежденным значением данного типа данных, а оставшиеся биты слова длины дают общий размер данных (включая слово длины) в байтах. Когда установлен старший или младший бит, значение имеет только однобайтовый заголовок вместо обычного четырехбайтового заголовка, и оставшиеся биты этого байта дают общий размер данных (включая байт длины) в байтах. Этот альтернативный вариант поддерживает экономию места при хранении значений короче 127 байт, позволяя при этом типу данных увеличиваться до 1 ГБ по мере необходимости. Значения с однобайтовыми заголовками не выровнены ни на какой конкретной границе, тогда как значения с четырехбайтовыми заголовками выровнены хотя бы на четырехбайтовой границе; это отсутствие выравнивания экономит дополнительное пространство, что существенно для коротких значений. В качестве особого случая, если оставшиеся биты однобайтового заголовка все равны нулю (что было бы невозможно для самодостаточной длины), значение представляет собой указатель на данные вне строки, с несколькими возможными альтернативами, описанными ниже. Тип и размер такого указателя TOAST определяются кодом, сохраненным во втором байте данных. Наконец, когда старший или младший бит очищен, но соседний бит установлен, содержимое данных было сжато и должно быть распаковано перед использованием. В этом случае оставшиеся биты четырехбайтового слова длины дают общий размер сжатых данных, а не исходных данных. Обратите внимание, что сжатие также возможно для данных вне строки, но заголовок varlena не указывает, произошло ли оно - об этом говорит содержимое указателя TOAST.

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

Как упоминалось ранее, существует несколько типов указателей TOAST. Самый старый и наиболее распространенный тип - это указатель на данные вне строки, хранящиеся в таблице TOAST, которая отделена от таблицы, содержащей сам указатель TOAST. Эти указатели на диске создаются кодом управления TOAST (в access/common/toast_internals.c) при попытке сохранить кортеж на диск, который слишком велик для хранения как есть. В качестве альтернативы, указатель TOAST может содержать указатель на данные вне строки, которые находятся где-то еще в памяти. Такие данные обязательно будут недолговечными и никогда не появятся на диске, но они очень полезны для предотвращения копирования и избыточной обработки больших значений данных.

Хранение TOAST вне строки, на диске

Если любая из колонок таблицы поддерживает функцию TOAST, у этой таблицы будет ассоциированная таблица TOAST, идентификатор которой хранится в записи pg_class/reltoastrelid. Значения TOAST, сохраненные на диске, хранятся в таблице TOAST, как описано ниже более подробно.

Внешние значения разделяются (после сжатия, если оно используется) на части размером не более 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). Для удобства указатели также сохраняют логический размер данных (исходную длину несжатой данных), физический сохраненный размер (отличается, если было применено сжатие) и метод сжатия, если таковой имеется. Учитывая заголовочные байты varlena, общий размер указателя TOAST на диске составляет 18 байт независимо от фактического размера представляемого значения.

Код управления TOAST запускается только тогда, когда значение строки, которое должно быть сохранено в таблице, шире, чем TOAST_TUPLE_THRESHOLD байт (обычно 2 КБ). Код TOAST будет сжимать и/или перемещать значения полей вне строки до тех пор, пока значение строки не станет короче TOAST_TUPLE_TARGET байт (также обычно 2 КБ, регулируемое) или больше никаких выгод получить нельзя. Во время операции ОБНОВЛЕНИЯ значения неизмененных полей обычно сохраняются без изменений; поэтому обновление строки с внешними значениями не влечет за собой затрат TOAST, если ни одно из внешних значений не изменилось.

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

  • PLAIN предотвращает либо сжатие, либо хранение вне строки. Это единственная возможная стратегия для столбцов типов данных, которые не являются TOAST-able.
  • EXTENDED позволяет как сжатие, так и хранение вне строки. Это значение по умолчанию для большинства типов данных TOAST-able. Сначала будет предпринята попытка сжатия, затем внестрочного хранения, если строка все еще слишком велика.
  • EXTERNAL позволяет хранить данные вне строки, но не поддерживает сжатие. Использование EXTERNAL сделает операции с подстроками для широких столбцов text и bytea быстрее (за счет увеличения объема хранилища), потому что эти операции оптимизированы для выборки только необходимых частей значения вне строки, когда оно не сжато.
  • MAIN поддерживает сжатие, но не хранение данных вне строки. (На самом деле, хранение данных вне строки все равно будет выполняться для таких столбцов, но только в крайнем случае, когда нет другого способа сделать строку достаточно маленькой, чтобы она поместилась на странице.)

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

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

Эта схема имеет ряд преимуществ по сравнению с более прямолинейным подходом, таким как разрешение значений строки занимать несколько страниц. Предполагая, что запросы обычно квалифицируются сравнениями относительно небольших ключевых значений, большая часть работы исполнителя будет выполнена с использованием основной записи строки. Большие значения атрибутов TOAST будут извлекаться только тогда (если они вообще выбраны), когда набор результатов отправляется клиенту. Таким образом, основная таблица намного меньше и больше ее строк помещается в общий буферный кэш, чем было бы без какого-либо внестрочного хранилища. Наборы сортировки также уменьшаются, и сортировка чаще всего выполняется полностью в памяти. Небольшой тест показал, что таблица, содержащая типичные веб-страницы HTML и их URL-адреса, хранилась примерно в половине размера необработанных данных, включая таблицу TOAST, а основная таблица содержала около 10% всех данных (URL-адресов и некоторых небольших веб-страниц HTML). Не было никакой разницы во времени выполнения по сравнению с таблицей сравнения без TOAST, в которой все веб-страницы HTML были сокращены до 7 кБ, чтобы поместиться.

Хранение TOAST вне строки, в памяти

Указатели TOAST могут указывать на данные, которые не находятся на диске, а где-то еще в памяти текущего процесса сервера. Такие указатели, очевидно, не могут быть долговечными, но они все равно полезны. В настоящее время существует два подслучая: указатели на косвенные данные и указатели на расширенные данные.

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

Расширенные указатели TOAST полезны для сложных типов данных, чье представление на диске не особенно подходит для вычислительных целей. Например, стандартное представление varlena массива PostgreSQL включает информацию о размерности, карту битовых масок нулевых элементов, если таковые имеются, затем значения всех элементов по порядку. Когда тип элемента сам по себе является переменной длины, единственный способ найти элемент 'th - это просмотреть все предыдущие элементы. Это представление подходит для хранения на диске из-за его компактности, но для вычислений с массивом гораздо приятнее иметь "расширенное" или "разобранное" представление, в котором определены все начальные местоположения элементов. Механизм указателей TOAST поддерживает эту потребность, позволяя передаваемому по ссылке значению Datum указывать либо на стандартное значение varlena (представление на диске), либо на указатель TOAST, который указывает на расширенное представление где-то в памяти. Детали этого расширенного представления зависят от типа данных, хотя оно должно иметь стандартный заголовок и соответствовать другим требованиям API, приведенным в . Функции уровня C, работающие с типом данных, могут выбирать обработку любого из представлений. Функции, которые не знают о расширенном представлении, но просто применяют к своим входным данным, автоматически получат традиционное представление varlena; поэтому поддержка расширенного представления может быть введена поэтапно, одна функция за раз.

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

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