食べログの大規模なレガシーシステムを段階的に改善していく取り組み

こんにちは、食べログシステム本部長の京和です。
今年の4月から本部長になりました。さらに4月に娘が生まれました:tada:

本エントリでは食べログで1年を通じて取り組んだ、大規模なレガシーシステムの段階的な改善について紹介します。[\[翻訳\] Shopifyにおけるモジュラモノリスへの移行](https://qiita.com/tkyowa/items/ae9fa550237cb6f48318) に続いて2記事目のアドベントカレンダーになります。

# どのように段階的に進めるか
食べログは今年で15年目のサービスで、Railsになってからは13年が経過しています。これだけ歴史があればあちこちにガタが来ているのは当然で、無数にある課題に対してどこからどのように取り組んでいくかを最初に決める必要がありました。

まず最初の前提として以下のように考えました。

- 既存のビジネスや開発を止めるような悪影響を与えない。むしろなるべく早くポジティブな影響を与えていきたい。
 - これだけ歴史のあるシステムを改善していくのは長い時間がかかるため、取り組みの価値について経営陣やビジネスサイドから理解を得ることが必要
- 初期は専門チームで推進するが、いずれは食べログ上で開発するすべてのエンジニアを巻き込んで取り組めるようにしたい
  - 継続的に改善するためには、サービス開発エンジニアがオーナーシップを持ち、自分たちで改善していける体制を作る必要がある
  - 専門チームだけで進めるには時間がかかりすぎる
- これらを踏まえると、まったく新しいシステムを作ってそこに移行していくよりも、今のシステムを徐々に改善していく方針がよさそう。

上記の考えの元、下記のような方針を立て進めることにしました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3232/b8cfbca7-62f1-da56-3fd8-8634ce9a7537.png" width=800/>

ざっくりいうと、最初にインフラの改善、次にアプリケーションの改善、そして最後にもし必要があればマイクロサービスにする、という順序です。

最初にインフラに取り組むことにしたのは、インフラのレガシー化によってシステム変更のリスクや開発者の調査コストが増加していて、そのことが結果として保守的な開発姿勢を生み、それがコード品質の悪化に繋がるという悪循環に生んでおり、最初にこの**悪循環の構造を打ち破る必要があると考えた**からです。アプリケーションの設計変更やリファクタリングを進めるにも現状のインフラではリスクが高く、必要であることが分かっていても行うのが難しい状態でした。

また、マイクロサービスについては、インフラアーキテクチャとアプリケーションアーキテクチャが整っていない状態でマイクロサービスへ移行するのは非常にリスクが高く、期待する成果が出るかどうかも不透明なため、選択肢として視野には入れつつ、優先度としては後ろ倒しにするべきだと判断しました。

それでは、ここからはインフラ・アプリケーションそれぞれの取り組みについて紹介していきます。

# インフラ
まず、インフラ基盤にはKurbenetes(K8s)を採用することにしました。ただ、K8sは学習コストが高い、更新が早く追従が大変、などという声もあり、本当にK8sにするべきかどうかの確信が持てなかったため、まずは新規事業でGKEを採用することにしました。新規事業であれば技術的なチャレンジには合理性がありますし、フルマネージドな環境であれば初期コストも低いため、PoC的な位置づけで始めるには最適でした。

GKEをプロダクションでしばらく運用してみて使用感を確かめ手応えを得たこと、またアップデートの頻度については、最新のエコシステムに追従し続けることが技術的な競争優位性に繋がると前向きに捉え、K8sの採用を決めました。

これに限らずですが、最初は小さく始め、素早く検証を重ねて成功体験を得てから大きく展開する、というのはどの領域においても重要なプロセスだと思っています。また、インフラ構成を考えていく上ではCNCFの [Cloud Native Trail Map](https://github.com/cncf/landscape/blob/master/README.md#trail-map)や[Cloud Native Interactive Landscape](https://landscape.cncf.io/)がとても参考になりました。

## デプロイちょっぱやプロジェクト
食べログでは当時、Capistrano を使用した push型のデプロイ方式を採用していました。push型のデプロイは中央のホストから各サーバにファイルを配布するため、サーバ台数の増加に伴いデプロイ時間も増加します。当時は様々な改良を施した上でもデプロイに約30分かかっており、時間がかかりすぎていました。K8s化すればこの課題は解決しますが、デプロイのパフォーマンスは組織全体に与える影響が大きく、見過ごせない課題でした。

そこで、K8s化の前に先行して pull 型のデプロイに切り替えることにしました。名付けてデプロイちょっぱやプロジェクトです。もともと長期の運用を想定していたわけではなかったため、ベースはCapistranoのままで最小限の変更で S3 から デプロイイメージを pull する方式に切り替え、デプロイ時間を約10分の1に短縮しました。K8sでもデプロイは pull 型で行われるため、ここで得た資産や知見は無駄になることなく引き継がれています。

## バッチジョブ
K8s化後はGitOpsを全面的に導入するため、CDツールにはArgo CDを選定しています。その流れでバッチのワークフローエンジンには同じアルゴプロジェクトであるArgo Workflowを採用することにしました。
食べログでは現在はWebSAMを使っており、ジョブの設定変更をSREチームへの依頼ベースで行い、SREチームは手動で設定変更する、、、というなかなかに厳しい運用を強いられています。Argo WorkflowによってIaCを実現し、バッチジョブ管理のセルフサービス化を実現したいと考えています。
少し話は飛びますが[NetflixのFull Cycle Developer](https://techlog.voyagegroup.com/entry/2019/02/04/171325)の考え方には強く共感していて、開発チームがオーナーシップを持って自分たちのプロダクトを改善・運用できる状態にするために、ジョブに限らずセルフサービス化は積極的に進めていきたいと考えています。

## オブザーバビリティ
メトリクスについては今の事実上標準になっているPrometheus / Grafanaを採用しています。また、Full Cycle Developerの実践のため、GrafanaのダッシュボードはSREチームだけでなくサービス開発の各チームでも自由に運用できるようにしたいと考えており、PromQLを叩けるサーバをスケールアウトできるようにThanosを導入する予定です。

ロギングについてはGCP Stackdriver Loggingに集約する構成にしました。これまでは本番サーバにSSHやSCPでログを持ってくるというラディカルな運用だったので、ここの改善は非常に評判が良かったです。

その他、Datadog APMの導入なども検討しています。
 **オブザーバビリティはデータに基づく意思決定を行い、フィードバックループを回すための基盤の一つだと捉えていて、データドリブンな組織文化の実現のためにも極めて重要な領域だと捉えています。** 

## メッセージング
食べログではエコシステムが成熟する前に必要に迫られて自前で実装したミドルウェアが幾つか存在します。メッセージングもその一つで、独自のメッセージングサーバを実装していましたが、改修や障害時の調査などが属人化しており、メッセージング基盤のビジネス重要度が高まっていく中で課題の一つになっていました。

昔と違って今ではほとんどのケースでエコシステムに乗っかるほうが良いと思っていて、自前での実装は、自前でなければ実現できない要求がある時のみで良いと考えています。そこで、OKRとして「メッセージ基盤を障害に強く・追跡調査を容易にする」というObjectiveを設定し、Apache Kafkaへのリプレイスを進めています。また、メッセージの検索にはElastic Cloudを採用することにしました。
食べログではユーザートラフィックを処理するサーバ群はオンプレで運用していますが、社内利用が中心のシステムについては積極的にクラウドを積極的に採用することで、運用管理のコストを削減しています。

## 社内での情報発信
新しいインフラ基盤の大まかな検証を終え、実現が見えてきた段階で、「食べログインフラの未来」と「技術の食べログになる」という2つのテーマで、 @tsukasa_oishi と @weakboson と @yzusa の3名から食べログの全エンジニアに向けて社内で発表会を行いました。
これまでのOKRのウィンセッション[^1]で培ったデモ力を駆使した素晴らしい発表で、サービス開発エンジニアにとって今後より開発しやすい環境になる期待感を醸成するとともに、 **「私たちは技術の力で食べログを使ってくれる人々を幸せにするためにここにいる」**という原点を見つめ直すきっかけにもなりました。 

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3232/6080ee29-bb15-9629-80c7-ecd139fbd2b5.png" width=240 />
_発表を受けて、感動したり感極まったりする人たちの図_

今年中にはK8sの本番稼働まではいきませんでしたが、今後数ヶ月の間で食べログのほとんどがK8sに移行していく予定で、今後がとても楽しみです。

# アプリケーション
次はアプリケーションについてです。とはいえ全体像でも話したとおり、アプリケーションへの取り組みは次のステップで、まだ構想段階の状態です。ということで現時点で考えていることを書き残しておきます。

## アーキテクチャの技術選定
「マイクロサービスにするべきかどうか、（もしするとしたら）いつ・どのようにマイクロサービス化を進めるか」というのは多くの組織で悩むポイントではないでしょうか。[\[翻訳\] Shopifyにおけるモジュラモノリスへの移行](https://qiita.com/tkyowa/items/ae9fa550237cb6f48318)ではモジュラモノリスについて紹介しましたが、モジュラモノリス or マイクロサービスということではなく、 モジュラモノリスはモノリスからマイクロサービスへの段階的移行の一形態と捉えています。

[Modular Monoliths — A Gateway to Microservices](https://medium.com/design-and-tech-co/modular-monoliths-a-gateway-to-microservices-946f2cbdf382)  で指摘されているとおり、マイクロサービスによってコードが改善されるわけではありません。むしろ、事前にアプリケーションのドメイン境界やコンポーネントとインターフェースを適切に定義し、高凝集・疎結合な状態を実現できていなければ、マイクロサービス化が失敗する確率は高いでしょう（ここでいう失敗とは組織の開発生産性が向上しない状態を指します）。食べログではマイクロサービスでシステムを分割する前に、まずはモノリスのアプリケーションを段階的に改善していこうと考えています。

密結合で技術的負債が溜まったアプリケーションというのは散らかった部屋のようなものです。散らかった部屋を見て「よし、部屋を分けよう！」なんていきなり言いだす人はあまりいなくて、まずはいらないものを捨てたり、ホコリや汚れをキレイに掃除したり、収納グッズを買って用途ごとに置き場所を決めたりするなどして、部屋を整理しようと考えるのが自然です。システムもそれと同じようなものだと考えています。

トレンドとして最新の方法論を学ぶことは必要ですが、それが今の自分たちの課題を解決するものとして適切かどうかは切り分けて考えるべきです。また、 **その方法論がオーバーエンジニアリングでないか** 、つまり、必要以上に時間がかかったり、新たな問題を増やさないか、という点は技術選定において極めて重要視している観点の一つです。

なお、Shopifyの翻訳記事については続編として  [Under Deconstruction: The State of Shopify’s Monolith](https://shopify.engineering/shopify-monolith) というブログが公開されています。Wedgeについては期待された効果が出なかったそうで、代わりにPackwerkという静的な定数への参照[^2]のみを分析対象とするツールが開発され、OSSとして公開されています。Packwerkについても[Enforcing Modularity in Rails Apps with Packwerk](https://shopify.engineering/enforcing-modularity-rails-apps-packwerk) というエントリで紹介されています。
翻訳記事は予想以上に反響があったので、 **続編についても頑張って翻訳しよう**と思っています。

## モノリスの限界
マイクロサービスという概念が提唱されたのは2012年ですが、当時と比べると現在ではクラウドネイティブを実現するためのエコシステムが大きく発展しました。これらはマイクロサービスのような分散システムのみならず、モノリシックなアプリケーションでも恩恵を受けることができます。
また、最近では Jenkins作者の川口さんが立ち上げた [Launchable](https://kohsuke.hatenadiary.com/entry/hello-launchable) のような機械学習を活用したテスト効率化のプロダクトなども出始めています。

Shopifyの事例やこれらを踏まえると、モノリスの限界というのはこれまで考えていたほど早く迎えるものではなくなってきたのではないか、と考えています。マイクロサービスは、システムのスケールよりも組織のスケールを目的として導入するべきではないかというのが今の肌感です。
そのため、マイクロサービス化の適切なタイミングやアーキテクチャを設計するには、事業構造や組織構造、そして中長期で事業が目指すべき方向性の理解が不可欠になってくると思います（つまり、逆コンウェイの法則ですね）。
究極的には、事業が最も力を入れるべき領域に対して柔軟にマイクロサービスとして切り出すことができるような状態が理想的なのかもしれません。

## フロントエンド
フロントエンドについては jQueryからReact / TypeScript へとリプレイスする取り組みを以前から進めています。こちらは [食べログ フロントエンドエンジニアブログ](https://note.com/tabelog_frontend)で積極的に情報発信していますので、詳しくはブログをご覧ください。

ちなみに最近Railsとフロントエンドの間で議論が活発になっていますが、個人的には対立構造で捉えるようなものではないと思っています。アプリケーションレイヤにおいては処理の主体がフロントエンドかバックエンドかということよりも、高凝集・疎結合なコードベースへの変革が重要かつ高難易度な課題だと考えており、フロントエンドのモダン化については、技術環境の変化に対応できる柔軟性と、ユーザーに対してより良いUI/UXを提供する選択肢を手に入れることが主な価値だと考えています。
フロントエンドはユーザー体験にも大きく影響する領域なので、引き続き力を入れて取り組んでいきます。

# その他の取り組み
組織面のアプローチとしては、10月からマトリクス型組織を一部で導入し、自己組織化されたクロスファンクショナルなチームづくりへのチャレンジを始めています。また、[ギルドワークス](https://guildworks.jp/)さんにアジャイルコーチとして、仮説検証型のアジャイル開発のコーチングをしてもらっています。技術部では引き続きOKRを運用していて、ストレッチゴールを設定し、逆算思考で仕事を進める考え方がだいぶ浸透してきました。最近では私が主導することはほとんどなくなっていて、ウィンセッションで娘の成長を自慢するくらいしかやることがありません。

iOS/Androidアプリでも隔週から毎週リリースにするためにリリースプロセスの改善や自動化を進めていたり、レイヤードアーキテクチャへのリアーキテクトなどが進んでいます。また、在宅勤務になってからはMiroを活用した振り返りやブレインストーミングをするチームが多くなってきました。Miroはとても便利で、個人的にはリアルな付箋よりもMiroの方が便利なケースが多いと感じるようになりました。
他にも様々な方面で新しい取り組みが始まっており、ユーザーや飲食店に価値を素早く安定して届けられる開発組織づくりを進めています。

# 最後に
外食産業は今、新型コロナウイルスによって大きな苦境に立たされています。私は技術の力で少しでも多くの飲食店を救い、更にその先の未来にある、外食産業のニュースタンダードを食べログで作っていきたいと考えています。日本の外食産業は世界に誇る文化です。その文化を守り、新しい形へと発展させることが、日本一のグルメサイトである私たちが社会に果たすべき使命だと考えています。

食べログに興味がある方、共感していただける方はぜひ  [Twitter](https://twitter.com/tkyowa)  や  [Facebook](https://www.facebook.com/takayuki.kyowa)  などで気軽にお声がけください。
俺たちの戦いはこれからだ！

[^1]: OKRについては2019年のアドベントカレンダーに書いているのでよろしければぜひご覧ください。 [技術部門にOKRを導入したら3ヶ月で部の雰囲気がめちゃくちゃ良くなった話 - Qiita](https://qiita.com/tkyowa/items/fa432d9cf25ec54aeda2)
[^2]: RubyではクラスもClassクラスのインスタンスとして表現されていて、クラスの定義式ではクラス名の定数にClassオブジェクトを代入しています。