Диагностика и решение проблем планировщика Quartz

Данный материал предназначен для продвинутых пользователей. Если у вас есть вопросы по применению, обратитесь в Техническую поддержку BPMSoft.

В некоторых случаях могут возникать проблемы с запуском бизнес-процессов: письма и рассылки не отправляются или отправляются с задержкой. В логах приложения могут появляться ошибки Quartz.

Если проблемы наблюдаются, необходимо выполнить диагностику планировщика. Диагностика включает в себя следующие этапы:

Анализ логов приложения

Необходимо проанализировать логи приложения на предмет ошибок, связанных с Quartz.

Ошибка генерации instance Id

Бизнес-процессы по таймерам не запускаются и не отрабатывает синхронизация. В логе Scheduler.log следующая ошибка:

Quartz.Impl.StdSchedulerFactory ExecutionContextCallback - Couldn't generate instance Id!
Quartz.SchedulerException: Couldn't get host name!
 ---> System.Net.Internals.SocketExceptionFactory+ExtendedSocketException (00000005, 0xFFFDFFFF): Name or service not known

Ошибка возникла из-за того, что система не смогла создать уникальное имя ноды планировщика.

Проверьте настройку quartz.scheduler.instanceId. Если она установлена на AUTO, убедитесь, что указано имя хоста. Для этого откройте папку /etc/hosts или /etc/sysconfig/network в Linux и выполните команду hostname. Если имя хоста отсутствует, присвойте ему значение.

Другой вариант: вручную добавьте уникальные значения quartz.scheduler.instanceId в файл BPMSoft.WebHost.dll.config.

Зависание бизнес-процессов

Бизнес-процессы запускаются, но иногда зависают и не доходят до конца. В логе Application.log можно увидеть:

...
2025-02-06 13:38:45,152 [1] WARN IIS APPPOOL\ocrm BPMSoft.WebApp.Global Application_Start - Application start
2025-02-06 15:32:34,430 [1] WARN IIS APPPOOL\ocrm BPMSoft.WebApp.Global Application_Start - Application start
2025-02-06 17:26:54,758 [1] WARN IIS APPPOOL\ocrm BPMSoft.WebApp.Global Application_Start - Application start
...

Видно, что приложение часто перезапускается в течение дня. В журнале процессов можно найти зависший бизнес-процесс от 15:28, а в логе — перезапуск сайта в 15:32. Процессы, которые выполнялись на момент остановки или перезапуска приложения, не возобновляются. Это может указывать на проблему в коде бизнес-процесса.

Нужно проверить остальные логи на возможные ошибки, которые могли привести к перезагрузкам сайта. Если их не окажется (что вполне возможно), следует изучить Event Log в Windows или journalctl в Linux. Особое внимание нужно уделить критическим ошибкам. В большинстве случаев проблема связана с ошибкой в кастомизации, которая вызывает критические ошибки, такие как StackOverflow.

Примеры исключений
Quartz.SchedulerException: Unable to unschedule trigger [DEFAULT.ce86f88b-e2e0-489b-80e2-729fd3bcad31] while deleting job [ProcessMaintenanceGroup.BPMSoft.Configuration.ProcessMaintenanceJob] at Quartz.Core.QuartzScheduler.d__86.MoveNext()

Quartz.JobPersistenceException: Couldn't remove trigger: Transaction (Process ID 78) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction. ---> System.Data.SqlClient.SqlException: Transaction (Process ID 78) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Error executing [BPMSoft.Core.Scheduler.RunAppJob] in context [JobExecutionContext: trigger: 'Mailing.BPMSoft.Configuration.TriggerEmailFailoverHandlerTrigger' job: 'Mailing.BPMSoft.Configuration.TriggerEmailFailoverHandler' fireTimeUtc: 'Wed, 31 Aug 2022 10:35:27 GMT' scheduledFireTimeUtc: 'Wed, 31 Aug 2022 10:35:26 GMT' previousFireTimeUtc: 'Wed, 31 Aug 2022 08:32:27 GMT' nextFireTimeUtc: 'Wed, 31 Aug 2022 10:40:26 GMT' recovering: False refireCount: 0]

Если ошибки не очевидны, то переходите к этапу Проверка конфигурационного файла и выполнение запросов по проблемам Quartz.

Проверка конфигурационного файла и выполнение запросов по проблемам Quartz

Для выявления проблем Quartz выполните в базе данных следующие действия:

  1. Запустите скрипты, которые представлены ниже. Если проблема проявляется явно, запустите их в момент ее возникновения (например, при запуске отложенной рассылки, если прошло 15 минут с запланированного времени, а рассылка не началась, или при запуске бизнес-процесса с промежуточным таймером, если время истекло, а процесс не выполнился). В противном случае запустите скрипты в рабочее время, когда нагрузка на стенд максимальна:
    • Если известно, что Quartz не работает или задача не выполнена давно, а также для мониторинга производительности, раскомментируйте условие и отобразите только просроченные триггеры. Это сократит размер выгрузки;
    • Если проблема связана с бизнес-процессами или их элементами (стартовый таймер, промежуточный таймер, фоновый режим), используйте скрипты для вывода подробной информации. Они выводят название схемы или элемента для каждого триггера (в названии таких триггеров указан только GUID без осмысленного имени).
  2. Если есть подозрения, что планировщик перегружен, например, из-за большого количества просроченных триггеров (overdue_min > 0) или постоянно работающих триггеров (особенно если их 5 или больше), проверьте параметры, установленные в конфигурационном файле.
<add key="quartz.scheduler.instanceName" value="BPMCRMQuartzScheduler" />
<add key="quartz.threadPool.threadCount" value="5" />
<add key="quartz.jobStore.misfireThreshold" value="300000" />
  1. Выполните анализ собранной информации:
    • Если в результатах запроса на получение запланированных триггеров Quartz есть записи с TRIGGER_STATE=ERROR и значением overdue_min больше 60 минут, переходите к этапу Выполнение запроса по исправлению ошибок;
    • Если в запросе на получение запущенных триггеров Quartz среднее количество записей превышает 80% от параметра threadCount в web.config, переходите к этапу Дополнительная диагностика нагрузки на серверы;
    • Если в запросе на получение запланированных триггеров Quartz нет записей с TRIGGER_STATE=ERROR и overdue_min > 0, а в запросе на получение запущенных триггеров Quartz среднее количество записей не превышает 80% от параметра threadPool.threadCount в конфигурационном файле, то обратитесь за консультацией в Техническую поддержку .

Скрипты для получения триггеров

Скрипт получения запланированных триггеров Quartz (для Microsoft SQL Server)
SELECT
  DATEDIFF_BIG(MINUTE, dbo.fn_QuartzTimeToDateTime(t.NEXT_FIRE_TIME), GETUTCDATE()) AS overdue_min,
  GETUTCDATE() AS [now],
  dbo.fn_QuartzTimeToDateTime(t.NEXT_FIRE_TIME) AS next_fire_time,
  dbo.fn_QuartzTimeToDateTime(t.PREV_FIRE_TIME) AS prev_fire_time,
  dbo.fn_QuartzTimeToDateTime(t.START_TIME) AS start_time,
  t.*
FROM qrtz_triggers t WITH (NOLOCK)
-- WHERE dbo.fn_QuartzTimeToDateTime(t.NEXT_FIRE_TIME) < GETUTCDATE()
-- опционально, показать только просроченные
ORDER BY 1 DESC;
Скрипт получения запущенных триггеров Quartz (для Microsoft SQL Server)
SELECT
  DATEDIFF_BIG(MINUTE, dbo.fn_QuartzTimeToDateTime(qft.FIRED_TIME), GETUTCDATE()) AS duration_min,
  GETUTCDATE() AS [now],
  dbo.fn_QuartzTimeToDateTime(qft.FIRED_TIME) AS fired_time,
  dbo.fn_QuartzTimeToDateTime(qft.SCHED_TIME) AS sched_time,
  qft.*
FROM qrtz_fired_triggers qft WITH (NOLOCK)
ORDER BY 1 DESC;
Скрипт получения запланированных триггеров Quartz (для PostgreSQL)
select
  extract(epoch from (now() - to_timestamp(t.next_fire_time/10000000.0 - 62135596800.0))) / 60 as overdue_min,
  now() at time zone 'UTC' as "now",
  to_timestamp(t.next_fire_time/10000000.0 - 62135596800.0) at time zone 'UTC' as next_fire_time,
  to_timestamp(t.prev_fire_time/10000000.0 - 62135596800.0) at time zone 'UTC' as prev_fire_time,
  to_timestamp(t.start_time/10000000.0 - 62135596800.0) at time zone 'UTC' as start_time,
  t.*
from qrtz_triggers t
-- where to_timestamp(t.next_fire_time/10000000.0 - 62135596800.0) < now()
-- опционально, показать только просроченные
order by 1 desc;
Скрипт получения запущенных триггеров Quartz (для PostgreSQL)
select
  extract(epoch from (now() - to_timestamp(qft.fired_time/10000000.0 - 62135596800.0))) / 60 as duration_min,
  now() at time zone 'UTC' as "now",
  to_timestamp(qft.fired_time/10000000.0 - 62135596800.0) at time zone 'UTC' as fired_time,
  to_timestamp(qft.sched_time/10000000.0 - 62135596800.0) at time zone 'UTC' as sched_time,
  now() at time zone 'UTC' as "now",
  qft.*
from qrtz_fired_triggers qft
order by 1 desc;

Скрипты для получения подробной информации по триггерам

Скрипт получения запланированных триггеров Quartz (для Microsoft SQL Server)
SELECT 
  IIF(spel.Id IS NULL, IIF(ss2.Id IS NULL, 'Other', 'Process'), 'Element') AS trigger_source, 
  spel.Caption AS element_name,  
  spl.Id AS sysprocesslog_id, 
  COALESCE(spl.Name, ss2.Caption) AS process_name,  
  COALESCE(ss.Name, ss2.Name) AS schema_name,  
  COALESCE(sp.Name, sp2.Name) AS package_name,  
  GETUTCDATE() AS [now],  
  DATEDIFF_BIG(MINUTE, dbo.fn_QuartzTimeToDateTime(t.PREV_FIRE_TIME), GETUTCDATE()) AS prev_fire_min,  
  DATEDIFF_BIG(MINUTE, GETUTCDATE(), dbo.fn_QuartzTimeToDateTime(t.NEXT_FIRE_TIME)) AS next_fire_min,  
  dbo.fn_QuartzTimeToDateTime(t.NEXT_FIRE_TIME) AS next_fire_time,   
  dbo.fn_QuartzTimeToDateTime(t.PREV_FIRE_TIME) AS prev_fire_time,    
  dbo.fn_QuartzTimeToDateTime(t.START_TIME) AS start_time,    
  t.*  
FROM QRTZ_TRIGGERS t WITH(NOLOCK) 
LEFT JOIN SysProcessElementLog spel WITH(NOLOCK) ON t.JOB_GROUP = CAST(spel.Id as nvarchar(max)) 
LEFT JOIN SysProcessLog spl WITH(NOLOCK) ON spel.SysProcessId = spl.Id 
LEFT JOIN SysSchema ss WITH(NOLOCK) ON spl.SysSchemaId = ss.Id   
LEFT JOIN SysPackage sp WITH(NOLOCK) ON ss.SysPackageId = sp.Id 
LEFT JOIN SysSchema ss2 WITH(NOLOCK) ON t.JOB_GROUP = CAST(ss2.UId as nvarchar(max)) 
LEFT JOIN SysPackage sp2 WITH(NOLOCK) ON ss2.SysPackageId = sp2.Id   
-- WHERE dbo.fn_QuartzTimeToDateTime(t.NEXT_FIRE_TIME) < GETUTCDATE() -- вывести только просроченные 
ORDER BY 9 ASC; 
Скрипт получения запущенных триггеров Quartz (для Microsoft SQL Server)
SELECT   
  IIF(spel.Id IS NULL, IIF(ss2.Id IS NULL, 'Other', 'Process'), 'Element') AS trigger_source, 
  spel.Caption as element_name, 
  spl.Id AS sysprocesslog_id,  
  COALESCE(spl.Name, ss2.Caption) AS process_name,  
  COALESCE(ss.Name, ss2.Name) AS schema_name,  
  COALESCE(sp.Name, sp2.Name) AS package_name,  
  GETUTCDATE() AS [now],   
  DATEDIFF_BIG(MINUTE, dbo.fn_QuartzTimeToDateTime(qft.FIRED_TIME), GETUTCDATE()) AS duration_min,   
  dbo.fn_QuartzTimeToDateTime(qft.FIRED_TIME) AS fired_time,  
  dbo.fn_QuartzTimeToDateTime(qft.SCHED_TIME) AS sched_time, 
  qft.* 
FROM QRTZ_FIRED_TRIGGERS qft WITH(NOLOCK) 
LEFT JOIN SysProcessElementLog spel WITH(NOLOCK) ON qft.TRIGGER_GROUP = CAST(spel.Id as nvarchar(max)) 
LEFT JOIN SysProcessLog spl WITH(NOLOCK) ON spel.SysProcessId = spl.Id 
LEFT JOIN SysSchema ss WITH(NOLOCK) ON spl.SysSchemaId = ss.Id   
LEFT JOIN SysPackage sp WITH(NOLOCK) ON ss.SysPackageId = sp.Id   
LEFT JOIN SysSchema ss2 WITH(NOLOCK) ON qft.TRIGGER_GROUP = CAST(ss2.UId as nvarchar(max)) 
LEFT JOIN SysPackage sp2 WITH(NOLOCK) ON ss2.SysPackageId = sp2.Id   
ORDER BY 8 DESC; 
Скрипт получения запланированных триггеров Quartz (для PostgreSQL)
SELECT 
  CASE WHEN spel."Id" IS NULL  
    THEN CASE WHEN ss2."Id" IS NULL THEN 'Other' ELSE 'Process' END 
    ELSE 'Element' END AS trigger_source,  
  spel."Caption" AS element_name, 
  spl."Id" AS sysprocesslog_id,  
  COALESCE(spl."Name", ss2."Caption") AS process_name,  
  COALESCE(ss."Name", ss2."Name") AS schema_name,  
  COALESCE(sp."Name", sp2."Name") AS package_name,  
  NOW() AS "now",  
  EXTRACT(epoch from (now() - to_timestamp(t.prev_fire_time /10000000.0 - 62135596800.0))) / 60 AS prev_fire_min, 
  EXTRACT(epoch from (to_timestamp(t.next_fire_time/10000000.0 - 62135596800.0) - now())) / 60 AS next_fire_min, 
  to_timestamp(t.next_fire_time/10000000.0 - 62135596800.0) at time zone 'UTC' AS next_fire_time, 
  to_timestamp(t.prev_fire_time/10000000.0 - 62135596800.0) at time zone 'UTC' AS prev_fire_time, 
  to_timestamp(t.start_time/10000000.0 - 62135596800.0) at time zone 'UTC' AS start_time,   
  t.*  
FROM qrtz_triggers t 
LEFT JOIN "SysProcessElementLog" spel ON t.job_group = spel."Id"::text 
LEFT JOIN "SysProcessLog" spl ON spel."SysProcessId" = spl."Id" 
LEFT JOIN "SysSchema" ss ON spl."SysSchemaId" = ss."Id"   
LEFT JOIN "SysPackage" sp ON ss."SysPackageId" = sp."Id" 
LEFT JOIN "SysSchema" ss2 ON t.job_group = ss2."UId"::text 
LEFT JOIN "SysPackage" sp2 ON ss2."SysPackageId" = sp2."Id"   
--WHERE to_timestamp(t.next_fire_time/10000000.0 - 62135596800.0) < NOW() -- вывести только просроченные 
ORDER BY 9 ASC; 
Скрипт получения запущенных триггеров Quartz (для PostgreSQL)
SELECT    
  CASE WHEN spel."Id" IS NULL  
    THEN CASE WHEN ss2."Id" IS NULL THEN 'Other' ELSE 'Process' END 
    ELSE 'Element' END AS trigger_source,  
  spel."Caption" AS element_name, 
  spl."Id" AS sysprocesslog_id,   
  COALESCE(spl."Name", ss2."Caption") AS process_name,   
  COALESCE(ss."Name", ss2."Name") AS schema_name,   
  COALESCE(sp."Name", sp2."Name") AS package_name,   
  NOW() at time zone 'UTC' AS "now", 
  EXTRACT(epoch from (now() - to_timestamp(qft.fired_time/10000000.0 - 62135596800.0))) / 60 AS duration_min,   
  to_timestamp(qft.fired_time/10000000.0 - 62135596800.0) at time zone 'UTC' AS fired_time,   
  to_timestamp(qft.sched_time/10000000.0 - 62135596800.0) at time zone 'UTC' AS sched_time, 
  qft.*  
FROM qrtz_fired_triggers qft 
LEFT JOIN "SysProcessElementLog" spel ON qft.trigger_group = spel."Id"::text 
LEFT JOIN "SysProcessLog" spl ON spel."SysProcessId" = spl."Id"  
LEFT JOIN "SysSchema" ss ON spl."SysSchemaId" = ss."Id"    
LEFT JOIN "SysPackage" sp ON ss."SysPackageId" = sp."Id"    
LEFT JOIN "SysSchema" ss2 ON qft.trigger_group = ss2."UId"::text 
LEFT JOIN "SysPackage" sp2 ON ss2."SysPackageId" = sp2."Id"    
ORDER BY 8 DESC;

Выполнение запроса по исправлению ошибок

Проанализируйте количество и характер ошибочных триггеров. Если их немного, восстановите работу триггеров с помощью запроса:

UPDATE QRTZ_TRIGGERS SET TRIGGER_STATE = 'WAITING' WHERE TRIGGER_STATE = 'ERROR';

После этого повторно выполните скрипт на получение просроченные триггеров Quartz и убедитесь, что статус ERROR сменился на WAITING.

Если ошибочных триггеров много, дополнительно изучите возможные причины и примите решение на основе анализа. При необходимости обратитесь за консультацией в Техническую поддержку .

Дополнительная диагностика нагрузки на серверы

Соберите данные о нагрузке стенда за 3 и более дней и проверьте активные триггеры Quartz.

Примечание. Если стенд работает на нескольких нодах, лимит активных триггеров Quartz будет равен quartz.threadPool.threadCount, умноженному на количество нод.

Если среднее количество активных триггеров (avg) превышает 50% от общего числа потоков (quartz.threadPool.threadCount), указанного в конфигурационном файле, или, если наблюдаются продолжительные периоды (регулярно по несколько часов) максимального использования потоков, как показано на рисунке ниже, увеличьте quartz.threadPool.threadCount.

Рисунок 1 — Лимит клиента в 20 потоков достигается при пиковых нагрузках и длится более 1 часа

Рисунок 1 — Лимит клиента в 20 потоков достигается при пиковых нагрузках и длится более 1 часа

Увеличьте quartz.threadPool.threadCount (примерно на 30-200% в зависимости от нагрузки на очередь и анализа запроса на получение просроченных триггеров Quartz) и повторите этот этап диагностики.

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

Если на графике нет явных завышений, а в запросе на получение просроченных триггеров Quartz отсутствуют зависшие триггеры, но ошибки продолжаются, то проблема не в Quartz.

Если стабильные показатели не достигаются, проконсультируйтесь с Технической поддержкой BPMSoft о горизонтальном масштабировании стенда.

При высокой нагрузке на базу данных в таблицах Quartz или для исключения влияния сопутствующей нагрузки (например, других таблиц системы на таблицы Quartz и наоборот) рассмотрите возможность переноса таблиц Quartz в отдельную базу данных. Подробнее: Перенос таблицы Quartz в отдельную базу данных.

Рекомендуем изучить

Перенос таблицы Quartz в отдельную базу данных

Материал был полезен для вас?
Вебинар: 22 апреля в 11:00
Приглашаем вас на вебинар «BPMSoft – от выбора к реальным процессам», где покажем, как компании на практике сокращают time-to-market и масштабируют бизнес с помощью решений BPMSoft.
Регистрация на мероприятие
Готовы сделать выбор CRM?
Оставьте заявку, и наши эксперты бесплатно проконсультируют вас, подберут подходящую конфигурацию и рассчитают стоимость проекта.
Готовы сделать выбор CRM? (детальная)
Оставьте заявку, и наши эксперты бесплатно проконсультируют вас, подберут подходящую конфигурацию и рассчитают стоимость проекта.
Готовы сделать выбор CRM?
Оставьте заявку, и наши эксперты бесплатно проконсультируют вас, подберут подходящую конфигурацию и рассчитают стоимость проекта.
Готовы сделать выбор CRM? (детальная)
Оставьте заявку, и наши эксперты бесплатно проконсультируют вас, подберут подходящую конфигурацию и рассчитают стоимость проекта.
Оставить заявку
Оставьте свои контакты и наш менеджер свяжется с Вами в ближайшее время.
Демонстрационная версия BPMSoft
Заполните заявку для получения бесплатного доступа к демонстрационному стенду на 14 дней.
Типовое внедрение
Внедрите BPMSoft CRM в свою компанию всего за 8 рабочих дней по фиксированной цене! Заполните заявку для уточнения условий.
Заказать презентацию
Наш менеджер свяжется с Вами в ближайшее время.
Рассчитать стоимость
Задать вопрос
Карта сценариев использования ИИ для управления маркетингом, продажами и сервисом
Заполните форму и мы отправим исследование на E-mail
Присоединяйтесь к партнерской сети BPMSoft
Оставьте свои контакты и наш менеджер свяжется с Вами в ближайшее время
Тип партнерства*
Управление полным жизненным циклом клиента: от генерации лидов и продаж до внедрения, поддержки и продления подписки.
Разработка собственного Приложения – производного программного обеспечения, созданного на платформе BPMSoft (Базовое ПО).
Есть вопросы?
Не нашли для себя подходящую вакансию, или остались вопросы?
*
Есть вопросы?
Не нашли для себя подходящую вакансию, или остались вопросы?
*
Стать образовательным партнёром
Оставьте свои контакты и наш менеджер свяжется с Вами в ближайшее время.
Заявка на консультацию
Оставьте свои контакты и наш менеджер свяжется с Вами в ближайшее время.
Подписка
Спасибо!
Ваша заявка принята.
Наш сотрудник свяжется с вами в течение 1-2 рабочих дней.
Внимание!
Обнаружена ошибка.
Проверьте вашу почту
Для завершения подписки перейдите по ссылке в письме, которое мы только что отправили. Если письма нет во «Входящих», проверьте папку «Спам».
Telegram Подписаться
Уважаемые клиенты! Предупреждаем о случаях недобросовестной конкуренции и мошенничестве в сети Интернет.
Подробнее