🎧 Listen to this article: English
🌍 Read this in your language: हिंदी · தமிழ் · తెలుగు · ಕನ್ನಡ · മലയാളം · ଓଡ଼ିଆ · 日本語 · 中文
ソフトウェアを扱う際、タスクが円滑かつ重複なく実行されるようにすることは極めて重要です。今回は、分散システムにおけるよくある落とし穴を取り上げます。それは、インプロセススケジューラによって夜間ジョブが複数回実行され、データベースに重複レコードが生成されてしまうという問題です。サービスを水平スケールさせるマイクロサービスアーキテクチャを採用する企業が増えている現在、このトピックは特に重要性を増しています。
前提構成
アプリケーション内のアクティブなエンティティに対してレコードを作成する夜間ジョブがあると想像してみてください。このジョブは1日に1回実行され、エンティティごとに1つの新しいレコードを作成することになっています。サーバーサイドアプリケーションを構築するための人気フレームワークであるNestJSを使用した一般的な構成では、以下の @Cron デコレータを使用してこのジョブをスケジュールすることがあります。その簡略化された例は以下のようになります。
@Injectable()
export class WindowGenerationService {
@Cron('0 8 * * *') // every day at 08:00
async generateNextWindows() {
const entities = await this.repo.findActiveEndingSoon();
for (const entity of entities) {
await this.repo.createNextWindow(entity);
}
}
}
このコードは、実行されているサービスインスタンスが1つだけの場合は完璧に動作します。しかし、複数のインスタンスを実行してアプリケーションを水平スケールさせると、問題が発生する可能性があります。
問題点
スケールアウトした構成では、サービスインスタンスが3つある場合、各インスタンスがスケジュールされたジョブの独自のコピーを同時に実行します。そのため、08:00に1つのジョブが実行される代わりに、3つのジョブが同時に起動することになります。各インスタンスはレコードがすでに存在するかどうかを確認せずに独自のレコードを作成します。その結果、同じエンティティに対してわずか数秒差で2つの同一レコードが作成され、データベース内でレコードの重複が発生します。
なぜこれが重要なのか
データの重複は、データの整合性の問題やリソースの浪費など、さまざまな問題を引き起こす可能性があります。不要なレコードが作成されるだけでなく、同じ処理を実行する3つのコンテナのコンピュートリソースに対しても余分なコストを支払うことになります。
考えられる解決策
問題を特定した後、これに対処する方法はいくつかあります。
選択肢1: 分散ロックを使用する
1つの解決策として、分散ロックの実装が挙げられます。これによりインスタンス間でロックを競合させ、1つのインスタンスのみにジョブを実行させることができます。しかし、このアプローチにはデメリットもあります。ロックのバックエンドに障害が発生したり、インスタンスがロックを保持したまま停止したりすると、ジョブがまったく実行されず、実行漏れが発生する可能性があります。
選択肢2: 冪等性を実装する
もう1つの選択肢は、冪等性チェックを追加することです。これにより、ジョブが再実行された場合でも、レコードがすでに存在していれば単に作成をスキップします。これはより低コストな解決策ですが、依然としてすべてのインスタンスが起動するため、冗長な作業が発生します。
選択肢3: スケジューリングをアプリケーションの外部に移動する
最も優れた解決策として分かったのは、スケジューリングをアプリケーションの外に出すことです。アプリにジョブの実行タイミングを管理させるのではなく、外部のスケジューラに処理させます。これにより、外部スケジューラがスケジュールされた時刻に1回だけトリガーするHTTPエンドポイントを作成できます。その構成例は次のようになります。
@Post('jobs/run')
async runJob(@Body() body: RunJobDto) {
this.assertValidSecret(body.secret);
return this.jobs.run(body.jobKey);
}
この構成により、ジョブのトリガーに応答するのは1つのインスタンスのみとなり、重複レコードの作成を防ぐことができます。また、万が一ジョブが複数回トリガーされた場合でも重複が作成されないよう、冪等性チェックは維持されます。
発生するコスト
ジョブをHTTPエンドポイントとして公開する場合、セキュリティを考慮する必要があります。このエンドポイントを見つけた人なら誰でもトリガーできてしまう可能性があるためです。不正アクセスを防ぐために、共有シークレットを使用してリクエストを検証します。これにより、正当なトリガーのみがジョブを実行できるようになります。
もう1つの考慮事項は、スケジュールの実行時刻です。ジョブはすべてのユーザーにとって1日の始まりの後に実行されるべきですが、複数のタイムゾーンを扱う場合はこれが難しくなることがあります。固定のUTC時刻を選択することで、異なる地域間での標準化が容易になります。
得られた教訓
この経験は、重要な教訓を浮き彫りにしています。それは、インプロセススケジューラが分散システムにおいて予期しない動作を引き起こす可能性があるということです。アプリケーションをスケールさせる際は、スケジューリングの関心事をアプリケーションロジックから分離することが不可欠です。そうすることで、重複書き込みの落とし穴を回避し、ジョブを意図したとおりに確実に実行できます。
まとめ
要約すると、マイクロサービスアーキテクチャでCronジョブを管理するには慎重な計画が必要です。スケジューリングをアプリケーションの外部に移し、適切なチェックを実装することで、重複レコードのような問題を防ぎ、サービスの効率を向上させることができます。
メリット
- データベースにおける重複レコードを防止する。
- 不要なコンピュートコストを削減する。
- スケジューリングを外部化することでジョブ管理を簡素化する。
デメリット
- 外部スケジューラの追加セットアップが必要になる。
- HTTPリクエストの処理やセキュリティ対策において複雑さが増す。
注意点
この記事は教育的な概要を提供することを目的としています。実際に利用する前に、プレースホルダーの値を実際の設定に置き換え、元の情報源に照らして記述内容を確認してください。
よくある質問
- Cronジョブとは何ですか? — Cronジョブとは、指定された間隔で自動的に実行されるスケジュールされたタスクのことです。
- Cronジョブが複数回実行されたのはなぜですか? — アプリケーションの複数のインスタンスでジョブがスケジュールされている場合に発生することがあります。
- データベースで重複レコードを防ぐにはどうすればよいですか? — 冪等性チェックを実装するか、スケジューリングをアプリケーションの外部に移動します。
- プログラミングにおける冪等性とは何ですか? — 冪等性とは、ある関数を何度呼び出しても、初回適用時以降の結果が変わらない性質のことを意味します。
- NestJSとは何ですか? — NestJSは、効率的でスケーラブルなNode.jsサーバーサイドアプリケーションを構築するためのフレームワークです。
- HTTPエンドポイントを保護するにはどうすればよいですか? — 共有シークレットやトークンなどの認証方法を使用してリクエストを検証します。
タグ
#cron #nestjs #microservices #scheduling #softwareengineering #dataintegrity #cloudcomputing #distributedsystems
Docker Security Checklist
Lock down your containers from build to runtime — 29 practical controls covering images, runtime flags, secrets, and the daemon. Enter your email — you'll get the PDF instantly, plus new posts on Docker, Linux & security.
Free. No spam — unsubscribe in one click.


Responses
Sign in to leave a response.