Почему регламентное задание запускается дважды и создает дубли в 1С?

Программист 1С v8.3 (Управляемые формы) 1С:Документооборот IT и автоматизация бизнеса
← На главную

Встречались ли вы с ситуацией, когда регламентное задание, которое должно выполняться в единственном экземпляре, внезапно запускается почти одновременно в разных сеансах? Это приводит к созданию дублирующихся документов, бизнес-процессов или записей в регистрах (поможет универсальный поиск и удаление дублей в 1С), нарушая целостность данных. Особенно часто такая проблема проявляется в клиент-серверных базах с кластером серверов 1С, даже если задание жестко привязано к конкретному серверу.

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

Анализ причины: "гонка состояний" в коде

Корень проблемы часто кроется в так называемой "гонке состояний" (race condition). Это ситуация, когда результат работы кода зависит от того, какой из параллельно выполняющихся потоков (в нашем случае — сеансов) успеет выполнить операцию первым. Часто такие ситуации становятся критическими факторами, влияющими на то, насколько быстро работает наша система, и где находится "узкое место".

Рассмотрим код, который проверяет наличие активного бизнес-процесса перед его созданием. Логика кажется правильной: сначала проверяем, потом создаем.


// ... фрагмент кода для проверки существующего БП
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
|	КомплексныйПроцессПредметы.Ссылка КАК Ссылка
|ИЗ
|	БизнесПроцесс.КомплексныйПроцесс.Предметы КАК КомплексныйПроцессПредметы
|ГДЕ
|	КомплексныйПроцессПредметы.Предмет = &Предмет
|	И КомплексныйПроцессПредметы.Ссылка.Завершен = Ложь
|	И КомплексныйПроцессПредметы.Ссылка.ПометкаУдаления = Ложь
|	И КомплексныйПроцессПредметы.Ссылка.Стартован
|	И КомплексныйПроцессПредметы.Ссылка.Состояние = ЗНАЧЕНИЕ(Перечисление.СостоянияБизнесПроцессов.Активен)
|	И КомплексныйПроцессПредметы.Ссылка.Шаблон = &Шаблон";

Запрос.УстановитьПараметр("Предмет", Событие.Источник);
Запрос.УстановитьПараметр("Шаблон", СтрокаТаблицы.ШаблонБизнесПроцесса);

РезультатПроверки = Запрос.Выполнить();

Если РезультатПроверки.Пустой() Тогда
	// Бизнес-процесс не найден, создаем новый...
КонецЕсли;

А теперь представим, что планировщик заданий по какой-то причине запустил два экземпляра нашего регламентного задания одновременно в Сеансе 1 и Сеансе 2.

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

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

Решение 1: Установка ключа регламентного задания

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

Как это сделать:

  1. Откройте конфигуратор вашей базы данных.
  2. Найдите нужное предопределенное регламентное задание в ветке Общие -> Регламентные задания — для этого подойдёт инструментарий для управления и отладки регламентных заданий.
  3. Откройте его свойства и найдите поле "Ключ".
  4. Задайте в этом поле любую уникальную в пределах конфигурации строку. Это может быть осмысленное имя, например, "ОбработкаБизнесСобытийДокументооборот".

После этого планировщик будет игнорировать повторные попытки запуска. Однако стоит изучить некоторые неочевидные особенности фоновых заданий и свойства Ключ, чтобы понимать все нюансы этого механизма на уровне платформы.

Решение 2: Использование управляемых блокировок в коде

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

Идея состоит в том, чтобы заблокировать данные, связанные с предметом, на время проверки и создания бизнес-процесса. Пока один сеанс держит блокировку, другой сеанс будет ждать ее освобождения.

Пример кода с управляемой блокировкой:


НачатьТранзакцию();
Попытка
	// 1. Создаем объект блокировки
	Блокировка = Новый БлокировкаДанных;

	// 2. Указываем, какие данные будем блокировать. 
	ЭлементБлокировки = Блокировка.Добавить("Справочник.ВнутренниеДокументы");

	// 3. Устанавливаем исключительный режим.
	ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;

	// 4. Указываем конкретный элемент, который блокируем
	ЭлементБлокировки.УстановитьЗначение("Ссылка", Событие.Источник);

	// 5. Выполняем блокировку.
	Блокировка.Заблокировать();

	// 6. Теперь, когда данные надежно заблокированы, выполняем нашу проверку
	Запрос = Новый Запрос;
	// ... текст запроса, как в примере выше ...
	РезультатПроверки = Запрос.Выполнить();

	Если РезультатПроверки.Пустой() Тогда
		// 7. Если БП не найден, создаем его внутри той же транзакции
		НовыйБП = БизнесПроцессы.КомплексныйПроцесс.СоздатьБизнесПроцесс();
		// ... заполняем реквизиты ...
		НовыйБП.Предметы.Добавить().Предмет = Событие.Источник;
		НовыйБП.Записать();
		НовыйБП.Стартовать();
	КонецЕсли;

	// 8. Если все прошло успешно, подтверждаем все изменения
	ЗафиксироватьТранзакцию();

Исключение
	// 9. Если произошла ошибка, отменяем все изменения
	ОтменитьТранзакцию();
	ВызватьИсключение; 
КонецПопытки;

Теперь, если два сеанса одновременно дойдут до этого участка кода, второй сеанс на строке Блокировка.Заблокировать() остановится и будет ждать, пока первый сеанс завершит транзакцию, что полностью исключает дублирование.

Диагностика на уровне кластера

В исходной проблеме упоминалось, что задание было вынесено на отдельный сервер кластера. Это правильный подход, если вы ускоряете 1С:Документооборот КОРП и распределяете нагрузку. Однако стоит убедиться, что настройки правил назначения функциональности не содержат конфликтов.

Для глубокой диагностики можно использовать Технологический журнал (ТЖ), отслеживая события SDBL и JOB. Это поможет выявить аномалии в работе планировщика и понять причины избыточных запусков.

Итоги

Для надежного решения проблемы дублирования данных рекомендуется применять комплексный подход:

  1. Установить уникальный Ключ для регламентного задания. Это средство предотвратит запуск второго экземпляра задания планировщиком.
  2. Использовать управляемые блокировки в коде. Это гарантирует целостность данных на уровне транзакций, делая ваш код устойчивым к параллельной работе.

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

← На главную