Quartznet: 40 % CPU-Spitze aufgrund der Abfrage von QRTZ_LOCKS

Erstellt am 8. Feb. 2021  ·  14Kommentare  ·  Quelle: quartznet/quartznet

Meine App läuft auf .NET Framework 4.7.2 und lief über ein Jahr lang mit einer älteren Quartz-Version 3.0.7 mit geringer CPU-Auslastung. Vor einigen Wochen haben wir Quartz auf 3.2.3 aktualisiert und eine sofortige 40-prozentige CPU-Steigerung festgestellt da diese Abfrage mit der neueren Version viel häufiger ausgeführt wird.

WÄHLEN *
VON QRTZ_LOCKS MIT (UPDLOCK, ROWLOCK)
WHERE SCHED_NAME = @SchedulerName
UND LOCK_NAME = @lockName

Version verwendet

Version: 3.2.3

Reproduzieren

Ich habe keinen Code zum Reproduzieren, aber meine App erstellt einfache Jobs und zu einem bestimmten Zeitpunkt werden 10 Sekunden davon auf 4 verschiedenen VM-Instanzen ausgeführt.

Erwartetes Verhalten

Die CPU soll so bleiben wie bei der älteren Version.

Übrigens werden von Quartz oder unserer App keine Fehler gemeldet, aber dies ist der einzige Nebeneffekt, den wir bei Version 3.2.3 sehen

Alle 14 Kommentare

Können Sie Ihre Scheduler-Factory-Konfiguration posten (Striping-Anmeldeinformationen usw.)?

Sicher.

==== ProzessorScheduler ====

<scheduler name="processorScheduler">
<quartz>
  <property key="quartz.scheduler.instanceName" value="ProcessorScheduler" />
  <property key="quartz.scheduler.instanceId" value="AUTO" />
  <property key="quartz.scheduler.idleWaitTime" value="1000" />
  <property key="quartz.scheduler.exporter.type" value="Quartz.Simpl.RemotingSchedulerExporter, Quartz" />
  <property key="quartz.scheduler.exporter.port" value="1111" />
  <property key="quartz.scheduler.exporter.bindName" value="ProcessorScheduler" />
  <property key="quartz.scheduler.exporter.channelType" value="tcp" />
  <property key="quartz.scheduler.exporter.channelName" value="httpQuartz" />
  <property key="quartz.threadPool.type" value="Quartz.Simpl.DefaultThreadPool, Quartz" />
  <property key="quartz.threadPool.threadCount" value="20" />
  <property key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" />
  <property key="quartz.serializer.type" value="binary" />
  <property key="quartz.jobStore.clustered" value="true" />
  <property key="quartz.jobStore.clusterCheckinInterval" value="1000" />
  <property key="quartz.jobStore.misfireThreshold" value="60000" />
  <property key="quartz.jobStore.dataSource" value="default" />
  <property key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" />
  <property key="quartz.jobStore.tablePrefix" value="QRTZ_" />
  <property key="quartz.jobStore.useProperties" value="true" />
  <property key="quartz.dataSource.default.connectionString" value="Server=xxx;Database=quartz;user id=xxx;PWD=xxx;" />
  <property key="quartz.dataSource.default.provider" value="SqlServer" />
</quartz>
</scheduler>
<scheduler name="notificationScheduler">
<quartz>
  <property key="quartz.scheduler.instanceName" value="notificationScheduler" />
  <property key="quartz.scheduler.instanceId" value="notificationSchedulerInstance" />
  <property key="quartz.scheduler.proxy" value="true" />
  <property key="quartz.scheduler.proxy.address" value="tcp://xxx:2222/notificationScheduler" />
  <property key="quartz.threadPool.type" value="Quartz.Simpl.DefaultThreadPool, Quartz" />
  <property key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" />
  <property key="quartz.jobStore.misfireThreshold" value="60000" />
  <property key="quartz.jobStore.dataSource" value="default" />
  <property key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" />
  <property key="quartz.jobStore.lockHandler.type" value="Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz" />
  <property key="quartz.jobStore.tablePrefix" value="QRTZ_" />
  <property key="quartz.jobStore.useProperties" value="true" />
</quartz>
</scheduler>

==== Benachrichtigungsplaner ====

<scheduler name="notificationScheduler">
<quartz>
  <property key="quartz.scheduler.instanceName" value="notificationScheduler" />
  <property key="quartz.scheduler.instanceId" value="AUTO" />
  <property key="quartz.scheduler.idleWaitTime" value="1000" />
  <property key="quartz.scheduler.exporter.type" value="Quartz.Simpl.RemotingSchedulerExporter, Quartz" />
  <property key="quartz.scheduler.exporter.port" value="2222" />
  <property key="quartz.scheduler.exporter.bindName" value="notificationScheduler" />
  <property key="quartz.scheduler.exporter.channelType" value="tcp" />
  <property key="quartz.scheduler.exporter.channelName" value="httpQuartz" />
  <property key="quartz.threadPool.type" value="Quartz.Simpl.DefaultThreadPool, Quartz" />
  <property key="quartz.threadPool.threadCount" value="20" />
  <property key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" />
  <property key="quartz.serializer.type" value="binary" />
  <property key="quartz.jobStore.clustered" value="true" />
  <property key="quartz.jobStore.clusterCheckinInterval" value="1000" />
  <property key="quartz.jobStore.misfireThreshold" value="60000" />
  <property key="quartz.jobStore.dataSource" value="default" />
  <property key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" />
  <property key="quartz.jobStore.tablePrefix" value="QRTZ_" />
  <property key="quartz.jobStore.useProperties" value="true" />
  <property key="quartz.dataSource.default.connectionString" value="Server=xxx;Database=quartz;user id=xxx;PWD=xxx;" />
  <property key="quartz.dataSource.default.provider" value="SqlServer" />
</quartz>
</scheduler>

Ich werde einige Zeit brauchen, um nachzuforschen, aber ich denke, die größte Änderung war, dass die Abfrage parametrisiert wurde, wodurch es auch so aussieht, als würde sie zweimal häufiger ausgeführt, wenn Sie zwei separate Scheduler haben (früher gab es für jeden einen anderen SQL-String). Ihnen).

Das ist richtig, ich habe mehrere Scheduler, die alle ähnliche Konfigurationen wie oben haben.
Danke, dass Sie sich das angesehen haben.

Hallo Marko, Kontaktaufnahme für ein Update?

Ich hoffe, dass ich dieses Wochenende Zeit zum Arbeiten habe. Ich habe nichts Offensichtliches gefunden, das eine solche Leistungsregression verursacht, aber ich denke, es gibt etwas zwischen 2.x und 3.x, das verbessert werden kann.

Hallo Marko, hattest du die Gelegenheit, dir diesen Kumpel anzuschauen?

Tut mir leid, bisher keine großen Gewinne. Ich habe dies mit SQL Server DBA besprochen und er konnte beim Testen keine offensichtlichen Gründe finden, daher sollte es nicht auf der DB-Seite liegen, da es auf derselben Datenbankseite ausgeführt wird, die superschnell sein sollte (kleine Zeilenanzahl, zwei Spalten). . Wenn Sie Zeit für ein Profil haben, um etwas Offensichtliches zu lokalisieren, das mir fehlt, wäre das super.

Leider können wir kein Profil erstellen. Aber ich habe noch eine andere Erkenntnis, die ich mit Ihnen teilen möchte.
Vor dem Tag, an dem wir unsere App mit dem aktualisierten Quartz veröffentlichten, wurde diese Abfrage 250.000 Mal am Tag ausgeführt. Unmittelbar nach der Veröffentlichung von Quartz v3.0.7 stiegen die Abfrageausführungszeiten auf 350.000 Mal pro Tag (ein Sprung von 125.000). Unser Anwendungsdatenverkehr hat sich kein bisschen verändert.

Außerdem haben wir festgestellt, dass Ihre Abfrage UPDLOCK, ROWLOCK in einer SELECT-Anweisung ausführt. Gibt es einen Grund, warum dies erforderlich ist, wenn sie nicht aktualisiert wird? Ich denke, wenn Sie dies in NOLOCK ändern, wäre es besser.

WÄHLEN *
VON QRTZ_LOCKS MIT (UPDLOCK, ROWLOCK)
WHERE SCHED_NAME = @SchedulerName
UND LOCK_NAME = @lockName

Etwas in v3.0.7 führt dazu, dass diese Abfrage häufiger ausgeführt wird.

Ich habe gerade noch einmal mit meinem DBA gesprochen und er hat noch etwas weiter gegraben und herausgefunden, dass in der alten Version diese UPDATE-Abfrage verwendet wurde, um ausgeführt zu werden, und seit dem Upgrade ist diese Abfrage verschwunden und das neue SELECT oben wurde angezeigt.

Beachten Sie auch, dass Sched_name als Text und nicht als Parameter ausgegeben wurde.

( @lockName nvarchar(14))
QRTZ_LOCKS AKTUALISIEREN
SET LOCK_NAME = LOCK_NAME
WHERE SCHED_NAME = 'MaintenanceScheduler' UND LOCK_NAME = @lockName

Und die alte SELECT-Anweisung wurde mit der neuen verglichen.

Vor dem 27. Januar
( @lockName nvarchar(14))
SELECT * FROM QRTZ_LOCKS MIT (UPDLOCK,ROWLOCK)
WHERE SCHED_NAME = 'Wartungsplaner'
UND LOCK_NAME = @lockName

Hoffe das hilft.

Vielen Dank für das Update und die Informationen.

Außerdem haben wir festgestellt, dass Ihre Abfrage UPDLOCK, ROWLOCK in einer SELECT-Anweisung ausführt. Gibt es einen Grund, warum dies erforderlich ist, wenn sie nicht aktualisiert wird? Ich denke, wenn Sie dies in NOLOCK ändern, wäre es besser.

Dies würde bedeuten, dass es keine Schlösser zum Schutz vor gleichzeitigem Zugriff geben würde, also würde ich nicht dorthin gehen 😉

Beachten Sie auch, dass Sched_name als Text und nicht als Parameter ausgegeben wurde.

Dies ist genau das Verhalten, das nach der Zusammenführung von #818 beabsichtigt war. Sie sollten jetzt viel mehr Abfragen mit genau diesem SQL sehen, wenn Sie mehrere Planer haben, verwenden sie alle denselben Abfrageplan, dank der Verwendung des Abfrageparameters, anstatt den Planernamen fest in die Abfrage zu codieren, was zu unterschiedlichen Plänen führt.

Erneutes Lesen Ihrer notificationScheduler Konfiguration:

<property key="quartz.jobStore.lockHandler.type" value="Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz" />

Dadurch wird die für SQL Server vorgesehene optimierte SQL-Anweisung nicht verwendet. Siehe https://github.com/quartznet/quartznet/blob/f612e3f66ab27e221b5632269ef5c25c0fe8bcc5/src/Quartz/Impl/AdoJobStore/JobStoreSupport.cs#L492 -L521 für die Logik.

Ich habe diese Eigenschaft entfernt und auch auf die neueste Version aktualisiert, werde Sie wissen lassen, wie sie sich verhält.

Hallo Marko, nur ein Update. Die CPU verhält sich nach den oben genannten Updates wieder normal.
Danke für deine Hilfe Kumpel.

Schön zu hören, danke für das Schließen der Schleife.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen