マイクロサービス化に半年くらい取り組んでいたので振り返ってみる
こんにちは。スペースマーケット エンジニアの西尾です。
この記事は スペースマーケットプロダクトチーム Advent Calendar 2019 の 12月13日の記事となります。
今日は約半年間取り組んできた一部機能のマイクロサービス化についてどんなことをやってたのかとか、何に悩んでいたかなど振り返ってみたいと思います。
マイクロサービス化について
1つのシステムに対して改修を行っていくと一般的にはシステムが大きくなるにつれて、新しい機能を作るための時間やコストは上がっていきます。
この1つの理由として何か変更しようとした時の影響やリスクが機能改修を繰り返すたびに大きくなっていくことがあげられます。
例えばAシステムがあるとして、少し大きめなサービス改善PJを行うたびにソースコードが2000行増えたとしたら、10回行えば20,000行、100回行えば200,000行増えます。
とある機能を少し直して欲しいとお願いされた場合、2,000行の中から影響箇所を探してコードを少し直す場合と200,000行の中から直すのでは大変さは全然違ってきます。
また、共通ライブラリをアップデートしようとした時も小さいコードベースのシステムをアップデートするよりも大きなコードベースのシステムをアップデートする方が遥かに大変なのは明らかです。
1つのシステムが大きくなるといろいろ起こるなら小さなシステムに分割し、組み合わせて1つのサービスを提供すれば良いのではないか。
小さければわかりやすいし、アップデートしやすくなるし、その他にもいいことがたくさんある ということが理由でいろいろな企業が最近マイクロサービス化に取り組んでいますが、スペースマーケットでも流れに乗ってマイクロサービス化に取り組んでいます。
2019年6月 前半 始まるまでの経緯
6月時点では機能開発/改善を行うチームに属していました。
すでに1つの機能がマイクロサービス化されていたのですが、そろそろもう一つやりたいねという話が数ヶ月前から上がっていたのですが、なかなか難しく。
というのもマイクロサービス化については機能追加/改修の類いとは違い、これを行ったからといってすぐに売り上げUPとはいかず、未来への投資という意味合いの方が強いものになります。なのでやる理由の説明が難しい、、、
そのため、何かの施策に混ぜてその時に一緒にやる という方針でいました。
その時、たまたま予約関連でいくつかやや大きめの変更を行うかもという企画がありそれに合わせ、予約関連部分を切り出してみるのが良さそうとなりました。(粒度や大まかな分割方針はすでにあったのでそれに合わせて)
ちなみに予約関連の変更施策の中で予約マイクロサービス化という要件が上位に組み込まれていたのはエンジニアマネージャーが施策を検討しているプロダクトマネージャの横でマイクロサービス化とささやき続けていた功績が大きいと思います。
2019年6月 中盤〜後半 方針決め/見積/説明
細かすぎる分割粒度にはしないという方針があったのと、予約の際の一貫性を維持する必要がある部分を分けるのはなかなか難易度や工数がかかりそうだったのでその部分は一緒に予約サービスに移すことにしました。
なので、まあまあの規模を本体から予約サービスに移すことになり、完璧な状態まで持っていくのはかなり時間がかかりそうだったので初回の分割では行わないことに決めたことも多かったです。
例えばマイクロサービス化では、DB分割を行ってからシステムを分けるのが良いらしいですが、DBは共通のままでいく方針にしました。(初回以降に分ける予定)
2019年7月中旬 実装開始。本体サービスからコピー
元々予約関連の施策に混ぜて予約をマイクロサービス化する予定でしたが、6月終盤に元の施策が頓挫。予約のマイクロサービス化も消えるのかと思われましたが、チーム移動で予約マイクロサービスの専任になったため続行できることに。
7月上旬はスコープ変更などがあったり、想定してた人数と違ったり(複数人でやる想定でしたが1人でやることに、、、)などありましたが、中盤頃から本格的に実装開始。まずは本体から関連部分をコピーするところから始めました。
ただ選別しながらコピーするのも芸がなく、せっかく新しいシステムなのだからということでRails4 -> Rails5にアップデートして移行しました。
大量に落ちるテストを修正しながらの移行で、途中一緒にバージョンアップするのはやめとけばよかったかもとも思いましたが、初回リリースが終わった今ではアップデートしてよかったと思っています。
精査しながらコピーする中でよかったことはテスト(Rspec)がとても早くなったことです。
元々が遅すぎたのもありますが、40分(4並列)が5分(4並列)にまで短縮され、落ちる大量のテストの修正もサクサクできました。
予約関連のテストが遅いのは実行前の準備データの用意部分ということはわかっていたのですが、予約サービスに関係ないModelのCallback処理やValidate処理を消したことで劇的に改善されました。
2019年7月下旬 Web側へのレスポンス返却部分対応(GraphQL側調査)
1つのシステムを2つに分けるのですが、今回分ける予約の情報について、Web側で表示するときは予約以外も様々な情報が付与されています。
例えば、予約情報を表示するときの部屋の情報やプランの情報、ユーザーの情報といったものです。
1つが2つに分かれるのですが、本体では予約の情報は持っていない、逆に予約サービスでは予約以外の情報を持っていない。しかしWeb側に返すレスポンスは分割前後で同じである必要があります。
スペースマーケットではGraphQLを採用しているので、このとき新しく出ていたApollo Federationというものを用いてこの問題に立ち向かいました。
Apollo Federationについては過去にブログを書いているのでそちらを参照ください。
Apollo Federationについては、この時はまだ出たばかりなこともありRailsでは対応するライブラリが開発途中版しかなく、あってるはずなのになぜか動かない。ライブラリ自体のバグっぽいので直接いじりエラー箇所を修正したり、直近のアップデートを反映させたら直ったりと探り探り動かしながらでなかなか大変でした。
また、Apollo Federationに対応するために既存の他サービスにもまあまあ手を加える必要があることがわかったのですが、物量が多いだけで単純作業になりそうだったのでまだ実装イメージがフワフワしていたREST側の対応に入ることにしました。
2019年8月上旬 Web側へのレスポンス返却部分対応(REST側調査・実装)
GraphQL側と同様に元は1つだったものが2つの分かれるけど、Web側には分割前と同じ結果を返さなければならない、ということで具体的にどうやるか検討、実装を始めました。
これがかなり大変で、そもそも他の人は具体的にどうやって複数のレスポンスを合成する方法を書いているんだろう といろいろ調べてみましたが私では具体例(ソース)は発見できませんでした。
なのでApollo Federationの劣化版みたいなものを独自実装することにしました。
具体的には__extend_resourceというレスポンスが含まれていたら各サービスの前段にあるGateway側で内容を解析し、他システムから必要情報を持ってきて合成しWeb側にレスポンスを返すといった感じで実装しました。
基本的には弊社はGraphQLをメインにするという方針があり、今後REST部分はさらに減っていくため、既存部分が正常に動くためのに必要な最低限の機能だけ実装しました。
その他、Geteway経由でリクエスト投げるとレスポンスが全然返ってこない(http header情報間違ってたから)などHTTP Header関連でめちゃくちゃハマったり、JSON形式以外のリクエスト横流し方法がわからなすぎて、(例えば画像アップロードをクライアント -> Gateway -> API と横流しするためにGateway(Node.js)でどう処理するか、)ひたすら調べまくって実装したり(しかもあまり情報が出てこない)とどうやって実現するか技術的にわからない状態が続きこの時期は胃が痛かったですが、とても実になりました。
2019年8月中旬〜下旬 Apollo Federation導入のために既存サービスのアップデート
Apollo Federationを導入するためには今回対応する予約サービスだけでなく、既存サービスも対応させる必要がありました。(予約サービスだけ入れるということはできず、全部を対応しないと全く動かない)
対応が必要なのはNode.jsで書かれたサービスと、Railsで書かれた本体サービスの2つ。単純にライブラリを入れれば良いと思っていましたが、調べていくみるといくつか既存の書き換えが必要な部分がありました。
Node.js 側は次の対応をしました。
・@apollo/federation ライブラリ導入
・GraphQLの表現方法をGraphQL.jsのGraphQLSchemaで記載する方法からGraphQL Schema Languageで書く方法に修正
結構多くのファイルを書き換えたのですが、オニオンアーキテクチャーを採用していることもあり、どこ直せば良いか明確になっていたので(インフラ層のコードを書き換えるだけでロジックには影響がない)割とすんなり対応することができました。
既存Rails側は次の対応をしました。
・apollo-federation-rubyの導入
・GraphQLのライブラリをほぼ最新に(最新でないと動かないため)
・GraphQLの記載方法をクラスベースに置き換え(変えないと動かないので)
特にGraphQLの記載方法を定義ベース(define-based syntax)からクラスベース(class-based syntax)に置き換える必要があり、しかも結構書き方が変わっているのでひたすら変換する作業をしました。
最初は手動でやろうとしましたが、量が多すぎて泣きそうになったので標準装備されていた自動アップグレードコマンドを利用して変換しました。
ただ、変換に失敗するものも多く、さらに変換に失敗すると元ファイルが消えるので1ファイルずつコミットしながら丁寧に変換。変換失敗した場合はファイルを戻して手動で書き換えということをやってました。
8月最後の方は無影響テストを実施、1度に全てをリリースすると大変なので既存サービスのアップデートは先行してリリースすることにしました。
2019年9月前半 Web側へのレスポンス返却部分実装(GraphQL実装)
Apollo Federation導入の準備が整ったので予約サービスに適用していきました。この作業が終わった時点である程度Web側から叩いても画面動く状態になりました。
2019年9月後半 非同期処理部分の対応
予約処理が完了するとダッシュボード側のメッセージタイムラインを流したり、メールを送ったりといった付随処理がいろいろとあるのですが、予約処理は予約サービスで、付随処理は既存サービスで、と一連の流れが分割されることになるので予約が終わったら完了したよというジョブを登録し、それを既存サービスが受け取ったら付随処理を実行する という形に書き換えていきました。
結構量が多く作業的には大変でした。
2019年9月 並行して新機能の開発
9月はかなり優先度が高かった予約画面のリニューアルPJが始まり、自分もアサインされたため、マイクロサービス化と新機能開発を並行して行っていました。
新機能開発にあたり予約関連なので、元の既存サービスに含めるか、あるいはまだ出てないけど予約サービスに混ぜるか自分の中で葛藤がありましたが、既存サービスに記載してもすぐに予約サービスに移動となる、またマイクロサービス化もなるべく止めたくなかったため新機能については初めから開発中の予約サービスの上に載せることにしました。
これにより予約マイクロサービス化が原因でリニューアルPJが遅延することは許されなくなったのでなるべく早く動作確認できる状態に持っていこうとがんばりました。
2019年10月 GraphQL側 バグ修正・テスト・リリース
思った以上に新機能の開発に時間を取られ、10月下旬(予約リニューアルPJのリリース日)に全てをリリースするのは無理そうだとわかったので、リリースの際は予約マイクロサービスのREST部分はまだリリースせず、GraphQL部分だけリリースするというように段階的にリリースする方法をとることにしました。
10月の上旬・中旬は主にバグ修正とWeb画面とつないでのテストを(マイクロサービス 、リニューアル共に)、下旬は移行手順の作成と検証環境で移行手順の実施などを行いリリース日に臨みました。
2019年11月前半 REST側 残開発・バグ修正・テスト
無事GraphQL側のリリースも終わり、9月後半に行っていた非同期処理部分の開発続きを行ました。
実装が終わりいざ検証環境で実行させようと試みたところなぜかJobがバックグラウンド実行せず。(フォアグラウンド実行はうまくいっていた)
エラー箇所を見ると複数DBに接続するためのOctopusというライブラリを利用していましたが、その箇所でエラーが発生していました。
いろいろ修正してみましたが、良い方法が見つからず。
その時ふと最近Rails6が出て複数DBの接続を標準装備していることを思い出したので移行をしてみることに。
Rails4 -> Rails5への移行は大変でしたが、Rails5 -> Rails6 への移行は簡単に行え、かつJobのバックグラウンド実行も無事うまくいくようになりました。
最後に既存システムから予約サービスに移行したロジック等を削除する作業を行い、テスト・バグ潰しを行いリリースできる状態まで持っていきました。
2019年11月後半 Web・アプリのREST向き先を変更、REST側もリリース
REST側についてもGatewayを経由しての接続に変わるため、既存のWeb及びアプリのAPI接続先を変更する必要がありました。
そのため、
1. Gatewayを経由するけどGatewayの先は既存サービスのみにつながっている状態でWeb, アプリの向き先を変える
2. 1週間半くらい立ってAPI向き先が変更になったアプリがある程度浸透したら残りのアプリを強制アップデート
3. 予約マイクロサービス REST側リリース
という形でリリースを行いました。
また、リリース当日はALBでのカナリアリリースも使い、影響を確認しながら徐々に切り替えていきました。
最後に
予約のマイクロサービス化の初めからリリースまでの約半年間のことについて今回は時系列で自分がどんなことをやっていたのか書いてみました。
予約部分についてRails4 -> Rails6と最終的にはほぼ最新の状況になったり、CIを早くできたり、システムを分割してもそれを使うWeb側に影響がないようなレスポンスを返すことができる仕組みが作れたので、本体からさらに別のものを切り出す となった時の土台になったりと今回やって良かったと思いました。
とても大変な期間でしたが、無事リリースできて今はほっとしています。
次のAdvent Calendar の記事は、同じくエンジニアの 斎藤さん です!
どうぞお楽しみに!
この記事が気に入ったらサポートをしてみませんか?