Каждый администратор СУБД и разработчик 1С рано или поздно сталкивается с ситуацией, когда процессор сервера базы данных загружен под 100%, а в топе самых «тяжелых» запросов оказывается системный запрос чтения конфигурации. На первый взгляд ситуация выглядит парадоксально: пользователи выполняют повседневные операции (проводят документы, строят отчеты), а СУБД тратит до половины своего времени на чтение служебной таблицы Config.
В этой статье мы подробно разберем, почему возникает аномальная нагрузка от запроса SELECT Creation, Modified, Attributes, DataSize, BinaryData FROM Config WHERE FileName = P1 ORDER BY PartNo, как эта техническая проблема напрямую связана с прикладным кодом, переполненными регистрами сведений и ошибками компиляции модулей, а также по шагам пройдем путь локализации и устранения этой неисправности.
Для начала давайте выясним причину, по которой платформа 1С обращается к таблице Config. В этой таблице хранятся все метаданные нашей информационной базы: структура конфигурации, формы, макеты, программный код общих модулей, модулей объектов и менеджеров. Поле BinaryData содержит непосредственно сжатые бинарные данные конфигурации. Так как размер отдельных файлов метаданных может превышать допустимый размер одной записи в СУБД, платформа разбивает их на части. Именно поэтому в запросе присутствует сортировка ORDER BY PartNo — она гарантирует, что фрагменты бинарного файла будут собраны сервером приложений 1С в правильном порядке.
В штатном режиме работы чтение этой таблицы происходит только при старте рабочих процессов rphost, подключении новых пользователей или первом вызове тех или иных объектов метаданных. После прочтения эти данные кэшируются в оперативной памяти сервера приложений 1С. Повторные обращения к СУБД за кодом модулей или формами выполняться не должны.
Если же этот запрос стабильно держится в топе по потреблению ресурсов CPU СУБД, это означает одно: кэш сервера приложений постоянно сбрасывается или не успевает накапливаться. Сервер приложений 1С вынужден непрерывно, тысячами раз в минуту, скачивать мегабайты бинарных данных конфигурации из SQL-сервера, заставляя последний тратить огромные вычислительные мощности на чтение дисков, распаковку LOB-данных (Large Object) и их передачу по сети.
На первый взгляд, данные бизнес-логики (содержимое таблиц регистров сведений) никак не связаны со служебной таблицей структуры конфигурации. Но на практике связь здесь самая прямая и деструктивная. Проанализируем цепочку событий, приводящую к коллапсу системы:
rphost исчерпывает выделенную ему оперативную память и аварийно завершается операционной системой (процесс падает по ошибке Out of Memory).rphost обязан с нуля инициализировать окружение для каждого входящего сеанса. Он начинает массово считывать конфигурацию из таблицы Config СУБД, чтобы скомпилировать программный код общих модулей. Если фоновое задание-убийца запускается автоматически сразу после старта системы, процесс падает снова, образуя бесконечный цикл: «старт -> чтение Config -> компиляция -> падение от нехватки памяти -> старт». В результате SQL-сервер непрерывно загружен чтением Config на 90%.Ситуация становится критической, если в программном коде конфигурации допущены грубые ошибки компиляции. Рассмотрим реальный лог проверки конфигурации:
{ОбщийМодуль.ОбменДаннымиГлобальный.Модуль(27,3)}: Процедура или функция с указанным именем не определена (ПоказатьОповещениеПользователя)
<<?>>ПоказатьОповещениеПользователя(...) (Проверка: Мобильное приложение-клиент)
{ОбщийМодуль._ИМЦ_СервисныйГлобальный.Модуль(97,53)}: Переменная не определена (УровеньЖурналаРегистрации)
ЗаписьЖурналаРегистрации(..., <<?>>УровеньЖурналаРегистрации.Ошибка, ...) (Проверка: Мобильное приложение-сервер)
Эти ошибки показывают, что разработчик использовал клиентские методы (такие как ПоказатьОповещениеПользователя) или платформенно-зависимые переменные в контекстах, где они недоступны (например, при компиляции модуля под мобильное приложение или внешний источник данных).
Когда фоновый процесс обмена пытается выполнить код, содержащий такие синтаксические ошибки, платформа не может успешно скомпилировать и закэшировать модуль. В некоторых случаях это приводит к моментальному падению потока исполнения, повторной попытке инициализации сеанса сервером приложений и очередному раунду чтения таблицы Config из базы данных.
Рассмотрим детальный алгоритм, который поможет нам локализовать и полностью устранить данную проблему в нашей информационной базе.
Прежде всего нам необходимо оценить физический масштаб проблемы со стороны СУБД. Выясним размеры таблицы Config и ключевых регистров сведений. Для этого выполним стандартный отчет СУБД по дисковому пространству таблиц или воспользуемся системным запросом:
SELECT
t.NAME AS TableName,
p.rows AS RowCounts,
(SUM(a.total_pages) * 8) / 1024 AS TotalSpaceMB,
(SUM(a.used_pages) * 8) / 1024 AS UsedSpaceMB
FROM
sys.tables t
INNER JOIN
sys.indexes i ON t.object_id = i.object_id
INNER JOIN
sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id
INNER JOIN
sys.allocation_units a ON p.partition_id = a.container_id
WHERE
t.NAME LIKE '%Config%' OR t.NAME LIKE '%InfoReg%'
GROUP BY
t.NAME, p.rows
ORDER BY
TotalSpaceMB DESC;
Если мы видим, что таблица Config имеет адекватный размер (в пределах 30–100 МБ для небольших кастомных баз), но при этом количество чтений к ней зашкаливает, значит, проблема действительно в постоянном сбросе кэша. Если же мы обнаружили регистры сведений с миллионами записей (десятки гигабайт данных), то мы нашли потенциальный источник падения процессов.
Если в процессе анализа СУБД были выявлены «раздутые» таблицы регистров сведений, необходимо выяснить, какие именно данные их переполняют. Часто в полях дат таких таблиц можно встретить аномальные значения со смещением на 2000 лет (например, 4017-12-11 вместо 2017-12-11). Это стандартный механизм платформы 1С для записи дат без часовых поясов или пустых дат в MS SQL Server.
Для устранения переполнения выполним следующие действия:
НаборЗаписей = РегистрыСведений.НашПроблемныйРегистр.СоздатьНаборЗаписей();
// Если регистр имеет измерения, устанавливаем необходимые отборы для очистки порциями
НаборЗаписей.Отбор.УзелОбмена.Установить(СсылкаНаУзел);
НаборЗаписей.Записать(Истина); // Записываем пустой набор для удаления записей по отбору
Если регистр содержит критически важные данные, которые нельзя просто удалить, необходимо провести реструктуризацию таблицы, настроить регулярную архивацию старых записей и добавить отсутствующие индексы СУБД для оптимизации выборок фоновыми заданиями.
Параллельно с очисткой данных мы должны устранить любые синтаксические ошибки в конфигурации, которые мешают стабильной компиляции на стороне сервера приложений. Запустим конфигуратор, выполним полную проверку конфигурации со всеми включенными флагами проверки клиент-серверных сред (особенно для мобильного клиента и сервера):
Конфигурация -> Проверка конфигурации -> Отметить все флаги проверки сред выполнения.
При нахождении ошибок компиляции в общих модулях исправим их, изолировав платформозависимый клиентский код с помощью инструкций препроцессора. Рассмотрим правильный пример организации серверного вызова:
Процедура ВыполнитьОбменНаСервере() Экспорт
// Выполняем логику обмена данными
Попытка
ОбработатьОчередьРегистра();
Исключение
ЗаписатьЛогОшибки(ОписаниеОшибки());
// КРИТИЧЕСКИ ВАЖНО: Нельзя вызывать интерактивные методы на сервере!
#Если Клиент Тогда
ПоказатьОповещениеПользователя("Ошибка обмена данными!");
#КонецЕсли
КонецПопытки;
КонецПроцедуры
Использование директив #Если Клиент Тогда ... #КонецЕсли гарантирует, что сервер приложений 1С при компиляции этого общего модуля успешно проигнорирует клиентский метод ПоказатьОповещениеПользователя, модуль скомпилируется без ошибок и корректно сохранится в кэше сервера приложений.
Иногда бесконечное перечитывание таблицы Config связано с неудачно проведенным динамическим обновлением конфигурации базы данных (так называемым «демоническим» обновлением). При таком обновлении в служебной таблице Params СУБД может зависнуть запись с именем DynamicallyUpdated. Наличие этой записи заставляет платформу при любом запросе сомневаться в актуальности локального кэша и повторно перечитывать структуру метаданных.
Для исправления этой проблемы выполним следующие действия в нерабочее время:
-- Проверим наличие признака динамического обновления в служебной таблице
SELECT * FROM Params WHERE Name = 'DynamicallyUpdated';
-- Если строка найдена, аккуратно удалим её
DELETE FROM Params WHERE Name = 'DynamicallyUpdated';
После удаления этой записи и перезапуска службы сервера 1С платформа перестанет принудительно сбрасывать кэш при каждом действии пользователя.
Чтобы обезопасить систему от лавинообразного падения процессов из-за возможных будущих утечек памяти в неоптимальном коде, правильно настроим параметры нашего кластера серверов 1С:
rphost (например, 80% от доступного объема памяти сервера).rphost. В случае падения одного процесса из-за перегрузки пострадает лишь небольшая часть пользователей, а остальные продолжат стабильную работу на соседних рабочих процессах без сброса общего кэша конфигурации.Проанализировав и последовательно выполнив все описанные шаги, мы сможем гарантированно выявить истинную причину перегрузки процессора нашего SQL-сервера, устранить неоптимальный код компиляции модулей, очистить переполненные таблицы обмена и вернуть производительность СУБД к нормальным штатным показателям.