В феврале 2026 года в тг-чате "PostgreSQL + 1C + Linux" один из участников столкнулся с проблемой:

"Ошибка СУБД:
53000: ERROR:  no empty local buffer available"

Ошибка возникала на типовой конфигурации 1С:ЗУП при формировании отчета по штатной численности. Версия PostgreSQL - 18.1, на 17-й версии проблемы не было. Запрос, приводившей к ошибке, пытался выполнить вставку во временную таблицу:

2026-02-24 23:34:11.579 MSK [3428264] ERROR:  no empty local buffer available
2026-02-24 23:34:11.579 MSK [3428264] STATEMENT:  INSERT INTO pg_temp.tt69 (_

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

Недавно на закрытии месяца ловили после перехода на 18 пг, в общем то уменьшил 
shared_buffers
effective_cache_size
рестартанул кластер, ошибка ушла

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

Но закрытие месяца в 1C:ERP дало зацепку, и мы решили попытаться воспроизвести проблему на своем нагрузочном стенде с Tantor Postgres SE 1C 18.1. Мы запустили закрытие месяца в нашей базе 1С:ERP, установив temp_buffers в значение по умолчанию (8 Мб). И на запросе вставки во временную таблицу ошибка воспроизвелась:

Развернуть исходный код
2026-02-25 22:16:57.448 MSK [1995310:2/2065] [erp_v] 192.168.5.210(46246) [unknown] ERROR:  no empty local buffer available
2026-02-25 22:16:57.448 MSK [1995310:2/2065] [erp_v] 192.168.5.210(46246) [unknown] STATEMENT:  INSERT INTO pg_temp.tt184 (_Q_001_F_000, _Q_001_F_001TRef, _Q_001_F_001RRef, _Q_001_F_002RRef, _Q_001_F_003_TYPE, _Q_001_F_003_RRRef, _Q_001_F_004) SELECT DISTINCT
	T1.Q_001_F_000_,
	T1.Q_001_F_001TRef,
	T1.Q_001_F_001RRef,
	T1.Q_001_F_002RRef,
	T1.Q_001_F_003_TYPE,
	T1.Q_001_F_003_RRRef,
	T1.Q_001_F_004_
	FROM (SELECT DISTINCT
	'СебестоимостьТоваров'::mvarchar AS Q_001_F_000_,
	T2._Q_000_F_001TRef AS Q_001_F_001TRef,
	T2._Q_000_F_001RRef AS Q_001_F_001RRef,
	T2._Q_000_F_002RRef AS Q_001_F_002RRef,
	CASE WHEN T2._Q_000_F_006RRef IS NOT NULL THEN '\\010'::bytea END AS Q_001_F_003_TYPE,
	T2._Q_000_F_006RRef AS Q_001_F_003_RRRef,
	CAST(CASE WHEN (T3._Q_000_F_000_TYPE IS NULL AND T3._Q_000_F_000_RTRef IS NULL AND T3._Q_000_F_000_RRRef IS NULL) THEN CAST(1 AS NUMERIC) WHEN (T2._Q_000_F_000_00 = CAST(0 AS NUMERIC)) THEN CASE WHEN (CASE WHEN T2._Q_000_F_007RRef IS NOT NULL THEN '\\010'::bytea END, T2._Q_000_F_007RRef) IN
	(SELECT
	T4._Q_000_F_003_TYPE AS Q_007_F_000_TYPE,
	T4._Q_000_F_003_RRRef AS Q_007_F_000_RRRef
	FROM pg_temp.tt77 T4
	WHERE CASE WHEN T2._Q_000_F_001TRef IS NOT NULL THEN '\\010'::bytea END = T4._Q_000_F_000_TYPE AND T2._Q_000_F_001TRef = T4._Q_000_F_000_RTRef AND ((T4._Q_000_F_001RRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) OR (T2._Q_000_F_006RRef = T4._Q_000_F_001RRef)) AND ((T4._Q_000_F_002_TYPE = '\\001'::bytea AND T4._Q_000_F_002_L = FALSE) OR ('\\002'::bytea = T4._Q_000_F_002_TYPE AND T2._Q_000_F_032 = T4._Q_000_F_002_L))) THEN CAST(0 AS NUMERIC) ELSE CAST(2 AS NUMERIC) END ELSE CASE WHEN (CASE WHEN T2._Q_000_F_007RRef IS NOT NULL THEN '\\010'::bytea END, T2._Q_000_F_007RRef) IN
	(SELECT
	T5._Q_000_F_004_TYPE AS Q_006_F_000_TYPE,
	T5._Q_000_F_004_RRRef AS Q_006_F_000_RRRef
	FROM pg_temp.tt77 T5
	WHERE CASE WHEN T2._Q_000_F_001TRef IS NOT NULL THEN '\\010'::bytea END = T5._Q_000_F_000_TYPE AND T2._Q_000_F_001TRef = T5._Q_000_F_000_RTRef AND ((T5._Q_000_F_001RRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) OR (T2._Q_000_F_006RRef = T5._Q_000_F_001RRef)) AND ((T5._Q_000_F_002_TYPE = '\\001'::bytea AND T5._Q_000_F_002_L = FALSE) OR ('\\002'::bytea = T5._Q_000_F_002_TYPE AND T2._Q_000_F_032 = T5._Q_000_F_002_L))) THEN CAST(0 AS NUMERIC) ELSE CAST(3 AS NUMERIC) END END AS NUMERIC(2, 0)) AS Q_001_F_004_
	FROM pg_temp.tt151 T2
	LEFT OUTER JOIN pg_temp.tt77 T3
	ON CASE WHEN T2._Q_000_F_001TRef IS NOT NULL THEN '\\010'::bytea END = T3._Q_000_F_000_TYPE AND T2._Q_000_F_001TRef = T3._Q_000_F_000_RTRef AND ((T3._Q_000_F_001RRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) OR (T2._Q_000_F_006RRef = T3._Q_000_F_001RRef)) AND ((T3._Q_000_F_002_TYPE = '\\001'::bytea AND T3._Q_000_F_002_L = FALSE) OR ('\\002'::bytea = T3._Q_000_F_002_TYPE AND T2._Q_000_F_032 = T3._Q_000_F_002_L))
	UNION ALL SELECT DISTINCT
	'СебестоимостьТоваров'::mvarchar,
	T6._Q_000_F_001TRef AS Q_000_F_001TRef,
	T6._Q_000_F_001RRef AS Q_000_F_001RRef,
	T6._Q_000_F_002RRef AS Q_000_F_002RRef,
	CASE WHEN T6._Q_000_F_006RRef IS NOT NULL THEN '\\010'::bytea END AS Q_000_F_006_TYPE,
	T6._Q_000_F_006RRef AS Q_000_F_006_RRRef,
	CAST(CASE WHEN (T6._Q_000_F_002RRef IN ('\\221\\226\\370\\321\\021\\006}F\\021\\343\\374\\357\\017#\\233$'::bytea)) AND (T6._Q_000_F_003RRef <> '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) AND (NOT (((T6._Q_000_F_003RRef IN ('\\221\\226\\370\\321\\021\\006}F\\021\\343\\374\\357\\017#\\233$'::bytea))))) AND (T6._Q_000_F_000_00 = CAST(1 AS NUMERIC)) THEN CASE WHEN (T6._Q_000_F_005_TYPE = '\\001'::bytea AND T6._Q_000_F_005_RTRef = '\\000\\000\\000\\000'::bytea AND T6._Q_000_F_005_RRRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) THEN CAST(0 AS NUMERIC) ELSE CAST(5 AS NUMERIC) END WHEN (NOT (((T6._Q_000_F_002RRef IN ('\\221\\226\\370\\321\\021\\006}F\\021\\343\\374\\357\\017#\\233$'::bytea))))) AND (T6._Q_000_F_003RRef IN ('\\221\\226\\370\\321\\021\\006}F\\021\\343\\374\\357\\017#\\233$'::bytea)) AND (T6._Q_000_F_000_00 = CAST(1 AS NUMERIC)) AND (T6._Q_000_F_015RRef <> '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) THEN CASE WHEN (T6._Q_000_F_005_TYPE = '\\010'::bytea AND T6._Q_000_F_005_RTRef = T6._Q_000_F_001TRef AND T6._Q_000_F_005_RRRef = T6._Q_000_F_001RRef) THEN CAST(0 AS NUMERIC) ELSE CAST(6 AS NUMERIC) END WHEN (T6._Q_000_F_002RRef IN ('\\221\\226\\370\\321\\021\\006}F\\021\\343\\374\\357\\017#\\233$'::bytea)) AND (T6._Q_000_F_000_00 = CAST(1 AS NUMERIC)) AND (NOT (((T6._Q_000_F_015RRef IN ('\\226x\\3069C5\\312\\256D!\\204 \\373\\2560\\331'::bytea, '\\253\\267i=\\306R\\026\\364J+\\360\\315\\037\\320\\274l'::bytea, '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea))))) AND (COALESCE(T7._Q_000_F_004_TYPE,'\\001'::bytea) = CASE WHEN T6._Q_000_F_007RRef IS NOT NULL THEN '\\010'::bytea END AND COALESCE(T7._Q_000_F_004_RRRef,'\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) = T6._Q_000_F_007RRef) AND (NOT (((T6._Q_000_F_011RRef IS NULL)))) AND ((T6._Q_000_F_001TRef = '\\000\\000\\0050'::bytea) AND (COALESCE(T7._Q_000_F_007,FALSE) = TRUE AND (((T6._Q_000_F_005_TYPE <> CASE WHEN T8._Fld33135RRef IS NOT NULL THEN '\\010'::bytea END OR T6._Q_000_F_005_RTRef <> CASE WHEN T8._Fld33135RRef IS NOT NULL THEN '\\000\\000\\001\\274'::bytea END OR T6._Q_000_F_005_RRRef <> T8._Fld33135RRef))) OR (NOT ((COALESCE(T7._Q_000_F_007,FALSE)) = TRUE)) AND (T6._Q_000_F_005_TYPE = CASE WHEN T8._Fld33135RRef IS NOT NULL THEN '\\010'::bytea END AND T6._Q_000_F_005_RTRef = CASE WHEN T8._Fld33135RRef IS NOT NULL THEN '\\000\\000\\001\\274'::bytea END AND T6._Q_000_F_005_RRRef = T8._Fld33135RRef)) OR (T6._Q_000_F_001TRef = '\\000\\000\\0048'::bytea) AND (COALESCE(T9._Fld19133RRef,'\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) = '\\205\\026^\\312\\201\\216\\277vF\\242\\350%\\005\\252;('::bytea) AND (COALESCE(T7._Q_000_F_007,FALSE) = TRUE AND ((COALESCE((T10._Fld61790_TYPE || T10._Fld61790_RTRef),'\\000\\000\\000\\000\\000'::bytea) <> '\\010\\000\\000\\003\\226'::bytea)) OR (NOT ((COALESCE(T7._Q_000_F_007,FALSE)) = TRUE)) AND (T10._Fld61790_TYPE = '\\010'::bytea AND T10._Fld61790_RTRef = '\\000\\000\\003\\226'::bytea)) OR (NOT (((T6._Q_000_F_001TRef = '\\000\\000\\0050'::bytea)))) AND (COALESCE(T7._Q_000_F_007,FALSE) = TRUE AND (T6._Q_000_F_005_TYPE = '\\001'::bytea AND T6._Q_000_F_005_RTRef = '\\000\\000\\000\\000'::bytea AND T6._Q_000_F_005_RRRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) OR (NOT ((COALESCE(T7._Q_000_F_007,FALSE)) = TRUE)) AND (NOT (((T6._Q_000_F_005_TYPE = '\\001'::bytea AND T6._Q_000_F_005_RTRef = '\\000\\000\\000\\000'::bytea AND T6._Q_000_F_005_RRRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea)))))) THEN CAST(6 AS NUMERIC) WHEN (NOT (((T6._Q_000_F_002RRef IN ('\\221\\226\\370\\321\\021\\006}F\\021\\343\\374\\357\\017#\\233$'::bytea))))) AND (T6._Q_000_F_000_00 = CAST(1 AS NUMERIC)) AND (T6._Q_000_F_005_TYPE = '\\010'::bytea AND T6._Q_000_F_005_RTRef = T6._Q_000_F_001TRef AND T6._Q_000_F_005_RRRef = T6._Q_000_F_001RRef) AND (NOT (((T6._Q_000_F_015RRef IN ('\\226x\\3069C5\\312\\256D!\\204 \\373\\2560\\331'::bytea, '\\253\\267i=\\306R\\026\\364J+\\360\\315\\037\\320\\274l'::bytea, '\\272\\213\\214V\\002(\\223\\262@-\\374\\024O\\315\\025'''::bytea))))) AND (NOT (((T6._Q_000_F_011RRef IS NULL)))) THEN CAST(5 AS NUMERIC) ELSE CAST(0 AS NUMERIC) END AS NUMERIC(2, 0))
	FROM pg_temp.tt151 T6
	LEFT OUTER JOIN pg_temp.tt77 T7
	ON CASE WHEN T6._Q_000_F_001TRef IS NOT NULL THEN '\\010'::bytea END = T7._Q_000_F_000_TYPE AND T6._Q_000_F_001TRef = T7._Q_000_F_000_RTRef AND ((T7._Q_000_F_001RRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) OR (T6._Q_000_F_006RRef = T7._Q_000_F_001RRef)) AND ((T7._Q_000_F_002_TYPE = '\\001'::bytea AND T7._Q_000_F_002_L = FALSE) OR ('\\002'::bytea = T7._Q_000_F_002_TYPE AND T6._Q_000_F_032 = T7._Q_000_F_002_L))
	LEFT OUTER JOIN _Document1328X1 T8
	ON (('\\000\\000\\0050'::bytea = T6._Q_000_F_001TRef AND T8._IDRRef = T6._Q_000_F_001RRef)) AND (T8._Fld2488 = CAST(0 AS NUMERIC))
	LEFT OUTER JOIN _Document1080 T9
	ON (('\\000\\000\\0048'::bytea = T6._Q_000_F_001TRef AND T9._IDRRef = T6._Q_000_F_001RRef)) AND (T9._Fld2488 = CAST(0 AS NUMERIC))
	LEFT OUTER JOIN _Reference444 T10
	ON (('\\010'::bytea = T6._Q_000_F_005_TYPE AND '\\000\\000\\001\\274'::bytea = T6._Q_000_F_005_RTRef AND T10._IDRRef = T6._Q_000_F_005_RRRef)) AND (T10._Fld2488 = CAST(0 AS NUMERIC))
	WHERE (T6._Q_000_F_027 = FALSE)
	UNION ALL SELECT DISTINCT
	'СебестоимостьТоваров'::mvarchar,
	T11._Q_000_F_001TRef AS Q_000_F_001TRef,
	T11._Q_000_F_001RRef AS Q_000_F_001RRef,
	T11._Q_000_F_002RRef AS Q_000_F_002RRef,
	CASE WHEN T11._Q_000_F_006RRef IS NOT NULL THEN '\\010'::bytea END AS Q_000_F_006_TYPE,
	T11._Q_000_F_006RRef AS Q_000_F_006_RRRef,
	CASE WHEN (T11._Q_000_F_000_00 = CAST(0 AS NUMERIC)) AND T12._Q_000_F_005 = TRUE AND (T11._Q_000_F_025_TYPE = '\\001'::bytea AND T11._Q_000_F_025_RTRef = '\\000\\000\\000\\000'::bytea AND T11._Q_000_F_025_RRRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) THEN CAST(18 AS NUMERIC) WHEN (T11._Q_000_F_000_00 = CAST(1 AS NUMERIC)) AND T12._Q_000_F_006 = TRUE AND (T11._Q_000_F_025_TYPE = '\\001'::bytea AND T11._Q_000_F_025_RTRef = '\\000\\000\\000\\000'::bytea AND T11._Q_000_F_025_RRRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) THEN CAST(19 AS NUMERIC) ELSE CAST(0 AS NUMERIC) END
	FROM pg_temp.tt151 T11
	LEFT OUTER JOIN pg_temp.tt77 T12
	ON CASE WHEN T11._Q_000_F_001TRef IS NOT NULL THEN '\\010'::bytea END = T12._Q_000_F_000_TYPE AND T11._Q_000_F_001TRef = T12._Q_000_F_000_RTRef AND ((T12._Q_000_F_001RRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) OR (T11._Q_000_F_006RRef = T12._Q_000_F_001RRef)) AND ((T12._Q_000_F_002_TYPE = '\\001'::bytea AND T12._Q_000_F_002_L = FALSE) OR ('\\002'::bytea = T12._Q_000_F_002_TYPE AND T11._Q_000_F_032 = T12._Q_000_F_002_L)) AND (T12._Q_000_F_005 = TRUE AND ('\\010'::bytea = T12._Q_000_F_003_TYPE AND T11._Q_000_F_007RRef = T12._Q_000_F_003_RRRef) OR T12._Q_000_F_006 = TRUE AND ('\\010'::bytea = T12._Q_000_F_004_TYPE AND T11._Q_000_F_007RRef = T12._Q_000_F_004_RRRef))
	WHERE (T11._Q_000_F_000_00 = CAST(0 AS NUMERIC)) AND T12._Q_000_F_005 = TRUE AND (T11._Q_000_F_025_TYPE = '\\001'::bytea AND T11._Q_000_F_025_RTRef = '\\000\\000\\000\\000'::bytea AND T11._Q_000_F_025_RRRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea) OR (T11._Q_000_F_000_00 = CAST(1 AS NUMERIC)) AND T12._Q_000_F_006 = TRUE AND (T11._Q_000_F_025_TYPE = '\\001'::bytea AND T11._Q_000_F_025_RTRef = '\\000\\000\\000\\000'::bytea AND T11._Q_000_F_025_RRRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea)
	UNION ALL SELECT DISTINCT
	'СебестоимостьТоваров'::mvarchar,
	T13._Q_000_F_001TRef AS Q_000_F_001TRef,
	T13._Q_000_F_001RRef AS Q_000_F_001RRef,
	T13._Q_000_F_002RRef AS Q_000_F_002RRef,
	'\\001'::bytea,
	'\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea,
	CAST(20 AS NUMERIC)
	FROM pg_temp.tt151 T13
	INNER JOIN _Document871 T14
	ON ('\\000\\000\\003g'::bytea = T13._Q_000_F_001TRef AND T14._IDRRef = T13._Q_000_F_001RRef) AND (T14._Fld5597RRef = T13._Q_000_F_002RRef)
	WHERE ((T14._Fld2488 = CAST(0 AS NUMERIC))) AND ((T13._Q_000_F_006RRef = '\\267\\276\\037\\306\\305\\345\\326\\333G\\230dW\\334\\361\\327\\356'::bytea) AND (T13._Q_000_F_007RRef = '\\271*\\304L\\007\\2627\\262E@`P(\\256l\\373'::bytea) AND (T13._Q_000_F_021RRef = T14._Fld5595RRef) AND (T14._Fld5595RRef IN ('\\214\\215\\006\\210\\375\\211\\012\\035F\\357\\224\\341P\\1779S'::bytea, '\\237\\267\\363\\324#\\012\\323>D\\000+\\214\\237p\\352\\304'::bytea, '\\222''\\261\\254}\\025\\212>OK\\231\\300\\3505/\\246'::bytea, '\\212\\245u\\212;/\\362\\306A6\\026\\344\\033\\310[\\342'::bytea, '\\225"\\036\\345\\216\\372\\240\\337J\\371\\327\\271wr*k'::bytea, '\\257\\347f\\277\\317\\221,\\246JP\\243\\213\\267W2~'::bytea, '\\207\\226\\320\\034M\\341\\213\\263KQ\\334\\010C\\036u\\262'::bytea, '\\260\\271 \\\\(\\342/)J\\233\\236q@#\\330\\324'::bytea, '\\213U\\0303\\374%\\235pJ\\015)\\025T\\013\\360W'::bytea, '\\2144\\324\\322\\205\\246\\214yN\\365L\\333\\022}\\010\\030'::bytea, '\\264EG\\\\\\346\\024_\\257C\\2252S4\\214I\\245'::bytea)) AND 1=0)
	UNION ALL SELECT DISTINCT
	'СебестоимостьТоваров'::mvarchar,
	T15._Q_000_F_001TRef AS Q_000_F_001TRef,
	T15._Q_000_F_001RRef AS Q_000_F_001RRef,
	T15._Q_000_F_002RRef AS Q_000_F_002RRef,
	'\\001'::bytea,
	'\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea,
	CAST(21 AS NUMERIC)
	FROM pg_temp.tt151 T15
	INNER JOIN _Document1176X1 T16
	ON ('\\010'::bytea = T15._Q_000_F_025_TYPE AND '\\000\\000\\004\\230'::bytea = T15._Q_000_F_025_RTRef AND T16._IDRRef = T15._Q_000_F_025_RRRef)
	WHERE ((T16._Fld2488 = CAST(0 AS NUMERIC))) AND ((T15._Q_000_F_012RRef = '\\207C\\331\\321\\035\\353\\252\\334M\\311\\0347\\331qK\\216'::bytea) AND (T15._Q_000_F_007RRef = '\\203hM<\\012]<\\302J\\201}{\\002W\\\\\\023'::bytea) AND (T15._Q_000_F_006RRef = '\\223\\207 \\202\\023\\271\\033gMs>v&\\312\\357\\346'::bytea) AND (T15._Q_000_F_001TRef = '\\000\\000\\003\\262'::bytea) AND (T16._Fld24354RRef = '\\201G~\\336\\011\\344V~E\\355p\\353\\305q\\211\\027'::bytea))) T1
	WHERE (T1.Q_001_F_004_ <> CAST(0 AS NUMERIC))

При этом, мы ранее уже запускали на этом же сервере закрытие месяца с параметром temp_buffers = 189 Mb, и такой ошибки не было. Можно было сделать вывод, что предложенный первый вариант увеличения параметра решить проблему помогает. Автор проблемы увеличил temp_buffers, и у него ошибка тоже ушла. Таким образом, обходное решение было найдено. Но интересовала нас первопричина.

Суть проблемы

Поскольку мы воспроизвели проблему на нашем стенде, то у нас были все готовые артефакты для передачи задачи в команду разработки. По итогам анализа коллег выяснилось, что проблема могла возникнуть на запросах, в которых во временной таблице происходили одновременно и чтение, и запись. Коллеги из разработки СУБД сэмулировали более простой пример, на котором воспроизводится проблема:

set temp_buffers to 100;

create temp table tt1(val char(2000));
insert into tt1(val) select 'FOO' from generate_series(1,600);

create temp table tt2(val char(2000));

insert into tt2 select val from tt1; 

Разберём причину возникновения ошибки. У каждой сессии есть свой local buffer pool, равный temp_buffers. В нашем примере он состоит из 100 слотов. Локальный буфер используется для операций чтения и вставки во временные таблицы. Изобразим локальный буфер следующим образом:

За этот пул при выполнении запроса "insert into tt2 select val from tt1;" конкурируют 2 операции:

  • read stream - читает данные из временной таблицы tt1;

  • insert stream - записывает данные во временную таблицу tt2;

Read stream данные из временной таблицы читает не целиком, а порциями. Сначала он занимает в local buffer pool 16 слотов:

Read stream не ждет, пока эти 16 слотов обработаются запросом, чтобы их освбодить. Оптимизация 18 версии PostgreSQL состоит в том, что read stream асинхронно начинает занимать и следующие блоки в local buffer pool, чтобы не делать операции синхронно, как в версии 17 (т.е. "прочитал - обработал запросом - отпустил"), а для роста производительности прочитывать следующие блоки, пока запросом обрабатываются предыдущие. Таким образом, Read stream занимает в данном пуле и следующие блоки, адаптивно (prefetch) их увеличивая: 16 → 32 → 64. И получается, что в какой-то момент мы имеем в local buffer pool 64 занятых слота:

Executor не ждет, пока будут прочитаны все записи из временной таблицы tt1, он также начинает записывать данные во временную таблицу tt2 и занимать 1 слот через insert stream:

 Insert stream, в отличие от read stream, сразу освобождает свои слоты после записи страницы в tt2.

Тем временем, Read stream занимает все оставшиеся слоты, и после того как insert stream пытается найти свободный слот в local buffer pool и такового не находит, мы и получаем ошибку "no empty local buffer available":

Таким образом, ошибка возникает из-за конкуренции двух потоков за слоты local buffer pool: read stream агрессивно занимает слоты через prefetch, не оставляя места для insert stream. Это принципиальное отличие от PostgreSQL 17, где чтение было синхронным и буфер освобождался до начала записи.

Решение проблемы

Коллеги из разработки СУБД подготовили соответствующее исправление и направили его на рассмотрение в pgsql-hackers. Суть фикса – в т.н. адаптивном торможении prefetch - read stream отслеживает количество свободных слотов и замедляет заполнение буфера, когда свободного места почти не осталось. В указанной теме pgsql-hackers был предложен и другой подход - резервировать 25% слотов под insert stream, - однако, на наш взгляд, при больших значениях temp_buffers это избыточно. 

В качестве обходного решения (до выхода официального исправления) рекомендуем увеличить параметр temp_buffers до 128–256 MB - этого достаточно, чтобы в буфере хватало места одновременно для prefetch read stream и страниц insert stream. Как только патч будет принят в основную ветку PostgreSQL 18, мы обновим статью с указанием версии, в которой ошибка устранена. В Tantor Postgres 18 версии, которая планируется к выпуску и в настоящее время проходит финальное тестирование, ошибка исправлена.