ミツモア Tech blog

「ミツモア」を運営する株式会社ミツモアの技術ブログです

ミツモアの数十万ランディングページを支える技術

※こちらはミツモアAdvent Calendar 2023の25日目の記事です。

メリークリスマス、ミツモアエンジニアの @teradonburiです。

今回はミツモアの数十万のランディングページ(以下LP)を支える技術に関して紹介します。
ミツモアでは多種多様のサービス提供事業者様と依頼者様を結びつけるため、 地域×提供サービスの組み合わせにて40万を超えるLPが存在しています。
これらのページは検索からの依頼に直結する導線となり、 プラットフォーマーとしての要となるため、 弊社はSEOに力を入れています。

LPの変遷

弊社のLPでは多岐にわたるサービスに登録している事業者様のコンテンツ、口コミなどをコンテンツリッチに表示しています。
大量のデータを各LPユニークに表示するということは、それぞれのコンテンツをLP独自に抽出し、一時保存する必要がありました。
当然大量のLPを生成するための負荷対策やデータ整合性(LP自体が存在しているか、内部リンク切れになっていないか)を考える必要があります。

初期(2017〜)

Dailyで深夜patch処理で数万ページをMongoDBのLocationService(地域×サービス)という専用テーブルに生成。 ReactのSSRにてLPを表示する手法です。 当時はReactのSSR技術はまだあまり確立されてはおらず、 コンポーネントのlazyload(dynamic import)を含む、サーバーサイドのwebpackビルドに苦労した覚えがあります。 patch処理にMongoDBに負荷やNodeJSの実行時間がかかったりするリスクがありました。 この辺の技術課題はmongodbのbulkWriteやtransactionで一括保存することで解消することができるようになりました。

BigQueryからページ読み込み(2021〜)

StitchからMongoDBのデータを定期的に吸い出し、BigQueryに保存し、フォーマットやBIツールで分析。 LPアクセス時にBigQueryからフォーマットしたデータをクエリ経由で取得、redisにキャッシュして、NextJSをホスティングするVercelをheadlessサーバにしてコンテンツを配信していました。 BigQueryにデータを集約することで多様なコンテンツを柔軟にフォーマットできるのと、ページ自体の存在判定、リダイレクト、noindex判定をBigQueryに保存したことによりビジネスチームの分析改善に使うことができました。 また、NextJSのISR機能を使うことで効率的にレンダリング済みページを静的化し、配信の高速化を実現できました。

※厳密にはBigQueryから返却しているデータ量の関係でObjectIdのみ返却し、 nodejsサーバからObjectIdでMongoDBへの検索しているものはあります。

負荷問題

本年(2023年)3〜5月の間でトラフィックの増大でサーバー全体のパフォーマンスが低下した時期(一時はサーバーダウンして503エラーが頻発した)がありました。
主な理由はMongoDBへの負荷(原因は大量データがある口コミテーブルのindex貼り忘れ)、BigQuery経由からのクエリ取得遅延でAPIサーバに負荷がかかっていたのが要因でした。
プロダクトの危機を脱するため、ミツモアのエンジニアチームは総出でサーバーパフォーマンス改善に取り掛かっていました。

  • Datadog Monitoringの強化(主にRUM機能導入によるAPIパフォーマンス監視)
  • MongoDBのレイテンシ監視

以下はMongoDBやAPIサーバの負荷監視や対策に関して詳しく書かれている弊社記事ですので、お見逃しの方は必見です。

Observing and improving API performance engineering.meetsmore.com

How do We Optimize MongoDB Latency and Reduce Cost engineering.meetsmore.com

BigQueryからページ読み込み(2023〜)

LPの負荷対策としては、Reverse ETLを導入しました。
DatabaseからDataLakeに保存する流れをETL「Extract (抽出)」「Transform (変換)」「Load (書き出し)」というのですが、 DataLakeでフォーマットしたデータをDatabaseに逆同期することをReverse ETLと言います。
弊社はBigQuery→MongoDBのデータ同期となるため、 これをサポートしているhightouchというプラットフォームを利用しています。
深夜のスケジューリング処理で一括同期となりますが、MongoDBにLP用のキャッシュテーブルを作成し、これと同期を行っています。
導入から半年以上経過していますが、現在も安定稼働しております。
(導入に際し、慎重に負荷テストを行いましたが、hightouch自体が差分同期をサポートしており、クエリで部分的なデータを同期することも可能であり、採用しました。)

今後の展望

1つ目はNodeJSサーバをビジネスサーバから分離してLP専用のサーバを作成することです。 これにより、サーバーの責務と障害ポイントを分離することができます。

2つ目はNextJSの代わりにQwikへのリプレースを検討しています。

Qwikを採用したい理由としては、今後も肥大化していくLPコンテンツに対し、NextJSなど主要なJSフレームワークが提供するhydrationでは主にLCPの限界が来ることを予見したからです。
Google Tag Managerを除いて、 NextJSではすでにCSRの無効化、SSGや遅延CSS読み込みやcontents-visibilityなどの対策を施し、ほとんど改善すべき箇所が無いほど速度改善しました。 しかし、事前生成したHTMLが肥大化していく問題はFrameworkレベルで解決する方法がありませんでした。
そんなおり、調べてたどり着いたのがQwikでした。
Qwikは従来のJSフレームワークと違い、ユーザのアクションに合わせてresumableにデータを遅延ロードすることがフレームワークレベルでサポートされています。
これにより、どんなにページ内のコンテンツが増えてもページ速度が落ちない、コンテンツスケーラブルなLPを実現できます。

Qwikに関しては別記事にまとめてみましたので、興味がある方はご一読ください。

Qwikが魅せるコンテンツスケーラブルで爆速なウェブページの最終形 qiita.com

最後に

ミツモアでは様々な職種のエンジニアを積極的に採用しています! ご興味がある方はぜひ気軽に面談しましょう!