概要
少し過去の出来事にはなりますが、弊社サービスである「ピクトリンク」について、サービス運用を止めずにバックエンドストレージであるMogileFSをAmazon S3に移行しました。
その当時の移行作業について、情報をまとめました。 技術的な詳細というよりは、どのような方向性・考え方で移行作業を行っていったのかという記録になっています。
背景や現状
ピクトリンクサービスは、各種データ(全国のプリントシール機にてユーザーが撮影したプリ画像を含む)を自サーバー群で大量保管している。
このうち、画像バイナリーデータを保存している社内MogileFSについて、日々のシステム負荷が高いこと、管理コスト(サーバー費用・運用費用)が膨大であることが頭痛のタネであった。 そのため、画像バイナリーデータの保存先をマネージドサービスであるAmazon S3に全面移行したいという社内要望が出てきていた。
そこで、関係各システムの事前調査や実証実験、下準備などを終えた後、S3への画像バイナリーデータ移行のマイルストーンを作成し、これに沿って移行作業を進めていくこととなった。
移行作業はマイルストーンに沿って着々と進行し、最終的に、サービス停止をせず大きな問題も無いまま移行を完遂することができた。
現在では、サーバー運用をさほど気にせずとも画像バイナリーデータを保管し使用することができている。
移行計画と実施
実施時期
2020年を軸として、全体としては2019年夏〜2021年春頃の話である。
対象データ量
数百テラバイト(数億オブジェクト)級のデータが移行対象となっていた。
MogileFS上では、耐久性と可用性のため、システムが三重コピー分散で保存していた。 そのため、実際のストレージ消費量は3倍増しとなっていた。
移行要件
- ピクトリンクサービスを停止せず実施すること
- 仮にサービス停止せざるを得ない場合でも、通常メンテナンス時のような短い期間(数時間程度)にすること
- ピクトリンクサービスの動作を重くしないこと
事前調査
ピクトリンク/MogileFS/AWS(S3およびその他)の仕様や実装などをもろもろ調査した。 調査詳細は省くが、結果として以下のようなこと(実施できる/判断材料として使用できる)が分かっていた。
- ピクトリンク
- 各サービスのファイル送受信クライアントを共通クライアントライブラリーに統一できる
- 大部分はMogileFS版の共通クライアントライブラリーを利用している
- 一部ではMogileFSに直アクセスしている
- 最近のデータがよく読み込まれる
- 過去のデータへのアクセスは継続して発生している
- 各サービスのファイル送受信クライアントを共通クライアントライブラリーに統一できる
- MogileFS
- 保持データにシステム内部管理ID(FID)が存在している
- FIDの昇順≒保存されたタイミングの古い順である
- 管理者コマンドツールや管理者APIでシステム内部事情を考慮しながら操作できる
- データをFIDでピックアップできる
- 保持データにシステム内部管理ID(FID)が存在している
- AWS
- 十分な速度で十分な処理量を捌ける
- S3
- 基本的なサーバーレス(API Gateway/Lambda/ECS Fargate)
- 基本的なキュー(SQS)
- 十分な速度で十分な処理量を捌ける
計画立案に際して
移行とその後の運用のため今回構築する中継システム(API)のことを、「ストレージゲートウェイ(storage-gateway-api)」と呼称することにした。
※以降、「ストレージゲートウェイ/Storage Gateway」と記述されるものは全て上記システムのことを指している。「AWS Storage Gateway」のことではない。
コンセプトの明確化
移行要件と調査結果から、コンセプトを明確にした。
- なるべく切り戻し(MogileFS利用に戻ること)ができるように配慮する
- ストレージゲートウェイ内部で作業すれば済む(ユーザーに変更を強い続けなくて済む)環境を早期に実現する
- ストレージゲートウェイ内部の挙動変更は少しずつ行う
このコンセプトが意図していることは、
- ピクトリンク各サービス(ストレージから見てユーザー)側ではなく、移行チーム側の作業だけでデータ流通を制御できる状態を保つ
- 制御においては「切り戻し」という安全カードをできるだけ持っていられるように、S3移行期間においてもMogileFS利用に戻れる状態をできるだけ維持する
- 移行スピードより安全を優先する
ということであり、利害関係者が増えたとしても移行チーム内の決断だけで物事を進める(もしくはこれまでの状態に戻す)ことができるような仕組みにしておくということであった。
コンセプトの決定まで
当案件は事業インパクトが大きい。 そのため一般論ではあるが、普段の案件にも増して、利害関係者が増え/工程のスピード感が落ち/信頼性が重要になってくる、といえる。
そのような難しい状況で、良い状態(=利害関係者数に依らずスピード感と信頼性が保たれている)であるためには、
- スピード感を阻害する事柄
- 他案件への影響範囲が大きい状態
- 作業時間と作業量の問題=容易に更新できない
- 各影響案件にて更新分の開発作業が必要
- 各影響案件とのデプロイまでのスケジュール調整が必要
- 社内の審査調整・承認プロセスなども影響してくる
- 緊急切り戻し時に各影響案件においても時間と手間がかかる
- ※そもそも緊急切り戻し時に「時間がかかる」のは論外なのではという面もある…
- 作業時間と作業量の問題=容易に更新できない
- 他案件への影響範囲が大きい状態
- 信頼性を阻害する事柄
- 実装のバグ
- 想定外のシステムエラー
- 実績の不足
- 社において、AWSとのDirect Connect回線(プライベート回線)を大規模活用する初のケース
を避けること、もしくは対応策を持っていることが重要といえる。 そこで、
- 対スピード感
- 移行チーム作業だけで更新が済むようなシステム実装にしておく
- 移行チーム判断だけで変更が進むような社内了承体制にしておく
- 対信頼性
- ※各テストを実施する、などは当たり前なので割愛
- 部分ごと少しずつの変更
- 移行チーム単独での作業・判断が間違っていた場合や、未知の要素により危険が迫った際の保険を確保しておく
- 保険=(データ的にもシステム的にも)完全に切り戻すことができ、移行を「無かったこと」にできる
という仕組み・方針を取るとベターなのではないか、という方向性に行き着いた。
実装方針
コンセプトを踏まえ、実装方針を決定した。
- ストレージゲートウェイ
- AWS上に構築したHTTP-API
- このAPIがMogileFSやS3など実際のストレージ層を隠蔽する
- 隠蔽しているので、内部の状況は移行チーム事情で自由に変更できる
- 統一クライアントライブラリから呼び出す
- 基本的なアクセス元は統一クライアントライブラリのみなので、クライアント側の「HTTP-APIの使い方」に修正が必要となった場合でも対応しやすい
- と言いつつ、統一クライアントライブラリを用いない特殊なパターンにも比較的容易に対応可能(一般的なHTTP-APIなのでMogileFS操作ライブラリは不要)
- 基本的なアクセス元は統一クライアントライブラリのみなので、クライアント側の「HTTP-APIの使い方」に修正が必要となった場合でも対応しやすい
- このAPIがMogileFSやS3など実際のストレージ層を隠蔽する
- AWS上に構築したHTTP-API
- MogileFSとS3のオブジェクトを同期するタスクキューシステム
- ストレージゲートウェイ本体とは非同期で処理動作するシステム
- 移行完了後は不要になる、暫定的なもの
- AWS上に構築したHTTP-API
- 指定のオブジェクトをMogileFSからS3へ、もしくはS3からMogileFSへコピーするというタスク
- API越しにタスクを受け取り内部キューに溜め、API動作とは非同期で処理する
- ストレージゲートウェイ本体とは非同期で処理動作するシステム
- MogileFSのオブジェクトをFID順にS3にコピーするバッチ
- ストレージゲートウェイ本体とは別で、単純にコピーしていくだけのもの
- 移行完了後は不要になる、暫定的なもの
- 処理時間削減のため、コピー動作の並列化対応なども行う
- ストレージゲートウェイ本体とは別で、単純にコピーしていくだけのもの
マイルストーンの作成
これまでの内容を踏まえ、複数フェーズを持つ移行マイルストーンを作成した。
作業実施
マイルストーンに沿って、開発や経路変更を実施していった。
マイルストーンの説明
マイルストーンの各フェーズを図示する。 また、各フェーズについて、
- 実現したかった「状態」
- なぜその状態を実現したかったのか(実現することでどんな効用があると思っていたのか)の「ポイント」
を合わせて説明していく。
フェーズ1:ストレージゲートウェイ作成
- 状態
- ピクトリンク各サービスが、統一クライアントライブラリを利用している
- 基本的には統一クライアントライブラリのみがMogileFSにアクセスしている
- ストレージゲートウェイの開発開始
- API
- MogileFSに正しく読み書きできるもの(MogileFSに対してのラッパー状態)の作成を進める
- S3については同時作業でも後作業でも良いが、本番投入はしない
- 速度測定
- MogileFS及びS3への読み書き速度を測定する
- 問題があればキャッシュを考慮する
- MogileFSに正しく読み書きできるもの(MogileFSに対してのラッパー状態)の作成を進める
- 統一クライアント
- ストレージゲートウェイ対応版の作成を進める
- API
- コピーバッチにて、MogileFSのオブジェクトを古い順(FID順)にS3にインポートし始めている
- ピクトリンク各サービスが、統一クライアントライブラリを利用している
- ポイント
- データ流通のコントロールを始める
- もしS3にアクセスできるとしたら、古いオブジェクトなら入手できる
- MogileFSデータのS3バックアップを早速始めているといえる
フェーズ2:ストレージゲートウェイへの移行
- 状態
- ストレージゲートウェイを本番稼働する
- ピクトリンク各サービスが、統一クライアントライブラリを利用する
- 統一クライアントライブラリがストレージゲートウェイにアクセスしている
- システム事情により統一クライアントライブラリを利用できないサービスも、MogileFS直アクセスではなくストレージゲートウェイにアクセスするよう変更する
- ポイント
- MogileFSへの直接アクセスが終了した
- ストレージゲートウェイ側の挙動でデータ統制できる=移行チーム側でデータ流通をコントロールできる状態になった
- ストレージゲートウェイを単純なHTTP-APIにしておくことで、HTTPで喋ることができるかどうかだけの問題に落とし込んでいる
- 統一クライアントライブラリ以外からのアクセスにおいても、MogileFS特有の処理を考えなくてよい状態にしている
- MogileFSをストレージゲートウェイが隠蔽(MogileFS→HTTP)し、ストレージゲートウェイを統一クライアントライブラリが隠蔽(HTTP→Java等)している
- 独立しているものが多段になっているので、割り込みやすい、挿げ替えやすい、責任(バグ、エラー、レイテンシーなど)の所在が分かり易い
- MogileFSへの直接アクセスが終了した
フェーズ3:ストレージゲートウェイのS3書き込み開始
- 状態
- 書き込みをMogileFSだけではなくS3にも行う
- ストレージゲートウェイへのPUTについて、同期システムを通してS3にも書き出すように設定変更する
- 同期システムに常にタスクを登録する
- MogileFSがプライマリー、S3がセカンダリーの体
- MogileFSにはこれまで通り書き込んでいるため、移行を中止しても問題無い
- ストレージゲートウェイへのPUTについて、同期システムを通してS3にも書き出すように設定変更する
- コピーバッチにとっては「FID何番までコピーすれば任務完了か」が明確になる必要があるが、それがこの段階で判明する
- 書き込みをMogileFSだけではなくS3にも行う
- ポイント
- MogileFSとS3の両方に書き込みたいが…
- 処理をなるべく重くしたくない
- S3書き込みのタスク登録だけを同期処理で済ませ、書き込み動作自体は非同期処理で実施する
- タスク登録処理で利用しているAWSサービス(API Gateway&Lambda&SQS)が爆速なので重くならない
- 読み込み時にクライアントが参照するのはあくまでMogileFS上のオブジェクトなので、S3書き込みが非同期で遅延していても運用上全く問題にならない
- S3書き込みのタスク登録だけを同期処理で済ませ、書き込み動作自体は非同期処理で実施する
- 泣き別れ(MogileFSとS3のどちらかに書き込み成功しどちらかに書き込み失敗する)によるクライアントへのエラー返却を避けたい
- S3はあくまでセカンダリーなので、そのエラーのためにクライアントにまでエラーを返してしまうことは、クライアント側でのユーザー向けサービス提供においてユーザー体験を損なう
- S3書き込みは遅延してもいいので、エラーが発生しても運営サイドが人力でゆっくりカバーすればいいだけ
- S3書き込みタスク登録(同期)→MogileFS書き込み(同期)→S3本書き込み(非同期)の順で実施
- 「MogileFS書き込みに成功しS3書き込みタスク登録に失敗する」というパターンは、実装やリカバリー対応が難しくなるため、そのパターンを回避した順序
- S3書き込みタスク登録失敗時はクライアントにリクエスト失敗を返す
- とはいえ、事前調査にて、タスク登録処理で利用しているAWSサービス自体がエラーになることはほぼ無いことが分かっている
- MogileFS書き込み失敗時はクライアントにリクエスト失敗を返す
- プライマリーに書き込めていないので、これまで通り通常の失敗扱いで良い
- S3本書き込み(非同期)失敗時は、移行チームが人力でリカバリー対応
- MogileFS書き込みに成功していた場合は同期システムにタスクを再投入
- MogileFS書き込みに失敗していた場合は同期システムからタスクを削除
- 処理をなるべく重くしたくない
- 最近のデータをS3に書き込んでおく流れ
- クライアント(ピクトリンク)は最近のデータを参照するケースが多いという調査結果に基づく決定
- 後のフェーズでの「S3からの読み込み処理」が効率的になる
- MogileFSが今すぐ壊れたとしても、とりあえず最近のデータがS3にバックアップされている体になるため、需要の高いそれらを救出することができる
- クライアント(ピクトリンク)は最近のデータを参照するケースが多いという調査結果に基づく決定
- 同期システム側は、MogileFSからのデータ読み込みにおいて、自分専用のストレージゲートウェイを別建てし利用する
- ストレージゲートウェイがもたらす隠蔽(MogileFS→HTTP)の恩恵を、同期システムも享受する
- 同期システムはMogileFSについて知らなくてよくなるため、開発の難易度が下がる
- ストレージゲートウェイがもたらす隠蔽(MogileFS→HTTP)の恩恵を、同期システムも享受する
- MogileFSとS3の両方に書き込みたいが…
フェーズ4:ストレージゲートウェイのMogileFSとS3の読み込み主従逆転
- 状態
- データ読み込み時に、まずS3から読み込もうとする
- S3がプライマリー、MogileFSがセカンダリーの体
- 何らかの理由で失敗したらMogileFSから読み込む
- コピーバッチで稼働していた「MogileFS古い順S3インポート」が終わるまでこのフェーズで待つ
- (リアルタイムレベルで)最新のデータを除き、S3にMogileFSのデータバックアップができているという形にする
- つまり「過去ファイルは全てS3にも存在」、「今後のファイルは同期システムがS3に投入」という状態になっている
- (リアルタイムレベルで)最新のデータを除き、S3にMogileFSのデータバックアップができているという形にする
- データ読み込み時に、まずS3から読み込もうとする
- ポイント
- 読み込みにおいてS3をプライマリーにすることで、MogileFSの負荷を減らすことができる
- 最近のデータはS3に非同期書き込みしているため、S3のデータ読み込みヒット率は高い=セカンダリーであるMogileFSから読み込む機会はとても少ない=MogileFSの読み込みアクセス負荷が大幅に減るはず
- MogileFSの負荷を減らしておくことで、移行前繁忙期アクセス増によってMogileFSのサーバーを増やさなければならなくなる可能性を低くしておくことができる(移行する気がある、将来的に無くなる事が分かっているものに対して掛けざるを得ない悲しいコストを回避)
- ストレージをS3に変更した場合の処理時間を計測できる
- 時間がかかっているようであれば、キャッシュ等を検討/導入する段階を持てる
- 読み込みにおいてS3をプライマリーにすることで、MogileFSの負荷を減らすことができる
フェーズ5:ストレージゲートウェイのMogileFSとS3の書き込み主従逆転開始
- 状態
- 書き込み先をS3にする
- ストレージゲートウェイへのPUTについて、同期システムを通してMogileFSにも書き出すように設定変更する
- 同期システムに常にタスクを登録する
- S3がプライマリー、MogileFSがセカンダリーの体
- 旧来のMogileFSからS3への同期タスクが残存していた場合は、それらも完了(キューが空になる)まで実施される
- タスクの新規追加が無くなるので、やがてタスクが捌けていき全て完了するだろう
- ストレージゲートウェイへのPUTについて、同期システムを通してMogileFSにも書き出すように設定変更する
- 書き込み先をS3にする
- ポイント
- 読み書き両方においてS3をプライマリーとする
- MogileFSの負荷をさらに減らす
- MogileFS運用に即戻れるように、MogileFSにはセカンダリーとして非同期でデータ同期を続けている
- 最悪のケースとして、MogileFSが今すぐ壊れたとしても、S3で事業継続することが可能
- 同期システム側は、MogileFSへのデータ書き込みにおいて、自分専用のストレージゲートウェイを別建てし利用する
- ストレージゲートウェイがもたらす隠蔽(MogileFS→HTTP)の恩恵を、同期システムも享受する
- 同期システムはMogileFSについて知らなくてよくなるため、開発の難易度が下がる
- ストレージゲートウェイがもたらす隠蔽(MogileFS→HTTP)の恩恵を、同期システムも享受する
- 読み書き両方においてS3をプライマリーとする
フェーズ6:ストレージゲートウェイのMogileFSとS3の書き込み主従逆転完了
- 状態
- 旧来のMogileFSからS3への同期タスクを全て消化する
- ポイント
- この時点でようやくS3がマスター、MogileFSがリードレプリカ化したことになる
- とはいえMogileFSが参照されるのは例外時のみ
- この時点でようやくS3がマスター、MogileFSがリードレプリカ化したことになる
※実際には、MogileFS→S3の同期システムはほぼリアルタイムにタスクを消化していた。そのため、フェーズ5〜フェーズ6は即時完了となった。
フェーズ7:ストレージゲートウェイのMogileFS読み込み終了
- 状態
- ストレージゲートウェイのMogileFS読み込み終了
- ストレージゲートウェイへのGETがS3のみで完結している(MogileFSへのフォールバックGETが発生していない)ことを確認し、読み込みを終了する
- データ読み書き時にMogileFSを一切参照しないということ
- ただしMogileFSへのデータ同期は実施している
- ストレージゲートウェイのMogileFS読み込み終了
- ポイント
- 基本的に読み書きをS3で賄う体制
- MogileFSはリードレプリカであるが、実質リードレプリカからも一歩引いたという感じ(セカンダリーからも引いた第3位というか…)
- MogileFSへのデータ同期は行われているため、いざという時はMogileFSをリードレプリカとして復帰できる
- 基本的に読み書きをS3で賄う体制
フェーズ8:ストレージゲートウェイのMogileFS同期停止
- 状態
- MogileFSへのデータ同期を停止
- タスク登録処理は行われているが、実際の同期処理をしていない
- この時点でMogileFSは世代遅れになり始める
- ただし、同期処理を再開しタスクを全て消化すれば最新状態に復帰できる
- MogileFSへのデータ同期を停止
- ポイント
- S3に比べてMogileFSが世代遅れになることを明確にする
- システムではなく関係者(人間)側の心の準備フェーズといえる
- 問題ありそうなら同期処理を復活させる
- S3に比べてMogileFSが世代遅れになることを明確にする
フェーズ9:ストレージゲートウェイのMogileFS取り扱い終了
- 状態
- ストレージゲートウェイの同期タスク登録終了
- MogileFSへのデータ同期を完全停止
- S3に比べてMogileFSが世代遅れになる
- つまり、この時点でMogileFSに戻ることが困難になる
- MogileFSへのデータ同期を完全停止
- ストレージゲートウェイの同期タスク登録終了
- ポイント
- 心の準備を終えた関係者が実施スケジュールを決めて、実行するフェーズ
- MogileFSサーバー運用停止スケジュールと合わせてタイミングを決定できる
- 心の準備を終えた関係者が実施スケジュールを決めて、実行するフェーズ
終わりに
「移行作業において正とする方向性とは何か」ということを、コンセプトやマイルストーンとして議論し明示していくことで、実際の開発作業時や変更作業時に発生する様々な決定を行い易くすることができました。
結果として、サービス無停止で移行作業を完了でき、移行を理由としたユーザーに不利益を与えるような事態は発生しませんでした。
ピクトリンクサービスはシステム強化を日々行なっており、引き続き様々な開発・移行作業が発生し得る状況です。 開発チーム一同、今後も安定したシステム運用を心がけていきたいと思います。