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 でアノテーションされたメソッドは、自動的に呼び出しがスケジュールされます。スケジュールされたメソッドは、abstractやprivateであってはなりません。静的または非静的であっても大丈夫です。スケジュールされたメソッドには、 @jakarta.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 Expressionを含む Property Expression に対応しています。(なお、"{property.path}"スタイル式はまだサポートされていますが、Property Expressionsの全機能を提供するものではありません。)

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.1.1. タイムゾーン

cron式は、デフォルトのタイムゾーンのコンテキストで評価されます。しかし、cron式を特定のタイムゾーンに関連付けることも可能です。

Time Zone Example
@Scheduled(cron = "0 15 10 * * ?", timeZone = "Europe/Prague") (1)
void myMethod() { }
1 タイムゾーンIDは、 java.time.ZoneId#of(String) を使って解析されます。

timeZone 属性は、デフォルト値やネストしたProperty Expressionを含む Property Expression に対応しています。

Time Zone Configuration Property Example
@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 分」と解析されます。

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

every 属性は、デフォルト値やネストされたProperty Expressionを含む プロパティ式 に対応しています。(なお、 "{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 Expressionを含む プロパティ式 に対応しています。(なお、 "{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 Expressionを含む Property Expressions に対応しています。(なお、 "{property.path}" スタイル式はまだサポートされていますが、Property Expressions の全機能を提供するものではありません。)

@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を使用して設定することができます
A CDI event is fired synchronously and asynchronously when the scheduler or a scheduled job is paused/resumed. The payload is io.quarkus.scheduler.SchedulerPaused, io.quarkus.scheduler.SchedulerResumed, io.quarkus.scheduler.ScheduledJobPaused and io.quarkus.scheduler.ScheduledJobResumed respectively.

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

注入された io.quarkus.scheduler.Scheduler は、プログラム的にジョブをスケジュールするために使用することもできます。

Programmatic Scheduling
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 プログラムによって追加されたジョブも削除することができます。
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 via quarkus.scheduler.start-mode=forced.
If the Quartz extension is present then the Quartz API can be also used to schedule a job programmatically.

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

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

Scheduler can be started in different modes. By default, the scheduler is not started unless a io.quarkus.scheduler.Scheduled business method is found.

Environment variable: QUARKUS_SCHEDULER_START_MODE

Show more

normalThe scheduler is not started unless a io.quarkus.scheduler.Scheduled business method is found., tooltip:forced[The scheduler will be started even if no scheduled business methods are found.

This is necessary for "pure" programmatic scheduling.], tooltip:halted[Just like the forced mode but the scheduler will not start triggering jobs until Scheduler#resume() is called.

This can be useful to run some initialization logic that needs to be performed before the scheduler starts.]

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

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

数字で始まる簡略化した書式を使うこともできます:

  • 数値のみの場合は、秒単位の時間を表します。

  • 数値の後に ms が続く場合は、ミリ秒単位の時間を表します。

その他の場合は、簡略化されたフォーマットが解析のために java.time.Duration フォーマットに変換されます:

  • 数値の後に h 、 m 、 s が続く場合は、その前に PT が付けられます。

  • 数値の後に d が続く場合は、その前に P が付けられます。