Мы завершаем нашу серию статей о нагрузочном тестировании 1С:ERP на 30 000 ВРМ от компании «ИТ-Экспертиза». Для тех, кто пропустил начало и первые выводы, можно пройти по соответствующим ссылкам.

В заключительной статье мы расскажем, как нам удалось достичь целевой нагрузки в 30 000 ВРМ в конфигурации мастер-реплика на Postgres Pro Enterprise for 1C 18.6.1.

Тесты на новом сервере БД: проблема с Xorg и lw_lock_shared_limit

В начале лета к нам на тесты пришло оборудование от компании YADRO — сервер на базе Intel Xeon Platinum 8490H, мощного серверного процессора 4-го поколения. Каждый сокет соответствовал отдельному NUMA-узлу, что в сумме давало нам 240 физических ядер, по 60 ядер на каждый из четырёх сокетов и 480 логических ядер с включением SMT: 

command: [lscpu]
Architecture:                            x86_64
CPU op-mode(s):                          32-bit, 64-bit
Address sizes:                           46 bits physical, 57 bits virtual
Byte Order:                              Little Endian
CPU(s):                                  480
On-line CPU(s) list:                     0-479
Vendor ID:                               GenuineIntel
Model name:                              Intel(R) Xeon(R) Platinum 8490H
CPU family:                              6
Model:                                   143
Thread(s) per core:                      2
Core(s) per socket:                      60
Socket(s):                               4
Stepping:                                8
CPU(s) scaling MHz:                      24%
CPU max MHz:                             3500.0000
CPU min MHz:                             800.0000
BogoMIPS:                                3800.00
Flags:                                   fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l3 cat_l2 cdp_l3 intel_ppin cdp_l2 ssbd mba ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local split_lock_detect avx_vnni avx512_bf16 wbnoinvd dtherm ida arat pln pts hwp hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke waitpkg avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq la57 rdpid bus_lock_detect cldemote movdiri movdir64b enqcmd fsrm md_clear serialize tsxldtrk pconfig arch_lbr ibt amx_bf16 avx512_fp16 amx_tile amx_int8 flush_l1d arch_capabilities
Virtualization:                          VT-x
L1d cache:                               11.3 MiB (240 instances)
L1i cache:                               7.5 MiB (240 instances)
L2 cache:                                480 MiB (240 instances)
L3 cache:                                450 MiB (4 instances)
NUMA node(s):                            4
NUMA node0 CPU(s):                       0-59,240-299
NUMA node1 CPU(s):                       60-119,300-359
NUMA node2 CPU(s):                       120-179,360-419

Запуски на новом сервере БД позволили достичь нам следующих показателей: 

  • 21 000 ВРМ, APDEX = 0.996;

  • 25 000 ВРМ, APDEX =  0.990 (8 серверов приложений 1С);

  • 25 000 ВРМ, APDEX =  0.996  (10 серверов приложений 1С).

По сравнению с нашим предыдущим сервером БД на базе Intel Xeon Gold 6338 @ 2.00 GHz, тест на новом сервере БД от YADRO при нагрузке в 21 000 ВРМ показал рост APDEX с 0,975 до 0,996.

Добавление ещё двух серверов приложений позволило распределить нагрузку между остальными серверами кластера 1С и снизить на них утилизацию CPU на последнем часе теста, которая достигала 100%. СУБД при этом была загружена всего на ~ 20%, что открывало нам путь к тесту на 30 000 одновременно работающих ВРМ. Именно на этом этапе мы столкнулись с последними двумя проблемами.

Во-первых, в ходе анализа нагрузки на тонкие клиенты через Grafana мы обнаружили несколько виртуальных машин с выраженной асимметрией загрузки по ядрам: одно из ядер было загружено значительно сильнее остальных. При сравнении этих виртуальных машин с соседними выяснилось, что непосредственной причиной наблюдаемой асимметрии является высокая загрузка процесса Xorg. На проблемных машинах Xorg мог загружать одно ядро практически на 100% времени, тогда как на остальных машинах его загрузка распределялась более равномерно и составляла примерно от 25 до 85%. При этом суммарная загрузка CPU виртуальной машины могла отличаться незначительно или даже быть немного ниже. Отличие заключалось в том, что на проблемных машинах Xorg практически не мигрировал между ядрами и преимущественно оставался на одном или двух ядрах.

В дальнейшем выяснилось, что проблема с высокой загрузкой Xorg непосредственно влияет на производительность 1С. Xorg работает в один поток и фактически концентрирует на себе обработку UI-потоков. Поэтому на одних виртуальных машинах он потребляет порядка 20% CPU, а на других может занимать практически всё одно ядро. В последнем случае виртуальная машина становится узким местом: клиент 1С перестает своевременно обрабатывать запросы, что проявляется как своеобразный «затык» тонкого клиента и визуально выглядит как использование сервером только одного ядра: 

Анализ, выполненный в ходе последующих запусков, привёл нас к тому, что возможным виновником является оконный менеджер Marco, используемый в DE MATE на наших vm c RED OS. Наше предположение состояло в том, что со временем в нём что-то накапливается или происходит утечка ресурсов, из-за чего некоторые вызовы GetXIDRange начинают выполняться значительно дольше. Судя по доступной информации, похожие проблемы ранее уже встречались в Marco, хотя в используемой нами версии они, предположительно, должны быть исправлены. Поскольку эффект наблюдался только на части виртуальных машин, нельзя было исключать и зависимость от конкретного сценария, выполняемого на тонком клиенте.

В качестве warkaround было решено периодически выполнять перезапуск оконного менеджера Marco на всех тонких серверах через ansible:

DISPLAY=:10 nohup marco --sm-disable --no-composite --replace 1>marco.log 2>&1 &	

Ещё одной проблемой, проявившейся при выходе на уровень работы 30 тысяч пользователей стало периодическое подвисание запросов по работе с регистрами. Например, операция вставки всего 15 строчек в таблицу могла работать более 20 секунд и прерываться по тайм-ауту. В журнале базы данных при этом появлялось большое количество сообщений об ожидании эксклюзивной блокировки на расширение таблицы:

2026-08-12 05:43:12.572 MSK [3631383]: <...> LOG: process 3631383 still waiting for ExclusiveLock on extension of relation 428257 of database 16394 after 1000.080 ms
2026-08-12 05:43:12.572 MSK [3631383]: <...> DETAIL: Process holding the lock: 3561441. Wait queue: 3664786, 3631383.
2026-08-12 05:43:12.572 MSK [3631383]: <...> STATEMENT: INSERT INTO _AccumRg45719 <...>
...
2026-08-12 05:43:31.572 MSK [3631383]: <...> LOG: process 3631383 still waiting for ExclusiveLock on extension of relation 428257 of database 16394 after 20000.114 ms
2026-08-12 05:43:31.572 MSK [3631383]: <...> DETAIL: Process holding the lock: 3561441. Wait queue: 3664786, 3631383.
2026-08-12 05:43:31.572 MSK [3631383]: <...> STATEMENT: INSERT INTO _AccumRg45719 <...>
...
2026-08-12 05:43:44.779 MSK [3631383]: <...> LOG: process 3631383 acquired ExclusiveLock on extension of relation 428257 of database 16394 after 33207.499 ms
2026-08-12 05:43:44.779 MSK [3631383]: <...> STATEMENT: INSERT INTO _AccumRg45719 <...>
...
2026-08-12 05:43:44.780 MSK [3631383]: <...> ERROR: canceling statement due to lock timeout
2026-08-12 05:43:44.780 MSK [3631383]: <...> STATEMENT: INSERT INTO _AccumRg45719 <...>

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

Что происходило в этом сценарии? Базе данных необходимо быстро находить буфер в памяти, который используется для работы с нужным блоком данных. Эта информация хранится в большой секционированной хеш-таблице в памяти, доступ к каждой секции которой и защищается с помощью легковесных блокировок, объединённых под именем BufferMapping. Блокировки получаются в разделяемом (Shared) режиме при чтении таблицы и в эксклюзивном режиме при её модификации, при этом каждая из этих блокировок получается на очень небольшое время. Секционирование таблички позволяет снизить конкуренцию за блокировки: при добавлении одного блока сессия конкурирует только с теми сессиями, которым работают с блоками, попадающими в одну секцию. При расширении таблицы сессия сразу добавляет новые блоки в буферный кэш, поэтому ей требуется последовательно получить блокировки на несколько секций. Однако если в этот момент с табличкой буферов работает много сессий (в Shared-режиме), то пишущей сессии может потребоваться прождать много времени, для того чтобы дождаться момента, когда у блокировки не окажется ни одной читающей транзакции и можно захватить её в эксклюзивном режиме.

Такое происходит из-за того, что легковесные блокировки в PostgreSQL по умолчанию не являются «честными»: сессия, которая хочет получить разделяемую блокировку, сможет сделать это если какая-то другая сессия уже получила такую же разделяемую блокировку. Это позволяет новой сессии получить доступ к блокировке, даже если её уже ждёт ранее пришедшая сессия, требующая эксклюзивного доступа. Такой подход увеличивает общую скорость работы, но может приводить к задержкам для модифицирующих операций при очень большом числе сессий, обращающихся к одной и той же блокировке. Именно это и стало происходить когда нагрузка на базу выросла до максимальных значений.

Для решения этой проблемы в Postgres Enterprise есть возможность включить «честный» режим для легковесных блокировок, при этом сессия ожидающая эксклюзивного доступа сможет гарантированно получить его за разумное время. Этот режим контролируется параметром lwlock_shared_limit, который задаёт максимальное количество разделяемых блокировок, которые могут быть обработаны до того, как блокировку получит сессия ожидающая эксклюзивный доступ. Для дальнейших тестов значение этого параметра было выставлено в 128, что позволило устранить длительные ожидания при расширении таблиц даже на максимальных нагрузках, не снижая при этом общем эффективности работы с легковесными блокировками.

Комбинация wa для рестарта оконного менеджера и значение lwlock_shared_limit = 128 позволили достичь нам итоговый APDEX, равный 0.987 для 30 000 ВРМ.

Финальный тест с выносом отчётов на реплики

В качестве финального теста мы поставили себе цель проверить работу 1С на кластере PostgreSQL в конфигурации «мастер + реплика», а заодно попробовать вынести построение части отчётов на реплику. 

Как видно из heatmap со временем исполнения бизнес-операций, с течением времени и ростом количества запущенных ВРМ, время исполнения двух отчётов начинает расти, что в итоге влияет на итоговый APDEX. Эти отчёты нам уже знакомы: именно они показывали резкий рост времени работы, пока мы не вынесли клиентскую нагрузку с основного узла кластера 1С и не добавили новые сервера приложений: 

На том же хосте СУБД мы развернули реплику на соседнем порту и настроили 1С на на работу с ней. Для этого в 1С нужно создать новою конфигурацию: внешняя репликация (за неё несёт ответственность сам Postgres), дата-акселератор выключен, все данные есть на реплике: 

Мастер мы закрепили за NUMA-узлами 0 и 2, а реплику — за 1 и 3. Вначале мы попробовали запустить тест с синхронной репликацией, но запуск теста в такой конфигурации не увенчался успехом: с ростом нагрузки ресурсов CPU выделенных для мастера перестало хватать и тест завершился падением. 

Затем мы решили выполнить тест с асинхронной репликой, на этот раз выделив три NUMA-ноды для мастера и оставшуюся одну ноду для реплики. Тест на 30 000 ВРМ успешно завершился с APDEX=0.986.

Наконец, мы решили вынести на реплику выполнение двух проблемных отчётов, по которым ранее мы видели ухудшение времени выполнения с ростом нагрузки. Выполняем настройку в конфигураторе 1С и не забываем поставить галочку “Use copies only”: 

Наш первый запуск с отчётами, вынесенными на реплику, упал на четвёртом часу, при переходе с 18 000 на 24 000 ВРМ. Проблема проявлялась на стороне серверов приложений 1С: потребление оперативной памяти быстро росло, что в итоге приводило к её исчерпанию и падению 1С кластера. Показатели atop за время теста показали, что всю память на сервере потребляют процессы rphost:

Как мы уже рассказывали, рабочие процессы сервера rphost обслуживают клиентов 1С. При текущей конфигурации (30 000 ВРМ, 128 клиентов на один rphost) после добавления реплики каждый процесс rphost при установке соединения загружал конфигурацию базы и, по всей видимости, конфигурацию реплики. Это приводило к увеличению потребления памяти каждым процессом.

В результате потребление памяти одним процессом выросло с 18–25 ГБ для конфигурации без механизма копии баз данных до 32–38 ГБ при конфигурации с репликацией.

После этого скачка памяти у нас менялось количество доступной памяти на узле и характер её потребления. До тестов с репликой: 

После них: 

Основная проблема возникала при достижении серверами приложений 1С 80–90% потребления памяти. 1С начинала перераспределять соединения между серверами: часть соединений переносилась с сервера 1 на сервер 2, где запускались новые процессы rphost, потреблявшие дополнительную память. В результате сервер 2 также достигал порогового значения и 1С начинала переносить соединения обратно.

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

Выходом из ситуации стало ограничение состава объектов, размещаемых в копии базы данных. За основу был взят подход, описанный в ИТС 1С, с использованием обработки “ПодборТаблицДляДатаАкселератора.epf“. Для этого в кластере 1С мы оставили только один сервер для клиентских соединений rphost, включили сбор технологического журнала для нужных отчётов и выполнили их. Затем полученный лог загрузили в обработку, после чего добавили в новую конфигурацию копии базы данных недостающие таблицы, тем самым ограничив объём данных, размещаемых в копии: 

После применения настроек и перезамера итоговый APDEX для 30 000 ВРМ в конфигурации мастер-реплика с вынесением отчетов на реплику составил 0.986: 

При этом, у отчетов вынесенных на реплику подрос APDEX и уменьшилось среднее время выполнения. 

Итоги

Настало время подвести итоги. «1С:ERP» — это комплексное решение для управления предприятием, используемое в том числе в крупных организациях со сложной структурой и большим количеством одновременно работающих пользователей. В таких системах тысячи пользователей одновременно выполняют различные операции, формируя интенсивную и неоднородную нагрузку, которая непосредственно влияет на работу СУБД: планы выполнения запросов, использование CPU и памяти, работу кеша, блокировки и другие механизмы. Поэтому особое значение приобретает комплексное нагрузочное тестирование, позволяющее оценить производительность системы в условиях, максимально приближенных к реальной эксплуатации.

Именно в ходе работы по достижению нагрузки в 30 000 одновременно работающих ВРМ нам удалось выявить и устранить ряд проблем, которые становились заметными по мере масштабирования нагрузки. 

Ещё раз подсветим основные изменения, внесённые в ходе работ:

  • Исправлена ошибка relation ... deleted while still in use, возникавшая при использовании in-memory каталога временных объектов.

  • Снижено количество сообщений об инвалидации кешей для временных объектов: были исключены ненужные инвалидации для временных таблиц, а также инвалидации smgr и snapshot-инвалидации. Дополнительно была оптимизирована обработка самих инвалидаций: принадлежность объекта к временным таблицам теперь определяется без лишних обращений к системному кешу.

  • Снижена нагрузка на механизм сбора статистики PostgreSQL. Для временных таблиц было отключено создание и дальнейшее ведение relation-статистики. Это позволило устранить рост ожиданий LWLock:PgStatsHash и LWLock:PgStatsDSA, а также связанный с ним постепенный рост потребления памяти в DSA. 

  • Оптимизирована работа с файлами временных таблиц и индексов и реализовано отложенное создание файлов на диске для таких отношнений: теперь они создаются только после первой реальной записи, например при вытеснении буферов из local buffers. Это позволяет уменьшить нагрузку на файловую систему при массовом создании и удалении временных объектов.

Кроме того, в ходе работы мы столкнулись с рядом проблем, которые не были напрямую связаны с производительностью СУБД, но существенно влияли на результаты тестирования. В частности, пришлось учитывать особенности распределения процессов 1С по NUMA-узлам, настройки самой 1С, а также влияние количества серверов приложений и их конфигурации на итоговую производительность всей системы.

Все ключевые доработки, о которых мы рассказали в статье, уже вошли в релиз Postgres Pro Enterprise for 1C 18.6.1.

Комментарии (0)