Сайт использует сookies для хранения данных. Продолжая использовать сайт, вы даёте согласие на работу с этими файлами.

ОК
🧱
Данные
Опубликовано:
14.08.2026
Обновлено:
14.08.2026

Когда TOAST уже не спасает: PostgreSQL, бинарные данные и large objects

Данил Мануйлов

PostgreSQL умеет хранить бинарные данные прямо в таблицах - для этого есть тип bytea. Пока объём не превышает нескольких мегабайт, всё работает прозрачно: за кулисами трудится механизм TOAST. Но стоит запросам вырасти до гигабайта, а затем и перевалить за него, TOAST упирается в архитектурный потолок. В этот момент на сцену выходят large objects - отдельная подсистема, которая предлагает потоковый доступ и масштабируется до 4 ТБ. Разберёмся, где проходит граница и как с ней работать.

TOAST: автоматический помощник, который не всемогущ

TOAST - это не просто удобное сокращение, а встроенная техника хранения раздувшихся атрибутов. Она спасает разработчика от ручного разбиения данных, но её возможности ограничены фундаментальными решениями, заложенными в ядро PostgreSQL.

Почему строка не должна быть длиннее страницы (8 КБ)

PostgreSQL работает с дисковыми страницами фиксированного размера - обычно 8 КБ. Одна запись таблицы обязана помещаться в одну страницу, иначе нарушится модель хранения. Поэтому все поля с данными переменной длины (текст, bytea, jsonb) должны либо умещаться в отведённое место, либо выноситься наружу особым образом. Именно эту задачу и решает TOAST.

Сжатие и вынос в тень: как TOAST прячет «толстые» значения

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

Жёсткий потолок ~1 ГБ и как он возникает из двух бит

Архитектурный лимит TOAST скрыт в формате varlena, который используется для всех типов переменной длины. В начале каждого такого значения хранится слово длины, и два бита из этого слова зарезервированы под служебные флаги. Из‑за этого логическая максимальная длина значения не может превысить 1 ГБ (2³⁰−1 байт). Именно этот потолок - примерно 1 ГБ на одно поле bytea, text или jsonb - и становится той стеной, в которую упирается TOAST. Для объектов, чей размер приближается к гигабайту или превышает его, TOAST‑типы уже не годятся.

Large Objects: взгляд в обход TOAST

Large objects (LO) - это не просто расширение, а самостоятельный слой хранения со своим API, метаданными и чанковой структурой. Он создавался для потоковой работы с очень большими бинарными данными, которые не должны проходить через механизмы TOAST.

Отдельная подсистема, а не просто тип данных

Large object не является типом столбца. В пользовательской таблице хранится лишь числовой идентификатор OID, который ссылается на объект в системном каталоге. Сами данные живут в служебной таблице pg_largeobject, а метаданные о владельце и правах доступа - в pg_largeobject_metadata. Такое разделение позволяет работать с объектом через специальный потоковый интерфейс, не затрагивая TOAST-логику.

Как физически хранятся large objects: чанки и pg_largeobject

Каждый large object разбит на чанки фиксированной длины. По умолчанию размер чанка составляет 2048 байт, то есть четверть блока данных. Все чанки лежат в pg_largeobject в виде отдельных строк, проиндексированных по OID и смещению. Благодаря B‑дереву по этим двум полям поиск нужного куска происходит быстро, а операции чтения и записи могут адресоваться произвольному смещению внутри объекта.

Потоковый доступ: читаем и пишем как в файл

Интерфейс large objects намеренно повторяет модель файлового ввода-вывода. Через функции lo_create, lo_open, lo_read, lo_write, lo_lseek и lo_close можно открыть объект, сдвинуть позицию, прочитать фрагмент или дописать новый кусок, не загружая весь объект в оперативную память. Такой подход особенно важен при работе с объектами, размер которых исчисляется гигабайтами: приложение может обрабатывать их порциями, как обычный файл.

Транзакции, владельцы и метаданные

Все операции с large objects обязаны выполняться внутри транзакционного блока. Если автокоммит выключен, перед вызовом lo_open нужно явно выполнить BEGIN. Это требование защищает целостность данных: изменения в large object фиксируются только при успешном завершении транзакции. Кроме того, у каждого объекта есть владелец и права доступа, которые можно менять командой ALTER LARGE OBJECT. Метаданные хранятся в упомянутой таблице pg_largeobject_metadata.

Максимальный размер - до 4 ТБ

В современных версиях PostgreSQL максимальный размер одного large object может достигать 4 ТБ. Это на четыре порядка больше, чем лимит TOAST‑типов, и полностью снимает вопрос о том, поместится ли в базу очень крупный бинарный файл. Ограничение в 2 ГБ, существовавшее в прошлом, осталось в истории.

TOAST vs Large Objects: сравнительная таблица и критерии выбора

Выбор между bytea/TOAST и large objects сводится к трём практическим параметрам: размер данных, способ доступа и предполагаемый сценарий использования.

Размер данных

Характеристика TOAST (bytea) Large Objects
Максимальный размер одного значения ~1 ГБ До 4 ТБ
Единица хранения Часть строки таблицы или TOAST-таблица Отдельные чанки в pg_largeobject
Зависимость от TOAST Полностью под управлением TOAST Полностью обходит TOAST

Если данные даже теоретически могут превысить гигабайт, TOAST‑тип исключён. Для файлов, которые гарантированно останутся в пределах нескольких сотен мегабайт, bytea остаётся удобным решением.

Частота и тип доступа

TOAST‑значения эффективны, когда приложение всегда читает или пишет объект целиком. Как только появляется потребность в частичном обновлении или потоковой обработке (например, дозапись в конец файла или чтение небольшого отрезка), large object выигрывает за счёт возможности позиционирования и работы с отдельными чанками без загрузки всего объёма в память.

Примеры сценариев

  • Хранение изображений и документов. Небольшие сканы, миниатюры, PDF размером до нескольких десятков мегабайт вполне комфортно живут в bytea. TOAST сам позаботится о сжатии и выносе.
  • Видео и аудио. Видеофайлы даже в сжатом виде легко перешагивают гигабайтную отметку. Здесь только large objects, особенно если требуется потоковая отдача клиенту или возможность дозаписи.
  • Резервные копии и бинарные дампы. Дампы баз данных или большие архивы часто превышают 1 ГБ. Их загрузка через lo_import и последующая выгрузка через lo_export гораздо эффективнее, чем попытка упаковать всё в поле bytea.
  • Потоковая обработка. Если приложение пишет лог или собирает данные по мере поступления, large object позволяет открыть дескриптор, дописывать чанки и закрыть его в конце транзакции, не накапливая гигабайты в оперативной памяти.

Практика перехода: когда TOAST перестаёт спасать

Переход на large objects обычно не внезапный, а продиктован конкретными признаками и требует осторожности.

Признаки, что пора смотреть в сторону LO

Первый звоночек - ошибки, связанные с превышением лимита в 1 ГБ при попытке вставить или обновить значение bytea. Второй - значительное падение производительности при чтении больших bytea‑полей, потому что TOAST вынужден собирать объект из множества строк, а приложение всё равно загружает его целиком. Третий - появление требований к частичному обновлению или потоковой загрузке, которые невозможно реализовать штатными средствами SQL без использования large objects.

Частые ошибки и подводные камни

Самая распространённая ошибка - забыть открыть транзакцию. Вызов lo_open при включённом автокоммите приводит к ошибке; нужен явный BEGIN. Вторая - незакрытые дескрипторы. Открытый large object, с которым не вызвали lo_close, остаётся занятым до конца сессии, что может привести к утечке ресурсов на стороне клиента. Третья - удаление large object. Нельзя просто удалить строку с OID из пользовательской таблицы и забыть про сам объект: на него останутся ссылки в pg_largeobject. Нужно явно вызвать lo_unlink, чтобы освободить место. И наконец, репликация: large objects реплицируются как обычные данные, но из‑за большого объёма могут вызывать заметные лаги. Стоит заранее оценить пропускную способность каналов и следить за правами доступа на репликах.

FAQ

Когда действительно нужно переходить с bytea/TOAST на large objects?

Когда размер данных начинает превышать ~1 ГБ, либо когда требуется потоковый доступ с частичным чтением и записью без загрузки всего объекта в память.

Какой максимальный размер может иметь large object?

В современных версиях PostgreSQL - до 4 ТБ. Раньше ограничение составляло 2 ГБ.

Можно ли работать с large objects вне транзакции?

Нет. Все операции открытия, чтения, записи и закрытия обязаны выполняться внутри явного транзакционного блока.

Что будет, если забыть закрыть дескриптор LO?

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

Как удалить ставший ненужным large object?

Вызовом функции lo_unlink(oid). Простого удаления строки с OID из пользовательской таблицы недостаточно - место в pg_largeobject не освободится.

Как загрузить большой файл в PostgreSQL напрямую, без программирования?

Утилита lo_import (или команда \lo_import в psql) позволяет импортировать файл с диска в large object, вернув его OID. Обратная операция - lo_export.

Large objects и репликация - есть ли подводные камни?

Large objects реплицируются вместе с остальными данными, но их большой объём может вызывать задержки. Рекомендуется тестировать пропускную способность и проверять корректность передачи прав доступа на репликах.

Заключение

TOAST - отличный помощник для большинства повседневных задач: он незаметно сжимает и раскладывает данные, позволяя разработчику не думать о внутренних страницах. Но когда размеры бинарных данных перерастают гигабайт, а сценарий требует потоковой работы, TOAST упирается в собственные архитектурные ограничения. Large objects закрывают именно эту нишу, предлагая чанковое хранение, файловый API и ёмкость до 4 ТБ. Выбор между ними - не вопрос «что лучше», а вопрос точного попадания в задачу.

Источники

Это авторская статья, основанная на личном опыте и субъективном взгляде автора. Заметили ошибку или битую ссылку? Сообщите нам: info@codesrc.ru - мы оперативно исправим. Спасибо, что помогаете делать блог лучше.
Следите за нами в соцсетях:

Читайте также