昨日が初めてのブログ投稿だったのですが、2夜連続での投稿です。23日と24日の枠を押さえて、25日の枠をプレゼントとして残すというサンタ的な計らいをしております。(←トリを飾る度胸がなかっただけ。滑り出しは好調ですが、内容は至極真面目です。最近、アンドパッドで最も古くから稼働している施工管理サービスについて、その非同期処理基盤をBlue/Greenデプロイ化しました。このデプロイフローの変更は大きく2段階を踏んで実現しており、本記事では、その前半部分にあたる、非同期処理基盤から定期実行ジョブの処理基盤を抽出・分離した話について説明します。元々の施工管理サービスのインフラ概要は下図の通りでした。左はユーザーリクエストが非同期ジョブをトリガして処理する様子を表しており、右は定期実行ジョブをスケジュールして処理する様子を表しています。上図からもわかるように、施工管理サービスには3種類のサーバが存在しており、それぞれ以下のような役割を担っていました。定期実行ジョブをエンキューするサーバなお、施工管理サービスでは、非同期処理の実装にSidekiqを利用しており、定期実行ジョブの実装にsidekiq-schedulerを利用しています。redis-namespaceというgemを利用しており、あたかもRedisにnamespaceが張られているかのようにアプリケーションに見せています。(以降、Redisのnamespaceと表現します。デプロイフローデプロイフローは下図に示す通りで、frontサーバのみがBlue/Greenデプロイ化されており、下表に示す3段階を経てデプロイが進むようになっていました。新環境のfrontサーバが起動し、社内ネットワーク経由のリクエストのみ、新環境のfrontサーバへルーティングされるようになる。backサーバとschedulerサーバがRollingUpdateされ、全てのリクエストが新環境のfrontサーバへルーティングされるようになる。旧環境のfrontサーバが削除される。注意点としては、この時点では非同期処理を担うbackサーバがBlue/Greenデプロイ化されていないので、Redisのデータベース、および、namespaceは全てのサーバで1つのものを共有していました。要件まず初めに、backサーバをBlue/Greenデプロイ化したときに満たすべき要件について整理し、以下2つの要件をインフラ側でサポートする決断1をしました。定期実行ジョブの実行保証まず一つ目ですが、これは、定期実行ジョブが重複せずにスケジューリングされることをインフラ側で保証するという意味です。定期実行ジョブの中には、アプリケーションとして冪等性の担保が怪しいロジックが複数存在していました。アプリケーション側のロジックに冪等性担保を期待するのではなく、インフラ側でも定期実行ジョブの唯一性を保証する方針を採用しました。二つ目ですが、これは定期実行ジョブがスキップされずに確実に実行されることをインフラ側で保証するという意味です。定期実行ジョブの中には、例えば、ユーザー通知を行うジョブが含まれており、ジョブがスキップされるとユーザーに直に影響が出るものが複数存在していました。定期実行ジョブが確実に実行されることをインフラ側で保証する方針を採用しました。最初に、インフラ構成を変えずにbackサーバのみをBlue/Greenデプロイ化できるのかを検討しました。backサーバをBlue/Greenデプロイ化するためにインフラ構成を変更する必要があることがわかりました。既存のインフラ構成では定期実行ジョブの唯一性と定期実行ジョブの実行保証が同時には実現できない理由を説明します。この場合のデプロイフローは、下図のようになります。図中の赤線はRedisのデータベース、ないし、namespaceの境界を表しており、backサーバをBlue/Greenデプロイ化する場合に必要になるものです。デプロイフロー(既存のインフラ構成を変更しない場合)一見すると、問題なくbackサーバのBlue/Greenデプロイ化が達成できているように見えます。実は、全体公開時のschedulerサーバの挙動に焦点を当てると、ここに落とし穴があることがわかります。全体公開時にschedulerサーバをRollingUpdateすることを考えます。すると、下図のように、必ずBlue環境のschedulerサーバとGreen環境のschedulerサーバとが同居する瞬間が発生します。このBlue環境とGreen環境のschedulerサーバが同居した瞬間に定期実行ジョブのスケジューリング処理が走ると、Blue環境とGreen環境のそれぞれにジョブがエンキューされてしまうことになり、この意味でスケジュールされるジョブの唯一性が損なわれてしまいます。一方で、全体公開時にschedulerサーバをRecreateする場合を考えてみます。すると、短い時間ですが、Blue環境にもGreen環境にもschedulerサーバが存在しない期間ができてしまいます。万が一、この期間中に何らかの定期実行ジョブのスケジューリング時刻を迎えてしまうと、そのジョブはスケジュールされないままスキップされてしまうことになります。この意味で、ジョブの実行保証ができないことがわかります。したがって、既存のインフラ構成を変更しないまま、backサーバをBlue/Greenデプロイ化することはできないと判断しました。定期実行ジョブの処理基盤を分離する場合インフラ構成を変えずにbackサーバをそのままBlue/Greenデプロイ化すると、定期実行ジョブの要件を満たせなくなってしまうことが前節から明らかになりました。解決策としては、非同期処理基盤から定期実行ジョブを処理する基盤を抽出することが自然に考えられました。この場合のインフラ構成とデプロイフローを下図に示します。構成の変更点としては大きく二点あり、一つは、定期実行ジョブをbackサーバではなく、新しく追加したworkerサーバで処理させることです。namespaceを作成し、schedulerサーバとworkerサーバにそちらを参照させることです。デプロイフロー(定期実行ジョブの処理基盤を分離する場合)こうすることで、schedulerサーバとworkerサーバをRollingUpdateさせて、下図のように全体公開時に新環境と旧環境のサーバが共存する瞬間があったとしても、sidekiq-schedulerの機能として定期実行ジョブの唯一性が保証されるようになります。加えて、RollingUpdateなので定期実行ジョブの瞬断も起きず、3-1節で説明した両方の要件が満たされることになりました。目標とするインフラ構成が決定したので、続いて、その構成への移行フローを検討しました。非同期処理基盤から定期実行ジョブの処理基盤を分離させることによる動作変更は大きいため、安全を期して、次の二段階に分けてリリースを行うこととしました。定期実行ジョブの処理基盤(定期実行ジョブ用のRedisのnamespaceを参照するschedulerサーバとworkerサーバ)をリリースし、その基盤上でダミージョブを定期実行させる。1で動作確認をしっかり行った後、定期実行ジョブを既存の非同期処理基盤から定期実行ジョブの処理基盤へ移植する。この様子を下図に示します。定期実行ジョブの処理基盤を分離するフローこうすることで、特段の問題なく非同期処理基盤から定期実行ジョブの処理基盤を分離することに成功しました。まとめこの記事では、アンドパッドで最も古くから稼働している施工管理サービスの非同期処理基盤をBlue/Greenデプロイ化する前段階として、定期実行ジョブの処理基盤とユーザーリクエストがトリガする非同期ジョブの実行基盤とを分離した話について説明しました。実際に、ユーザーリクエストがトリガする非同期ジョブの実行基盤をBlue/Greenデプロイ化する話については、また後日記事にまとめてご報告するつもりです!ぜひ、お楽しみに〜最後にアンドパッドでは、様々な技術要素を活用して開発に取り組んでいます。Controlの髙木さんからのテストデータ作成自動化についての紹介です。最後までお楽しみに!もちろん、他にも満たすべき要件はありますが、説明の都合上、他の要件は省略しています。こんにちは、SWEのあかりです。こんにちは、SWEのあかりです。