The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Quartzによる定期的なタスクのスケジューリング

最近のアプリケーションでは、定期的に特定のタスクを実行する必要があることがよくあります。このガイドでは、 Quartz エクステンションを使用して定期的にクラスター化されたタスクをスケジュールする方法を学びます。

この技術は、previewと考えられています。

preview では、下位互換性やエコシステムでの存在は保証されていません。具体的な改善には設定や API の変更が必要になるかもしれませんが、 stable になるための計画は現在進行中です。フィードバックは メーリングリストGitHub の課題管理 で受け付けています。

For a full list of possible statuses, check our FAQ entry.

インメモリーのスケジューラーを実行するだけであれば、 Schedulerエクステンションを使用してください。

前提条件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.8.1+

  • Docker and Docker Compose or Podman, and Docker Compose

  • 使用したい場合、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

アーキテクチャ

このガイドでは、10秒ごとに実行されるQuartzジョブによって作成されたタスクのリストを可視化するために、1つのRest API tasks を公開します。

ソリューション

次のセクションで紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。

Gitレポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.gitアーカイブ をダウンロードします。

ソリューションは quartz-quickstart ディレクトリ にあります。

Mavenプロジェクトの作成

まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。

CLI
quarkus create app org.acme:quartz-quickstart \
    --extension=resteasy-reactive-jackson,quartz,hibernate-orm-panache,flyway,jdbc-postgresql \
    --no-code
cd quartz-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストール方法については、Quarkus CLIガイドをご参照ください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.11.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=quartz-quickstart \
    -Dextensions="resteasy-reactive-jackson,quartz,hibernate-orm-panache,flyway,jdbc-postgresql" \
    -DnoCode
cd quartz-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

以下が生成されます:

  • Mavenの構造

  • ランディングページは次のURLでアクセス可能です: http://localhost:8080

  • nativejvm の両方のモードに対応した Dockerfile ファイルの例

  • アプリケーション設定ファイル

MavenプロジェクトはQuarkus Quartzエクステンションもインポートしています。

すでにQuarkusプロジェクトが設定されている場合は、プロジェクトのベースディレクトリーで以下のコマンドを実行することで、プロジェクトに quartz エクステンションを追加することができます:

CLI
quarkus extension add 'quartz'
Maven
./mvnw quarkus:add-extension -Dextensions="quartz"
Gradle
./gradlew addExtension --extensions="quartz"

これにより、ビルドファイルに以下が追加されます:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-quartz</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-quartz")

JDBCストアを使用するには、データソースのサポートを提供する quarkus-agroal エクステンションも必要です。

タスクエンティティーの作成

org.acme.quartz パッケージで、以下の内容の Task クラスを作成します:

package org.acme.quartz;

import javax.persistence.Entity;
import java.time.Instant;
import javax.persistence.Table;

import io.quarkus.hibernate.orm.panache.PanacheEntity;

@Entity
@Table(name="TASKS")
public class Task extends PanacheEntity { (1)
    public Instant createdAt;

    public Task() {
        createdAt = Instant.now();
    }

    public Task(Instant time) {
        this.createdAt = time;
    }
}
1 Panacheを使ってエンティティを宣言

スケジュールされたジョブの作成

org.acme.quartz パッケージで、以下の内容の TaskBean クラスを作成します。

package org.acme.quartz;

import javax.enterprise.context.ApplicationScoped;

import javax.transaction.Transactional;

import io.quarkus.scheduler.Scheduled;

@ApplicationScoped (1)
public class TaskBean {

    @Transactional
    @Scheduled(every = "10s", identity = "task-job") (2)
    void schedule() {
        Task task = new Task(); (3)
        task.persist(); (4)
    }
}
1 アプリケーション スコープでBeanを宣言する
2 @Scheduled アノテーションを使用し、このメソッドを10秒ごとに実行し、識別子を付与するようにQuarkusに指示します。
3 現在の開始時刻で新しい Task を作成します。
4 Panacheを使ってタスクをデータベースに永続化

プログラムによるジョブのスケジューリング

Quartz APIを直接利用することも可能です。どのBeanにも、基礎となる org.quartz.Scheduler を注入することができます。

package org.acme.quartz;

@ApplicationScoped
public class TaskBean {

    @Inject
    org.quartz.Scheduler quartz; (1)

    void onStart(@Observes StartupEvent event) throws SchedulerException {
       JobDetail job = JobBuilder.newJob(MyJob.class)
                         .withIdentity("myJob", "myGroup")
                         .build();
       Trigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity("myTrigger", "myGroup")
                            .startNow()
                            .withSchedule(
                               SimpleScheduleBuilder.simpleSchedule()
                                  .withIntervalInSeconds(10)
                                  .repeatForever())
                            .build();
       quartz.scheduleJob(job, trigger); (2)
    }

    @Transactional
    void performTask() {
        Task task = new Task();
        task.persist();
    }

    // A new instance of MyJob is created by Quartz for every job execution
    public static class MyJob implements Job {

       @Inject
       TaskBean taskBean;

       public void execute(JobExecutionContext context) throws JobExecutionException {
          taskBean.performTask(); (3)
       }

    }
}
1 基礎となる org.quartz.Scheduler インスタンスを注入します。
2 Quartz APIを使用して新しいジョブをスケジュールします。
3 Invoke the TaskBean#performTask() method from the job. Jobs are also container-managed beans if they belong to a bean archive.
デフォルトでは、 @Scheduled ビジネスメソッドが見つからない限り、スケジューラーは起動されません。純粋なプログラムによるスケジューリングを行うには、スケジューラーを強制的に起動させる必要があるかもしれません。 Quartz-configuration-reference も参照してください。

アプリケーション設定ファイルの更新

application.properties ファイルを編集し、以下の設定を追加します。

# Quartz configuration
quarkus.quartz.clustered=true (1)
quarkus.quartz.store-type=jdbc-cmt (2)
quarkus.quartz.misfire-policy.task-job=ignore-misfire-policy (3)

# Datasource configuration.
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/quarkus_test

# Hibernate configuration
quarkus.hibernate-orm.database.generation=none
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.sql-load-script=no-file

# flyway configuration
quarkus.flyway.connect-retries=10
quarkus.flyway.table=flyway_quarkus_history
quarkus.flyway.migrate-at-start=true
quarkus.flyway.baseline-on-migrate=true
quarkus.flyway.baseline-version=1.0
quarkus.flyway.baseline-description=Quartz
1 スケジューラーがクラスターモードで実行されることを示します。
2 データベースストアを使用してジョブ関連情報を永続化し、ノード間で共有できるようにします。
3 The misfire policy can be configured for each job. task-job is the identity of the job.

Valid misfire policy for cron jobs are: smart-policy, ignore-misfire-policy, fire-now and cron-trigger-do-nothing. Valid misfire policy for interval jobs are: smart-policy, ignore-misfire-policy, fire-now, simple-trigger-reschedule-now-with-existing-repeat-count, simple-trigger-reschedule-now-with-remaining-repeat-count, simple-trigger-reschedule-next-with-existing-count and simple-trigger-reschedule-next-with-remaining-count.

RESTリソースとテストの作成

org.acme.quartz.TaskResource クラスを以下の内容で作成します:

package org.acme.quartz;

import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/tasks")
public class TaskResource {

    @GET
    public List<Task> listAll() {
        return Task.listAll(); (1)
    }
}
1 作成されたタスクのリストをデータベースから取得します。

You also have the option to create a org.acme.quartz.TaskResourceTest test with the following content:

package org.acme.quartz;

import io.quarkus.test.junit.QuarkusTest;

import static org.hamcrest.Matchers.greaterThanOrEqualTo;

import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class TaskResourceTest {

    @Test
    public void tasks() throws InterruptedException {
        Thread.sleep(1000); // wait at least a second to have the first task created
        given()
                .when().get("/tasks")
                .then()
                .statusCode(200)
                .body("size()", is(greaterThanOrEqualTo(1))); (1)
    }
}
1 200 のレスポンスが返却されていることと、少なくとも 1 つのタスクが作成されていることを確認してください。

Quartz テーブルの作成

V2.0.0.0QuarkusQuartzTasks.sql からコピーした内容でファイルからコピーした内容で src/main/resources/db/migration/V2.0.0__QuarkusQuartzTasks.sql という名前のSQLマイグレーションファイルを追加します。

ロードバランサーの設定

ルートディレクトリーに、以下の内容の nginx.conf ファイルを作成します:

user  nginx;

events {
    worker_connections   1000;
}

http {
        server {
              listen 8080;
              location / {
                proxy_pass http://tasks:8080; (1)
              }
        }
}
1 すべてのトラフィックをタスクアプリケーションにルーティング

アプリケーションデプロイメントの設定

ルートディレクトリーに、以下の内容の docker-compose.yml ファイルを作成します。

version: '3'

services:
  tasks: (1)
    image: quarkus-quickstarts/quartz:1.0
    build:
      context: ./
      dockerfile: src/main/docker/Dockerfile.${QUARKUS_MODE:-jvm}
    environment:
      QUARKUS_DATASOURCE_URL: jdbc:postgresql://postgres/quarkus_test
    networks:
      - tasks-network
    depends_on:
      - postgres

  nginx: (2)
    image: nginx:1.17.6
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - tasks
    ports:
      - 8080:8080
    networks:
      - tasks-network

  postgres: (3)
    image: postgres:14.1
    container_name: quarkus_test
    environment:
      - POSTGRES_USER=quarkus_test
      - POSTGRES_PASSWORD=quarkus_test
      - POSTGRES_DB=quarkus_test
    ports:
      - 5432:5432
    networks:
      - tasks-network

networks:
  tasks-network:
    driver: bridge
1 タスクサービスの定義
2 受信トラフィックを適切なノードにルーティングするための nginx ロードバランサーを定義
3 データベースを実行するための設定を定義

データベースの実行

別のターミナルで以下のコマンドを実行します。

docker-compose up postgres (1)
1 docker-compose.yml ファイルで提供されている設定オプションを使用してデータベースインスタンスを起動します。

アプリケーションを開発モードで実行します。

アプリケーションを実行します:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

数秒後、別のターミナルを開き、 curl localhost:8080/tasks を実行して、少なくとも 1 つのタスクが作成されていることを確認します。

いつものように、アプリケーションは以下の方法でパッケージ化されます:

CLI
quarkus build
Maven
./mvnw clean package
Gradle
./gradlew build

そして次のコマンドで実行できます。 java -jar target/quarkus-app/quarkus-run.jar.

ネイティブ実行可能ファイルを生成することもできます:

CLI
quarkus build --native
Maven
./mvnw package -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

アプリケーションのパッケージ化と複数のインスタンスの実行

アプリケーションは以下でパッケージ化することができます:

CLI
quarkus build
Maven
./mvnw clean package
Gradle
./gradlew build

ビルドに成功したら、以下のコマンドを実行します:

docker-compose up --scale tasks=2 --scale nginx=1 (1)
1 アプリケーションとロードバランサーの2つのインスタンスを起動

数秒後、別のターミナルで curl localhost:8080/tasks を実行し、タスクが異なるタイミングで、10秒の間隔でのみ作成されていることを確認します。

ネイティブ実行可能ファイルを生成することもできます:

CLI
quarkus build --native
Maven
./mvnw package -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native
It’s the responsibility of the deployer to clear/remove the previous state, i.e. stale jobs and triggers. Moreover, the applications that form the "Quartz cluster" should be identical, otherwise an unpredictable result may occur.

プラグインとリスナーの登録

Quarkusの設定から、 pluginsjob-listenerstrigger-listeners を登録することができます。

以下の例では、 org.quartz.plugins.history.LoggingJobHistoryPlugin という名前のプラグインを jobHistory として登録し、プロパティー jobSuccessMessageJob [{1}.{0}] execution complete and reports: {8} として定義します。

quarkus.quartz.plugins.jobHistory.class=org.quartz.plugins.history.LoggingJobHistoryPlugin
quarkus.quartz.plugins.jobHistory.properties.jobSuccessMessage=Job [{1}.{0}] execution complete and reports: {8}

注入された org.quartz.Scheduler でリスナーをプログラムで登録することも出来ます。

public class MyListenerManager {
    void onStart(@Observes StartupEvent event, org.quartz.Scheduler scheduler) throws SchedulerException {
        scheduler.getListenerManager().addJobListener(new MyJogListener());
        scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());
    }
}

Quartz設定リファレンス

ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能

Configuration property

タイプ

デフォルト

Enable cluster mode or not. If enabled make sure to set the appropriate cluster properties.

Environment variable: QUARKUS_QUARTZ_CLUSTERED

boolean

false

The frequency (in milliseconds) at which the scheduler instance checks-in with other instances of the cluster.

Environment variable: QUARKUS_QUARTZ_CLUSTER_CHECKIN_INTERVAL

long

15000

The type of store to use. When using StoreType#JDBC_CMT or StoreType#JDBC_TX configuration values make sure that you have the datasource configured. See Configuring your datasource for more information. To create Quartz tables, you can perform a schema migration via the Flyway extension using a SQL script matching your database picked from Quartz repository.

Environment variable: QUARKUS_QUARTZ_STORE_TYPE

ram, jdbc-tx, jdbc-cmt

ram

The name of the datasource to use. Optionally needed when using the jdbc-tx or jdbc-cmt store types. If not specified, defaults to using the default datasource.

Environment variable: QUARKUS_QUARTZ_DATASOURCE

string

The prefix for quartz job store tables. Ignored if using a ram store.

Environment variable: QUARKUS_QUARTZ_TABLE_PREFIX

string

QRTZ_

The SQL string that selects a row in the "LOCKS" table and places a lock on the row. If not set, the default value of Quartz applies, for which the "{0}" is replaced during run-time with the table-prefix, the "{1}" with the instance-name. An example SQL string SELECT * FROM {0}LOCKS WHERE SCHED_NAME = {1} AND LOCK_NAME = ? FOR UPDATE

Environment variable: QUARKUS_QUARTZ_SELECT_WITH_LOCK_SQL

string

The name of the Quartz instance.

Environment variable: QUARKUS_QUARTZ_INSTANCE_NAME

string

QuarkusQuartzScheduler

The size of scheduler thread pool. This will initialize the number of worker threads in the pool.

Environment variable: QUARKUS_QUARTZ_THREAD_COUNT

int

25

Thread priority of worker threads in the pool.

Environment variable: QUARKUS_QUARTZ_THREAD_PRIORITY

int

5

Defines how late the schedulers should be to be considered misfired.

Environment variable: QUARKUS_QUARTZ_MISFIRE_THRESHOLD

Duration

60S

Scheduler can be started in different modes: normal, forced or halted. By default, the scheduler is not started unless a io.quarkus.scheduler.Scheduled business method is found. If set to "forced", scheduler will be started even if no scheduled business methods are found. This is necessary for "pure" programmatic scheduling. Additionally, setting it to "halted" will behave just like forced mode but the scheduler will not start triggering jobs until an explicit start is called from the main scheduler. This is useful to programmatically register listeners before scheduler starts performing some work.

Environment variable: QUARKUS_QUARTZ_START_MODE

normal, forced, halted

normal

Trigger listeners

タイプ

デフォルト

Class name for the configuration.

Environment variable: QUARKUS_QUARTZ_TRIGGER_LISTENERS__LISTENER_NAME__CLASS

string

required

The properties passed to the class.

Environment variable: QUARKUS_QUARTZ_TRIGGER_LISTENERS__LISTENER_NAME__PROPERTIES

Map<String,String>

Job listeners

タイプ

デフォルト

Class name for the configuration.

Environment variable: QUARKUS_QUARTZ_JOB_LISTENERS__LISTENER_NAME__CLASS

string

required

The properties passed to the class.

Environment variable: QUARKUS_QUARTZ_JOB_LISTENERS__LISTENER_NAME__PROPERTIES

Map<String,String>

Plugins

タイプ

デフォルト

Class name for the configuration.

Environment variable: QUARKUS_QUARTZ_PLUGINS__PLUGIN_NAME__CLASS

string

required

The properties passed to the class.

Environment variable: QUARKUS_QUARTZ_PLUGINS__PLUGIN_NAME__PROPERTIES

Map<String,String>

Misfire policy per job configuration

タイプ

デフォルト

The quartz misfire policy for this job.

Environment variable: QUARKUS_QUARTZ_MISFIRE_POLICY__IDENTITY_

smart-policy, ignore-misfire-policy, fire-now, simple-trigger-reschedule-now-with-existing-repeat-count, simple-trigger-reschedule-now-with-remaining-repeat-count, simple-trigger-reschedule-next-with-remaining-count, simple-trigger-reschedule-next-with-existing-count, cron-trigger-do-nothing

smart-policy

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

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

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