メルカリWebのマイクロサービス化、その4年

Author: @urahiroshi, Engineering manager of Web Platform team

2022年8月4日、メルカリで “web-2” と呼ばれるサーバがシャットダウンされました。これはメルカリWeb版の開発に携わっているチームにとって、一つの区切りとなる出来事でした。
web-2はPHPで記述されたwebサーバで、2015年から https://www.mercari.com/jp/ 配下のコンテンツを配信していましたが、現在では複数のWebマイクロサービスがその機能を担っており、 https://www.mercari.com/jp/ 配下のページは後継となるWebマイクロサービスが配信するページへリダイレクトされています。

メルカリWebのマイクロサービス化に向けた開発が始まり、最終的にweb-2がシャットダウンされるまで、実に4年以上の期間がかかりました。この記事では、この4年間に開発チームの中でどのような議論があり、どういった選択を行いながらアーキテクチャを変更していったのかを記述し、変化に強い組織やアーキテクチャについて考える上で参考にしていただければと思います。

Web re-architectureプロジェクトの始動 (2018/05〜)

メルカリWeb版のアーキテクチャを一新しようというre-architectureプロジェクトが始まったのは2018年でした。
当時、メルカリWeb版のコンテンツはすべてweb-2のサーバから配信されていました。web-2の開発当初はPHPの経験が豊富なメンバーが多く、Dietcube (https://github.com/mercari/dietcube) という自社製のPHPフレームワークを用いて効率的にサーバサイドの実装を記述していました。しかし、時間が経つにつれて、以下のような問題を感じられるようになってきました。

  • 開発当初よりも、操作に応じて画面を更新するようなインタラクティブな挙動を必要とする要件が増え、組織規模の拡大によりJavaScriptなどWeb Frontend技術を専門とするメンバーが増えたこともあり、JavaScriptでロジックの中心を記述してPHPの依存を減らしたいという要望が高まっていた
  • web-2が提供する機能が増えるにつれてコードやデプロイが複雑化し、変更に対する開発コストやリリース後に発生する問題が増加していた

2018年5月のアーキテクチャ図

【2018年5月のWeb】
(実際はCDNやプロキシサーバー、データベースなども利用していますが省略しています)

こうした問題を解決するため、コードベースやアーキテクチャを一新することでメンテナンス性を高めること、また最新のWeb Frontend技術を用いてページ描画のパフォーマンスを向上することを目標として、2018年5月よりWeb re-architectureプロジェクトが始まりました。

また、この年にはAPIサーバのマイクロサービス化の計画も始まっており、PHPで記述されていたモノリシックなAPIサーバをKubernetesのインフラストラクチャ上で稼働するGoのマイクロサービス群に一新するという方向性が打ち出されていました。
web-2のインフラやデプロイ処理はSREチームによって管理されていましたが、Webのアーキテクチャを一新するにあたってインフラやデプロイ処理についてもWebチームが主体となって設計・運用したいという要望があり、それがマイクロサービス化の方向性にマッチしていたため、Web re-architectureプロジェクトはWebのマイクロサービス化も目指すこととなりました。

こうして、どういった技術選定をするか、どういったマイクロサービスを提供するかという検討が始まりました。re-architectureプロジェクトで作ることとなったマイクロサービスと、その背景について以下に簡単にまとめています。

web-fuji: Server-Side Rendering (SSR) を提供するサービス

  • SEOの観点やOGPのサポート、ページ描画のパフォーマンスを向上する目的からSSRを利用することとして、フレームワークはNext.js (https://nextjs.org/) を選択しました。
  • 将来的にWebのマイクロサービスを作っていく中で山にちなんだ名称をつけていこうということで、このサービスは富士山からweb-fujiと名付けられました。しかし、残念ながらこの後に山から名付けられたサービスは作られませんでした。

web-graphql: Web APIを提供するサービス

  • web-2はAPI呼び出しのためのエンドポイントも提供しており、いわゆるBFF(Backend For Frontend)の機能も担っていました。このBFFとしての機能を提供するためのプロトコルとしてGraphQLを利用することとし、フレームワークとしてApollo Server (https://www.apollographql.com/docs/apollo-server/) を選択しました。

web-gateway: Webマイクロサービスに対するルーティングを行うサービス

  • Web re-architectureのリリースではページごとに段階的にリリースする方針を立てており、web-2とWebマイクロサービス間で徐々にリクエストを切り替えていくために、Webマイクロサービス間でのルーティングや、Canaryリリースなどのルーティングの制御を行うサービスとしてweb-gatewayマイクロサービスを作ることとしました。
  • web-2のルーティングを行っていたロードバランサがNGINXであり、NGINXのロジックの移行のしやすさや、Cookieベースのsession affinityやCanaryリリース機能が提供されていたingress-nginx (https://kubernetes.github.io/ingress-nginx/) を利用することとしました。

web-session: セッション情報の取得・更新を行うサービス

  • web-2ではCookie内のセッションIDに紐付けられたユーザーのセッション情報をデータベースに保持しており、セッションIDをアクセストークンに変換してAPIを呼び出す処理を行っていました。web-gatewayがweb-2とWebマイクロサービス間で段階的にルーティングを切り替えるにあたって、その移行期間はweb-2のセッション情報との同期を行う必要があり、処理が複雑化することが予想されたので、セッション情報の取得・更新に特化したマイクロサービスを作ることとしました。
  • web-sessionの技術スタックは、Webの各マイクロサービスで極力共通の技術を用いるためにNode.jsを選択し、一方でサービス間通信プロトコルとしてメルカリ内で推奨されていたgRPCを利用する方針としました(後述しますが、この選択は後に変更されることとなります)。また、セッション情報を格納するデータベースとしてCloud Spannerを選択しました。

2018年のメルカリTech Conferenceの際にWeb re-architectureの計画について発表しており、re-architectureの目的やアーキテクチャの概要についてはそちらの資料も参考になるかと思います。
https://speakerdeck.com/mercari/mtc2018-web-application-as-a-microservice

Webマイクロサービスのリリース (2019/06〜)

最初の目標は、なるべく早い時期にトップページだけ新しいアーキテクチャから配信するというものでした。しかし、いくつかの問題があり、リリースのスケジュールは遅れていきました。

  • 新しい技術に対する過小な見積もり:
    トップページだけのリリースであっても各マイクロサービスのインフラ構築、CI/CD基盤などは完全な形で整備する必要があり、多くの技術がWebチームとして初めて経験するものであったため、スケジュールの見積もりが甘い面がありました。

  • 要素技術の変更による手戻り:
    web-sessionの技術選定において、当初はNode.jsでgRPCサーバを実装するという予定でしたが、当時はgRPCがNode.jsエコシステムにおいて十分にサポートされておらず、Node.jsからGoでgRPCサーバを実装するように変更しました。この時点ではgRPC自体は利用する想定でしたが、やはりNode.jsからgRPCクライアントを実装する場合にも問題があり、最終的にgRPCではなくREST APIを利用することに変更しました。

  • サービスの依存関係によるスケジュール遅延:
    web-fujiやweb-graphqlから他のマイクロサービスのAPIを呼び出すためにはweb-sessionの機能が必要だったため、web-sessionの実装が遅れたことでweb-fujiとweb-graphqlの開発もブロックされてしまい、更に開発を遅らす要因となりました。

  • 開発プロセスの変更:
    全社的にスクラム開発プロセスやDesign Docの導入が始まり、Webチーム内でもそれらを取り入れましたが、チームとしての学習コストや、運用方法についての試行錯誤にも時間を費やしていました。

そうした中で、リリースの目処が立ったのは開発を開始しておおよそ1年が経過した2019年6月でした。まず最初にリリースしたのはweb-gatewayです。この時点では単にリクエストをweb-2に中継するだけですが、Webマイクロサービスのリリースに合わせてルーティング先をコントロールしていくため、最初にリリースを行ったものです。

2019年6月のアーキテクチャ図

【2019年6月のWeb】
web-gatewayを導入し、リクエストを中継しています

他のサービスのリリースについてはweb-gatewayのcanaryリリース機能を利用して、0%(社内ユーザーのみアクセス可能)、1%、5%、10%…と徐々にトップページのトラフィックをweb-2からweb-fujiに切り替えていき、8月に100%のリクエストをweb-fujiに切り替えることができました。

2019年8月のアーキテクチャ図

【2019年8月のWeb】
トップページをweb-fuji、web-graphqlから配信するようになりました

このアーキテクチャ変更後のweb-fuji、web-graphqlのリリースフローやweb-gatewayの機能にについては、 https://engineering.mercari.com/blog/entry/2019-10-30-105936/ に詳細を記載していますので、興味があればご参照ください。

トップページのリリース後は、他のページについても徐々にweb-fujiに切り替えていきました。これからどうやってweb-2のページを移行していくか。web-fujiやweb-graphqlが新たなモノリスになってしまう懸念もあり、どのようにサービスを切り分けるかという検討も徐々に始まりました。
そうした中で、2019年12月に転機が訪れたのです。

GroundUp Webプロジェクトの立ち上げ (2019/12〜)

2019年12月、Webチーム全員が集まり、新しい計画について話し合っていました。この計画はGroundUp Webプロジェクトと言われ、新たにゼロからメルカリWeb版を設計・実装し直そうというものでした。Web re-architectureのリリースから4ヶ月後のことで、なぜまた最初から設計、実装をやり直さなければいけないのかと議論になりました。

GroundUp Webプロジェクトの立ち上げには、いくつかの背景がありました。

  • デザインの一新:
    Web re-architectureプロジェクトの方針は、既存のデザインは変更せずに、アーキテクチャや実装を置き換えるというものでした。しかし、モバイルの利用者が増えていたことなどから、GroundUp Webプロジェクトではデザインを一新してモバイルブラウザ向けのレイアウトの提供を行うことでユーザー体験を向上したいと考えていました。

  • ページ単位のリリースではなく、コア機能をまとめてリリースする:
    re-architectureプロジェクトはページ単位でリリースしていたため、個々のページごとにリリース作業が必要でした。また移行前と移行後のページ間での互換性を考慮する必要があり、移行作業に時間がかかる要因となっていました。加えてデザインを一新したいということもあり、CtoCのマーケットプレイスとしてのコア機能をまとめてリリースしたいと考えたのです。

  • Android、iOSチームと同等の責務を持つアーキテクチャ:
    メルカリWeb版はAndroid、iOSアプリ版に比べて機能が不足している部分があり、機能の不足を補っていきたいと考えていました。将来的に特定の機能をWeb版とアプリ版で実装したいと考えた場合に、アプリ版の場合はまずbackendチームが必要なAPIを実装し、クライアントコードをアプリ上に実装するということを想定していましたが、Web版の場合はさらにGraphQL APIの設計・実装やServer-Side Renderingの処理を考慮する必要があり、開発チームの責務や開発フローがAndroid、iOSチームと異なることが機能実装の際のネックとなっていました。アーキテクチャの変更によりAndroid、iOSチームと同等の責務を持ち、より迅速な機能提供を行いたいという意図もありました。

もちろん、Web re-architectureプロジェクトのアーキテクチャをベースとしてこうした点を解消するというアプローチも考えられましたが、既存のアーキテクチャにとらわれずにゼロベースで考えていこうという方針から、GroundUp Webプロジェクトが始まりました。

GroundUp Webプロジェクトのアーキテクチャにおいて、以下のような意思決定が行われました。

Web Componentsを用いたDesign Systemの刷新

  • Web re-architectureプロジェクトでは、UIコンポーネントとして内製のDesign Systemのライブラリを利用していました。Design SystemのライブラリはReactをベースに記述されていましたが、メルカリグループ内でより広く利用されるために、特定のフレームワークに依存しないWeb Compentsを用いてDesign Systemを作り変え、GroundUp Webプロジェクトでそれを利用することとしました。Design Systemの詳細については https://engineering.mercari.com/blog/entry/20210823-8128e0d987/ に詳しく記載されています。

Static Site Generation (SSG)、Dynamic Renderingの採用 (web-surugaマイクロサービス)

  • Web Componentsを利用するとSSRを行うことは難しくなることや、SSRに伴う開発負荷を減らしてiOS、Androidの開発チームと同等の開発フローを持ちたいという点から、Static Site Generation (SSG) をベースとしてアーキテクチャを設計し、SSGを行うフレームワークとしてGatsby (https://www.gatsbyjs.com/) を選択しました。
  • SSGで生成されたファイルにはAPI呼び出しの結果取得できるコンテンツは含まれず、SEO上のリスクがあるため、SSGに加えてDynamic Renderingというアーキテクチャを採用しました。具体的には、Rendertron (https://github.com/GoogleChrome/rendertron) を利用したPrerenderというサーバを配置し、Googleのクローラなどの代わりにPrerenderがメルカリWeb上のJavaScript処理を実行し、クローラに対して動的に生成されたHTMLを返すというものです。
  • GroundUp Webを提供するマイクロサービスは “web-suruga” と名付けられました。これはWeb re-architectureプロジェクトのweb-fujiが富士山から名付けられていたため、富士山を見渡せる駿河湾から名前を取ったものです。

web-authマイクロサービスの作成

  • ログインや新規登録の機能を実装するにあたって、web-suruga上に実装するのではなく、web-authという新しいマイクロサービスを作るという選択を行いました。
  • ログインや新規登録画面は、将来的にメルカリWeb版だけではなく他のWebサービスからも利用されることを想定していたので、オーナーシップの観点からメルカリWeb版を提供するweb-surugaとは切り離したほうが良いと考えたことがマイクロサービスを分離した理由です。
  • web-surugaはGoogle Cloud Storage(GCS)から静的ファイルを返すようなアーキテクチャを考えていましたが、Google、Facebook、Appleアカウントを利用したログインや新規登録機能の実装において認証後のコールバックリクエストを受け取る箇所があり、GCSから静的ファイルを返すアーキテクチャでは対応が難しいため、Goのサーバから動的にレスポンスを返せるようにするなど、アーキテクチャ上の差異もありました。

web-graphql、web-sessionの削除

  • セッション管理やAPI呼び出しについても見直しました。GraphQL APIを経由するのではなく、Android、iOSアプリと同様にBackendチームが提供しているAPIをクライアントから直接呼び出す形にしたいと考えました。
  • 当初はセキュリティの観点からアクセストークンを直接ブラウザ上に保存せず、アクセストークンを含むセッション情報をJWTの形式でHttpOnly Cookieに保持させようと考えていました。しかし、十分に有効期限の短いアクセストークンを利用することで、アクセストークンをブラウザのLocalStorage上に保持し、アクセストークンを再発行する場合に限りHttpOnly Cookieとして保存されたセッションIDを用いるよう変更しました。
  • アクセストークンはブラウザ上に保存され、トークンの再発行にはiOS、Androidアプリと共通のマイクロサービスを利用していたため、Webチームとしてweb-sessionマイクロサービスを運用する必要がなくなりました。

なお、Webマイクロサービスの責務や分割を考慮する際に、明確な理由がない限りはマイクロサービスを分割しないという方針を取りました。Webサービスは、Webページが動作するために必要なJavaScriptなどのファイルをブラウザからダウンロードすることで動作しています。そのため、URLごとに異なるマイクロサービスからコンテンツを配信する場合、サービスが変わると必要なファイルも変わるためキャッシュ効率が悪くなり、サービスを細かく分割しすぎるとユーザー体験に悪影響をもたらします。そのため、web-surugaマイクロサービスが多くの役割を持つことを許容し、サービスを極力分割しない方針を取りました。

こうして新しいアーキテクチャの枠組みが決まり、開発が進みました。Web re-architectureの開発での反省を活かして、セッション管理やAPI呼び出しの実装ができるまでは、簡易的にAPIを呼び出せる開発用のBFFを提供することで、フロントエンドの実装がスムーズに進められるようにしました。また、CI/CD設定、End-To-Endテストの実装、Kubernetes上のインフラ構築などでもWeb re-architectureの際に培った知識やノウハウを活かすことができました。

しばらくはweb-2やweb-fuji上の機能追加も並行して行われていましたが、やがてweb-2、web-fujiに対する機能追加を止め、Webの開発チーム全体でGroundUp Webの実装を進めました。

1年前後の開発期間を経て、リリースの準備が整ってきました。これまでのメルカリWeb版は https://www.mercari.com/jp/ 配下のURLを利用していましたが、メルカリUS版も https://www.mercari.com を利用しており、日本版とUS版でドメインを分けるため、GroundUp Webのリリース後は https://jp.mercari.com のドメインに切り替えようと考えていました。
そのため、以下のように段階的にリリースを行っていく方針としました。

  1. メルカリ社員を対象に https://jp.mercari.com を限定的に公開し、フィードバックを集める (Internal Release)
  2. 一定の割合のユーザーに対し、 https://www.mercari.com/jp/ から https://jp.mercari.com にopt-in形式でリダイレクトする (Limited Release)
  3. https://www.mercari.com/jp/ 配下の移行対象のページに対するすべてのリクエストを https://jp.mercari.com にリダイレクトする (Full Release)

まずInternal Releaseを2021年3月に実施することができましたが、Codecovへの不正アクセスによるセキュリティインシデント (https://about.mercari.com/press/news/articles/20210521_incident_report/ ) に対する全社的な対応のため、開発を一度停止する期間を挟みました。
開発を再開した後、Limited Release、Full Releaseに向けたリダイレクト処理の設計・実装を行いました。これまでは https://www.mercari.com/jp/ 配下のパスごとにweb-2、web-fujiがリクエストを処理するような形となっていたため、web-2、web-fujiのそれぞれにリダイレクトの処理を実装しました。
そうした開発の後、無事8月5日にLimited Releaseを開始し、徐々にリダイレクトの割合を増やしていった後に、9月29日をもってFull Releaseを実施できました。

2021年9月のアーキテクチャ図

【2021年9月のWeb】
GroundUp Webをリリースし、jp.mercari.com のドメインからweb-suruga、web-authのコンテンツを配信する形になりました

この記事では各Webマイクロサービスの役割を中心に記述していますが、GroundUp Webプロジェクトの中心を担っているweb-surugaマイクロサービスのアーキテクチャや開発体制などについては、以下の記事に記載されています。
https://engineering.mercari.com/blog/entry/20210810-the-new-mercari-web/

web-2 sunset を目指して (2021/09〜)

GroundUp WebのFull Releaseは無事に終わりましたが、すべての https://www.mercari.com/jp/ 配下のページが https://jp.mercari.com にリダイレクトされたわけではなく、引き続きweb-2が配信しているページが残っていました。また、Web re-architectureの際に作成したweb-fuji、web-graphql、web-sessionといったサービスはリダイレクト処理だけのために残されていました。大部分の機能が利用されなくなったといっても、インフラのメンテナンスやセキュリティ対策などは継続して行う必要が生じるため、次の目標として、web-2、web-fuji、web-graphql、web-sessionといったサービスのインフラを完全に削除したいと考えました。これはweb-2 sunsetプロジェクトと名付けられました。

まず、リダイレクト処理のみを行っているweb-fuji、web-graphql、web-sessionの削除に取り掛かります。リダイレクト処理自体は引き続き残しておく必要がありますが、そのためだけにこれらのインフラを維持する必要はありません。また、GroundUp Webのリリース後1ヶ月間はログイン済みのユーザーのログイン状態を維持したままリダイレクトを行っていたため、cookieとアクセストークンの切り替えやGroundUp web向けのアクセストークン発行などの処理を行っていましたが、この処理についても削除する方針でした。そうなると、他のサービスとの依存関係も考慮する必要がなく、 https://www.mercari.com/jp/ 配下のURLと https://jp.mercari.com のURLをマッピングするだけで良いです。
そのため、2021年11月にこれらのリダイレクト処理のみを実装したweb-redirectionというマイクロサービスを提供し、それによりweb-fuji、web-graphql、web-sessionのインフラを削除することができました。
web-redirectionは、運用の負担を減らしたいという点や、実装にある程度の柔軟性をもたせたいという点から、Cloud Functionにて実装されています。

2021年11月のアーキテクチャ図

【2021年11月のWeb】
web-redirectionサービスがリダイレクト処理を行うことで、web-fuji、web-graphqlのサービスを削除できました

次に、web-2が配信しているページをどうするかです。残っていたページは、以下のように分類できました。

  • 各サービスの規約やプライバシーポリシーなど
  • キャンペーンや特定のサービスに対するランディングページ
  • お客さま向けにメルカリの利用方法を案内するページ(メルカリガイド)
  • Universal Linkなどを用いてアプリ版のメルカリを立ち上げるページ

このうち、メルカリガイドについては、内製のCMS機能などもあり独立性が高い機能であるため、新たにhelp-centerマイクロサービスを立ち上げてコンテンツを移行する計画が既にありました。
それ以外の「各サービスの規約やプライバシーポリシー」「キャンペーンや特定のサービスに対するランディングページ」については、API呼び出しなどが必要なく、HTMLなどの静的なファイル配信より提供できると考えられたため、新たに web-static-page というサービスを立ち上げ、そちらに移行することを決めました。
また、「Universal Linkなどを用いてアプリ版のメルカリを立ち上げるページ」は実質的にリダイレクト処理をしているものだったので、web-redirectionサービス上に実装することとしました。
複数のサービス間で共通のドメインを用いると、LocalStorageやCookieの情報が意図せず共有されるなどの潜在的な問題が生じるため、これらのサービスはそれぞれ別のドメイン上でコンテンツを配信することとしました。

それぞれのサービスのオーナーシップを持つチームにより、これらの開発は並行して進められ、以下のようにページの移行を実施できました。

  • 2022年4月: help.jp.mercari.com ドメインとそれに対応するhelp-centerマイクロサービスのリリース、対応する https://www.mercari.com/jp/ 配下のページのリダイレクト
  • 2022年5月: static.jp.mercari.com ドメインとそれに対応するweb-static-pageマイクロサービスのリリース、対応する https://www.mercari.com/jp/ 配下のページのリダイレクト
  • 2022年6月: web-2上のリダイレクト処理をすべてweb-redirectionサービスに切り替え

2022年6月をもって https://www.mercari.com/jp/ 配下のすべてのリクエストがリダイレクトされ、web-2へのトラフィックがなくなっていました。そして、ついに8月4日をもってweb-2のインフラは完全に削除されました。こうして、4年以上に渡ったメルカリWebのマイクロサービス化は完了したのです。

2022年6月のアーキテクチャ図

【2022年6月のWeb】
コンテンツごとに適切なマイクロサービスがリクエストを処理しています

おわりに

アーキテクチャはその時々のアプリケーションや組織のニーズ、利用できる技術に合わせて変わり続けるものであり、この4年を経て作られたマイクロサービスも変わり続けるでしょう。
この記事を執筆している現時点でも、Webチームでは以下のようなアーキテクチャの変更を計画しています。

  • Dynamic RenderingからSSRへの移行。Dynamic RenderingサーバのCPU負荷によるコストの増加や、レスポンスの遅さによるSEOへの影響を鑑みて、SSRへの切り替えを行いたいもの
  • web-authを https://jp.mercari.com から独立したドメイン上に提供する。web-surugaからの独立性を担保し、ルーティングを容易にするためのもの

前者の変更については、実はアーキテクチャ設計時にもパフォーマンス上の懸念はあり、Dynamic Renderingの採用前に負荷テストを実施していました。しかし、当時の負荷テストではまだアプリケーション自体ができていなかったため、仮のコンテンツに対してレンダリング処理を実施しており、その結果が最終的なアプリケーションと大きく乖離していました。負荷の問題が認識されたのはリリースの直前で、その時点でSSRに移行することは現実的ではなく、お客さまに影響のあるサービスでもないため見送っていたものでした。
一方で、Dynamic Renderingを利用していたのはweb-surugaのみであり、この変更は他のサービスには影響しません。この面ではマイクロサービスアーキテクチャのメリットが出ていると言えるでしょう。

この4年間のアーキテクチャ変更や開発の流れを振り返って見てみると、改善できた点や反省すべき点が多く見つかります。それを恥じたり非難したりするのではなく、変更の目的とプロセス、結果を振り返り、また次の変更に活かしていくことで変化に強い組織が生まれるのだと考えています。この記事がその一助となれば幸いです。

最後に、これまでweb-2の開発に貢献いただいたすべての方、Web re-architectureプロジェクト、GroundUp Webプロジェクト、web-2 sunsetプロジェクトに関わったすべての方に、心よりお礼を申し上げたいと思います。これまでのメルカリWebの基盤を支えていただいたこと、一つ一つのプロジェクトの過程があり今のメルカリWebを形作られたことにとても感謝しています。本当にありがとうございました。

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追加