Работа с длинными строками в PostgreSQL

22.04.2025
Поделиться
Ссылка скопирована

При выгрузке баз данных PostgreSQL часть строк таблиц может не выгружаться и выдаваться ошибка ERROR: out of memory. Такая ошибка чаще всего возникает при выгрузке утилитой pg_dump таблиц, в которых хранятся XML-документы, а также выгрузке таблицы public.config приложения "1C Предприятие". В таких таблицах присутствуют поля большого размера. В статье рассматриваются причины ошибок и способ решения проблемы.

В полях типов text и bytea могут храниться данные до 1Гб. Если размер строки в процессе обработки превышает 1Гб, то возникают ошибки нехватки памяти. Обнаруживают ошибки, обычно, при выгрузке данных утилитой pg_dump. Утилита преобразует бинарные данные в текстовый вид.

В процессе выгрузки строк таблиц командой COPY TO или обработки данных любыми командами выделяется память под строковый буфер. Строковый буфер выделяется динамически в процессе обработки каждой строки, а не при запуске серверного процесса. Максимальный размер буфера задан в stringinfo. c константой MaxAllocSize, которая равна 0x3fffffff , что равно 1Гб-1 байт (1 073 741 823 байт).

По умолчанию команда COPY выгружает значения полей в текстовом формате. В этом формате для символов типа перехода на новую строку, табуляции, забоя используются спецпоследовательности \r, \t, \b, которые в текстовом виде занимают два байта. Поэтому если поле содержит спецсимволы (табуляции, возврата каретки, новой строки), то при выгрузке в текстовом виде его размер может превысить 1Гб.

Пример:

ои1.jpg

При выполнении команды COPY будет выдана ошибка:

ои2.jpg
Размер поля - треть гигабайта. При выгрузке в текстовом виде содержимое поля заменяются на последовательность символов: a\na\na\na\n и размер поля увеличится в три раза до 1073741823 байт, что на 1 байт превышает максимальный размер буфера строк (1Гб-1 байт).

Такую строку можно выгрузить, используя формат binary:

ои3.jpg

Однако, утилита pg_dump не имеет опции для выгрузки данных с WITH BINARY и это создаёт неудобство: поля большого размера приходится выгружать отдельно и исключать их из выгрузки утилитой pg_dump.

 При выгрузке поля bytea также выгружаются в текстовом виде, но размер полей этого типа увеличивается побайтно и выдаётся ошибка о том, что нельзя выделить 1 байт памяти или больше:

ои3 5.jpg

Для выгрузки таких полей или строк также можно использовать бинарный формат: COPY .. TO .. WITH BINARY;

Выгрузка длинных строк утилитой pg_dump

Создадим базу данных testdb и таблицу с длинной строкой:

ои4.jpg

Параметр storage external используется, чтобы поля в TOAST не сжимались. Один повторяющийся символ хорошо сжимается и TOAST займёт 12Мб. На результаты команд это не влияет, но более близко к практике.

Попробуем выгрузить базу данных утилитой pg_dump:

ои5.jpg

Возникла ошибка невозможности выгрузить содержимое таблицы из-за превышения размера памяти под string buffer. Сначала была выделена память под поле столбца c1 размером 536870913 байт (512Мб+1 байт). При выделении памяти под поле столбца c2 возникла ошибка, так как суммарный размер превысил ограничение на 2 байта.

Такие поля придётся выгружать по частям: по столбцам; фильтруя строки и выгружая проблемные строки отдельно по полям. Размер поля любого типа не может превысить размер строкового буфера, поэтому, в таблицах не может быть полей размера, больше 1Гб. Да-да любых типов https://www.postgresql.org/docs/current/limits.html . Данные из одного столбца всегда можно выгрузить в бинарном виде.

Варианты ошибок, связанных со строковым буфером

Текст ошибок, связанный со строковым буфером может варьироваться. Примеры:

1)     Текст ошибки: "invalid memory alloc request size" Причина ошибки: попытка выделить блок памяти, превышающий ограничение 2Гб или 1Гб-1, установленный макросом  MaxAllocSize. Ошибка с текстом "invalid memory alloc request size" вызывается проверкой на превышение этого ограничения.

ои6.jpg

2) Текст ошибки: "array size exceeds the maximum allowed" при работе с массивами.

3) В текстовые функциях типа lpad(..), repeat(..) вставлены проверки, которые выдают менее пугающую ошибку: "requested length too large"

ои7.jpg

4) Выделение памяти типа Dynamic Shared Memory (DSA), которая используется при распараллеливании команд также имеет проверки. Текст ошибки при превышении ограничений: "invalid DSA memory alloc request size".

Если размер памяти, который указан после текста ошибки больше 2Гб, то это означает, что размер памяти, который должен быть выделен, рассчитан неверно и может указывать на наличие повреждения записей в блоках данных. Также расчет может быть неверно выполнен из-за ошибок в библиотеках расширений, загруженных в память процесса.

Параметр конфигурации enable_large_allocations

Для решения проблемы выгрузки-загрузки строк, превышающих 1Гб, в двух российских форках PostgreSQL имеются параметры конфигурации. В Postgres Pro Enterprise параметр называется enable_large_mem_buffers  https://postgrespro.ru/docs/enterprise/17/runtime-config-compatible , в Tantor enable_large_allocations  https://wiki.astralinux.ru/tandocs/oshibka-invalid-memory-alloc-request-size-311339036.html , которые увеличивают размер строкового буфера до 2Гб. А вот в Панголине параметра не наблюдается. Параметр можно установить  на любом уровне, а также параметром утилит pg_dump, pg_dumpall.

Пример установки параметра на уровне функции

ои8.jpg

Команда создания таблицы выполнилась без ошибки. Без параметра команда выдаст ошибку.

Пример использования параметра утилитой pg_dump

Выгрузим содержимое базы данных с использованием параметра --enable-large-allocations:

ои9.jpg

Данные были выгружены в файл размером чуть больше 1Гб:

ои10.jpg

Проверим, что полученный файл с дампом выполняется без ошибок:

ои11.jpg

Команда COPY успешно загрузила строку. Параметр в примере явно не устанавливался, однако данные загрузились и на уровне сессии параметр был  enable_large_allocations. Значение параметра enable_large_allocations было установлено утилитой pg_dump и сохранено в файле дампа:

ои12.jpg

Характеристики параметра enable_large_allocations

ои13.jpg

Использование параметра увеличивает максимальный размер строкового буфера с 1Гб до 2Гб. Строковый буфер не ограничен параметрами конфигурации, в том числе work_mem.

Процесс может выделить память вплоть до максимального размера строкового буфера. При этом в операционной системе может запрашиваться двойной размер от необходимого.

Объем выделяемой памяти

Проверим, что при выполнении скрипта дампа "\i testdb.sql" выделяется объем памяти 4Гб, а не 2Гб. Перед выполнением скрипта можно запустить терминал с командой free для наблюдения за свободной памятью в linux и выполнить скрипт ещё раз:

ои14.jpg

При работе команды COPY в скрипте дампа выделяется память размером 4Гб, с 1ГБ до ~5ГБ. Команда сброса страничного кэша дана для удобства, чтобы при повторных выполнениях скрипта размер страничного кэша linux оставался таким же.

При этом размер таблицы (с TOAST) немногим больше 1Гб:

ои15.jpg

В каком контексте выделена память? Можно получить PID серверного процесса функцией pg_backend_pid() и вывести в журнал список контекстов памяти функцией pg_log_backend_memory_contexts(PID) во время работы команды:

ои16.jpg

Память выделяется в контексте портала. Размер памяти, выделенной командой COPY  4295073944 байт в 6 блоках. Всего виртуальной памяти выделено 6Гб. Выделенная память не зависит от числа строк и размера файла дампа, команда COPY обрабатывает данные построчно.

Сколько памяти выделится при выполнении команды SELECT, а не COPY. Выполним команду:

ои17.jpg

В окне терминала:

ои18.jpg

Утилитой psql было выделено ~2Гб памяти. Серверный процесс выделил немного, RSS=153664 байт.

При выполнении SELECT с обычным размером буфера будет выдана ошибка:

ои19.jpg

при этом в контекстах памяти ExecutorState и printtup будет выделено по ~1Гб памяти:

ои20.jpg

Заключение

Были рассмотрены ошибки, которые возникают при выгрузке длинных строк таблиц. По умолчанию, в PostgreSQL нельзя работать со строками, размер которых превышает 1Гб. Для выгрузки таблиц со строками большого размера можно использовать параметры конфигурации enable_large_mem_buffers или enable_large_allocations, особенности применения которых были рассмотрены.


Автор:  ведущий преподаватель УКЦ ФОРС - Олег Иванов.


Поделиться
Ссылка скопирована

Возврат к списку

WhatsApp
Telegram