По следам PgConf: быстрое закрытие месяца в 1С:ERP на PostgreSQL

Закончилось основное событие года в мире PostgreSQL — PgConf 2025. В статье рассматривается патч, который ускоряет закрытие месяца в 1С-ERP в 10 раз, что довольно значительно. Патч был анонсировано в докладе «Быстрое закрытие месяца в 1С: ERP на PostgreSQL» или «Закрывай месяц в 1С ERP на PostgreSQL быстро и незаметно».

С 1С: ERP я не знаком, но знаю, что для 1С выпускаются специальные сборки PostgreSQL. Наполнившись решимостью узнать, что в этом 1С происходит я пожертвовал докладом про карту видимости, который шёл параллельно и не пожалел.

Я узнал, что в 1С никто ничего не делает, кроме как месяца закрывают и больше никого ничего не интересует, а также то, что по статистике пользователи приложения 1С: ERP делают что-либо, в среднем, раз в 20 минут. «Закрытие месяца» — набор расчетов и действий, которые могут выполняться часами. При этом с первого раза месяц обычно не закрывается, так как обнаруживаются ошибки учёта, которые должны быть исправлены и закрытие месяца повторяется заново. И так несколько раз. В докладе осветили нюансы установки границы итогов, удобство использования клонов кластера баз данных, описали причины проблем: вкратце, как я понял, проигрываются проводки, удаляется добавляется много строк.

Основная интрига доклада была в том, что «секретный патч Фёдора Сигаева» ускоряет закрытие месяца в 10 раз (на порядок!). На этом месте я проснулся и встрепенулся. Не каждый день встретишь ускорение на порядок. Про патч докладчик сказал «что-то там патч поправляет в статистике. Этот патч ещё никуда не вышел. Не могу сказать когда он появится, а может и не появится». После этого мне резко этот патч понадобился, хотя у меня нет 1С. Я обернулся в поисках моральной поддержки и спросил слушавшего доклад чуть позади меня: «что эа патч, это fasttrun что ли?». Участник сказал «Не знаю. Спроси Сигаева» и указал направление позади себя.

Смысл патча в том, что вместо Nested Loop для многих запросов начинает выбираться Hash Join. C Nested Loop планировщик часто ошибается, недооценивая число строк в выборке. При сколько-нибудь большом числе строк метод соединения Nested Loop становится чрезвычайно медленным и ошибки фатальны. При этом, планировщик как не собирай статистику, неизменно использует Nested Loop из-за особенностей конструкций запросов. Менять значение параметра конфигурации enable_nestloop нельзя, так как Nested Loop оптимален для большого числа запросов с небольшим числом соединяемых строк.

Выслушав доклад, заинтересованные участники конференции окружили Фёдора Сигаева с политкорректным вопросом «Когда в PGpro Enterprise появится патч?». Фёдор сказл кратко «патч выложен в коммтфесте». Что характерно, заинтересованных участников было немного — человек 5 при достаточно заполненном зале. Большая часть людей не решались задать нескромный вопрос. После ответа Федора я не то чтобы присвистнул, но воодушевился. Ответ означал, что патч несекретный, а самый что ни наесть открытый — безвозмездно передан сообществу. В наше суровое время такое редко встретишь. Более того, патч похоже просмотрен и есть шанс добавления в будущем в ванильную версию.

Фёдор решил отойти от толпы группы вопрошавших, но тут молодой, но дерзкий юноша задал вопрос мэтру «а что это на слайде докладчика для соединения хэшированием выбиралась большая по размеру таблица?» Юноша явно решил блеснуть недавно полученным знанием о том, чтохэш-таблица должна строиться по меньшей выборке. Хотя, кто из нас не грешил таким бахвальством? Какое отношение имеет Фёдор к слайду докладчика юношу не интересовало. Фёдор моментально ответил: планировщик не смог переставить порядок, наверняка было левое соединение. Юноша не готовый к такому повороту (про это он не знал) начал спорить с мэтром, что как так, должен переставлять. И как обычно бывает притянул «в оракле переставляет». Фёдор ещё раз повторил и заботливо детализировал юноше истину про реляционную алгебру. Юноша не унимался. Я наполнился возмущением, потому что и у меня под одной из статей не юный, но по свежим критериям ВОЗ нестарый специалист убеждает меня, что меня нет  , что ограничения целостности не нужно устанавливать, лучше блокиговать и ещё раз блокиговать. Более того, мы воспитаны в традициях, что заговорить с мэтром без спроса это верх неприличия. На конференции был свидетелем еще одного монолога «Ой Рогов, тот самый что ли?». На что второй мэтр ответил просто: «тот самый», а не «глаза разуй».

Воодушевленный существованием, а главное, доступностью патча, я побрёл к сцене ловить докладчика, который, как мне показалось, разбирался в этих самых 1С. Меня издавна глодал вопрос: сколько временных таблиц может создать 1С в одной сессии. Докладчик в докладе сказал что в час 1С средней руки создает миллион временных таблиц, а в месяц миллиард.

Я подошел к группе товарищей, окруживших харизматичного докладчика и смиренно стал слушать о чем говорят умные люди ожидая удобного момента ввернуть свой вопрос. В это время невдалеке Фёдор объяснял Ивану Панченко в терминах линейных и квадратичных оценок что делает патч. Не только я был заинтригован патчем.

Самостоятельно искать патч я не стал, так как не силён в коммитфестах. В общем, кое-как нашел патч (ну как нашел, помогла техподдержка): https://www.postgresql.org/message-id/flat/9789f79b-34f0–49ee-9852–783392a3615c@sigaev.ru

В обсуждении Илья Евдокимов из Тантор Лабс поддержал Фёдора Сигаева, ответив на нескромный вопрос Тома Лейна:

отечественные разработчики поддерживают друг друга
отечественные разработчики поддерживают друг друга

Вспомнив доклад «PostgreSQL: истории, оставленные между строк кода» несравненной Екатерины Соколовой, который был несколькими часами раньше, я углубился в чтение комментариев между строк кода. Всамомкомментарии, неизвестныйразработчиквопрошал доколе?:

* Essentially, we are assuming that the not-yet-known
* comparison value is equally likely to be any of the possible
* values, regardless of their frequency in the table.  Is that a good
* idea?

Идея, конечно, была плохой. И этот текст был заменён на объяснение сути патча:

Estimate the selectivity as quadratic mean of non-null fraction
divided by number of distinct values and set of MCV selectivities.
Use quadratic mean because it includes the squared deviation (error)
as well and here it would be nice to compute upper limit of estimation
to prevent wrong choose of nested loop, for example.

Пример структуры проблемных запросов:

explain analyze
     SELECT
        COUNT(*)
     FROM
        t1
     LEFT JOIN
         t2
             ON t2.b = t1.a
             AND t2.c = 0  //line of interest
             ;

С закомментированной строкой 'line of interest' все работает отлично, без проблем, но с этой строкой мы получим другой план, гораздо худший. Интересно, что столбец t2.c содержит только одно значение (ноль). Планировщик выбирает вложенный цикл, вместо хэш-соединения и делает неправильную оценку:

  Index Only Scan using i1_t2 on t2  (.. rows=6 ..) (.. rows=3555 loops=2000)
     Index Cond: ((c = 0) AND (b = t1.a))

Предполагалось шесть строк, но при выполнении получено 3555 строк. Это произошло потому, что функция var_eq_non_const () предполагает равномерное распределение столбца t2.b, что неверно.

Сам патч, который должен скокоро сделать закрытие месяца в 1C быстрее на порядок состоит из нескольких строк:

int i;
double sum_selec = 0.0;
+
/*
 * Compute quadratic mean, walk on array in reverse direction to
 * do not lose accuracy. We don't bother about sslot.nnumbers
 * equality to zero, because in this case we just get the same
 * result. But equality to zero is unlikely.
 */
for(i=sslot.nnumbers - 1; i>=0; i--)
  sum_selec += sslot.numbers[i] * sslot.numbers[i];

selec = sqrt((selec * selec + sum_selec) / ((double)sslot.nnumbers + 1.0));

Глядя на эти строки мне даже захотелось написать какой-нибудь патч. Но надо знать какую формулу и в какую часть кода вставить. В поисках функции могут помочь комментарии с вопросительными предложениями типа «правильно ли это?».

Есть патч для ванильной версии. Те, кто самостоятельно компилируют версии PostgreSQL,  могут его использовать. Когда ждать официальные сборки? Патч появится в сборке Tantor SE 1C 17 версии. В ванильной версии PostgreSQL патч появится не раньше 18 версии. Примеры реальных запросов 1С: ERP, которые приводились в докладе Антона Дорошкевича «Закрывай месяц в 1С ERP на PostgreSQL быстро и незаметно»:
Запрос с Nested Loop на 482 секунды:

https://explain.tensor.ru/archive/explain/f76499ba8f4ff6f01f3c6a1d6f0121d0:0:2025–03–16

Запрос с Hash Join на 25 секунд:

https://explain.tensor.ru/archive/explain/1bc253be588285ae617c621001cb5759:0:2025–03–16

изменения в плане проблемного запроса
изменения в плане проблемного запроса

Заключение

В статье раскрыт патч Фёдора Сигаева, благодаря которому закрытие месяца в 1С: ERP ускоряется на порядок. Также описана история нахождения патча и небольшой репортаж с конференции

Habrahabr.ru прочитано 5767 раз