Scheduler Reference Guide

最近のアプリケーションでは、定期的に特定のタスクを実行する必要があります。Quarkusには2つのスケジューラーエクステンションがあります。 quarkus-scheduler エクステンションには、APIと軽量なインメモリースケジューラーの実装が含まれています。 quarkus-quartz エクステンションは、 quarkus-scheduler エクステンションのAPIを実装し、Quartzライブラリをベースにしたスケジューラーの実装が含まれています。 quarkus-quartz が必要になるのは、永続的タスク、クラスターリング、ジョブのプログラマティックスケジューリングなど、より高度なスケジューリングのユースケースのみです。

If you add the quarkus-quartz dependency to your project the lightweight scheduler implementation from the quarkus-scheduler extension is automatically disabled.

1. スケジュールされたメソッド

メソッドに @io.quarkus.scheduler.Scheduled をアノテーションすると、自動的に呼び出されるようにスケジュールされます。実際には、そのようなメソッドはCDI Beanの非プライベート非静的メソッドでなければなりません。CDI Beanのメソッドであることの結果として、スケジュールされたメソッドは、 @javax.transaction.Transactional や @org.eclipse.microprofile.metrics.annotation.Counted のようなインターセプターバインディングでアノテーションすることができます。

If there is a bean class that has no scope and declares at least one non-static method annotated with @Scheduled then @Singleton is used.

さらに、アノテーションされたメソッドは void を返し、パラメーターを宣言しないか、 io.quarkus.scheduler.ScheduledExecution 型のパラメーターを 1 つだけ宣言する必要があります。

The annotation is repeatable so a single method could be scheduled multiple times.

サブクラスは、スーパークラスで宣言された @Scheduled メソッドのメタデータを継承することはありません。次の例では、 everySecond() メソッドは Jobs のインスタンスに対してのみ呼び出されます。

class Jobs {

   @Scheduled(every = "1s")
   void everySecond() {
     // ..do something
   }
}

@Singleton
class MyJobs extends Jobs {
}

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分に起動します。

CRON Trigger Example
@Scheduled(cron = "0 15 10 * * ?")
void fireAt10AmEveryDay() { }

CRON 式で使用される構文は quarkus.scheduler.cron-type プロパティーによって制御されます。 値は cron4j , quartz , unix および spring のいづれかです。 quartz がデフォルトで使用されます。

cron 属性は、デフォルト値とネストされた値プロパティー式を含むProperty Expressions をサポートします。 ("{property.path}" スタイルの式は引き続きサポートされますが、プロパティー式の機能がすべて提供されるわけではありません。)

CRON Config Property Example
@Scheduled(cron = "${myMethod.cron.expr}")
void myMethod() { }

特定のスケジュールメソッドを無効にしたい場合は、そのcron式を "off" または "disabled" に設定します。

application.properties
myMethod.cron.expr=disabled

プロパティ式では、そのプロパティが構成されていない場合に使用されるデフォルト値を定義することができます。

CRON Config Property Example with default 0 0 15 ? * MON *
@Scheduled(cron = "${myMethod.cron.expr:0 0 15 ? * MON *}")
void myMethod() { }

プロパティ myMethod.cron.expr が未定義または null の場合は、デフォルト値( 0 0 15 ? * MON * )が使用されます。

1.1.2. インターバル

インターバルトリガーは、呼び出しの間の期間を定義します。期間式は ISO-8601 の期間フォーマット PnDTnHnMn.nS に基づいており、 @Scheduled#every() の値は java.time.Duration#parse(CharSequence) で解析されます。ただし、式が数字で始まる場合は、 PT の接頭辞が自動的に追加されます。例えば、 PT15M の代わりに 15m を使用することができ、「15 分」と解析されます。

Interval Trigger Example
@Scheduled(every = "15m")
void every15Mins() { }

every 属性は、デフォルト値とネストされた値プロパティー式を含むProperty Expressions をサポートします。 ("{property.path}" スタイルの式は引き続きサポートされますが、プロパティー式の機能がすべて提供されるわけではありません。)

Interval Config Property Example
@Scheduled(every = "${myMethod.every.expr}")
void myMethod() { }

Intervals は、その値を "off" または "disabled" に設定することで無効にすることができます。そのため、例えば、デフォルト値 "off" のプロパティ式は、そのConfig Propertyが設定されていない場合、トリガーを無効にするために使用することができます。

Interval Config Property Example with a Default Value
@Scheduled(every = "${myMethod.every.expr:off}")
void myMethod() { }

1.2. アイデンティティ

デフォルトでは、スケジュールされた各メソッドに対して一意の ID が生成されます。このIDはログメッセージやデバッグ中に使用されます。時には、明示的な id を指定することが便利な場合もあります。

Identity Example
@Scheduled(identity = "myScheduledMethod")
void myMethod() { }

identity 属性は、デフォルト値とネストされた値プロパティー式を含むProperty Expressions をサポートします。 ("{property.path}" スタイルの式は引き続きサポートされますが、プロパティー式の機能がすべて提供されるわけではありません。)

Interval Config Property Example
@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時間後に初めてトリガーが発射されます。
The final value is always rounded to full second.

@Scheduled#delayed() は、上記のプロパティーのテキスト形式での代替です。ピリオド式は ISO-8601 duration format PnDTnHnMn.nS に基づいており、値は java.time.Duration#parse(CharSequence) で解析されます。ただし、式が数字で始まる場合は、 PT の接頭辞が自動的に追加されます。そのため、例えば PT15S の代わりに 15s を使用することができ、「15 秒」と解析されます。

@Scheduled(every = "2s", delayed = "2h")
void everyTwoSeconds() { }
If @Scheduled#delay() is set to a value greater than zero the value of @Scheduled#delayed() is ignored.

@Scheduled#delay() と比較した場合の主な利点は、値が設定可能であることです。 delay 属性は、デフォルト値やネストされたプロパティ式を含む Property Expressions をサポートしています。(なお、 "{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 同時実行はスキップされます。
A CDI event of type io.quarkus.scheduler.SkippedExecution is fired when an execution of a scheduled method is skipped.
Note that only executions within the same application instance are considered. This feature is not intended to work across the cluster.

1.5. 条件付き実行

@Scheduled#skipExecutionIf() を通じて、スケジュールされたメソッドの実行をスキップするロジックを定義することができます。指定されたBeanクラスは、 io.quarkus.scheduler.Scheduled.SkipPredicate を実装する必要があり、 test() メソッドの結果が true である場合に実行がスキップされます。

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...
     }
   }
}

主なアイデアは、スケジュールされたビジネスメソッドの外で実行をスキップするロジックを維持することで、再利用やリファクタリングが容易にできるようにすることです。

A CDI event of type io.quarkus.scheduler.SkippedExecution is fired when an execution of a scheduled method is skipped.

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を提供しており、これを注入してスケジューラーを一時停止/再開するために使用することができます。

Scheduler Injection Example
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を使用して設定することができます

3. プログラムスケジューリング

プログラムでジョブをスケジュールする必要がある場合は、 Quartzエクステンション を追加してQuartz APIを直接使用する必要があります。

Programmatic Scheduling with Quartz API
import org.quartz.Scheduler;

class MyJobs {

    void onStart(@Observes StartupEvent event, Scheduler quartz) throws SchedulerException {
        JobDetail job = JobBuilder.newJob(SomeJob.class)
                .withIdentity("myJob", "myGroup")
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("myTrigger", "myGroup")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(1)
                        .repeatForever())
                .build();
        quartz.scheduleJob(job, trigger);
    }
}
By default, the scheduler is not started unless a @Scheduled business method is found. You may need to force the start of the scheduler for "pure" programmatic scheduling. See also Quartz Configuration Reference.

4. スケジュールされたメソッドとテスト

テストを実行するときは、スケジューラーを無効にすることが望ましい場合がよくあります。 スケジューラーは、ランタイム設定プロパティー quarkus.scheduler.enabled を通じて無効にできます。 false に設定すると、アプリケーションにスケジュールされたメソッドが含まれていても、スケジューラーは開始されません。 特定のスケジューラー (Test Profiles) を無効にすることもできます。

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 エクステンションが存在する場合、 @io.opentelemetry.instrumentation.annotations.WithSpan アノテーションはすべての @Scheduled メソッドに自動的に追加されます。その結果、このメソッドの各実行には、新しい io.opentelemetry.api.trace.Span が関連付けられます。

Non-blocking methods are not supported, i.e. a new span is associated with the actual invocation but it’s not available within the asynchronous computation.

7. 設定リファレンス

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

タイプ

デフォルト

The syntax used in CRON expressions.

Environment variable: QUARKUS_SCHEDULER_CRON_TYPE

Show more

cron4j, quartz, unix, spring, spring53

quartz

Scheduled task metrics will be enabled if a metrics extension is present and this value is true.

Environment variable: QUARKUS_SCHEDULER_METRICS_ENABLED

Show more

boolean

false

Tracing will be enabled if the OpenTelemetry extension is present and this value is true.

Environment variable: QUARKUS_SCHEDULER_TRACING_ENABLED

Show more

boolean

false

If schedulers are enabled.

Environment variable: QUARKUS_SCHEDULER_ENABLED

Show more

boolean

true

Scheduled task will be flagged as overdue if next execution time is exceeded by this period.

Environment variable: QUARKUS_SCHEDULER_OVERDUE_GRACE_PERIOD

Show more

Duration

1S

期間フォーマットについて

期間のフォーマットは標準の java.time.Duration フォーマットを使用します。詳細は Duration#parse() javadoc を参照してください。

数値で始まる期間の値を指定することもできます。この場合、値が数値のみで構成されている場合、コンバーターは値を秒として扱います。そうでない場合は、 PT が暗黙的に値の前に付加され、標準の java.time.Duration 形式が得られます。