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 ТБ. Выбор между ними - не вопрос «что лучше», а вопрос точного попадания в задачу.




.svg.webp)

