惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog RSS Feed
宝玉的分享
宝玉的分享
腾讯CDC
博客园_首页
T
Tailwind CSS Blog
月光博客
月光博客
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
SecWiki News
SecWiki News
美团技术团队
P
Privacy International News Feed
H
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
Y
Y Combinator Blog
D
DataBreaches.Net
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Cyberwarzone
Cyberwarzone
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cisco Blogs
S
Schneier on Security
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
Cisco Talos Blog
Cisco Talos Blog
小众软件
小众软件
Forbes - Security
Forbes - Security
D
Docker
T
Tenable Blog
S
Secure Thoughts
雷峰网
雷峰网
S
Security @ Cisco Blogs
T
The Exploit Database - CXSecurity.com
The Cloudflare Blog
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
阮一峰的网络日志
阮一峰的网络日志

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
В погоне за APDEX-ом, или как создать HighLoad на недорогом серверном железе
mizhka (Post · 2026-05-20 · via Все публикации подряд на Хабре

Уровень сложностиПростой

Время на прочтение24 мин

Охват и читатели520

В прошлом году компания 1С реализовала свой собственный тест, имитирующий одновременную работу 30 000 пользователей в «1С:ERP Управление предприятием», в рамках которого осуществляется моделирование работы ключевых пользователей ERP, таких как диспетчеры производства, менеджеры по закупкам и продажам, кладовщики, бухгалтеры.

В прошлом году вместе с коллегами из компании ИТ-Экспертиза мы решили повторить данное тестирование, используя в качестве СУБД Postgres Pro Enterprise. 

Что из себя представляет тест

Тестирование выполняется с применением инструмента «Тест-Центр», входящего в состав «1С:Корпоративный инструментальный пакет». 

В рамках теста моделируется работа 30 000 пользователей, распределённых по 15 функциональным сценариям, охватывающим ключевые процессы системы 1C:ERP. Основную нагрузку формируют массовые операционные сценарии: продажи, закупки, склад, производство, взаиморасчёты и бухгалтерский учёт. Дополнительно моделируются менее массовые, но ресурсоёмкие процессы: различные виды планирования, регламентированная отчётность (НДС), бюджетирование. Длительность теста составляет порядка 11 часов и проводится на реалистичной базе данных, итоговый объём которой составляет около 1 Тб. База данных распространяется компанией 1С в виде файла выгрузки информационной базы в формате .dt (~31 Гб), который доступен по запросу. Нам его предоставили коллеги из «ИТ-Экспертизы».

По итогам нагрузочного тестирования на основе времени выполнения ключевых операций рассчитывается показатель APDEX (Application Performance Index), по которому определяется, был ли тест успешным.

Если в двух словах, то APDEX — это международный стандарт оценки производительности информационных систем, который рассчитывается как отношение количества удовлетворительных откликов к общему числу операций с учётом весов: быстрые операции (в пределах целевого времени) учитываются полностью, допустимые (до четырёхкратного превышения целевого времени) — с коэффициентом 0,5, а медленные не учитываются. Формула расчёта имеет вид:

APDEX = (NS + NT / 2 ) / N

где

  • NS — количество быстрых операций;

  • NT — количество допустимых операций;

  • N — общее число операций.

Для прохождения теста, APDEX 0,85 и выше считается хорошим результатом (https://its.1c.ru/db/metod8dev/content/5807/hdoc): при таком значении оценки значительная часть операций выполняется в пределах целевого времени, и лишь некоторая часть попадает в допустимый диапазон задержек, не оказывая заметного влияния на общее восприятие производительности системы.

Результаты теста от самой 1С: APDEX равный 0.858, полученный на собственной сборке PostgreSQL 16.4−49.1C ( https://v8.1c.ru/metod/article/firma-1s-uspeshno-provela-nagruzochnoe-testirovanie-1s-erp-upravlenie-predpriyatiem-na-30-000-polzovateley.htm ). Допускаем, что в рамках теста 1С:ERP целевые времяена ключевых бизнес-операций заданоы достаточно честным образом, поскольку даже небольшое количество неидеальных операций снижает итоговый индекс и не позволяет легко приблизиться к APDEX = 1. 

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

Подготовка стенда 

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

  • сервер приложений 1С;

  • сервер СУБД;

  • нагрузочная станция (терминальный сервер).

Начинаем потрошить нашу лабу. С серверами приложений «1С:Предприятие» и сервером СУБД всё получилось довольно просто. Кластер серверов приложений 1С состоял из трёх физических железок, под каждую из машин мы нашли и выделили отдельные bare-metal-серверы на базе процессоров Intel Xeon Gold 6338 третьего поколения. Не самые новые и топовые процессоры (базовая частота — 2.0 ГГц и пиковая — 3.2 ГГц), зато все одинаковые. Сервер с PostgreSQL использовал аналогичное железо, разве что оперативной памяти на нём было побольше. 

Ввиду того что проведение теста невозможно без клиентских и серверных лицензий для самого 1С, для их размещения нужен отдельный сервер лицензирования. Но больших мощностей для него не требуется: нам с большим запасом хватило виртуальной машины с 4 vCPU и 8GB RAM. 

Таким образом, конфигурация получилась такой: 

Назначение

Кол‑во

CPU

RAM

SSD

Сервер приложений «1С:Предприятие»

3

64 cores

2x Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz (2 Sockets, 32 cores/socket)

1,5TB

SAMSUNG MZQL215THBLA-00A07 15TB

Сервер СУБД

1

64 cores

2x Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz (2 Sockets, 32 cores/socket)

2TB

SAMSUNG MZQL215THBLA-00A07 15TB

Сервер лицензирования

1

4vCPU 

8GB 

30GB

А вот подобрать оборудование под терминальные сервера оказалось не так просто.

Во время нагрузочного теста основными источниками нагрузки выступают виртуальные рабочие места (ВРМ), создаваемые с помощью инструмента «Тест-центр». В процессе работы ВРМ формируют нагрузку как на кластер серверов приложений 1С, так и на сервер системы управления базами данных, по сути осуществляя открытие форм, заполнение документов и построение отчетов так, как это делают настоящие пользователи системы. 

ВРМ запускаются на терминальных серверах (они же «нагрузчики»). Принято считать, что один терминальный сервер может обеспечить стабильную работу около 200 ВРМ. Была и ещё одна рекомендация от коллег из «ИТ Экспертизы», которой мы придерживались: терминальный сервер с 10 CPU и 70 Гб RAM позволяет без проблем запустить ~170 ВРМ. Простая математика подсказывает, что для создания нагрузки в 30 000 пользователей нам должно хватить 180 небольших виртуалок — звучит как задача для нашего лаборатного кластера Proxmox. 

Учитывая большое число необходимых ядер (1800), мы задействовали почти все ноды нашего лабораторного кластера, состоящего из оборудования разных вендоров, а потому итоговый стенд получился весьма разношёрстным по характеристикам.

Большая часть состояла из серверов на базе уже далеко немолодого Intel(R) Xeon(R) Gold 5220R, с базовой частотой 2.20 ГГц, а также следующего поколения Intel(R) Xeon(R) Gold 6338, с базовой частотой  2.00 ГГц. Но было и два сервера с AMD-шными EPYC-ами на 198 ядер, и экзотика в виде Intel(R) Xeon(R) Gold 6348H с четырьмя сокетами.

Итоговая конфигурация оборудования для терминальных серверов выглядела следующим образом: 

Назначение

Кол-во

CPU

RAM

SSD

Сервера гипервизора для ВМ с терминальными серверами 1С

5

48  

Intel(R) Xeon(R) Gold 5220R CPU @ 2.20GHz (2 Sockets, 24 cores/socket)

1TB

iSCSI-хранилище на FreeBSD через CTL (ctld)

3

64

Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz (2 Sockets, 32 cores/socket)

2TB

2

192

AMD EPYC 9654 @ 2.40GHz (2 Sockets, 96 cores/socket

2TB

1

96

Intel(R) Xeon(R) Gold 6348H CPU @ 2.30GHz (4 Sockets, 24 cores/socket)

1,5TB

Итого:

912 physical cores

Кроме виртуальных машин для нагрузчиков нам понадобятся вспомогательные ВМ для сервера с которого непосредственно запускается «Тест-Центр» и сервер мониторинга. 

Даже с учётом использования SMT (Simultaneous Multithreading), выдать всем виртуалкам по 10 vCPU поровну удалось не везде: некоторым нагрузчикам досталось только по 8 ядер. Забегая вперёд, даже несмотря на это и на различия в характеристиках серверов виртуализации и, как следствие, использование разных типов CPU у виртуальных машин, потребление ресурсов терминальными машинами не являлось узким местом в рамках данного тестирования.

Подготовка окружения

С железом определились, теперь поговорим про софтверную часть.

В качестве операционной системы мы везде использовали RED OS 8.0.2 (с ядром 6.12.56-1.red80.x86_64 на момент тестирования), как одну из рекомендуемых отечественных ОС, подходящих как для серверных, так и клиентских инсталляций 1С. На железных серверах приложений и СУБД использовалась ОС без графического режима, а на терминальных серверах было установлено графическое окружение Mate. Здесь же заметим, что для корректной работы теста, на терминальных серверах потребуется выставить русскую локаль ru_RU.UTF-8.

В нашей команде мы активно используем связку Packer + Terraform + Ansible для управления и конфигурации виртуальных машин, поэтому для создания почти двух сотен виртуальных машин был адаптирован уже существующий подход.

Первым шагом мы подготовили так называемый kickstart-файл для RED OS, который автоматизирует процесс установки и первичной конфигурации ОС. Kickstart позволяет автоматически развернуть систему, настроить дисковые разделы, сеть, установить необходимые пакеты, настроить пользователей и выполнить другие задачи конфигурации, не требующие вмешательства пользователя. 

Далее с помощью packer была выполнена автоматическая установка ОС в Proxmox с использованием kickstart-файла. В результате был сформирован эталонный шаблон (template) виртуальной машины, содержащий готовую базовую систему. 

На следующем этапе инфраструктура виртуальных машин описывается и создаётся с помощью Terraform, который взаимодействует с Proxmox API. Terraform выполняет массовое клонирование виртуальных машин из шаблона и разворачивает их в нужной нам конфигурации. 

При создании каждой виртуальной машины используется механизм cloud-init, который выполняет первичную настройку (hostname, сеть, ssh-ключи и базовые параметры системы). Финальная конфигурация и установка прикладного ПО выполняются с помощью Ansible, который обеспечивает точечную настройку и конфигурацию сервисов внутри уже запущенных виртуальных машин.

Будучи единожды отлаженным, такой подход позволил нам быстро поднять и единообразно настроить все 180 виртуальных машин.

На сервера приложений мы установили технологическую платформу версии 8.3. Использовалась сборка 8.3.27.1786, содержащая все необходимые для успешного теста доработки. Данная сборка доступна для скачивания всем имеющим доступ к порталу https://releases.1c.ru. Грамотная настройка 1С-кластера является отдельной наукой, но к счастью, потребовалась единоразово. 

На сервере БД была развёрнута Postgres Pro Enterprise 17.7.1.

Перед тестами

Сразу отметим, что перед каждым тестовым запуском выполнялись следующие шаги:

  • сбор и последующее удаление логов СУБД и 1С (технологический журнал);

  • перезапуск всех сервисов и служб 1С;

  • перезапуск терминальных серверов;

  • восстановление БД из бэкапа, со всеми ранее применёнными настройками, с помощью ProBackup.

Также после восстановления БД из бэкапа требуется выполнить запуск регламентной задачи на «обновление доступа на уровне записей».  С первого запуска мы столкнулись с тем, что 1С обрывал её по таймауту в 900 секунд. Происходило это из-за слишком долгого выполнения запросов к регистрам, что приводило к тому, что 1С зависал на этой задаче бесконечно.

На скриншоте видно 5 регистров, которые никак не могли обработаться из-за долгих sql-запросов: 

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

Нагрузочные испытания 

Тест на 10 000 ВРМ

Начинаем наши замеры с пристрелочного запуска на 10 000 ВРМ и получаем итоговый APDEX 0.853. Неплохо, но могло быть и лучше. При этом на диске nvme1n1, на котором располагалась $PGDATA, во время всего теста наблюдалась очередь:

Поскольку дисков у нас было несколько, далее последовал самый очевидный шаг — уменьшить нагрузку в $PGDATA вынесением pg_wal на отдельный диск nvme2n1.

Заодно включаем опцию "--no-collect.database" для postgres_exporter, чтобы избавиться от чрезмерно большого кол-ва запросов "SELECT pg_database_size($1)" к нашей базе данных.

Тест на 20 000 ВРМ: разбираемся с дисками на СУБД 

Поднимаем число ВРМ до 20 000, перезапускаем тест и получаем APDEX 0.790 и более 800 невыполненных задач 1С.

Несмотря на вынос pg_wal, на диске с pg_data продолжали наблюдаться высокие задержки на запись на диск и очереди:

Первым делом решаем выполнить простую проверку - заглянуть в вывод команды "nvme list". Пристальный взгляд показал, что все наши диски были отформатированы с размером логического блока (lbaf) 512 байт: 

[root@asusmicro27 ~]# nvme list
Node                  Generic               SN                   Model                                    Namespace  Usage                      Format           FW Rev  
--------------------- --------------------- -------------------- ---------------------------------------- ---------- -------------------------- ---------------- --------
/dev/nvme0n1          /dev/ng0n1            S6VANN0X514555       SAMSUNG MZQL215THBLA-00A07               0x1         12.63  GB /  15.36  TB    512   B +  0 B   GDD5302Q
/dev/nvme1n1          /dev/ng1n1            S6VANN0X605836       SAMSUNG MZQL215THBLA-00A07               0x1          1.97  GB /  15.36  TB    512   B +  0 B   GDD5302Q
/dev/nvme2n1          /dev/ng2n1            S6VANN0X605833       SAMSUNG MZQL215THBLA-00A07               0x1          2.51  TB /  15.36  TB    512   B +  0 B   GDD5302Q
/dev/nvme3n1          /dev/ng3n1            S6VANT0WB00560       SAMSUNG MZQL215THBLA-00A07               0x1         38.55  GB /  15.36  TB    512   B +  0 B   GDD5302Q

LBAF (Logical Block Address Format) — это формат логического блока, который используется NVMe-диском для адресации данных. Он определяет размер минимальной единицы ввода-вывода, с которой работает диск на логическом уровне. Современные NVMe-диски обычно поддерживают несколько LBA-форматов с разными размерами блока. Посмотреть их можно командой:

$ nvme id-ns -H /dev/nvme0n1 | grep LBA

Пример вывода:

LBA Format  0 : Metadata Size: 0 bytes - Data Size: 512 bytes - Relative Performance: 0 Best (in use)
LBA Format  1 : Metadata Size: 0 bytes - Data Size: 4096 bytes - Relative Performance: 0 Best

В нашем случае видно, что используется LBAF = 0 с размером 512 B. Это значение по умолчанию применяется для обеспечения совместимости, хотя для нашей нагрузки 4K-блоки потенциально могут обеспечить более высокую эффективность за счёт снижения накладных расходов на операции ввода-вывода.

Форматируем все диски, кроме системного, под 4K-блоки (осторожно, все данные будут удалены):

$ nvme format /dev/nvme1n1 --lbaf 1 --ses=1
$ nvme format /dev/nvme2n1 --lbaf 1 --ses=1
$ nvme format /dev/nvme3n1 --lbaf 1 --ses=1

Кроме того, решаем задействовать последний свободный диск и под $PGDATA собрать raid0 под mdadm:

$ mdadm --create --verbose /dev/md0 --level=0 --raid-devices=2 --chunk=512K /dev/nvme1n1 /dev/nvme2n1
$ mkfs.ext4 -b 4096 -O fast_commit -E stride=128,stripe-width=256 -L u02 /dev/md0
$ mkfs.ext4 -b 4096 -O fast_commit -E stride=0,stripe-width=0 -L u03 /dev/nvme3n1

По результатам данного запуска обнаружилась и другая проблема: мониторинг загрузки ядер виртуалок с терминальными серверами показал, что у части машин некоторые ядра были недозагружены, а у других, наоборот, был сильный перегруз, всё это на одной и той же железной ноде Proxmox кластера. Дело оказалось вот в чём: для того чтобы гарантированно выделить физические ядра ноды гипервизора конкретному виртуальному серверу, мы привязывали виртуальные ядра к физическим. При этом ядра выделяются не последовательно, а в рамках одного numa-узла, что позволяет улучшить эффективность использования памяти. Поскольку виртуальных машин много и наши ресурсы не гомогенны, при первых запусках этот процесс был отлажен не до конца, а потому имела место ошибка: некоторые ВМ пересекались по аффинити ядер, например за двумя разными виртуалками были закреплены ядра 36–39, 88–91 и 40–43, 88–91. Получалось, что ядра 88–91 оказывались перегружены. Перед последующими запусками мы исправили эти ошибки.

Тест на 30 000 ВРМ — первый запуск. Разбираемся с временными объектами

Правки сделаны, сразу подаём целевую нагрузку в 30 000 ВРМ и спустя несколько часов работы сталкиваемся с 100%-й утилизацией CPU на сервере БД и зависанием серверов приложений 1С: запуск не прошёл, APDEX не посчитался. Очереди на дисках всё равно высокие. Смотрим подробнее: часть ВРМ у нас не запустились с ошибкой

"Отправитель: erp-thin37.

Следующие ВРМ не удалось инициализировать до истечения таймаута инициализации (1,500 с):

 - erp-thin37.0 (PID: 45245, сеанс: 52021); ОЦИР Реализация_ТЦ000033; ВРМ №783"

В логах серверов приложений 1С видим исключения вида: 

EXCP,4,level=ERROR,process=rphost,p:processName=erp30k,OSThread=1089368,t:clientID=1227,t:applicationName=1CV8C,t:computerName=erp-thin167,t:connectID=8391,SessionID=9633,Usr=ЦНТИБ Внутреннее потребление_ТЦ000030,AppID=1CV8C,DBMS=DBPOSTGRS,DataBase=database\erp30k,Exception=DatabaseException,Descr='The database connection is closed by the administrator
Connection to server at "database" (10.7.0.174), port 5432 failed: timeout expired

Увы, сервер БД перестал отвечать серверу 1С, и тест упал. 

К счастью, в базе у нас были настроены pgpro_stats с pgpro_pwr и каждые пять минут создавался снимок метрик через функцию take_sample(). Исследуем pwr-отчёт за проблемный интервал: один из запросов обращающийся к служебным регистрам создавал 50% всей нагрузки, постоянно выполнял seq scan-ы по таблицам и висел в топе. Выглядит скорее как следствие проблем, а не причина. Смотрим дальше и замечаем, что мы забыли включить параметр enable_temp_memory_catalog, который позволяет для временных таблиц, индексов и других временных объектов использовать системный каталог в оперативной памяти, что снижает нагрузку на запись в отношения обычного системного каталога: pg_class, pg_attribute и некоторые другие. 

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

Был и ещё один полезный параметр, который мы добавили перед следующим запуском —  skip_temp_rel_lock. Он позволяет пропускать блокировки для временных отношений и индексов этих отношений. На профиле нагрузки от 1С это проявлялось в том, что во время вызова SQL функции fasttruncate (активно используется в коде 1С приложения для урезания временных таблиц) значительное время занимал вызов Си-функции LockRelationOid(), то есть создание блокировки на таблицы. Поэтому когда происходит активная работа с временными таблицами, включение параметра способно заметно повлиять на производительность.

Тест на 30 000 ВРМ — второй запуск

Делаем повторный запуск на 30 000 ВРМ. Несмотря на то что по итогу теста утилизация на сервер БД снизилась до 50%, а серверы приложений 1С и вовсе были загружены до 20% (терминалки — до 80%), APDEX мы получили всего лишь 0.731. Хорошо то, что первый запуск на 30 000 который завершился корректно. Плохо, что APDEX неудовлетворительный. 

Отмечаем, что в директорию pgsql_tmp постоянно записывались временные файлы, общий объём которых достигал порядка 2–3 ГБ. Это связано с особенностями работы PostgreSQL: при выполнении операций, таких как сортировка или агрегация больших объёмов данных, превышающих доступный объём памяти, система использует временные файлы на диске.

Решение есть — выносим данный каталог на tmpfs:

tmp_sql on /u02/data/base/pgsql_tmp type tmpfs (rw,relatime,size=104857600k,inode64)

Делаем перезапуск и получаем APDEX = 0.744. Продолжаем вносить изменения: 

  • Ещё немного разгружаем наши диски. Мы вынесли временные файлы на RAM диск, но не вынесли временные таблицы. Поэтому создаём и выносим на tmpfs каталог, в котором будут создаваться временные таблицы и их индексы. Делается это путём указания табличного пространства, созданного в tmpfs (см. temp_tablespace

  • Включаем опцию enable_background_freezer. При частом создании и удалении большого количества временных таблиц autovacuum может не успевать очищать данные системных таблиц, что приводит к их распуханию (bloating). Background freezer очищает удалённые записи на страницах в памяти и «разрастание» системных таблиц существенно уменьшается

Каждое изменение улучшало и APDEX и утилизацию CPU и IO.

Тест на 30 000 ВРМ — последующие запуски. Тюним СУБД

После последних правок утилизация CPU на сервере БД не превышала 40% большую часть теста: 

Также очередь на диски не превышала 20, до этого пики были до 100-500: 

Утилизация серверов приложений 1С тоже была на невысоком уровне: 

И всё же APDEX снова меньше необходимого — 0.767.

Тут самое время вспомнить о проблеме с перерасчётом прав доступа, о которой мы говорили ранее. 

Устав ждать лишние пять часов перед каждым пересчётом, мы придумали workaround — создавать дополнительные индексы для тормозящих запросов перед выполнением перерасчёта (а тормозящих запросов было 5 штук). Пять свежесозданных индексов для пяти запросов перерасчёта решают проблему, однако способ этот не совсем легальный: право создавать индексы в базе 1С имеет только сам 1С. И поскольку менять мы можем только свой код и настройки базы, начинаем разбираться предметно. Как оказалось, нужная комбинация значений work_mem + hash_mem_multiplier может превратить correlated query, присоединяемый по (not) exists в связке с OR, в hash table в памяти, после чего сложность проверки условия заметно снижается.

Для проблемных запросов со значением параметра hash_mem_multiplier = 2 необходимый объём памяти для получения хорошего запроса составил следующие значения (в скобках — объём work_mem, где план неоптимальный):

  • Query 1 work_mem = 183MB (182)

  • Query 2 work_mem = 256MB (240)

  • Query 3 work_mem = 150MB (140)

  • Query 4 work_mem = 150MB (140)

  • Query 5 work_mem = 160MB (150)

Оптимальный план содержит в себе предикаты hashed SubPlan, тогда как в неоптимальном плане такого нет:

Скрытый текст
Limit  (cost=80086.42..790621738.62 rows=10000 width=171) (actual time=3095.094..3095.098 rows=0 loops=1)
   Buffers: shared hit=52383 read=111367
   I/O Timings: shared read=438.879
   ->  Incremental Sort  (cost=80086.42..9003321800.85 rows=113887 width=171) (actual time=3095.092..3095.096 rows=0 loops=1)
         Sort Key: t1._fld127378, t1._fld127379_type, t1._fld127379_rtref, t1._fld127379_rrref, t1._fld127380_type, t1._fld127380_rtref, t1._fld127380_rrref, t1._fld127381_type, t1._fld127381_rtref, t1._fld127381_rrref, t1._fld127382_type, t1._fld127382_rtref, t1._fld127382_rrref, t1._fld127383_type, t1._fld127383_rtref, t1._fld127383_rrref
         Presorted Key: t1._fld127378, t1._fld127379_type, t1._fld127379_rtref, t1._fld127379_rrref, t1._fld127380_type, t1._fld127380_rtref, t1._fld127380_rrref, t1._fld127381_type, t1._fld127381_rtref, t1._fld127381_rrref, t1._fld127382_type, t1._fld127382_rtref
         Full-sort Groups: 1  Sort Method: quicksort  Average Memory: 25kB  Peak Memory: 25kB
         Buffers: shared hit=52383 read=111367
         I/O Timings: shared read=438.879
         ->  Result  (cost=0.69..9003308295.67 rows=113887 width=171) (actual time=3095.082..3095.086 rows=0 loops=1)
               One-Time Filter: (ANY (SubPlan 3).col1)
               Buffers: shared hit=52383 read=111367
               I/O Timings: shared read=438.879
               ->  Index Scan using _inforg127376_1 on _inforg127376 t1  (cost=0.69..9003308010.95 rows=113887 width=170) (actual time=3095.076..3095.077 rows=0 loops=1)
                     Index Cond: ((_fld1551 = '0'::numeric) AND (_fld127377_type = '\\x08'::bytea) AND (_fld127377_rtref = '\\x000000a4'::bytea) AND (_fld127377_rrref = '\\xa7a9000d884fd00d11e4c17ff00e69af'::bytea))
                     Filter: ((_fld127379_type >= '\\x01'::bytea) AND (NOT CASE WHEN (_fld127378 = '0'::numeric) THEN false ELSE CASE WHEN ((_fld127378 - ((((((_fld127378)::numeric(12,8) / '2'::numeric))::numeric(12,8) - 0.5))::numeric(15,0) * '2'::numeric)) = '1'::numeric) THEN true ELSE false END END) AND ((_fld127379_type >= '\\x01'::bytea) OR ((_fld127379_type = '\\x01'::bytea) AND (_fld127379_rtref = '\\x00000000'::bytea) AND (_fld127379_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127380_type >= '\\x01'::bytea)) OR ((_fld127379_type = '\\x01'::bytea) AND (_fld127379_rtref = '\\x00000000'::bytea) AND (_fld127379_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127380_type = '\\x01'::bytea) AND (_fld127380_rtref = '\\x00000000'::bytea) AND (_fld127380_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127381_type >= '\\x01'::bytea)) OR ((_fld127379_type = '\\x01'::bytea) AND (_fld127379_rtref = '\\x00000000'::bytea) AND (_fld127379_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127380_type = '\\x01'::bytea) AND (_fld127380_rtref = '\\x00000000'::bytea) AND (_fld127380_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127381_type = '\\x01'::bytea) AND (_fld127381_rtref = '\\x00000000'::bytea) AND (_fld127381_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127382_type >= '\\x01'::bytea)) OR ((_fld127379_type = '\\x01'::bytea) AND (_fld127379_rtref = '\\x00000000'::bytea) AND (_fld127379_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127380_type = '\\x01'::bytea) AND (_fld127380_rtref = '\\x00000000'::bytea) AND (_fld127380_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127381_type = '\\x01'::bytea) AND (_fld127381_rtref = '\\x00000000'::bytea) AND (_fld127381_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127382_type = '\\x01'::bytea) AND (_fld127382_rtref = '\\x00000000'::bytea) AND (_fld127382_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127383_type >= '\\x01'::bytea))) AND ((_fld127378 <> '4'::numeric) OR ((_fld127378 = '4'::numeric) AND (NOT (ANY ((_fld127379_type = (hashed SubPlan 2).col1) AND (_fld127379_rtref = (hashed SubPlan 2).col2) AND (_fld127379_rrref = (hashed SubPlan 2).col3) AND (_fld127380_type = (hashed SubPlan 2).col4) AND (_fld127380_rtref = (hashed SubPlan 2).col5) AND (_fld127380_rrref = (hashed SubPlan 2).col6)))))))
                     Rows Removed by Filter: 384644
                     Buffers: shared hit=52383 read=111367
                     I/O Timings: shared read=438.879
                     SubPlan 2
                       ->  Seq Scan on _accumrg36802 t2  (cost=0.00..171017.98 rows=2624798 width=168) (actual time=0.228..1383.270 rows=2624798 loops=1)
                             Filter: (_fld1551 = '0'::numeric)
                             Buffers: shared hit=44489 read=93719
                             I/O Timings: shared read=339.306
               SubPlan 3
                 ->  Materialize  (cost=0.00..0.37 rows=21 width=1) (actual time=0.004..0.004 rows=1 loops=1)
                       ->  Values Scan on "*VALUES*"  (cost=0.00..0.26 rows=21 width=1) (actual time=0.001..0.001 rows=1 loops=1)
 Planning Time: 0.698 ms
 Execution Time: 3100.215 ms

План того же запроса, если памяти недостаточно:

Скрытый текст
 Limit  (cost=80086.42..790621738.62 rows=10000 width=171)
   ->  Incremental Sort  (cost=80086.42..9003321800.85 rows=113887 width=171)
         Sort Key: t1._fld127378, t1._fld127379_type, t1._fld127379_rtref, t1._fld127379_rrref, t1._fld127380_type, t1._fld127380_rtref, t1._fld127380_rrref, t1._fld127381_type, t1._fld127381_rtref, t1._fld127381_rrref, t1._fld127382_type, t1._fld127382_rtref, t1._fld127382_rrref, t1._fld127383_type, t1._fld127383_rtref, t1._fld127383_rrref
         Presorted Key: t1._fld127378, t1._fld127379_type, t1._fld127379_rtref, t1._fld127379_rrref, t1._fld127380_type, t1._fld127380_rtref, t1._fld127380_rrref, t1._fld127381_type, t1._fld127381_rtref, t1._fld127381_rrref, t1._fld127382_type, t1._fld127382_rtref
         ->  Result  (cost=0.69..9003308295.67 rows=113887 width=171)
               One-Time Filter: (ANY (SubPlan 2).col1)
               ->  Index Scan using _inforg127376_1 on _inforg127376 t1  (cost=0.69..9003308010.95 rows=113887 width=170)
                     Index Cond: ((_fld1551 = '0'::numeric) AND (_fld127377_type = '\\x08'::bytea) AND (_fld127377_rtref = '\\x000000a4'::bytea) AND (_fld127377_rrref = '\\xa7a9000d884fd00d11e4c17ff00e69af'::bytea))
                     Filter: ((_fld127379_type >= '\\x01'::bytea) AND (NOT CASE WHEN (_fld127378 = '0'::numeric) THEN false ELSE CASE WHEN ((_fld127378 - ((((((_fld127378)::numeric(12,8) / '2'::numeric))::numeric(12,8) - 0.5))::numeric(15,0) * '2'::numeric)) = '1'::numeric) THEN true ELSE false END END) AND ((_fld127379_type >= '\\x01'::bytea) OR ((_fld127379_type = '\\x01'::bytea) AND (_fld127379_rtref = '\\x00000000'::bytea) AND (_fld127379_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127380_type >= '\\x01'::bytea)) OR ((_fld127379_type = '\\x01'::bytea) AND (_fld127379_rtref = '\\x00000000'::bytea) AND (_fld127379_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127380_type = '\\x01'::bytea) AND (_fld127380_rtref = '\\x00000000'::bytea) AND (_fld127380_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127381_type >= '\\x01'::bytea)) OR ((_fld127379_type = '\\x01'::bytea) AND (_fld127379_rtref = '\\x00000000'::bytea) AND (_fld127379_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127380_type = '\\x01'::bytea) AND (_fld127380_rtref = '\\x00000000'::bytea) AND (_fld127380_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127381_type = '\\x01'::bytea) AND (_fld127381_rtref = '\\x00000000'::bytea) AND (_fld127381_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127382_type >= '\\x01'::bytea)) OR ((_fld127379_type = '\\x01'::bytea) AND (_fld127379_rtref = '\\x00000000'::bytea) AND (_fld127379_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127380_type = '\\x01'::bytea) AND (_fld127380_rtref = '\\x00000000'::bytea) AND (_fld127380_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127381_type = '\\x01'::bytea) AND (_fld127381_rtref = '\\x00000000'::bytea) AND (_fld127381_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127382_type = '\\x01'::bytea) AND (_fld127382_rtref = '\\x00000000'::bytea) AND (_fld127382_rrref = '\\x00000000000000000000000000000000'::bytea) AND (_fld127383_type >= '\\x01'::bytea))) AND ((_fld127378 <> '4'::numeric) OR ((_fld127378 = '4'::numeric) AND (NOT EXISTS(SubPlan 1)))))
                     SubPlan 1
                       ->  Result  (cost=0.56..23820.56 rows=1 width=0)
                             One-Time Filter: (('\\x08'::bytea = t1._fld127379_type) AND ('\\x000000e6'::bytea = t1._fld127379_rtref) AND ('\\x08'::bytea = t1._fld127380_type) AND ('\\x000001dd'::bytea = t1._fld127380_rtref))
                             ->  Index Scan using _accumrg36802_3 on _accumrg36802 t2  (cost=0.56..23820.56 rows=1 width=0)
                                   Index Cond: ((_fld1551 = '0'::numeric) AND (_fld36803rref = t1._fld127379_rrref))
                                   Filter: (_fld36804rref = t1._fld127380_rrref)
               SubPlan 2
                 ->  Materialize  (cost=0.00..0.37 rows=21 width=1)
                       ->  Values Scan on "*VALUES*"  (cost=0.00..0.26 rows=21 width=1)

Таким образом решаем перед следующим запуском теста выставить с запасом:

  • hash_mem_multiplier=4

  • work_mem=256MB

А заодно поправить: 

  • from_collapse_limit=20 

  • join_collapse_limit=20

  • effective_io_concurrency=1

Также мы составили Top самых долгих запросов, наблюдаемых при тестировании,  собранных из технологического журнала 1С и сопоставленных с операциями в 1С. Некоторые из них мы смогли поправить точечными настройками: так, например при выполнении теста операция печать формы МБ-8 из документа «Списание из эксплуатации» выполнялась под нагрузкой в среднем более 10 минут, вместо ожидаемых 2–3 секунд, а потому операция сильно вылезала за пределы целевого  времени и в зачёт APDEX по ней шёл 0. Воспроизводим проблему и получаем план выполнения соответствующего запроса в базе с помощью модуля auto_explain

Оказалось, что в одной из колонок (_value_rrref) очень неравномерное распределение значений: много редких и немного очень частых.

select cnt2, count (*), sum (count (*)) over (order by cnt2 range between current row and unbounded following) running_total,
       round (100 * count (*) / sum (count (*)) over (), 2) rate
  from (select _value_rrref, power(2, trunc(log(2, count (*))))::bigint cnt2
          from test_AccRgED1632 group by _value_rrref) group by cnt2 order by cnt2;


  cnt2   |  count  | running_total | rate  
---------+---------+---------------+-------
       1 |  159218 |       3650382 |  4.36
       2 | 1153795 |       3491164 | 31.61
       4 | 1058374 |       2337369 | 28.99
       8 |  637616 |       1278995 | 17.47
      16 |  315185 |        641379 |  8.63
      32 |  225637 |        326194 |  6.18
      64 |   50144 |        100557 |  1.37
     128 |   14245 |         50413 |  0.39
     256 |   19446 |         36168 |  0.53
     512 |    9912 |         16722 |  0.27
    1024 |    1966 |          6810 |  0.05
    2048 |    2155 |          4844 |  0.06
    4096 |     373 |          2689 |  0.01
    8192 |    1302 |          2316 |  0.04
   16384 |     585 |          1014 |  0.02
   32768 |     396 |           429 |  0.01
   65536 |      12 |            33 |  0.00
  131072 |       6 |            21 |  0.00
  262144 |       7 |            15 |  0.00
  524288 |       2 |             8 |  0.00
 1048576 |       3 |             6 |  0.00
 4194304 |       3 |             3 |  0.00
(22 rows)

При стандартном default_target_statistics=100 PostgreSQL хранит слишком грубую статистику, а потому планировщик сильно ошибался в оценках селективности и  выбирал неоптимальный план: 

Insert on ttt  (cost=127.08..127.13 rows=0 width=0) (actual time=194421.376..194421.390 rows=0 loops=1)
   Buffers: shared hit=13328056 read=12, local hit=5

 Planning:
   Buffers: shared hit=889
 Planning Time: 61.241 ms
 Execution Time: 194423.395 ms

Как итог - запрос выполнялся катастрофически долго и читал десятки миллионов буферов. Увеличиваем точность статистики для проблемной колонки и делаем ANALYZE:

erp30k=# alter table _accrged1632 alter column _value_rrref set statistics 1000;
ALTER TABLE
erp30k=# analyze (verbose) _accrged1632;
ИНФОРМАЦИЯ:  анализируется "public._accrged1632"
ИНФОРМАЦИЯ:  "_accrged1632": просканировано страниц: 300000 из 2403324, они содержат "живых" строк: 17850934, "мёртвых" строк: 0; строк в выборке: 300000, примерное общее число строк: 143005260
ANALYZE

В результате получаем больше значений в MCV (most common values) и адекватный план: 

Insert on ttt  (cost=74.28..75.63 rows=0 width=0) (actual time=126.198..126.207 rows=0 loops=1)
   Buffers: shared hit=4384, local hit=5
...
 Planning:
   Buffers: shared hit=2
 Planning Time: 45.781 ms
 Execution Time: 126.502 ms

К сожалению, не со всеми запросами нам удалось побороться точечными правками. 

Финальный запуск (но это неточно)

Ещё несколько тестовых запусков с внесёнными изменениями не привели к ощутимому повышению APDEX.

Чтобы ускорить получение результата, мы решили снизить длительность теста с 11 часов до 3, но получили рост утилизации CPU на СУБД до 100% CPU и зависание сервера 1С. Судя по отчётам pg_profile, уменьшение времени теста до двух часов привело к резкому росту интенсивности запросов (по части операций возросло количество вызовов в секунду) по сравнению со стандартным 11-часовым тестом. Изучив код самих сценариев, стало понятно, что такой подход с данным тестом не работает. Зависимость интенсивности выполнения от таймаута теста была нелинейной и просто уменьшить параметр «интенсивность» и получить менее интенсивное выполнение задач оказалось невозможно.

В дальнейшем было решено запускать тест как есть, а для экономии времени через 3 часа просто прерывать его выполнение. Часть ключевых операций за это время уже успевала выполниться и по промежуточному APDEX мы могли оценить влияние наших изменений.

В какой-то момент на самом сервере СУБД мы развернули  сервис, который периодически выполнял запрос "SELECT * FROM config LIMIT 100", обращаясь к базе через  unix socket, и замерял время и исполнение. По логу видно, что порой время исполнения сильно варьируются:

[18:18:01] Query executed in 3589 µs
[18:18:04] Query executed in 264955 µs
[18:18:07] Query executed in 3662 µs
[18:18:10] Query executed in 214534 µs
[18:18:13] Query executed in 4237 µs

Такой огромный разброс при выполнении простого запроса вкупе с ещё одним наблюдением — довольно частых LWLock-ах pgpro_stats в pg_stat_activity — сподвигли нас убрать pgpro_stats из shared_preload_libraries перед следующими замерами. Как известно, в любом расследовании главное — не выйти на самих себя :) Правда, подобные ожидания также наблюдались и при замене pgro_stats на pg_stat_statements. В ближайших релизах мы планируем внести правки, снижающие влияние сбора статистики на производительность. 

Также, несмотря на невысокую утилизацию процессора СУБД при последних запусках, обнаруживаем, что процессор по ходу нагрузки фактически спал, находясь в глубоком режиме энергосбережения (С6). Взаимодействия с С-состояниями linux осуществляет с помощью драйверов: acpi_idle (берёт настройки состояний из BIOS) и intel_idle (современная реализация Intel). Узнать какой драйвер используется в системе можно в файле /sys/devices/system/cpu/cpuidle/current_driver

В нашем случае использовался драйвер intel_idle, а настройки были сделаны для acpi_idle и потому не оказывали нужного влияния. Правим /etc/default/grub: добавляем в параметр загрузчика ядра опцию intel_idle.max_cstate=1, ограничивая максимальное C-состояние до C1, снижая задержки выхода процессора из состояния простоя.

И поскольку ранее мы уже видели, что утилизация CPU не является боттлнеком, вдобавок решаем разместить СУБД на одном процессоре (ведь утилизация CPU очень низкая), чтобы снизить кросс-NUMA взаимодействие CPU и памяти.

Итак, делаем запуск со всеми ранее примененными настройками:

  • from_collapse_limit=20

  • join_collapse_limit=20

  • hash_mem_multiplier=4

  • work_mem=256MB

  • effective_io_concurrency=1

  • auto_explain.log_min_duration = '60s'

  • shared_preload_libraries = 'pg_wait_sampling,pg_query_state,auto_explain’

  • skip_temp_rel_lock=on

  • enable_temp_memory_catalog = on

  • enable_background_freezer = on

psql_tmp вынесены на tmpfs:

  • tmp_sql on /u02/data/base/pgsql_tmp type tmpfs (rw,relatime,size=104857600k,inode64)

поправлена статистика для столбца: 

  • alter table _accrged1632 alter column _value_rrref set statistics 1000;
    analyze (verbose) _accrged1632;

из shared_preload_libraries убраны pgpro_stats и pg_stat_statements

Ура! На этот раз APDEX составил 0.856. А это значит, что совокупность всех внесённых изменений позволила нам достичь общей производительности ключевых бизнес-операций по итогам нагрузочного теста на уровне «Хорошо». Оборудование тоже показало себя с лучшей стороны. Утилизация СУБД с Postgres Pro Enterprise 17.7.1 в ходе теста не превышала 30%:

Время отклика дисков на чтение находилось в пределах 1 мс, на запись — в среднем не превышало 2,5 мс: 

Серверы приложений 1С также чувствовали себя хорошо: каждый из трёх серверов в среднем был загружен на 20%: 

Время отклика дисков на запись в период выполнения теста находилось в пределах 10 мс:

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

Выводы

Не будем скрывать, что тестирование 1C:ERP доставило нам немало хлопот, начиная с подготовки и развёртывания окружений (не каждый день приходится создавать 180 виртуалок в Proxmox-е) и заканчивая тюнингом параметров операционной системы и самого PostgreSQL. Тем приятнее оказался конечный результат: Postgres Pro Enterprise 17.7.1 не только продемонстрировал уверенную работу на типовом оборудовании на базе процессоров Intel Xeon Gold второго и третьего поколений с частотой 2.0–2.2 ГГц, но и обеспечил стабильную производительность под нагрузкой, характерной для промышленной эксплуатации ERP-систем. 

Хотя и не сразу, но нам удалось подобрать оптимальный набор параметров конфигурации PostgreSQL, учитывающих специфику 1С. Эти настройки уже находят своё применение в нашей утилите pgpro_tune и доступны для применения через пресет “1c.tune”. Уверены, что и особенности настройки ОС и аппаратных компонентов, с которыми мы столкнулись в данном нагрузочном тестировании, также будут полезны сообществу.

От начала работ до финального запуска прошло чуть более трёх месяцев. За это время было:

  • выпито немало кофе нашей командой в нескольких часов поясах

  • сделан 31 запуск теста 1С ERP 30K или 14 дней (341 часов) общего времени или 400 тысяч процессорных ядер-часов

  • создана автоматизация тестирования 1С позволяющая запускать тесты по нажатию на одну кнопку

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

Прошло пара месяцев... И снова запуск

Итак, тестирование прошло успешно и про тест на некоторое время забыли. В недавнем выпуске Postgres Pro Enterprise 17.9 добавилось несколько улучшений, связанных с производительностью временных таблиц: Замечания к релизу Postgres Pro Enterprise 17.9.1

Снова запускаем тест. В этот раз подготовка к запуску занимает меньше суток, что не может не радовать. Благодаря упомянутым улучшениям, APDEX стал 0.875 (был 0.856). При этом утилизация CPU также на том уровне около 20%. На графике ниже можно увидеть, что среднее время самых частных операций уменьшилось примерно на одинаковую величину:

Хочется отметить, что тест запускался и запускается на типовом оборудовании. Насколько известно, большинство продуктовых окружений использует виртуализацию, поэтому в ближайшие недели запланирована миграция стенда с bare-metal на виртуализацию. Как говорится: "Но это уже совсем другая статья".