スケジューラーリファレンスガイド
最近のアプリケーションでは、特定のタスクを定期的に実行する必要があることがよくあります。Quarkusには、2つのスケジューラエクステンションがあります。 quarkus-scheduler エクステンションは、APIと軽量のインメモリスケジューラー実装を提供します。 quarkus-quartz エクステンションは、 quarkus-scheduler エクステンションのAPIを実装し、Quartzライブラリに基づくスケジューラ実装を含んでいます。 quarkus-quartz が必要になるのは、永続タスクやクラスタリングなど、より高度なスケジューリングのユースケースだけです。
プロジェクトに quarkus-quartz 依存関係を追加すると、 quarkus-scheduler エクステンションからの軽量スケジューラーの実装は自動的に無効になります。
|
1. スケジュールされたメソッド
@io.quarkus.scheduler.Scheduled でアノテーションされたメソッドは、自動的に呼び出しがスケジュールされます。スケジュールされたメソッドは、abstractやprivateであってはなりません。静的または非静的であっても大丈夫です。スケジュールされたメソッドには、 @jakarta.transaction.Transactional や @org.eclipse.microprofile.metrics.annotation.Counted のようなインターセプターバインディングをアノテーションすることができます。
スコープを持たないBeanクラスで、 @Scheduled でアノテーションされた非静的メソッドが少なくとも一つ宣言されている場合、 @Singleton が使用されます。
|
さらに、アノテーションされたメソッドは void を返し、パラメーターを宣言しないか、 io.quarkus.scheduler.ScheduledExecution 型のパラメーターを 1 つだけ宣言する必要があります。
| アノテーションは繰り返し可能なので、1つのメソッドを複数回スケジュールすることができます。 |
|
サブクラスは、スーパークラスで宣言された
|
io.quarkus.scheduler.SuccessfulExecution 型の CDI イベントは、スケジュールされたメソッドの実行が成功したときに、同期および非同期で発生します。 io.quarkus.scheduler.FailedExecution タイプの CDI イベントは、スケジュールされたメソッドの実行が例外をスローしたときに、同期および非同期で発生します。
1.1. トリガー
トリガーは、 @Scheduled#cron() 属性または @Scheduled#every() 属性によって定義されます。両方が指定されている場合、cron 式が優先されます。何も指定されていない場合、ビルドは IllegalStateException で失敗します。
1.1.1. CRON
CRONトリガーは、cronライクな式で定義されています。例えば "0 15 10 * * ?" は毎日午前10時15分に起動します。
@Scheduled(cron = "0 15 10 * * ?")
void fireAt10AmEveryDay() { }
CRON 式で使用される構文は quarkus.scheduler.cron-type プロパティーによって制御されます。 値は cron4j , quartz , unix および spring のいづれかです。 quartz がデフォルトで使用されます。
cron 属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式 をサポートしています。(なお、"{property.path}"スタイルの表現もサポートされていますが、プロパティ式の完全な機能は提供されていません)。
@Scheduled(cron = "${myMethod.cron.expr}")
void myMethod() { }
特定のスケジュールメソッドを無効にしたい場合は、そのcron式を "off" または "disabled" に設定します。
myMethod.cron.expr=disabled
プロパティ式では、そのプロパティが構成されていない場合に使用されるデフォルト値を定義することができます。
0 0 15 ? * MON * のCRON設定プロパティーの例@Scheduled(cron = "${myMethod.cron.expr:0 0 15 ? * MON *}")
void myMethod() { }
プロパティ myMethod.cron.expr が未定義または null の場合は、デフォルト値( 0 0 15 ? * MON * )が使用されます。
1.1.1.1. タイムゾーン
cron式は、デフォルトのタイムゾーンのコンテキストで評価されます。しかし、cron式を特定のタイムゾーンに関連付けることも可能です。
@Scheduled(cron = "0 15 10 * * ?", timeZone = "Europe/Prague") (1)
void myMethod() { }
| 1 | タイムゾーンIDは、 java.time.ZoneId#of(String) を使って解析されます。 |
timeZone 属性はデフォルト値とネストされた値のプロパティー式を含む
Property Expressions をサポートします。
@Scheduled(cron = "0 15 10 * * ?", timeZone = "{myMethod.timeZone}")
void myMethod() { }
1.1.2. インターバル
インターバルトリガーは、呼び出しの間の期間を定義します。期間式は ISO-8601 の期間フォーマット PnDTnHnMn.nS に基づいており、 @Scheduled#every() の値は java.time.Duration#parse(CharSequence) で解析されます。ただし、式が数字で始まる場合は、 PT の接頭辞が自動的に追加されます。例えば、 PT15M の代わりに 15m を使用することができ、「15 分」と解析されます。
@Scheduled(every = "15m")
void every15Mins() { }
| 1 秒未満の値は、基盤となるスケジューラー実装ではサポートされていない可能性があります。その場合、ビルドおよびアプリケーションの起動時に警告メッセージが記録されます。 |
every 属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式 をサポートしています。(なお、 "{property.path}" スタイル式はサポートされていますが、プロパティ式の完全な機能は提供されていません)。
@Scheduled(every = "${myMethod.every.expr}")
void myMethod() { }
Intervals は、その値を "off" または "disabled" に設定することで無効にすることができます。そのため、例えば、デフォルト値 "off" のプロパティ式は、そのConfig Propertyが設定されていない場合、トリガーを無効にするために使用することができます。
@Scheduled(every = "${myMethod.every.expr:off}")
void myMethod() { }
1.2. アイデンティティ
デフォルトでは、スケジュールされた各メソッドに対して一意の ID が生成されます。このIDはログメッセージやデバッグ中に使用されます。時には、明示的な id を指定することが便利な場合もあります。
@Scheduled(identity = "myScheduledMethod")
void myMethod() { }
identity 属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式をサポートしています。(なお、 "{property.path}" スタイル式はサポートされていますが、プロパティ式の完全な機能は提供されていません)。
@Scheduled(identity = "${myMethod.identity.expr}")
void myMethod() { }
1.3. 遅延実行
@Scheduled では、トリガーが発報を開始時刻を遅らせるための2つの方法を提供しています。
@Scheduled#delay() と @Scheduled#delayUnit() は初期遅延を一緒に形成します。
@Scheduled(every = "2s", delay = 2, delayUnit = TimeUnit.HOUR) (1)
void everyTwoSeconds() { }
| 1 | アプリケーション開始から2時間後に初めてトリガーが発射されます。 |
| 最終的な値は常に1秒単位で丸められます。 |
@Scheduled#delayed() は、上記のプロパティーのテキスト形式での代替です。ピリオド式は ISO-8601 duration format PnDTnHnMn.nS に基づいており、値は java.time.Duration#parse(CharSequence) で解析されます。ただし、式が数字で始まる場合は、 PT の接頭辞が自動的に追加されます。そのため、例えば PT15S の代わりに 15s を使用することができ、「15 秒」と解析されます。
@Scheduled(every = "2s", delayed = "2h")
void everyTwoSeconds() { }
@Scheduled#delay() がゼロよりも大きい値に設定されている場合、 @Scheduled#delayed() の値は無視されます。
|
@Scheduled#delay() と比較した場合の主な利点は、値が設定可能であることです。 delay 属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式をサポートしています。(なお、 "{property.path}" スタイルの表現はサポートされていますが、プロパティ式の完全な機能は提供されていません)。
@Scheduled(every = "2s", delayed = "${myMethod.delay.expr}") (1)
void everyTwoSeconds() { }
| 1 | 遅延の設定には、configプロパティー myMethod.delay.expr を使用します。 |
1.4. 同時実行
デフォルトでは、スケジュールされたメソッドは同時に実行することができます。それにもかかわらず、 @Scheduled#concurrentExecution() を通じて同時実行を処理するための戦略を指定することが可能です。
import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP;
@Scheduled(every = "1s", concurrentExecution = SKIP) (1)
void nonConcurrent() {
// we can be sure that this method is never executed concurrently
}
| 1 | 同時実行はスキップされます。 |
スケジュールされたメソッドの実行がスキップされると、 io.quarkus.scheduler.SkippedExecution のタイプのCDIイベントが発生します。
|
| 同じアプリケーションインスタンス内での実行のみが考慮されることに注意してください。この機能は、クラスター間での動作を意図したものではありません。 |
1.5. 条件付き実行
@Scheduled#skipExecutionIf() を使用して、スケジュールされたメソッドの実行をスキップするロジックを定義できます。
指定されたクラスは io.quarkus.scheduler.Scheduled.SkipPredicate を実装する必要があり、 test() メソッドの結果が true の場合、実行はスキップされます。
クラスは CDI Bean を表すか、引数なしのパブリックコンストラクターを宣言する必要があります。
CDI の場合、Bean タイプのセット内に指定されたクラスを持つ Bean が 1 つだけ存在する必要があります。そうでない場合、ビルドは失敗します。
さらに、ジョブの実行中は Bean のスコープがアクティブである必要があります。
スコープが @Dependent の場合、Bean インスタンスは特定のスケジュールメソッドだけに属し、アプリケーションがシャットダウンされると破棄されます。
class Jobs {
@Scheduled(every = "1s", skipExecutionIf = MyPredicate.class) (1)
void everySecond() {
// do something every second...
}
}
@Singleton (2)
class MyPredicate implements SkipPredicate {
@Inject
MyService service;
boolean test(ScheduledExecution execution) {
return !service.isStarted(); (3)
}
}
| 1 | MyPredicate.class の Bean インスタンスは、実行をスキップすべきかどうかを評価するために使用されます。Beanタイプのセットに指定されたクラスを持つBeanが正確に1つ存在する必要があり、そうでない場合はビルドが失敗します。 |
| 2 | Beanのスコープは、実行中を通じてアクティブです。 |
| 3 | Jobs.everySecond() は、 MyService.isStarted() が true を返すまでスキップされます。 |
なお、これは以下のコードと同等のものです:
class Jobs {
@Inject
MyService service;
@Scheduled(every = "1s")
void everySecond() {
if (service.isStarted()) {
// do something every second...
}
}
}
主なアイデアは、スケジュールされたビジネスメソッドの外で実行をスキップするロジックを維持することで、再利用やリファクタリングが容易にできるようにすることです。
スケジュールされたメソッドの実行がスキップされると、 io.quarkus.scheduler.SkippedExecution のタイプのCDIイベントが発生します。
|
アプリケーションの起動/シャットダウン中にスケジュールされた実行をスキップするには、 io.quarkus.scheduler.Scheduled.ApplicationNotRunning のスキップ条件を使用できます。
|
1.6. ノンブロッキング方式
デフォルトでは、スケジュールされたメソッドは、ブロッキングタスク用のメインエクゼキュータ上で実行されます。そのため、Vert.xイベントループ上で実行するように設計された技術(Hibernate Reactiveなど)は、メソッド本体内で使用することができません。このため、 java.util.concurrent.CompletionStage<Void> または io.smallrye.mutiny.Uni<Void> を返す、あるいは @io.smallrye.common.annotation.NonBlocking でアノテーションされたスケジュール型メソッドは、代わりに Vert.x のイベントループで実行されます。
class Jobs {
@Scheduled(every = "1s")
Uni<Void> everySecond() { (1)
// ...do something async
}
}
| 1 | リターンタイプ Uni<Void> は、Vert.xのイベントループでメソッドを実行するようにスケジューラに指示します。 |
2. スケジューラー
Quarkusは、 io.quarkus.scheduler.Scheduler 型のビルトインBeanを提供しており、これを注入してスケジューラーを一時停止/再開するために使用することができます。
import io.quarkus.scheduler.Scheduler;
class MyService {
@Inject
Scheduler scheduler;
void ping() {
scheduler.pause(); (1)
scheduler.pause("myIdentity"); (2)
if (scheduler.isRunning()) {
throw new IllegalStateException("This should never happen!");
}
scheduler.resume("myIdentity"); (3)
scheduler.resume(); (4)
scheduler.getScheduledJobs(); (5)
Trigger jobTrigger = scheduler.getScheduledJob("myIdentity"); (6)
if (jobTrigger != null && jobTrigger.isOverdue()){ (7)
// the job is late to the party.
}
}
}
| 1 | すべてのトリガーを一時停止します。 |
| 2 | スケジュールされた特定のメソッドをそのIDで一時停止する |
| 3 | スケジュールされた特定のメソッドを、そのIDで再開する |
| 4 | スケジューラーを再開します。 |
| 5 | スケジューラーを再開します。 |
| 6 | スケジュールされた特定のメソッドを、そのIDで再開する。 |
| 7 | isOverdue()の猶予期間は、quarkus.scheduler.overdue-grace-periodを使用して設定することができます |
CDIイベントは、スケジューラーやスケジュールされたジョブが一時停止/再開されたときに、同期および非同期で発生します。ペイロードはそれぞれ io.quarkus.scheduler.SchedulerPaused , io.quarkus.scheduler.SchedulerResumed , io.quarkus.scheduler.ScheduledJobPaused , io.quarkus.scheduler.ScheduledJobResumed です。
|
3. プログラムスケジューリング
注入された io.quarkus.scheduler.Scheduler は、プログラム的にジョブをスケジュールするために使用することもできます。
import io.quarkus.scheduler.Scheduler;
@ApplicationScoped
class MyJobs {
@Inject
Scheduler scheduler;
void addMyJob() { (1)
scheduler.newJob("myJob")
.setCron("0/5 * * * * ?")
.setTask(executionContext -> { (2)
// do something important every 5 seconds
})
.schedule(); (3)
}
void removeMyJob() {
scheduler.unscheduleJob("myJob"); (4)
}
}
| 1 | これは、 @Scheduled(identity = "myJob", cron = "0/5 * * * * ?") でアノテーションされたメソッドに代わるプログラム的な代替です。 |
| 2 | ビジネスロジックはコールバックで定義されます。 |
| 3 | JobDefinition#schedule() メソッドが呼び出されると、ジョブがスケジュールされます。 |
| 4 | プログラムによって追加されたジョブも削除することができます。 |
デフォルトでは、 @Scheduled ビジネスメソッドが見つからない限り、スケジューラは開始されません。「純粋な」プログラムによるスケジューリングでは、 quarkus.scheduler.start-mode=forced を使ってスケジューラを強制的に起動させる必要があるかもしれません。
|
| Quartz エクステンション が存在し、DB ストアタイプが使用されている場合、タスクインスタンスをジョブ定義に渡すことはできず、代わりにタスククラスを使用する必要があります。Quartz API を使用して、プログラムでジョブをスケジュールすることもできます。 |
4. スケジュールされたメソッドとテスト
テストを実行する際には、スケジューラーを無効にすることが望ましいことがよくあります。スケジューラーは、ランタイム設定プロパティー quarkus.scheduler.enabled を通じて無効にすることができます。 false に設定すると、アプリケーションにスケジュールされたメソッドが含まれていても、 スケジューラーは起動しません。特定の テストプロファイル に対してスケジューラーを無効にすることもできます。
5. メトリクス
quarkus.scheduler.metrics.enabled が true に設定されていて、metrics エクステンションが存在する場合、いくつかの基本的なメトリクスがすぐに公開されます。
Micrometer エクステンション が存在する場合、すべての @Scheduled メソッドに @io.micrometer.core.annotation.Timed インターセプター・バインディングが自動的に追加され (すでに存在する場合を除く)、 scheduled.methods という名前の io.micrometer.core.instrument.Timer と scheduled.methods.running という名前の io.micrometer.core.instrument.LongTaskTimer が登録されます。宣言するクラスの修飾子名と @Scheduled メソッド名がタグとして使用されます。
SmallRye Metrics エクステンションが存在する場合、 @org.eclipse.microprofile.metrics.annotation.Timed インターセプターバインディングがすべての @Scheduled メソッドに自動的に追加され(すでに存在している場合を除く)、 org.eclipse.microprofile.metrics.Timer が各 @Scheduled メソッドに対して作成されます。名前は、宣言したクラスの完全修飾名と @Scheduled メソッドの名前で構成されます。タイマーには、タグ scheduled=true が付いています。
6. OpenTelemetry Tracing
quarkus.scheduler.tracing.enabled を true に設定し、OpenTelemetry エクステンション が存在する場合、 @Scheduled アノテーションで定義されているか、プログラムでスケジュールされているかに関係なく、ジョブの アイデンティティ をもとにした名前でスパンが自動的に作成されます。
7. 仮想スレッド上で @Scheduled メソッドを実行
@Scheduled でアノテーションされたメソッドは、 @RunOnVirtualThread でアノテーションすることもできます。
この場合、メソッドは仮想スレッド上で呼び出されます。
メソッドは void を返す必要があり、Java ランタイムは仮想スレッドのサポートを提供する必要があります。
詳細については、 仮想スレッドガイド をお読みください。
8. 設定リファレンス
ビルド時に固定される設定プロパティ - 他のすべての設定プロパティは実行時にオーバーライド可能
タイプ |
デフォルト |
|
|---|---|---|
The syntax used in CRON expressions. Environment variable: Show more |
|
|
Scheduled task metrics will be enabled if a metrics extension is present and this value is true. Environment variable: Show more |
boolean |
|
Controls whether tracing is enabled. If set to true and the OpenTelemetry extension is present, tracing will be enabled, creating automatic Spans for each scheduled task. Environment variable: Show more |
boolean |
|
If schedulers are enabled. Environment variable: Show more |
boolean |
|
Scheduled task will be flagged as overdue if next execution time is exceeded by this period. Environment variable: Show more |
|
|
Scheduler can be started in different modes. By default, the scheduler is not started unless a Environment variable: Show more |
|
|
期間フォーマットについて
期間の値を書くには、標準の 数字で始まる簡略化した書式を使うこともできます:
その他の場合は、簡略化されたフォーマットが解析のために
|