Springのスケジューリングで重複実行を防ぐ!初心者向けShedLock入門ガイド
生徒
「Springの@Scheduledでジョブを実行しているんですが、複数サーバーで動かすと、同じジョブが同時に動いてしまいます。どうすれば防げますか?」
先生
「それは分散環境特有の問題ですね。ShedLockというライブラリを使うことで、同時実行を防ぐことができますよ。」
生徒
「ShedLockって何ですか?どうやって使うんですか?」
先生
「それでは、分かりやすく解説していきましょう!」
1. 分散環境での@Scheduled実行の問題点とは?
「1. 分散環境での@Scheduled実行の問題点とは?」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
Spring Frameworkでは、定期的に処理を実行したいときに@Scheduledアノテーションを使います。しかし、複数のサーバー(インスタンス)で同じアプリケーションが動いている場合、各インスタンスがそれぞれ@Scheduledを実行してしまい、処理が重複して走ってしまうという問題があります。
このような状況は、マイクロサービスやクラウド環境、Kubernetes環境などでスケーリングしている場合に頻発します。
2. 重複実行を防ぐにはロック戦略が必要
この重複実行問題を解決するためには、どこか一箇所で「今この処理は実行中だよ」と他のインスタンスに知らせる「ロック機構」が必要です。これにより、複数サーバーでも1つのジョブだけが実行されるようになります。
このロック機構を実現するために使われるのが、ShedLock(シェッドロック)というライブラリです。
3. ShedLockとは?仕組みの概要
ShedLockは、Spring Bootの@Scheduledタスクの重複実行を防止するためのオープンソースライブラリです。ShedLockは、データベースやRedis、MongoDBなどに「ロック情報」を保存し、それを使ってジョブが同時に実行されないようにします。
ロックは「このジョブは誰かが実行中だよ」という目印のようなもので、ジョブ開始前にロックを取得し、終了後に解放します。
4. ShedLockの対応ストレージ
「4. ShedLockの対応ストレージ」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
ShedLockは以下のストレージをロックの保存先として利用できます。
- JDBC(PostgreSQL、MySQL、H2など)
- MongoDB
- Redis
- ZooKeeper
- DynamoDB
- Hazelcast
ここでは最も一般的なJDBCを使った例を紹介します。
5. ShedLockの導入方法(JDBC版)
まずは、MavenまたはGradleでShedLockの依存関係を追加します。
<!-- Mavenの場合 -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>5.13.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>5.13.0</version>
</dependency>
次に、ロック情報を保存するためのテーブルを作成します。
CREATE TABLE shedlock(
name VARCHAR(64),
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
);
6. 実際の@Scheduled処理にShedLockを組み込む
次に、@Scheduledのメソッドにロックを適用するコードを書いてみましょう。
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class SampleJob {
@Scheduled(cron = "0 0 * * * *") // 毎時0分に実行
@SchedulerLock(name = "SampleJob_execute", lockAtLeastFor = "10m", lockAtMostFor = "14m")
public void execute() {
System.out.println("ジョブ実行中...");
// 処理内容をここに書く
}
}
lockAtLeastFor:最低でもこの時間ロックを保持する(例:10分)
lockAtMostFor:最大でもこの時間ロックを保持する(例:14分)
これにより、どのサーバーでも同時にジョブが実行されることはなくなります。
7. SpringでShedLockを有効にする設定
「7. SpringでShedLockを有効にする設定」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
設定クラスを作成して、ShedLockを有効にしましょう。
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class ShedLockConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
.usingDbTime() // DBサーバーの時刻を使用
.build()
);
}
}
8. ShedLockの注意点とベストプラクティス
- ロック時間は、ジョブの実行時間を超えるように余裕を持って設定しましょう。
- 例外処理が発生しても、ロックが解放されるように注意が必要です。
- DBが高負荷な場合は、Redisなどの分散キャッシュの利用を検討するとよいでしょう。
- アプリ間でロック名が重複すると問題になるため、ユニークな名前を付けましょう。
9. 実行結果の確認
正しく設定されていれば、同じジョブが同時に複数回実行されることはありません。以下のようなログが1台のサーバーだけに表示されます。
ジョブ実行中...
まとめ
「まとめ」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
Springのスケジューリングと重複実行防止の全体像を理解する
Spring BootやSpring Frameworkでバッチ処理や定期実行処理を実装する際には、Scheduledアノテーションを使うことで簡単にスケジューリング処理を実現できます。しかし、クラウド環境やマイクロサービス構成、複数インスタンスでアプリケーションを運用している場合、同じジョブが複数のサーバーで同時に実行されてしまうという問題が発生します。この問題は開発初期には気づきにくいものの、本番環境では重大な障害につながる可能性があります。
そこで重要になるのがロック機構の導入です。複数サーバー間でジョブの実行状態を共有し、同時実行を防ぐ仕組みを持つことで、安全にスケジューリング処理を行うことができます。この課題をシンプルに解決するためのライブラリがShedLockです。
ShedLockの仕組みと導入メリット
ShedLockは、データベースやRedisなどの外部ストレージを利用してロック情報を管理し、分散環境におけるジョブの重複実行を防止するためのライブラリです。ジョブの開始時にロックを取得し、処理終了後にロックを解放することで、同一ジョブが同時に実行されないように制御します。
この仕組みにより、Kubernetesやクラウド環境などでスケールアウトした場合でも、必ず一つのインスタンスだけがジョブを実行するようになります。これにより、データの重複登録や二重処理といった問題を未然に防ぐことができます。
実務で重要な設定ポイント
ShedLockを使う際には、lockAtLeastForとlockAtMostForの設定が非常に重要です。lockAtLeastForは最低限ロックを保持する時間を指定し、lockAtMostForは最大でどれくらいロックを保持するかを指定します。この値を適切に設定しないと、ジョブが途中で終了した場合にロックが残り続けたり、逆に早く解放されてしまう可能性があります。
また、ロック情報を保存するデータベースの選定も重要です。一般的にはJDBCを使ったデータベースがよく利用されますが、高負荷な環境ではRedisなどの高速なストレージを選択することでパフォーマンスを向上させることができます。
サンプルコードで復習する
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class SummaryJob {
@Scheduled(cron = "0 */5 * * * *")
@SchedulerLock(name = "SummaryJob_execute", lockAtLeastFor = "5m", lockAtMostFor = "10m")
public void run() {
System.out.println("スケジュールジョブ実行中");
}
}
スケジュールジョブ実行中
このように、ScheduledとSchedulerLockを組み合わせることで、分散環境でも安全にジョブを実行することができます。設定自体はシンプルですが、効果は非常に大きく、実務では必須の知識となります。
HTMLでの補足説明例
<div class="alert alert-warning">
<strong>重要ポイント</strong>
分散環境では必ずロック機構を導入してジョブの重複実行を防ぎましょう。
</div>
このように重要なポイントを強調することで、読者にとって理解しやすい記事になります。技術記事では内容の正確さに加えて、視認性や読みやすさも重要な要素です。
よくある失敗と対策
初心者が陥りやすい失敗として、EnableSchedulingやShedLockの設定を忘れるケースがあります。また、ロック名の重複やロック時間の設定ミスによって、意図しない動作になることもあります。これらはログを確認しながら一つずつ原因を特定することで解決できます。
さらに、例外発生時にロックが解放されない問題にも注意が必要です。適切な例外処理を実装し、常にロックの状態を意識することで、安全な非同期バッチ処理を実現することができます。
生徒
「分散環境でジョブが重複実行される問題がよく理解できました」
先生
「とても重要なポイントですクラウド環境では必ず意識しなければいけません」
生徒
「ShedLockを使えば簡単に解決できるのが便利ですね」
先生
「はいシンプルですが非常に強力な仕組みです」
生徒
「ロック時間の設定が重要だということもわかりました」
先生
「その通りです設定次第で動作が大きく変わるので注意が必要です」
生徒
「実務で使える知識が増えてきて嬉しいです」
先生
「その調子です実際に動かしながら理解を深めていきましょう」
生徒
「これで安全にスケジュール処理が作れそうです」
先生
「とても良いですねしっかり活用していきましょう」
Spring FrameworkやThymeleafを使った Webアプリ開発の全体像をやさしく理解したい人には、 この入門書が定番です。
Spring Framework超入門をAmazonで見る※ Amazon広告リンク