71.2. TOAST#

71.2. TOAST

71.2. TOAST

Этот раздел предоставляет обзор TOAST (The Oversized-Attribute Storage Technique).

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

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

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

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

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

71.2.1. Отдельное хранение TOAST на диске

Если любой из столбцов таблицы может быть сжат, таблица будет иметь связанную таблицу TOAST, OID которой хранится в записи reltoastrelid таблицы pg_class. Сжатые значения на диске хранятся в таблице TOAST, как описано более подробно ниже.

Внешние значения разделяются (после сжатия, если используется) на части размером не более TOAST_MAX_CHUNK_SIZE байт (по умолчанию это значение выбирается так, чтобы на страницу помещалось четыре строки чанка, что составляет около 2000 байт). Каждый чанк хранится как отдельная строка в таблице TOAST, принадлежащей владеющей таблице. У каждой таблицы TOAST есть столбцы chunk_id (OID, идентифицирующий конкретное TOAST значение), chunk_seq (порядковый номер чанка внутри значения) и chunk_data (фактические данные чанка). Уникальный индекс на chunk_id и chunk_seq обеспечивает быстрое извлечение значений. Указательный датум, представляющий внешнее значение на диске, TOASTed, поэтому должен хранить OID таблицы TOAST, в которой нужно искать, и OID конкретного значения (его chunk_id). Для удобства указательные датумы также хранят размер логического датума (длину исходных несжатых данных), физический размер хранения (отличается, если было применено сжатие) и используемый метод сжатия, если таковой имеется. Учитывая байты заголовка varlena, общий размер указателя на диске TOAST составляет 18 байт, независимо от фактического размера представленного значения.

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

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

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

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

  • 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-страницы). Не было разницы во времени выполнения по сравнению с некомпрессированной таблицей, в которой все HTML-страницы были урезаны до 7 кБ для помещения.

71.2.2. Отдельное хранение TOAST в памяти

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

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

Расширенные указатели TOAST полезны для сложных типов данных, которые не особенно подходят для вычислений в их дисковом представлении. В качестве примера, стандартное varlena представление массива Tantor SE включает информацию о размерности, битовую карту нулевых элементов, а затем значения всех элементов в порядке. Когда сам тип элемента имеет переменную длину, единственный способ найти элемент с индексом N - это просканировать все предшествующие элементы. Это представление подходит для хранения на диске из-за его компактности, но для вычислений с массивом намного удобнее иметь "расширенное" или "разложенное" представление, в котором все начальные позиции элементов были идентифицированы. Механизм указателей TOAST поддерживает эту потребность, позволяя ссылке на pass-by-reference указывать на стандартное varlena значение (дисковое представление) или указатель TOAST, который указывает на расширенное представление где-то в памяти. Детали этого расширенного представления зависят от типа данных, хотя оно должно иметь стандартный заголовок и соответствовать другим требованиям API, указанным в src/include/utils/expandeddatum.h. Функции на уровне C, работающие с типом данных, могут выбрать обработку любого из представлений. Функции, которые не знают о расширенном представлении, но просто применяют PG_DETOAST_DATUM к своим входным данным, автоматически получат традиционное varlena представление; поэтому поддержка расширенного представления может быть введена постепенно, по одной функции за раз.

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

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