Динамическое обновление конфигурации (в обиходе системных администраторов и программистов — «демоническое») часто является вынужденной мерой в крупных и круглосуточно работающих базах данных. Оно позволяет вносить изменения в метаданные без монопольного выгона пользователей. Однако у этого механизма есть существенный минус: сеансы пользователей, которые не перезапустили программу после такого обновления, начинают работать со старым кэшем метаданных, что нередко приводит к трудноуловимым ошибкам, падению платформы и даже повреждению данных.
Для минимизации рисков разработчику важно четко фиксировать точный момент применения динамического обновления. В этой статье мы подробно разберем, как перехватить это событие в реальном времени, зафиксировать дату и время его проведения, а также рассмотрим технические решения как средствами встроенного языка 1С, так и на уровне систем управления базами данных (СУБД).
Самый очевидный способ перехватить обновление на стороне платформы — периодически опрашивать систему на предмет изменения конфигурации базы данных. Для этого во встроенном языке 1С предусмотрен метод КонфигурацияБазыДанныхИзмененаДинамически(). Этот метод возвращает значение Истина, если в базу были внесены динамические изменения, которые еще не были приняты текущим активным сеансом.
Проанализируем базовый алгоритм реализации данного подхода:
Рассмотрим пример программного кода для реализации этого механизма:
// В модуле управляемого приложения
Процедура ПриНачалеРаботыСистемы()
#Если Клиент Тогда
// Подключаем проверку раз в 5 минут (300 секунд)
ПодключитьОбработчикОжидания("ПроверитьДинамическоеОбновлениеИБ", 300);
#КонецЕсли
КонецПроцедуры
// В глобальном или общем клиентском модуле
Процедура ПроверитьДинамическоеОбновлениеИБ() Экспорт
Если КонфигурацияБазыДанныхИзмененаДинамически() Тогда
// Передаем управление на сервер для фиксации события
ЗафиксироватьДатуОбновленияНаСервере();
// Отключаем обработчик текущего сеанса, чтобы не спамить базу данных
ОтключитьОбработчикОжидания("ПроверитьДинамическоеОбновлениеИБ");
КонецЕсли;
КонецПроцедуры
На сервере нам необходимо корректно обработать запись события. Поскольку обработчик сработает у всех активных пользователей, сервер приложений может получить лавину одновременных запросов на запись одной и той же даты. Чтобы избежать блокировок и дублирования записей, применим транзакционную блокировку или простую проверку времени последней записи:
// В общем серверном модуле
Процедура ЗафиксироватьДатуОбновленияНаСервере() Экспорт
УстановитьПривилегированныйРежим(Истина);
ТекущийМомент = ТекущаяДатаСеанса();
// Блокируем чтение/запись регистра для предотвращения дублей от разных сеансов
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ИсторияДинамическихОбновлений");
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
// Проверяем, была ли уже сделана запись за последние 10 минут
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| История.Период КАК Период
|ИЗ
| РегистрСведений.ИсторияДинамическихОбновлений КАК История
|УПОРЯДОЧИТЬ ПО
| Период УБЫВ";
РезультатЗапроса = Запрос.Выполнить();
НужноЗаписать = Истина;
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
// Если последняя запись сделана менее 10 минут назад, значит, это то же самое обновление
Если ТекущийМомент - Выборка.Период < 600 Тогда
НужноЗаписать = Ложь;
КонецЕсли;
КонецЕсли;
Если НужноЗаписать Тогда
МенеджерЗаписи = РегистрыСведений.ИсторияДинамическихОбновлений.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Период = ТекущийМомент;
МенеджерЗаписи.Инициатор = ПараметрыСеанса.ТекущийПользователь;
МенеджерЗаписи.Записать();
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("ДинамическоеОбновление",
УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
Если ваша конфигурация разработана на базе БСП, в ней уже присутствует встроенная подсистема контроля обновления конфигурации. Платформа и БСП автоматически отслеживают появление динамических изменений программы для вывода предупреждений пользователям.
Чтобы не создавать собственный периодический опрос и не нагружать сервер лишними вызовами, мы можем интегрироваться в уже существующий цикл проверок БСП с помощью механизма расширений конфигурации:
СтандартныеПодсистемыСервер или процедуры модуля управляемого приложения, вызывающие проверку версии программы).ПриВыполненииСтандартныхПериодическихПроверокНаСервере) с помощью аннотации &После.Этот подход гарантирует минимальную нагрузку на систему, так как периодичность опроса централизованно управляется настройками БСП.
Если ваша информационная база развернута в клиент-серверном варианте, наиболее точным и производительным решением будет перехват события непосредственно в СУБД. При проведении динамического обновления платформа 1С производит запись новых версий объектов метаданных в системную таблицу dbo.Config (или public.config в случае PostgreSQL).
Выясним подробнее, что происходит внутри таблицы Config при запуске динамического обновления:
FileName принимает значение 'dynamicCommit' или 'DynamicallyUpdated'.Мы можем создать триггер СУБД класса AFTER INSERT, UPDATE на таблицу Config, который будет автоматически записывать текущую системную дату сервера баз данных в специально созданную таблицу логирования.
Рассмотрим пример реализации SQL-триггера для MS SQL Server:
-- 1. Создаем отдельную таблицу для хранения истории в нашей БД 1С
CREATE TABLE dbo.My_DynamicUpdateLog (
ID INT IDENTITY(1,1) PRIMARY KEY,
UpdateDateTime DATETIME NOT NULL,
HostName NVARCHAR(128),
AppUser NVARCHAR(128),
Spid INT
);
GO
-- 2. Создаем триггер на системную таблицу Config
CREATE TRIGGER trg_Config_DynamicUpdate
ON dbo.Config
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
-- Проверяем, были ли изменены или добавлены записи с маркерами динамического обновления
IF EXISTS (
SELECT 1
FROM inserted
WHERE FileName IN ('dynamicCommit', 'DynamicallyUpdated')
)
BEGIN
-- Записываем системное время СУБД, имя ПК и учетную запись, из-под которой запущено обновление
INSERT INTO dbo.My_DynamicUpdateLog (UpdateDateTime, HostName, AppUser, Spid)
VALUES (
GETDATE(),
HOST_NAME(),
ORIGINAL_LOGIN(),
@@SPID
);
END
END;
GO
Преимущества использования СУБД-триггера:
HOST_NAME() и ORIGINAL_LOGIN() вы можете зафиксировать имя рабочей станции и учетную запись оператора Windows/SQL, который инициировал обновление из Конфигуратора.Для администраторов крупных систем хорошей альтернативой без изменения кода конфигурации и СУБД является настройка Технологического журнала (ТЖ) платформы. При динамическом обновлении сервер приложений 1С (процессы rphosts) начинает генерировать системные события, связанные с пересогласованием конфигураций и несоответствием версий метаданных у пользователей.
Для детального логирования достаточно настроить файл конфигурации ТЖ logcfg.xml на сбор событий, связанных с конфигурацией базы данных, или отслеживать появление предупреждений вида: «Попытка подключения к контексту сервера с неподходящей версией метаданных». Анализируя лог-файлы ТЖ внешними парсерами (например, скриптами на Python или с помощью систем сбора логов ELK), можно оперативно фиксировать и визуализировать моменты обновления ИБ.
Мы рассмотрели несколько подходов для решения поставленной задачи. Выбор конкретного метода зависит от структуры вашей инфраструктуры: