Files
Monorepo/Service.EDMIService/Scheduler/Scheduler.vb
Developer01 8220852e56 Verbesserung von Performance und Monitoring
Neue Performance-Metriken in `EDMIService` hinzugefügt, inkl. Felder für Job-Dauer, langsame Queries und Timestamps. `ServiceStatusResponse` um diese Metriken erweitert.
`DatatableJob` optimiert: `<DisallowConcurrentExecution>` hinzugefügt, Logging für langsame Queries (>10s) und Jobs (>30s) verbessert, sowie Gesamt-Dauer-Messung integriert.
`Scheduler` nutzt nun dedizierte MSSQL-Instanz zur Entkopplung von Datenbankoperationen. Thread-Pool-Größe reduziert und gestaffeltes Starten von INIT-Jobs eingeführt.
Connection-Strings in `WindowsService` angepasst: `ApplicationName` spezifisch gesetzt und `MaxPoolSize` erhöht. Allgemeine Logging-Verbesserungen für bessere Skalierbarkeit und Stabilität.
2026-10-13 12:04:13 +02:00

148 lines
6.3 KiB
VB.net
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Imports System.Collections.Specialized
Imports System.Data.SqlClient
Imports DigitalData.Modules.Database
Imports DigitalData.Modules.Logging
Imports Quartz
Imports Quartz.Impl
Imports Quartz.Impl.Matchers
Imports Quartz.Logging
Public Class Scheduler
Inherits BaseClass
Private ReadOnly _Factory As StdSchedulerFactory
Private ReadOnly _SchedulerMSSQL As MSSQLServer ' ✅ Dedizierte Connection für alle Scheduler-Operationen
Private _Scheduler As IScheduler
Private ReadOnly _JobListener As JobListener
Private ReadOnly _GlobalState As GlobalState
Private ReadOnly _Props = New NameValueCollection From {
{"quartz.serializer.type", "binary"},
{"quartz.threadPool.maxConcurrency", 2} ' ← von 3 auf 2
}
Private Const JOB_GROUP As String = "DatatableJobs"
Public Sub New(pLogConfig As LogConfig, pMSSQL_Config As MSSQLServer, pTableStore As DataSet, pGlobalState As GlobalState)
MyBase.New(pLogConfig)
_Factory = New StdSchedulerFactory(_Props)
' ✅ Separater Connection-Pool ausschließlich für Scheduler-Jobs
' Application Name weicht ab → ADO.NET legt eigenständigen Pool an
' → Scheduler-Queries blockieren nie die WCF-Verbindungen des Service
Dim oBuilder As New SqlConnectionStringBuilder(pMSSQL_Config.CurrentConnectionString)
oBuilder.ApplicationName = "EDMIService-Scheduler"
oBuilder.MaxPoolSize = 2 ' ← NEU: begrenzt SQL Server-Last unter Volllast
_SchedulerMSSQL = New MSSQLServer(pLogConfig, oBuilder.ToString())
Logger.Info("Scheduler: dedicated MSSQLServer instance created | AppName=[{0}]", oBuilder.ApplicationName)
_JobListener = New JobListener(pLogConfig, _SchedulerMSSQL, pTableStore)
_GlobalState = pGlobalState
_GlobalState.JobListener = _JobListener
Logger.Debug("JobListener registered in GlobalState")
Logging.LogProvider.SetCurrentLogProvider(New LogProvider(Logger))
End Sub
Public Async Sub Start()
_Scheduler = Await _Factory.GetScheduler()
_Scheduler.ListenerManager.AddJobListener(_JobListener,
GroupMatcher(Of JobKey).GroupEquals(JOB_GROUP))
Await _Scheduler.Start()
Dim oCronjobs As DataTable = Await GetCronJobs()
Try
If oCronjobs IsNot Nothing Then
Logger.Debug("Loaded {0} cron jobs", oCronjobs.Rows.Count)
For Each oRow As DataRow In oCronjobs.Rows
Dim oDefinition As String = oRow.Item("CRON_DEFINITION")
Dim oTitle As String = oRow.Item("TITLE")
Dim oGuid As Integer = oRow.Item("GUID")
Logger.Debug("Trying to schedule a new job forr Cron Job [{0}] ...", oTitle)
Dim oCronTrigger As ITrigger
Dim oInitTrigger As ITrigger
Dim oJob As IJobDetail
Dim oInitJob As IJobDetail
Dim oJobData As New JobDataMap From {
{"LogConfig", LogConfig},
{"MSSQL", _SchedulerMSSQL},
{"CronJobId", oGuid},
{"CronJobTitle", oTitle}
}
' INIT-Job: gestaffelt starten – 15s Abstand statt 10s
' Bei maxConcurrency=2 laufen max. 2 Jobs gleichzeitig
Dim oInitDelaySeconds As Integer = oCronjobs.Rows.IndexOf(oRow) * 15
Dim oIdentity As String = $"CRON-JOB-{oGuid}"
oCronTrigger = TriggerBuilder.Create().
WithIdentity(oIdentity & "-TRIGGER", JOB_GROUP).
WithCronSchedule(oDefinition).
Build()
oJob = JobBuilder.Create(Of DatatableJob)().
WithIdentity(oIdentity, JOB_GROUP).
UsingJobData(oJobData).
Build()
Dim oNextFireTime As DateTimeOffset? = Await _Scheduler.ScheduleJob(oJob, oCronTrigger)
Logger.Info("Cron Job [{0}] -> next scheduled run: {1}", oTitle,
If(oNextFireTime.HasValue, oNextFireTime.Value.LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss"), "unknown"))
' Also create variant of the job that runs immediately, once
Dim oInitIdentity As String = $"CRON-JOB-INIT-{oGuid}"
oInitTrigger = TriggerBuilder.Create().
WithIdentity(oInitIdentity & "-TRIGGER", JOB_GROUP).
StartAt(DateTimeOffset.Now.AddSeconds(oInitDelaySeconds)). ' ✅ war: StartNow()
Build()
oInitJob = JobBuilder.Create(Of DatatableJob)().
WithIdentity(oInitIdentity, JOB_GROUP).
UsingJobData(oJobData).
Build()
Await _Scheduler.ScheduleJob(oInitJob, oInitTrigger)
Logger.Debug("Scheduled a new job for Cron Job [{0}]", oTitle)
Next
Logger.Info("✔ Scheduler started with {0} cron jobs", oCronjobs.Rows.Count)
Else
Logger.Warn("⚠ CronJobs could not be fetched!")
End If
Catch ex As Exception
Logger.Error(ex)
Logger.Warn("⚠ Unexpected Error while setting up scheduler: " & ex.Message)
End Try
End Sub
Public Async Function GetCronJobs() As Task(Of DataTable)
Try
Dim oSQL As String = "SELECT * FROM TBAPPSERV_CRON_JOB WITH (NOLOCK) WHERE ACTIVE = 1"
Return Await _SchedulerMSSQL.GetDatatableAsync(oSQL) ' ✅ dedizierte Instanz
Catch ex As Exception
Return Nothing
End Try
End Function
Public Async Function GetCronJobDetails(CronJobId As Integer) As Task(Of DataTable)
Try
Dim oSQL As String = $"SELECT * FROM TBAPPSERV_CRON_DETAIL WITH (NOLOCK) WHERE CRON_ID = {CronJobId}"
Return Await _SchedulerMSSQL.GetDatatableAsync(oSQL) ' ✅ dedizierte Instanz
Catch ex As Exception
Return Nothing
End Try
End Function
Public Async Sub [Stop]()
Await _Scheduler.Shutdown()
End Sub
End Class