Мониторинг MS SQL Server для 1С и учётных систем: ключевые счётчики и готовый шаблон Zabbix
Заявка от клиента звучит всегда одинаково: «1С тормозит, сделайте что-нибудь». В девяти случаях из десяти проблема не в конфигурации и не в коде — упёрлась подсистема MS SQL Server: не хватает памяти под буферный пул, диск не успевает отдавать страницы, или tempdb захлёбывается во временных таблицах. Показываю, на какие счётчики и DMV мы смотрим первыми, какие пороги считаем тревожными и как за один вечер разворачиваем это в Zabbix, чтобы не бегать с DBCC вручную при каждой жалобе.
Почему разбор всегда начинается с SQL Server, а не с конфигурации 1С
У нас на сопровождении десятки баз 1С:Предприятие 8.3 в клиент-серверном варианте — от «Бухгалтерии» на 5 пользователей до УТ/ЗУП на 40–50 рабочих мест. Платформа почти везде 8.3.20–8.3.27, СУБД — MS SQL Server 2016, 2019 или 2022 (Standard Edition, реже Web Edition на совсем небольших конфигурациях). Когда приходит жалоба «стало тормозить», первый вопрос, который мы себе задаём, — не «что там в конфигурации поменяли», а «куда упёрся сервер БД»: в память, в диск или в конкурентный доступ.
Причина в архитектуре 1С: тонкий или толстый клиент формирует запрос, платформа превращает его в T-SQL, а дальше вся тяжесть — на СУБД. Если у SQL Server не хватает оперативной памяти под буферный пул, каждый повторный запрос читает данные заново с диска. Если диск под базой или под tempdb не тянет по IOPS и латентности, любая операция с временными таблицами (а 1С генерирует их массово — виртуальные таблицы регистров, временные таблицы СКД) начинает залипать на PAGEIOLATCH. Пользователь при этом видит только зависшую форму и жалуется на «1С», хотя конфигурация тут ни при чём.
Дальше — конкретные счётчики, которые мы снимаем на диагностике, пороги, которые считаем тревожными по опыту (там, где это не прямая рекомендация Microsoft, помечаю отдельно), и готовый шаблон для Zabbix, который разворачиваем клиенту, чтобы не приезжать на диагностику руками каждый раз.
Память и буферный пул: где на самом деле «тормозит 1С»
Первое, что проверяем, — не хватает ли SQL Server оперативной памяти. Смотрим счётчики объекта SQLServer:Memory Manager и SQLServer:Buffer Manager (доступны и через Perfmon, и через DMV sys.dm_os_performance_counters).
- Total Server Memory (KB) — сколько памяти SQL Server реально держит сейчас;
- Target Server Memory (KB) — сколько он хочет держать при текущих настройках
max server memory. Если Total стабильно ниже Target — сервер ещё не дорос до лимита, это нормально сразу после рестарта службы. Если Total почти равен Target и оба близки к выделенномуmax server memory— буферный пул исчерпан, дальше расти некуда; - Page Life Expectancy (PLE) — сколько секунд страница в среднем живёт в буферном пуле, прежде чем её вытеснят. Классический порог из документации Microsoft — 300 секунд, но это правило родом из эпохи, когда типовой буферный пул был 4 ГБ. Для современных серверов с 32–128 ГБ памяти под SQL Server мы в ITfresh считаем ориентиром формулу
(объём буферного пула в ГБ / 4) × 300— это оценка по практике, а не официальный норматив, но на клиентских серверах она ловит деградацию памяти куда раньше, чем плоские 300 секунд; - Buffer cache hit ratio — доля обращений, которые обслужены из памяти без похода на диск. У здоровой базы 1С на постоянной нагрузке — обычно выше 98–99%. Просадка ниже 95% на протяжении рабочего дня — повод смотреть в сторону памяти или в сторону резко выросшей базы данных.
Отдельно проверяем настройку max server memory (MB) в свойствах сервера — на терминальных серверах, где на одной машине крутится ещё и сама 1С, RDS-сессии или файловый сервер, дефолтный «неограниченный» лимит SQL Server регулярно душит остальные службы по памяти. Мы всегда выставляем явный лимит, оставляя операционной системе и остальным сервисам не менее 20–25% RAM, а на серверах с NUMA — считаем отдельно на узел через SQL Server:Buffer Node.
| Счётчик / источник | Что показывает | Порог, на который ориентируемся |
|---|---|---|
| SQLServer:Buffer Manager \ Page life expectancy | Среднее время жизни страницы в буферном пуле, сек. | <300 сек — официальный порог MS; для серверов >16 ГБ используем формулу (ГБ/4)×300 (оценка по практике) |
| SQLServer:Memory Manager \ Total Server Memory (KB) | Память, фактически занятая под буферный пул | Стабильно ≈ Target Server Memory и ≈ max server memory — сигнал «упёрлись в потолок» |
| SQLServer:Buffer Manager \ Buffer cache hit ratio | Доля чтений из кэша, а не с диска | <95% на рабочей нагрузке — оценка по практике, повод разбираться |
| sys.dm_os_process_memory (physical_memory_in_use_kb) | Реальное потребление процесса sqlservr.exe | Сверяем с max server memory и физической RAM хоста |
Диск: латентность важнее, чем «сколько всего IOPS»
Второй блок — подсистема хранения. Здесь мы смотрим не абсолютные IOPS (они у всех разные — HDD RAID, SSD, NVMe, виртуальный диск на гипервизоре), а именно латентность отклика, потому что 1С крайне чувствительна к задержкам на операциях с журналом транзакций и с tempdb.
- PhysicalDisk \ Avg. Disk sec/Read и Avg. Disk sec/Write — среднее время одной операции чтения/записи на диске, где лежат файлы БД. Для файлов данных приемлемо до ~10–20 мс, для журнала транзакций (
.ldf) — счёт должен идти на единицы миллисекунд, потому что запись в лог синхронная и блокирует commit транзакции; - PhysicalDisk \ Avg. Disk Queue Length — длина очереди запросов к диску; устойчиво выше 2 на один физический диск — диск не успевает;
- sys.dm_io_virtual_file_stats — DMV, которая даёт то же самое, но в разрезе конкретного файла базы (данные / лог / tempdb) без необходимости настраивать Perfmon на сервере. Мы обычно смотрим по нему
io_stall_read_ms / num_of_readsиio_stall_write_ms / num_of_writes— это и есть средняя латентность операции в миллисекундах по каждому файлу отдельно.
Дальше — что даёт понять, ждёт ли сервер именно диск. Смотрим агрегированную статистику ожиданий:
SELECT wait_type, wait_time_ms, waiting_tasks_count FROM sys.dm_os_wait_stats WHERE wait_type IN ('PAGEIOLATCH_SH','PAGEIOLATCH_EX','WRITELOG','ASYNC_IO_COMPLETION') ORDER BY wait_time_ms DESC;
По документации Microsoft, если средние ожидания PAGEIOLATCH стабильно выше 10 мс — подсистема ввода-вывода под давлением: либо не хватает IOPS, либо диск физически не тот класс, что нужен под нагрузку. Ожидание WRITELOG отдельно указывает на журнал транзакций — часто это RAID-контроллер без battery-backed write cache или журнал, лежащий на том же массиве, что и файлы данных. Один из типовых P0-фиксов у нас на выезде — просто развести .mdf и .ldf по разным физическим томам, и жалобы на «тормозит проведение документов» снимаются без единого изменения в 1С.
CPU и параллелизм: когда тормозит не диск, а сам план запроса
Третий частый сценарий — SQL Server упирается не в диск и не в память, а в CPU из-за неудачно распараллеленных запросов. 1С генерирует немало тяжёлых аналитических запросов (отчёты, СКД, закрытие месяца), и на многоядерных серверах SQL Server может «раздувать» план на десятки потоков там, где выгоднее было бы выполнить его последовательно.
- CXPACKET (и его «преемник» в новых версиях — CXCONSUMER) — ожидание синхронизации потоков параллельного плана. Само по себе не патология, но высокая доля этого ожидания в сумме по серверу — сигнал пересмотреть
MAXDOPиCost Threshold for Parallelism; - На типовом сервере 1С мы обычно выставляем
MAXDOPне больше числа ядер в одном NUMA-узле (для большинства терминальных серверов клиентов это 4–8) и поднимаемCost Threshold for Parallelismс дефолтных 5 до 50–75 — дефолтное значение 5 осталось от SQL Server 2000-х и на современных отчётах 1С почти всегда занижено; - SQLServer:SQL Statistics \ Batch Requests/sec и Compilations/sec — если компиляций почти столько же, сколько запросов, это означает, что план запроса не переиспользуется (частая причина — динамически формируемые запросы СКД без параметризации), и CPU греется на компиляции, а не на полезной работе.
Диагностируем текущую нагрузку по CPU без сторонних утилит через sys.dm_exec_requests и sys.dm_exec_sessions — в паре с sys.dm_exec_sql_text это даёт список самых тяжёлых сейчас запросов с указанием конкретной сессии 1С (по host_process_id и program_name обычно видно, чей это клиентский процесс).
Блокировки и конкурентный доступ: когда «зависла» одна операция, а не весь сервер
Отдельный класс жалоб — «зависло у одного пользователя, у остальных работает». Это почти всегда блокировки на уровне строк/таблиц, а не общая деградация сервера. Смотрим:
- LCK_M_* (например,
LCK_M_X,LCK_M_IX) вsys.dm_os_wait_stats— ожидание блокировок конкретного типа; - sys.dm_tran_locks в связке с
sys.dm_exec_requests— кто кого блокирует прямо сейчас, поblocking_session_id; - RESOURCE_SEMAPHORE — по документации Microsoft это ожидание при нехватке памяти на memory grant для выполнения запроса (обычно тяжёлые сортировки/хэш-соединения в отчётах); если это ожидание регулярно в топе — снова возвращаемся к разделу про память, часто помогает не «добавить памяти вообще», а поднять
min memory per queryлибо переписать отчёт; - Уровень изоляции — читаем, включён ли
READ_COMMITTED_SNAPSHOTна базе. 1С в клиент-серверном варианте по рекомендациям платформы должна работать с включённым RCSI: это резко снижает блокировки на чтение за счёт версионирования строк вtempdb, но одновременно повышает нагрузку именно на tempdb — отсюда прямая связь со следующим разделом.
tempdb: самое частое узкое место в базах 1С, которое не видно из интерфейса программы
tempdb в 1С работает на порядок интенсивнее, чем в среднем OLTP-приложении: временные таблицы для виртуальных таблиц регистров, промежуточные результаты СКД, версии строк при включённом RCSI (см. предыдущий раздел) — всё это уходит именно туда. Если tempdb сконфигурирован «по умолчанию, одним файлом», то на нагруженной базе это стабильно узкое место.
Что проверяем и приводим в порядок:
- Количество файлов данных
tempdb— по рекомендации Microsoft стартуем с одного файла на ядро CPU, до 8 файлов, все файлы одинакового размера и с одинаковым автоприростом; - Начиная с SQL Server 2016 поведение trace-флагов 1117 (совместный автоприрост всех файлов) и 1118 (выделение полными экстентами, без примеси в
tempdb) уже включено по умолчанию — отдельно их поднимать не нужно, но на старых серверах 2014 и ниже мы их выставляем вручную; - Ожидания PAGELATCH_UP/EX (не путать с PAGEIOLATCH — это про латч в памяти, а не про физический диск) на страницах GAM/SGAM/PFS — классический маркер, что файлов
tempdbмало для числа параллельных сессий; - Физическое размещение — по возможности отдельный быстрый диск/том под
tempdb, не общий с файлами пользовательских баз.
Короткий скрипт, которым проверяем текущее число файлов и их размер перед выездом к клиенту:
SELECT name, physical_name, size/128 AS size_mb, growth FROM tempdb.sys.database_files;
Готовый шаблон в Zabbix: как мы это автоматизируем у клиента
Ручной прогон DMV на диагностике — это разовая история. Чтобы не приезжать каждый раз, когда «опять тормозит», мы разворачиваем клиенту постоянный мониторинг на Zabbix — благо у большинства уже стоит Zabbix Server для мониторинга инфраструктуры (см. наш опыт с Zabbix 7.0 на других проектах), и добавить туда MS SQL — вопрос одного вечера.
Официальный путь — плагин MSSQL для Zabbix agent 2 (входит в поставку начиная с Zabbix agent 2 версии 6.0) и типовой шаблон «Microsoft SQL Server by Zabbix agent 2» из официального репозитория. Плагин подключается к SQL Server напрямую по протоколу TDS (порт 1433 по умолчанию), поэтому дополнительный ODBC-драйвер на сервере агента не требуется — только сетевая доступность и учётная запись SQL с правами VIEW SERVER STATE.
Шаги внедрения, которые мы повторяем у каждого клиента:
- Заводим отдельного SQL-логина только для мониторинга (не sa!) и даём ему
GRANT VIEW SERVER STATE TO [zbx_monitor]— этого достаточно для чтения всех нужных DMV, права на изменение данных не нужны; - Устанавливаем/обновляем Zabbix agent 2 на сервере СУБД (или рядом, если агент стоит на другом хосте с сетевым доступом к 1433 порту);
- Прописываем сессию подключения в конфиге агента;
- Импортируем в Zabbix Server шаблон Microsoft SQL Server by Zabbix agent 2, привязываем к узлу сети, назначаем макрос с именем сессии;
- Донастраиваем пороги триггеров под конкретного клиента — дефолтные значения шаблона рассчитаны на «типовой» сервер, для терминалок с 1С мы всегда правим их под пороги из таблиц выше (PLE, латентность диска, RESOURCE_SEMAPHORE).
Фрагмент конфигурации агента (zabbix_agent2.conf или отдельный .d-файл плагина):
Plugins.MSSQL.Sessions.MAIN1C.Uri=sqlserver://SQLSRV01:1433
Plugins.MSSQL.Sessions.MAIN1C.User=zbx_monitor
Plugins.MSSQL.Sessions.MAIN1C.Password=<пароль_из_секрет-хранилища>
Быстрая проверка, что плагин вообще видит сервер, без ожидания опроса по расписанию:
zabbix_get -s 127.0.0.1 -k mssql.ping[MAIN1C]
| Группа items в шаблоне | Что мониторим | Наш триггер (клиентский профиль 1С) |
|---|---|---|
| Instance discovery | Автообнаружение баз данных и job-ов SQL Agent на инстансе | Информационно, база для остальных LLD-правил |
| Performance counters (Buffer Manager) | Page life expectancy, buffer cache hit ratio | PLE ниже расчётного порога 15+ минут подряд — Warning; ниже 300 сек — Average |
| Memory Manager | Total/Target Server Memory, Memory Grants Pending | Memory Grants Pending > 0 дольше 5 минут — Average |
| Database status | Состояние БД (ONLINE/RESTORING и т.д.), размер файла лога | Статус ≠ ONLINE — Disaster (немедленно) |
| Backup discovery | Время последнего успешного backup по каждой базе | Полный backup давностью > 26 часов — Average (у 1С обычно ночной full + журнальные) |
| Job discovery (SQL Agent) | Статус последнего выполнения job-ов обслуживания (реиндексация, backup) | Последний запуск job = Failed — High |
Отдельно на уровне ОС держим классический шаблон Windows by Zabbix agent (или Linux-аналог для SQL Server на Linux) для диска и памяти хоста — латентность и очередь диска технически относятся к ОС, а не к плагину MSSQL, и без них шаблон MSSQL показывает только «внутреннюю» картину сервера БД, без физического диска под ней.
Как читать дашборд целиком, а не по одному счётчику
Главная ошибка, которую мы видим у клиентов, которые пытались настроить мониторинг сами, — реакция на один сработавший триггер без контекста. PLE ниже порога сам по себе не диагноз: если это произошло сразу после ночной реиндексации или после рестарта службы SQL Server, это ожидаемое и временное поведение — буферный пул просто прогревается заново.
Наш порядок разбора инцидента в Zabbix (проверка сверху вниз, по убыванию вероятной причины):
- Смотрим триггеры уровня Database status и Backup — если база не ONLINE или backup давно не проходил, это приоритет №1, к производительности отношения не имеющий, но критичный сам по себе;
- Смотрим Memory Grants Pending и PLE в связке — если оба «красные» одновременно и держатся не первый час, это память, идём разбираться в раздел про буферный пул;
- Проверяем графики диска на уровне ОС (латентность + очередь) за тот же промежуток времени — если они тоже поднялись, то первопричина в диске, а память лишь следствие (SQL Server агрессивнее держится за страницы, потому что подкачка с диска медленная);
- Если ни память, ни диск не показывают отклонений, а жалобы всё равно есть — смотрим CPU и RESOURCE_SEMAPHORE/CXPACKET, это, скорее всего, конкретный тяжёлый отчёт или неудачный план запроса, а не системная деградация.
Такой порядок разбора мы фиксируем прямо в описании triggers в Zabbix (поле Description), чтобы дежурный инженер, увидевший алерт ночью, сразу понимал, в какую сторону копать, не поднимая нас по телефону среди ночи по каждому чиху.
Что это даёт на практике: конкретный пример из нашего сопровождения
На одном из клиентских серверов (терминальный сервер + SQL Server 2019 Standard на одной машине, база УТ 11 на 30+ РМ) жалобы «зависает при закрытии месяца» шли из месяца в месяц, и каждый раз списывались на «тяжёлую операцию, надо подождать». После разворачивания описанного мониторинга картина за первую же неделю стала прозрачной: PLE регулярно проваливался в момент ночного backup — полная резервная копия писалась на тот же физический том, где лежали файлы данных, и вытесняла буферный пул. Отдельно ожидания WRITELOG в это же окно подскакивали кратно — журнал транзакций был на том же диске.
Решение оказалось не в «добавить памяти» и не в «оптимизировать 1С», а в разнесении файлов: .ldf перенесли на отдельный SSD-том, окно backup сдвинули на менее нагруженное время и добавили сжатие backup средствами SQL Server, чтобы сократить время самой операции записи. После этого жалобы на закрытие месяца прекратились полностью, а метрики PLE и WRITELOG в Zabbix стали ровной линией без ночных провалов — именно эти графики мы и показываем клиенту как подтверждение, что проблема закрыта, а не «вроде стало лучше на слух».
Частые вопросы
- Обязательно ли ставить ODBC-драйвер SQL Server, чтобы мониторить его через Zabbix?
- Нет, если используете официальный плагин MSSQL для Zabbix agent 2 — он общается с сервером напрямую по протоколу TDS (тот же порт 1433, что и клиентские подключения 1С), отдельный ODBC-драйвер не требуется. ODBC нужен только если вы собираете метрики альтернативным способом через odbc.get и самописные шаблоны.
- Почему Page Life Expectancy иногда падает даже на здоровом сервере?
- Кратковременное падение PLE — ожидаемое поведение сразу после рестарта службы SQL Server, после ночной реиндексации/обновления статистики или сразу после резервного копирования, которое вытесняет часть буферного пула. Тревожный сигнал — не разовое падение, а устойчиво низкое значение в течение рабочего дня под обычной нагрузкой.
- Нужно ли клиенту с 1С в файловом варианте (без сервера СУБД) что-то из этого мониторинга?
- Нет, всё описанное относится к клиент-серверному варианту 1С:Предприятие с MS SQL Server. Файловая база (.1CD) работает без отдельной СУБД, и там симптомы медленной работы почти всегда связаны с сетевым диском, антивирусом или самим файлом базы, а не со счётчиками SQL Server.
- Сколько занимает внедрение такого мониторинга у клиента, если Zabbix Server уже стоит?
- По нашей практике — один визит: заведение SQL-логина для мониторинга с правом VIEW SERVER STATE, установка/обновление Zabbix agent 2, настройка сессии подключения к MSSQL-плагину и импорт официального шаблона с донастройкой порогов под конкретный сервер укладываются в несколько часов работы.
- Что делать, если RESOURCE_SEMAPHORE регулярно в топе ожиданий, а памяти на сервере вроде достаточно?
- Это ожидание указывает не на нехватку памяти вообще, а на нехватку memory grant под конкретный тяжёлый запрос (обычно сортировки или хэш-соединения в отчётах). Сначала находим сам запрос через sys.dm_exec_requests и sys.dm_exec_query_stats, и часто решение — переписать отчёт или обновить статистику, а не увеличивать max server memory.