TECH PLAY

株式会社メルカリ

株式会社メルカリ の技術ブログ

270

こんにちは。メルカリのバックエンドエンジニアの @amenbo と、プラットフォームチームの @siroken3 です。この記事は、この記事は、 Merpay & Mercoin Advent Calendar 2025 の19日目の記事です。 メルカリでは AI-Native company を目指し、さまざまな AI エージェントの導入や運用改善に取り組んでいます。開発者の生産性向上もその一環であり、コーディング支援ツールの積極的な活用を進めています。そんな中、今年の7月にAWSからPreview公開された「Kiro」の試験導入を進めています。 Kiro は従来の「vibe coding」を超えた、仕様書駆動の開発を実現する IDE です。ただ、新しいツールを組織に導入する際には、技術的な評価だけでなく、アカウント管理や課金管理といった運用面での課題にも向き合う必要があります。 本記事では、メルカリが Kiro をどのように導入し、Okta と AWS IAM Identity Center を連携させたアカウント管理の自動化をどのように実現したかを紹介します。 Kiro とは Kiroの概要 Kiro は、2025年7月に AWS からパブリックプレビューとしてリリースされ、11月に一般提供(GA)が開始された spec-driven development を特徴とする agentic IDE です。VS Code のオープンソース版である Code OSS をベースに開発されています。 従来の AI コーディングアシスタントは、その場その場で「なんとなくいい感じに」コードを生成する「vibe coding」と呼ばれるアプローチが主流でした。一方、Kiro は仕様書を中心に据えた開発フローを実現します。開発者は以下の3つのマークダウンファイルで構成される「Kiro Specs」を作成し、これを基に AI エージェントがコード生成や実装を支援します。 requirements.md : ユーザーストーリーと受け入れ基準を定義 design.md : 技術設計とアーキテクチャを記述 tasks.md : 実装タスクをチェックリスト形式で管理 Kiroの料金プラン Kiro の有償プランを組織で利用するには、AWS の AI コーディングアシスタントサービスである AWS Q Developer の Pro サブスクリプションプランを使用します。具体的には、AWS IAM Identity Center を通じてユーザーを管理し、Q Developer Proのライセンスを割り当てる仕組みになっています。 なお、2025年11月の GA 以降、Kiro は独自のサブスクリプション体系に移行しましたが、引き続き IAM Identity Center によるユーザー管理が必要です。このKiro 独自サブスクリプション体系への移行については、この記事の後半に述べます。 アカウント管理における課題と解決策 既存のOkta連携基盤 組織で Q Developer Pro のライセンス管理を行うには、AWS IAM Identity Center との連携が不可欠です。メルカリでは Kiro の登場以前から、Okta と Identity Center の連携を構築しており、社内のさまざまな AWS リソースへのアクセス管理に活用していました。 この連携では SCIM 2.0(System for Cross-domain Identity Management)プロトコルによる自動プロビジョニングを採用しています。Okta 上でユーザーやグループを追加・変更すると、その情報が自動的に Identity Center へ同期される仕組みです。 Kiro のアカウント管理にもこの仕組みを活用しました。Okta に Kiro 利用者用のグループ( AWS_kiro_JP_Users )を作成し、そのグループに Q Developer Pro のサブスクリプションを割り当てます。これにより、Okta のグループにユーザーを追加するだけで、そのユーザは Kiro が利用可能になります。 ただし、この時点では Okta へのユーザー追加自体は手動で行っており、利用申請があるたびに AWS 管理担当者がコンソールからユーザーを登録する運用でした。作業自体は数分で終わるものの、担当者の手が空いていなければ対応は後回しになり、申請者を待たせてしまうケースが発生しました。 AI エージェントによるアカウント発行の自動化 Okta との連携をさらに一歩進め、すでに Okta と連携している社内の AI エージェントを Kiro のグループを扱えるように設定し、申請プロセスそのものも自動化しました。 ユーザーは Slack から「I want kiro」のように AI エージェントに伝えるだけで、以下のフローが自動的に実行されます。 AI エージェントがリクエストを受け取る AI エージェントが Okta の API を呼び出し、ユーザーを Kiro グループ( AWS_kiro_JP_Users )に登録 Okta が SCIM プロトコルで Identity Center にユーザー情報を同期 Identity Center で Q Developer Pro アカウントが有効になる ユーザーが Kiro を使い始めた瞬間から従量制課金が開始される ▲kiro アカウント申請の仕組み ▲AIエージェントを用いたアカウント申請 この仕組みにより、AWS 管理担当者の手を一切煩わせることなく、ユーザーが自律的に Kiro を利用開始できるようになりました。 Q Developer Pro から Kiro への移行 2025年11月のアップデートと移行の必要性 2025年11月、Kiro は大幅なアップデートを経て一般提供(GA)が開始されました。このアップデートにより、Kiro は Amazon Q Developer から独立した製品となり、独自のサブスクリプション体系が導入されました。 先ほど説明した仕組みは GA 以前に実施したものであったため、既存の Q Developer Pro サブスクリプションを Kiro プランに移行する必要がありました。移行しない場合でも Q Developer Pro のサブスクリプションは引き続き有効ですが、Kiro 専用の新機能(Property-based Testing、Checkpointing など)は利用できない可能性があります。 移行手順自体は非常に簡単で、AWS Management Console から対象グループ(今回の例では AWS_kiro_JP_Users )を選択し、希望の Kiro プランを指定するだけです。グループ単位またはユーザー単位で柔軟に移行でき、段階的な移行も可能です。 なお、一度 Kiro サブスクリプションにアップグレードすると、Q Developer Pro には戻せません。 まとめ 本記事では、メルカリにおける AWS Kiro の導入と、Okta、AWS IAM Identity Center、AI エージェントを組み合わせたアカウント管理の自動化について紹介しました。 手動運用から始まった Kiro のアカウント管理は、既存の Okta と Identity Center の SCIM 連携を活用し、さらに AI エージェントと組み合わせることで、完全に自動化されました。この取り組みにより、以下を実現できました。 管理負荷の大幅な削減 : AWS 管理担当者の手作業がゼロに スケーラブルな運用 : ユーザー数が増えても運用コストが増加しない セキュアなアクセス管理 : Okta を中心とした統一された ID 管理とアクセス制御 また、2025年11月の Kiro GA に伴う Q Developer Pro からの移行もスムーズに完了し、新しい課金体系のもとで運用を継続しています。 今後、AI ツールの導入はさらに加速していくと考えられます。その際、技術的な評価だけでなく、運用面での自動化をどう実現するかが重要になってきます。本記事で紹介した Okta と Identity Center の連携パターンは、他の AI ツール導入においても参考になる取り組みではないでしょうか。 メルカリは引き続き AI-Native company としての歩みを進めていきます。今後も、開発者の生産性向上とスケーラブルな運用を両立させる取り組みを続けていきたいと思います。 明日の記事は @Yu Sasaki さんです。引き続きお楽しみください。
こんにちは。メルカリ IDP チームの @task(mima) です。本記事は メルカリアドベントカレンダー 2025 の 19 日目の記事です。 はじめに 株式会社メルカリには、グループ全体のシステムの認証と認可を統括する IdP が存在します。本記事では、この IdP における Dynamic Client Registration (以下 DCR とする) の活用事例をご紹介します。活用事例は大きく分けて以下の2つです。 Terraform を用いた OAuth / OIDC クライアントの宣言的な管理 MCP 認可フローにおける動的なクライアント生成 これらの紹介のために、本記事では DCR と関連仕様について触れた後、それぞれの事例について背景とメルカリでの利用例を紹介します。 いずれも認証認可の領域に携わっているエンジニア向けの題材ですが、調査した限りでは DCR 自体の利活用をしたケースは少なかったため、少しでも参考になれば幸いです。なお、認証認可における基本的な概念や用語については説明を割愛します。ご承知おきください。 従来の OAuth / OIDC クライアント管理の課題 メルカリの IdP は、メルカリグループの各サービスや、 メルカリと公式連携した越境EC事業者様 に応じて OAuth / OIDC クライアントを登録・管理する必要があります。これらの登録・管理に際して、従来以下のような手作業が必要でした。 クライアント情報を登録・編集を目的とした IdP のデータベースに対する SQL 実行 作成されたクライアント ID / シークレットの Vault での管理 そのため、変更時の人的ミスやオペレーションコストが従来の課題でした。これらの課題を解決するために、OAuth / OIDC クライアントを機械的に登録・管理する仕組みが必要でした。 そこで白羽の矢が立ったのが DCR でした。 Dynamic Client Registration (DCR) とは DCR は、OAuth / OIDC クライアントを動的に登録するための標準仕様です。これにより、API 経由での登録・参照・更新・削除が可能になります。 DCR に関連する仕様は以下の通りです。必要に応じて一次情報を参照してください。 クライアント登録 OAuth 2.0: RFC 7591 – OAuth 2.0 Dynamic Client Registration Protocol OIDC: OpenID Connect Dynamic Client Registration 1.0 incorporating errata set 2 クライアント管理 OAuth 2.0: RFC 7592 – OAuth 2.0 Dynamic Client Registration Management Protocol 続いて、クライアント登録およびクライアント管理のための概要を紹介します。 クライアント登録フロー RFC 7591 で定義されている 抽象的な DCR のフローは以下の図 1 の通りです。 +--------(A)- Initial Access Token (OPTIONAL) | | +----(B)- Software Statement (OPTIONAL) | | v v +-----------+ +---------------+ | |--(C)- Client Registration Request -->| Client | | Client or | | Registration | | Developer |<-(D)- Client Information ------------| Endpoint | | | +---------------+ +-----------+ 図1: Abstract Dynamic Client Registration Flow(出典: RFC 7591 Section 1.3 ) この図の「Client Registration Endpoint」は、クライアントを認可サーバーに登録できるように設計された OAuth 2.0 のエンドポイントです。そして、このエンドポイントとの (C) のリクエストと (D) のレスポンスがDCR のメイン処理 です。(C) で「Client or Developer」が 「Client Registration Endpoint」 に対して、希望する登録メタデータを含むリクエストを送信します。認可サーバは、OAuth クライアントを登録し、(D) でクライアント ID、クライアントシークレット、登録メタデータなどを返却します。 上記フロー図の (A) と (B) はオプショナルなセキュリティ機構 です。 (A) Initial Access Token 「Client Registration Endpoint」 へのアクセスを制御するためのトークン 誰でも自由にクライアントを登録できる状態を防ぐアクセス制御機構 (B) Software Statement クライアントのメタデータを含む署名付き JWT 事前承認されたソフトウェアであることを証明 詳細は RFC 7591 Section 3.1.1 を参照 これらは (C) のリクエストに含めて送信できます。 具体的には、(C) と (D) の基本的なリクエストとレスポンスは以下のようになります。 リクエスト例: POST /register HTTP/1.1 Content-Type: application/json Accept: application/json Host: server.example.com { "redirect_uris": [ "https://client.example.org/callback", "https://client.example.org/callback2"], "client_name": "My Example Client", "client_name#ja-Jpan-JP": "クライアント名", "token_endpoint_auth_method": "client_secret_basic", "logo_uri": "https://client.example.org/logo.png", "jwks_uri": "https://client.example.org/my_public_keys.jwks", "example_extension_parameter": "example_value" } レスポンス例 : HTTP/1.1 201 Created Content-Type: application/json Cache-Control: no-store Pragma: no-cache { "client_id": "s6BhdRkqt3", "client_secret": "cf136dc3c1fc93f31185e5885805d", "client_id_issued_at": 2893256800, "client_secret_expires_at": 2893276800, "redirect_uris": [ "https://client.example.org/callback", "https://client.example.org/callback2"], "grant_types": ["authorization_code", "refresh_token"], "client_name": "My Example Client", "client_name#ja-Jpan-JP": "クライアント名", "token_endpoint_auth_method": "client_secret_basic", "logo_uri": "https://client.example.org/logo.png", "jwks_uri": "https://client.example.org/my_public_keys.jwks", "example_extension_parameter": "example_value" } クライアント管理 RFC 7592 は、登録されたクライアント情報(Client Metadata)を管理(参照 / 更新 / 削除)する仕組みを定義しています。表1に示す通り、クライアント ID をパスに含む REST API として構成することができます。 表1: Client Metadata を管理する用途とリクエスト例 用途 リクエスト例 参照 GET /register/${clientID} 更新 PUT /register/${clientID} 削除 DELETE /register/${clientID} これらの仕様を踏まえた上で、活用事例の紹介に移りましょう。 活用事例1: Terraform を用いた OAuth / OIDC クライアントの宣言的な管理 背景 前述の通り、従来はメルカリグループの OAuth / OIDC クライアントを作成・運用するために、データベースに直接 SQL を実行してクライアント情報を登録していましたが、以下のような課題がありました。 変更履歴の追跡が困難 レビュープロセスの欠如 人的ミスによるセキュリティリスク 複数環境(開発・ステージング・本番)での一貫性の維持が困難 これらの課題に対処すべく、OAuth / OIDC クライアントを宣言的に管理する仕組みとして、Terraform と DCR を組み合わせた仕組みを導入しました。 実装概要 前提として、 Mercari Microservices Platform における Terraform 0.12 対応 で紹介されているように、メルカリでは社内のインフラストラクチャを microservices-terraform と呼ばれる1つのリポジトリで管理しています。この仕組みと DCR を組み合わせることで、Terraform の HCL (HashiCorp Configuration Language) で OAuth / OIDC クライアントを定義し、PR をマージすると自動的にクライアントが生成・更新・削除されます。 図2. Terraform を用いた OAuth / OIDC クライアントの宣言的な管理の概要図 例えば、以下の図 3 に示す通り、Terraform の HCL で宣言すると Plan 結果が GitHub 上にコメントとして表示されます。後はマージするだけで自動的に OAuth / OIDC クライアントが作成される仕組みです。 図3: 新しい OAuth / OIDC クライアント登録のための Terraform の HCL 表現とGitHub 上に表示される Terraform の Plan 結果 この仕組みにより、以下のメリットが得られました。 変更履歴の可視化 : Git によるバージョン管理 レビュープロセスの確立 : PR ベースのワークフロー 自動化 : マージ後の自動適用 IaC : 宣言的な管理による一貫性の確保 技術詳細 Terraform Custom Provider の実装などの技術詳細については、昨年の技術書典16で「OAuth 2.0 Client を Terraform Custom Provider で宣言的に管理してみた」と題して、 Unleash Mercari Tech! vol.3 に寄稿しておりますので、興味のある方はそちらをご覧ください。Terraform の Custom Provider 開発については、 公式ドキュメント も参考になります。 活用事例2: MCP 認可フローにおける動的なクライアント生成 背景 メルカリでは、AI-Native な会社への変貌のため、様々な取り組みを実施しています。その取り組みの一環として Mercari MCP サーバを立ち上げました。この MCP サーバは、2025年11月13日に開催された mercari GEARS 2025 にて、 ChatGPT からメルカリの機能を利用するデモが発表されました。 このデモでは、MCP サーバの認可のために DCR が利用されています。 メルカリでDCR を採用するに至った経緯 MCP の最新仕様である Authorization – Model Context Protocol – Version 2025-11-25 を追っている方は、「DCR は Client ID Metadata Document (CIMD) のフォールバックという位置付けなのでは?」と思われたかもしれません。その通りです。 実際に、 Authorization – Model Context Protocol – Version 2025-11-25 では、クライアント登録のアプローチは次の優先度に従うべき(SHOULD)とされています。 クライアント側で利用可能なサーバの事前登録済情報がある場合、それを利用 認可サーバがサポートしている場合、 Client ID Metadata Document (CIMD) を利用 認可サーバがサポートしている場合、フォールバックとして DCR を利用 他に手段がない場合は、ユーザにクライアント情報の入力を催促 DCR がこのような位置付けになっている理由として、以下の理由などが挙げられます。 登録されるクライアントが無制限に増えるため 登録エンドポイント自体が攻撃に利用されるリスクがあるため クライアントの有効性検証ができないため 詳しくは、 Evolving OAuth Client Registration in the Model Context Protocol | Model Context Protocol Blog をご参照ください。 この優先順位にもかかわらず、メルカリで DCR を選択した理由は以下の通りです。 実装時期の問題 : MCP サーバの実装を開始した時点では、CIMD の仕様がまだ確定していなかったため( Authorization – Model Context Protocol – Version 2025-06-18 では DCR は SHOULD 扱いでした) 既存実装の活用 : メルカリの IdP では、Terraform による管理のために既に DCR を実装済みであり、新規実装コストが低かったため これらの理由で当時は DCR を採用しましたが、複数選択肢のある今、それぞれの長所短所を考慮して選定することが好ましいでしょう。 実装フロー MCP サーバでの DCR 利用フローは以下の図 4 の通りです。 図4: DCR を利用する場合の認可フローを表すシーケンス図 図 4 のフローは以下の通り実行されます。 1.MCP クライアント(例: ChatGPT)がメルカリの MCP サーバへの接続を開始する 2.MCP サーバは、WWW-Authenticate ヘッダとともにステータスコード 401 を返却する 3-4. MCP クライアントは、MCP サーバから Protected Resource Metadata を取得し、認可サーバの情報を取得する 5-6. MCP クライアントが 認可サーバのメタデータエンドポイントを呼び出して、DCR エンドポイントの情報を取得する 7-8. MCP クライアントが、DCR エンドポイント経由で OAuth / OIDC クライアントを作成して、Client Metadata を取得する 9-14-. MCP クライアントは、生成された Client Metadata を使用して認可サーバとの間で認可コードフローを実行し、アクセストークンを取得する 15-16. MCP クライアントは、取得したアクセストークンでMCP サーバの機能にアクセスしてレスポンスを受け取る この仕組みにより、各 MCP クライアントに対して動的にクライアントを作成し、適切なスコープで認可を行うことが可能になりました。 おわりに 本記事では、DCR の活用事例として Terraform を用いた OAuth / OIDC クライアントの宣言的な管理 MCP 認可フローにおける動的なクライアント生成 の2点を紹介しました。 DCR は IdP を内製化している組織だけでなく、外部 IdP を利用する場合でも有用な仕様です。一方で、MCP 認可フローにおいては先述した優先順位を鑑みて選定すると良いと思います。 本記事が皆様の OAuth / OIDC クライアント管理の参考になれば幸いです。ここまでお読みいただきありがとうございました。 明日の記事は @yuさんです。引き続きお楽しみください。
こんにちは、Merpay の Payment Core チームで EM をしている komatsu です。普段はメルカリグループ全体の決済基盤や銀行接続まわりを担当しています。 この記事は Merpay & Mercoin Advent Calendar 2025 の 19 日目の記事です。 年末ということで、今回は Payment Platform がこの 1 年で取り組んできた 2 つの大きな進化の方向性を振り返ります。1 つは Checkout Solution による統合決済体験の提供、もう 1 つは 2B (法人向け) 決済への展開です。 背景: 決済ニーズの多様化 メルカリグループでは、C2C マーケットプレイスを起点としながらも、さまざまなプロダクトが展開されています。メルカリShops やメルカリNFT、さらには直近リリースした メルカリ グローバルアプリ など、2C の提供先が広がると同時に、スキマバイトアプリであるメルカリ ハロや広告事業であるメルカリAds、外部事業者がメルカリの商品をオファー経由で購入・再販する C2B (Consumer-to-Business) パートナーシップなど、2B の決済スキームも登場し、多様化しています。 Payment Platform は、こうした多様な決済ニーズに応えるため、これまでマイクロサービスアーキテクチャによる決済基盤を構築し、gRPC API を通じて各プロダクトに決済機能を提供してきました。 この 1 年では、さらに 2 つの軸で大きな進化を遂げています。 Checkout Solution – UI を含む統合決済ソリューションへ 背景: gRPC API を超えた、統合ソリューションという選択肢 従来、Payment Platform は gRPC API を通じて決済のバックエンド機能を提供していました。各プロダクトチームは、この API を利用して独自に決済フローや UI を実装していました。 この方式は柔軟性が高い一方で、いくつかの課題もありました。 開発負荷の重複 : 新しい決済手段を追加する際、各プロダクトが個別に UI や決済フローを実装する必要がある UX の分断 : プロダクトごとに決済体験が異なり、統一された UX を提供しにくい ローンチ速度 : 新規プロダクトが決済機能を実装する際、フロントエンドからバックエンドまで一から構築する必要がある これらの課題を解決するため、gRPC API に加えて、 UI を含む統合決済ソリューション という新しい選択肢を提供し始めました。これが Checkout Solution です。 Checkout Solution のアーキテクチャ Checkout Solution は、決済のフロントエンド (UI) とバックエンド (API) を一体として提供する統合ソリューションです。 上図のように、プロダクトは Checkout Solution の UI を組み込むだけで、決済手段の選択から決済完了までの体験を提供できます。バックエンドでは Payment Service をはじめとする既存の決済基盤と連携し、各決済手段の処理を実行します。 詳しいアーキテクチャについては、以下の記事をご覧ください。 決済基盤の新たな挑戦: 決済チェックアウトソリューションの開発 チェックアウトソリューションのバックエンドアーキテクチャ プロダクトチームは Checkout Solution を組み込むだけで、決済フローや UI を一から実装する必要がなくなりました。新しい決済手段の追加も、Payment Platform 側で対応すれば全プロダクトで使えます。 従来のプロダクトは引き続き gRPC API を使いながら、新しいプロダクトは Checkout Solution で迅速な開発とリリースを実現しています。 展開の歩み メルカリNFT での初導入 Checkout Solution は、まずメルカリNFT で初めて導入されました。新しいプロダクトのローンチにあたり、MVP として決済機能を素早く統合できることが実証されました。ユースケースごとの設定の切り替えや、プロダクト固有のコンポーネントの描画も含め、基礎となる機能が実現できました。 メルカリ グローバルアプリへの拡大 その後、先日公開されたメルカリ グローバルアプリでも Checkout Solution のサポートが始まりました。 グローバルアプリでは、日本のお客さま向けに提供しているメルペイの残高やポイント、メルペイのあと払いといった決済手段ではなく、海外のお客さま向けのクレジットカード決済機能を新たに開発しました。Cross Border (XB; 越境販売) 事業に必要な決済機能も、Checkout Solution を通して提供することでスピーディーに立ち上げることができました。 こうして、異なるプロダクト間で統一された決済体験を提供できるようになりました。 また、このタイミングは Payment Platform にとって初めての日本円以外の決済ユースケースでした。 日本円前提だったシステムの多通貨対応も行い、決済やその裏にある会計や帳簿も含めて今後のグロースに耐えうる基盤に進化を遂げました。 多通貨対応の詳細は近日公開予定の Guest post from FT payment platform — Engineering for Multi-Currency and Multi-Provider Payments をお楽しみに。 メルカリモバイルへの導入、サブスクリプション対応: Mandate の導入 グローバルアプリとは別の新しい導入先として、既存のメルカリモバイルの決済方法登録画面の置き換えも行いました。 これまでとは異なり、サブスクリプション型の決済スキーム対応のため、お客さま不在時に自動課金を行うための決済手段選択モードを追加し、 Mandate (継続的な課金への同意) という概念を導入しました。これにより、タイミングに依らないより自由な決済スキームの構築が可能となりました。Mandate の詳細設計については以下の記事をご覧ください。 多様な支払い手段と継続課金を安全に扱う「Mandate」の設計 2C から 2B へも拡張可能なアーキテクチャ また、Payment Platform には以前から、メルカリShops やメルペイ加盟店向けの 2B 精算の仕組みがありました。とはいえ、これらは法人が決済すると言うより、お客さまの決済によって資金が移動する先、というユースケースにとどめていました。そこにメルカリ ハロやメルカリAds が登場し、法人自身が決済を行うケース ^1 も出てきたことで、2B 決済のニーズがさらに多様化してきています。 Checkout Solution は現在 2C をメインに展開していますが、将来的にはこうした 2B 向けの決済でも活用できるアーキテクチャになっています。 Payment Platform があらゆる決済の裏側に加えて、カスタマイズ可能な UI も提供することで、新しい決済手段を追加するたびに各プロダクトが個別に開発する必要がなくなり、統一した UX をお客さまや加盟店に届けられます。 2B 決済への展開 2C 決済と 2B 決済の違い Checkout Solution でも 2B プロダクトのサポートを視野に入れているように、メルカリ ハロやメルカリAds をはじめとして、メルカリグループでは 2B の決済ニーズも増えています。 2B の決済体験を考えるうえで重要なのは、2C との違いを理解することです。 特に 2C と 2B では、アカウントや決済の座組が大きく異なります。 2C: シンプルな構造 1 人 = 1 アカウント 与信、請求、決済の単位が基本的に一致 2B: 複雑な構造 法人の下に支社・店舗・部署が存在し、木構造での管理が必要 与信、請求、決済の単位が異なる場合がある (例: 与信は店舗別、請求は支社単位) 座組が複雑で、柔軟な対応が求められる インボイス制度など 2B 固有の要件が存在 2B 決済のユースケース メルカリグループでは、すでにいくつかの 2B 決済のユースケースが存在しています。 メルカリ ハロ 「メルカリ ハロ」では事業者掛け払いというスキームを提供していました。メルカリが事業者の代わりにクルーに仕事完了時に給料を支払い、月末締め翌月払いで事業者から給与および手数料を回収する仕組みです。 詳しくは以下の記事をご覧ください。 メルカリ ハロにおける事業者請求払いの内製化 メルカリAds メルカリAds では、パートナー企業の広告をメルカリアプリに掲載し、月末締め翌月払いを請求書経由で行う仕組みを提供しています。 C2B 決済 C2B パートナーシップでは、外部事業者がメルカリの商品をオファー経由で購入・再販するビジネスモデルを提供しています ( 大黒屋とのパートナーシップ事例 )。 メルカリ出品者が販売しても売れ残った商品に対して、パートナー企業から買取リクエストが送信されます。出品者が承諾すると、メルカリが出品者から商品を購入し、その後パートナー企業に販売する C2B モデルです。 パートナー企業からメルカリへの決済は、与信に基づくオファーで与信枠内で行われ、月末締め翌月払いで請求されます。現在、パートナー数が限定的であることから与信枠の管理は Finance チームがスプレッドシートを中心に手動で行っています。 2B 決済基盤に求められる要素 2B の決済基盤、特に事業者請求払い (掛け払い) のような与信を伴う決済では、以下の要素が不可欠となります。 上図のように、決済から入金、会計処理まで一連のフローを支える必要があります。 決済および債権の管理 : 決済実行と売掛金の計上 与信管理 : 事業者ごとの与信枠の設定と残高管理、木構造での柔軟な管理 請求書の発行・送付・消込 : 適格請求書の自動発行と送付、入金管理と売掛金の消し込み (入金消込)、事業者ごとに異なる管理単位への対応 入金の受け取り : 入金管理 精算処理 : 月次での精算と会計処理 督促 : 未払いに対する督促処理 これまでの取り組み Payment Platform では、これらの要素を段階的に構築してきました。 外部サービスの活用 メルカリ ハロやメルカリAds のローンチ時には、外部企業が提供する掛け払いサービスを採用しました。ローンチの速度や与信管理の容易性・貸倒リスクを加味した結果、与信審査や貸倒リスクを外部に委譲し、スピーディーにビジネスを立ち上げることにフォーカスしました。Payment Platform としては、これまでの決済 API のインターフェースを保ったまま法人の与信を利用した決済手段である Invoice Payment を Payment Service に導入しました。 一部内製化と手動運用の組み合わせ メルカリAds では一部の企業に対して、外部サービスを使わない内製掛け払いソリューションを提供しました。決済時に Debt Service で債権を計上し、月次で Settlement Service を通じて精算する仕組みです。利用先を限定していたため、請求書の発行や消込は手動運用で対応しました。このタイミングでは、中長期の内製化や掛け払いプロバイダの多様化を見越して、外部サービスの掛け払いを利用する場合と内製掛け払いを利用する場合を抽象化した概念として Invoice Payment API を提供しました。 この取り組みにより、既存の Payment Service と Debt Service を活用した 2B 決済の基礎が築かれました。 Invoice Service のローンチ 2025 年には、2B に対する請求書の発行・管理・送付などを行う Invoice Service システムをローンチしました。これはメルカリAds やその他 2B プロダクトで内製掛け払いに移行していく際に必要となるだけでなく、メルカリ – メルペイ間の精算など、他のビジネスでも利用するための基盤として構築されました。 ここまでで実現できたこと これまでの取り組みにより、事業者請求払いに必要な要素のうち、以下がシステムとして整いました。 ✅ 決済トランザクションの管理 (Payment Service) ✅ 債権の管理 (Debt Service) ✅ 請求書の発行・送付・消込 (Invoice Service) ✅ 精算処理 (Settlement Service) ✅ バーチャル口座 (Virtual Account) による入金管理 (Bank Service) ❌ 与信管理 ← まだシステム化されていない ❌ 督促 ← まだユースケースがない 重要なのは、これらの多くがこれまでのメルカリのプロダクトのために構築した既存マイクロサービスを活用できている点です。汎用性の高い設計が、新しい決済スキームへの迅速な対応を支えています。 これから: 与信管理の内製化 前述の通り、現在 C2B の与信枠は Finance チームがスプレッドシートで手動管理しています。パートナー数が限定的な間はこの運用で対応できていますが、規模拡大に向けてシステム化が必要です。 手動管理では与信残高の即時更新が困難で、ヒューマンエラーのリスクや運用負荷が増大します。また C2B、メルカリAds など複数プロダクトで独自に与信管理を行うと、メンテナンスコストが増大し一貫性も保ちにくくなります。同じ法人が複数プロダクトを利用する場合、与信枠の重複管理や過剰付与のリスクもあります。 与信管理サービスのシステム化 これらの課題を解決するため、統一的な与信管理基盤をシステム化する計画があります。 与信管理サービス として、リアルタイムの与信残高照会・更新、複数事業で再利用可能な汎用設計、法人・支社・店舗といった木構造での柔軟な管理、既存決済基盤との一貫した会計処理を提供していく予定です。 与信管理が内製化されると、以下のように Payment Platform のマイクロサービス群が連携して 2B 決済を支えることになります。 この図から見えるのは、 既存マイクロサービスと新規サービスを組み合わせて全体のソリューションを構築している 点です。 これまでに構築してきた Payment Service、Debt Service、Settlement Service、Bank Service といった共通サービスは、それぞれが明確なドメイン責務を持つため、新しいユースケースでもそのまま活用できます。2B 特有の与信管理や請求書発行だけを新しいサービスとして追加し、請求書送付には Payment Platform 外の Notification Service を活用します。 各サービスは API を通じて疎結合に連携し、Payment Service が決済のオーケストレーションを担うことで、全体として 2B 決済ソリューションを実現しています。このアーキテクチャにより、新しい決済スキームにも柔軟に対応できる拡張性を持っています。 Payment Platform が目指す姿 Payment Platform は、メルカリグループにおける既存サービスのグロース・新規サービスのローンチを簡単に・早く・効率的にできるようにすることを目指しています。 スピード : 新規プロダクトが決済機能を素早く統合できる Checkout Solution のような統合ソリューションを提供することで、プロダクトチームは決済フローや UI を一から実装する必要がありません。 効率 : 各プロダクトの開発負荷を削減 新しい決済手段を追加する際、Payment Platform 側で対応すれば、すべてのプロダクトで使えるようになります。各プロダクトが個別に実装する必要がなくなり、開発効率が向上します。 一貫性 : 統一された UI/UX の提供 UI を含む統合ソリューションを提供することで、異なるプロダクト間でも統一された決済体験を届けられます。 拡張性 : 2C から 2B まで、あらゆる決済シーンをカバー 2B の決済ニーズをキャッチしながら機能を育て、汎用的なソリューションにすることで、メルカリグループの多様な決済ニーズに応えていきます。 まとめ この 1 年、Payment Platform は 2 つの大きな軸で進化してきました。 1 つ目は Checkout Solution による統合決済体験の実現 です。gRPC API に加えて、UI を含む統合ソリューションという新しい選択肢を提供することで、プロダクトチームの開発負荷を削減し、統一された UX を提供できるようになりました。また、多通貨対応も合わせて行い、今後のグローバルなプロダクト展開に耐えうる決済基盤の仕組みも加わりました。 2 つ目は 2B 決済への展開 という新たな挑戦です。2B 決済は始まったばかりの領域ですが、既存の 2C で培った強固なマイクロサービス群を活用しながら、段階的に基盤を構築しています。与信管理サービスによる与信管理の内製化により、2B 決済基盤が完成に近づいていきます。 Payment Platform は、これまで多様な決済ニーズに応えてきた実績を基盤に、今後もメルカリグループの成長を決済で支えていきます。 明日の記事は kobaryo さんです。引き続きお楽しみください。 関連記事 Checkout Solution を活用したメルカリの決済体験 Checkout Solution: Backend 編 Mandates for Recurring Payments: サブスクリプション決済を支える仕組み メルカリ ハロにおける事業者請求払いの内製化 大黒屋とのパートナーシップ
こんにちは。メルカリのPlatform Network team/SREの @mshibuya です。 この記事は、 Mercari Advent Calendar 2025 の18日目の記事です。 年の瀬って忙しくなりがちですよね。ただでさえ忙しいのに、Advent Calendar記事執筆の予定まで入れてしまい、半泣きでこの記事を書いています。いや、こうなるのはわかってはいたんですが、一年を振り返ってアウトプットの少なさに焦ってしまうとやはり記事執筆に名乗りをあげてしまうのですよね…。 さて、今回はKubernetes環境におけるネットワークのパケットキャプチャの話をします。筆者は現在前述の通り Network team に所属しており、そこではメルカリのプロダクト開発を支える様々なPlatformとしてのコンポーネント群のうち、ネットワークに関わるものを中心に扱っています。 メルカリでは数百を超える規模のマイクロサービスが稼働しており、そのサービス内外におけるネットワークコミュニケーションも複雑多岐にわたります。Network teamはその性質上、そういった環境で発生したネットワークに起因すると思われる不具合・問題の調査を依頼されることが多くあります。もちろん単純な設定不備などが原因の場合もありますが、問題が複雑で解決の糸口がなかなか見出せないような局面において、深い分析を行う手段を必要としていました。そこでパケットキャプチャが登場します。 こういった問題調査のための手法は、それ自身の実行手順が明確になっていなければいざ問題が発生した際に役立てることはできません。緊急度の高い障害が発生しているような状況下であればなおさらでしょう。今回確立したこの手法は、みなさまの環境においてそのまま適用可能とは限りませんが、それでも安定して実行できる調査手順を紹介しておくことは、みなさまがご自身の組織で同様の手順を作る際にも役立つであろうと考えたのがこの記事を公開する狙いです。 なぜKubernetesでのパケットキャプチャは難しいのか みなさまご存知の通り、KubernetesはハードウェアやOSといった様々なレイヤにおいて抽象化を提供しており、それによって開発者が生のリソースに煩わされることなくワークロードを実行可能な環境を提供しています。セキュリティ上、一般には利用者である開発者は生のノードにはアクセスできないようになっており、またその上で動くPodのようなワークロードもMulti-tenancyとして互いに隔離されています。そのため、昔のように単純にサーバ上でtcpdumpを叩けばおっけー、とはいかないわけですね。 また、Service Meshにまつわる難しさもあります。メルカリではIstioを導入していますが、クラスタ内の通信は基本的にmTLSによって暗号化されているため、そのままでは通信の中身を見ることができません。この点を考慮した手法を確立する必要があります。 こうしたKubernetesクラスタを含め、開発者が機能を簡単に素早くデリバリーするために必要な道具立て一式を提供しているのが我々Platformとしての立ち位置です。このようなネットワークにおける深いトラブルシューティングの必要性がいつ生じるかは予想できません。特別なPlatform特有の権限によることなく、必要であれば開発者がセルフサービスによってこのような調査を行えることも大事な要件としました。 Ephemeral Containersを活用したPodレベルでのキャプチャ 前述の条件を満たす手法として我々が確立したのが、Kubernetesの機能である Ephemeral Containers を活用した手法です。 Ephemeral Containers(エフェメラルコンテナ)は、Kubernetes v1.25でGAとなった機能です。ノード全体へのアクセス権限を持つことなく、実行中のPodのネットワーク名前空間などの共有リソース内に、一時的なデバッグ用コンテナを「外付け」する形でアタッチできます。これにより、tcpdumpなどのデバッグツールをアプリケーションコンテナ内に含める必要なくトラブルシューティングを行えるため、このようなパケットキャプチャに用いるにはうってつけです。NodeやCluster全体への特別な権限を必要としない点も大きなメリットで、Platformメンバーも開発者も同じ方法によって調査を行うことができます。 具体的な手順としては、下記のようになります。 Step 1. 必要な権限の取得 メルカリでは、 Carrier と呼ばれる一時的な権限取得を可能にする内製ツールによって、通常時は本番環境に対する操作権限を持たないZero Touch Productionを実現しています。 このため、本番環境における問題調査においてパケットキャプチャを実施する際には、対象となるPodに対する操作権限を持つRoleをまず取得する必要があります。 上記Roleには、Ephemeral Containersに対する操作を行うために必要なpermissionを事前に付与しておきます。 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: example-role rules: # ... - apiGroups: [""] resources: ["pods/ephemeralcontainers"] verbs: ["create", "delete", "deletecollection", "patch", "update"] Step 2. Ephemeral Containerの起動 権限が得られたら、対象となるPodに対してEphemeral Containerをアタッチします。ここではパケットキャプチャも含む、ネットワーク全般のトラブルシューティングに対応したリッチなツールセットがインストール済みのnetshootを用いています。 kubectl debug -it -n <your-namespace> <target-pod> \ --image=nicolaka/netshoot \ --custom=./root.yaml --container=netshoot-debug ここで、ファイル ./root.yaml には事前に以下の内容を準備しておきます。 securityContext: runAsUser: 0 runAsNonRoot: false これにより、コンテナ内でtcpdumpを実行するための必要条件である「rootとしてnetshootコンテナを実行する」を実現します。大して長い内容ではないので、本当はコマンドラインにインラインで書いてしまいたいのですが、今のところkubectl debugの引数としてはファイルを渡すことしかできないようです…。 Step 3. キャプチャの実施 無事にnetshootコンテナのシェルが開いたら、キャプチャをスタートできます。ここではファイル /tmp/capture.pcap に書き込んでいます。 tcpdump -i any -w /tmp/capture.pcap Istioが有効化された環境化においては、この -i any がポイントとなります。トラフィックは eth0 だけでなく、iptablesによってリダイレクトされた仮想インターフェースをも通るため、これらを取りこぼさないように全インターフェースを対象にしています。eth0のみをキャプチャすると、mTLSによって暗号化済みの内容しか取れないため、調査の目的に対し不足することが多いでしょう。 単純に全トラフィックをキャプチャするとデータが膨大となってしまい得ます。ここでは詳細は触れませんが、tcpdumpのオプションによってキャプチャするパケットをフィルタできるため、調査しようとしている問題に関係するパケットにできるだけ絞り込んでキャプチャするとその後の調査が楽です。もちろん、絞り込みすぎると「必要なデータが取れてなかった」というトレードオフになります。 Step 4. ファイルの回収 上記によってEphemeral Containerにファイルが作成されるので、ローカルよりkubectl cpでダウンロードすれば完了です。Step 2でつけたコンテナ名を指定するのをお忘れなく。 これでキャプチャしたデータの分析に移れます。 kubectl cp -n <your-namespace> <target-pod>:/tmp/capture.pcap ./capture.pcap -c netshoot-debug 慣れてきたら、Step 2-4までをワンラインで行ってしまってもいいでしょう。このような見た目になります。余計な出力がファイルに混入しないよう -iq を使い、また標準エラー出力を捨てています。 -G 10 でキャプチャを行う秒数を指定しています。 kubectl -n <your-namespace> debug <target-pod> -iq --image=nicolaka/netshoot --custom=./root.yaml -- bash -c 'tcpdump -i any -G 10 -W 1 -s0 -w - 2>/dev/null' > tcpdump.pcap Nodeレベルでのキャプチャ 上記Podレベルでのキャプチャ方法とは別に、GKE NodeにSSHし CoreOS Toolbox を用いてパケットキャプチャを行う手順も整備済みです。が、Nodeに対しSSHを行える権限を持つ必要があること、また前述のようにIstioのトラフィックは暗号化されたものしか取得できないこともあり、あくまで補助的な位置づけです。主にPlatformメンバーがNodeレベルでしか観測できないようなトラブルシューティングに用いることを想定しています。 Step 1. 必要な権限の取得 メルカリではKubernetesクラスタをGoogle Kubernetes Engineによって構築・運用しています。まず、GKEノードにSSHするために必要な権限を、前掲のCarrierを用いて取得する必要があります。該当プロジェクトに対し下記の権限があれば十分なはずです。 roles/compute.instanceAdmin.v1 roles/iam.serviceAccountUser roles/iap.tunnelResourceAccessor Step 2. Nodeの特定 kubectl get podコマンドで、対象Podがホストされているノード名を確認します。 $ kubectl get pod -n <your-namespace> your-app-pod-7f5b7f7d9f-abcde -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES your-app-pod-7f5b7f7d9f-abcde 2/2 Running 0 2d1h 10.1.2.3 gke-cluster-1-node-pool-1-a1b2c3d4 <none> <none> Step 3. toolbox環境に入る gcloud compute ssh を使用してノードにSSHし、 toolbox コマンドで、デバッグツールが揃ったシェル環境に入ります。 gcloud compute ssh --project <your-project> gke-cluster-1-node-pool-1-a1b2c3d4 # On the GKE node $ toolbox Step 4. キャプチャの実施 toolboxシェル内でtcpdumpを実行します。ホストのルートファイルシステムは /media/root にマウントされているため、キャプチャファイルはノードの /tmp に相当する /media/root/tmp/ に保存します。 -i any で全インターフェースからキャプチャを行うことを指定し、フィルタとしてStep 2で確認したPodのIPアドレスを指定します。 # Inside the toolbox shell $ tcpdump -i any -w /media/root/tmp/node_capture.pcap host 10.1.2.3 Step 5. ファイルの回収 toolbox シェル ( exit )、SSHセッション ( exit ) の順で終了し、ローカルマシンからgcloud compute scpでファイルを手元にコピーします。 gcloud compute scp --project <your-project> gke-cluster-1-node-pool-1-a1b2c3d4:/tmp/node_capture.pcap ./node_capture.pcap こちらのNodeレベルでのキャプチャはまだ実際の調査で利用した実績はありませんが、こうして手順として整備しておけばいざ問題が発生した際も落ち着いて調査に入れます。 まとめ この記事では、メルカリにおけるKubernetesパケットキャプチャのプラクティスについて紹介しました。 とりわけPodレベルにおいては、Ephemeral Containersを活用することで、セキュリティと利便性のバランスを取りながら開発者が自身でトラブルシューティングを行える手順を整えています。 Podレベル (Ephemeral Containers) Nodeレベル (Toolbox) 主な用途 アプリケーション固有の問題調査、mTLS化された通信内容の確認 Node全体に関わるネットワーク問題(CNI、iptablesルールなど)の調査 必要な権限 比較的低い (Podレベルの権限) 高い (NodeへのSSHアクセス権限) Istio環境でのトラフィックの可視性 暗号化前の平文トラフィックをキャプチャ可能 暗号化後のトラフィックしかキャプチャできない 対象の絞り込みやすさ 調査対象のPodに直接アタッチするため、トラフィックの特定が容易 複数Podが稼働するため、対象Podのトラフィックを分離するのが比較的困難 推奨される利用者 アプリケーション開発者、SRE Platformチーム、SRE セルフサービスへの親和性 高い (開発者が自身で調査可能) 低い (強い権限が必要なため限定的) 今回ご紹介した内容をさらに発展・一般化したお話を来年3月開催の SRECon26 Americas において「It’s Not Always the Network (But Here’s How to Prove It): Kubernetes Packet Capture for SREs」というタイトルで発表予定です。いや、まだ実感がないというか信じられないのですが、ProposalがAcceptされたという連絡はいただいたので、登壇しにシアトルに行ってくると思います…。 パケットキャプチャを行った次のステップとしては、実際にキャプチャしたデータを分析するフェーズが待っています。紙面の都合上、また筆者がその分野ではまだまだ修行中ということもあり今回は触れませんでしたが、今後またなんらかの知見を共有していけたらと思います。 最後までお読みいただきありがとうございました。明日の記事はamenboさん・siroken3さんによる「AI-Native開発を加速する AWS Kiro の導入と、Okta を活用したアカウント管理の自動化」となります!引き続きお楽しみくださいー
はじめに こんにちは。メルペイの Balance チームでインターンをしている @minato です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の18日目の記事です。 2025年10月からメルペイでバックエンドのインターンを始め、3ヶ月目になります。 本記事では、インターン期間中に取り組んだタスク、得ることのできた学びについて紹介します。 取り組んだタスク 今回のインターンでは、債権データを管理するマイクロサービスにおいて、これまで同期的に行っていた債権残高の更新処理を非同期処理へと移行するタスクに取り組みました。 以下、非同期処理へ移行するに至った背景、設計、実装、負荷試験について説明していきます。 背景 メルペイでは現在、世界共通のプラットフォームを提供する「 メルカリ グローバルアプリ 」プロジェクトが進められています。これまで国ごとに限定されていたプロダクトをグローバルに展開するにあたり、これまでになかった量の Transaction が集中することが想定され、負荷分散機能の検討・開発が必要となりました。 このような理由から、僕は、負荷分散を必要とする機能の一つである債権の作成機能の改善に取り組みました。 債権データの作成プロセスでは、債権を作成した後、該当する債務者の債権残高を更新する必要があります。なので、債権データの作成リクエストが連続して行われた際、特定の行やカラムに更新が集中してしまい、その箇所が hotspot となり、以下の問題を起こします。 高負荷が特定の行に集中し、ロック競合が多発する 更新待ちが発生し、スループットが低下する この問題を解決するために、債権残高をリクエスト中に同期的に更新するのではなく、非同期で更新することにしました。非同期化することで、本来複数の Transaction で処理されるはずの複数の Request による債権残高の更新を、一つの Transaction でまとめて処理することができ、hotspot 問題を解決することができます。 設計 大まかな設計としては、債権残高の更新処理が発生した際に債権更新イベントテーブルへレコードを作成し、worker がそのテーブルを取得して Job Queue に push、Job Processor が Job Queue 内のアイテムに基づいてアカウントを更新するという流れとしました。 また、Job Queue については、債権更新イベントテーブルに、Job Queue に入っているかを表すフィールドを設けることで、DB を Job Queue として使用できる仕組みとしました。なぜイベントの管理を、外部サービスではなく DB で管理としたのかというと、外部サービスへの依存を避けられること、同様の処理を行うマイクロサービスにおいて DB で管理する構成で正常に機能しているためになります。 以下の画像は、非同期イベントをどのように処理しているのかについて表したものです。Workerは DB から処理されていないイベントレコードを取得し、server上のメモリ(以下の図の Queue) に保存します。その後、Job Processor が Queue 内のイベントをまとめて処理するという構成になります。 実装では、Worker、Queue、Job Processor のそれぞれについてカスタマイズしやすい struct を定義し、他の非同期処理において再利用できるようにしました。 実装 今回の実装では、社内で活用が推進されている ASDD(Agent Spec Driven Development) という方法を使用しました。ASDD とは、AI agent が仕様書を作成し、別agentがその仕様書に基づいて実装を進めるという手法です。詳しくは、 pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌 の記事をご覧ください。 アカウントの非同期化は、システム全体に関わる非常に大きな機能改善であったため、厳密に設計をDocsとして記録すること、レビューのしやすさを考慮し、仕様書自体は僕が手作業で作成したため、時間を消費してしまいましたが、その後の実装はあっという間にAIによって完了し、とても驚きました。 「もし社内の全員が ASDD を使いこなす未来が来たらどうなるのか」 と想像するだけでワクワクする体験でした。 負荷試験 負荷試験については、まだ行っていないため、これから取り組む予定のことを説明します。今回の負荷試験では、想定される平均リクエスト数と最大リクエスト数をテスト環境に対して送信し、システムがどの程度安定して処理できるかを確認する予定です。 また、債権残高の更新イベントが生成されてから、非同期的に実際に更新されるまでの処理時間についても計測しようと思います。というのも、債権残高の更新される速度は、債権の処理を行う上流のサービスにとって非常に重要なものになるためです。 テスト計画を立てる中、驚いたことは、負荷試験のためのリポジトリが整備されていたことです。専用のリポジトリが存在することで、誰でも容易に負荷試験シナリオを作成でき、さらに他者が同じ条件で負荷を再現できるため、検証の透明性と再現性を保つことができます。 インターンを通して得た学び 今回のインターンでは、日常の開発ではなかなか扱うことのできない規模のリクエスト数を処理するシステムの開発を経験することができ、技術的な学びはもちろん、コーディングに対する姿勢やプロダクトへの向き合い方について学びました。 コーディングへの取り組み方 インターンを通じて特に印象的だったのは、コメントを書かずとも、関数名、コードから類推がつき、後から読んだ際に一目で何をする処理なのかを理解できるレベルを目指す姿勢です。保守・運用のことまで考えたコードを書くことは普段の開発においても、心掛けているのですが、普段の自分を見つめ直し、コーディングへの姿勢を改めるきっかけになりました。 また、レビューの際に、ファイル内のコメント一つに対しても、どのような意図を持って書いたのか、また、そもそもそのファイルは何のためにあるのかといったことを指摘していただき、開発においてメタ的に考えることと具体的に考えることの両方を行うことの重要性を実感しました。 プロダクトのことを第一に考える開発 また、プロダクトに対する心構えについても、非常に刺激を受けました。チームのメンバー一人一人が、プロダクトの未来を考えながら、意思決定をしており、「将来的にこう使われる可能性があるから、今の内にこの手法を採用しておく」、「お客さま体験を良くするためにこのデータ構造はこうあるべき」といった議論が開発中だけではなく食事中にも頻繁に行われており、プロダクトへの愛とメルカリの Values の一つである Be a Pro の精神を強く感じました。 その他の活動と経験 インターン期間中は、開発のタスクにのみ取り組むのではなく、Qに1回開催されている社内イベントのTech Talkに登壇してみたり、他のチームの方と1on1やランチに行ったり、技術書やテックブログの読み合わせ会に参加したりと、いろいろな活動をしました。 ランチではバックエンドエンジニアの方だけではなく、セキュリティエンジニアの方とも話す機会をいただき、なぜその職種を選んだのかというキャリアの話から、最近の攻撃手法、日々の開発で意識すべきセキュリティ観点まで、バックエンドに閉じない視点を得ることができました。 また、Tech Talk や読み合わせ会に参加し、メルカリは、自発的に技術を学びに行ったり、技術について情報発信する環境が本当に整っていることを強く実感し、エンジニアにとって最高の環境だと思いました。 さいごに インターンを通して、タスクに取り組む中で技術的な学びだけではなく、AIの活用方法、エンジニアとしての姿勢についても多くの学びを得ることができました。特に、エンジニアとしての姿勢は、これからの働き方に活かしていこうと思います。インターンとして参加できる残りの期間では、負荷試験までやり切り、可能であればリリースまで完了させたいと考えています。 今回の開発をリードしてくださった@yutaroさん、メンターの@kobaryoさん、そしてチームの皆さんを始め関わってくださった全ての方々、本当にありがとうございました! 明日の記事は@komatsuさんです。引き続きお楽しみください。
はじめに こんにちは。メルペイのPayment Core team/Backend Engineerの@tomoです。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の17日目の記事です。 都度払いから継続決済へ これまでメルペイの決済は、メルカリでの買い物や、お客さまがスマホを取り出してバーコードを提示したりボタンを押したりすることで完了する、いわゆる都度決済が中心でした。しかし、メルカリのエコシステムが拡大するにつれ、決済のあり方は大きく変化しています。 例えば、私たちが提供を開始したメルカリモバイルでは、毎月の利用料金を支払うためにお客さまが都度アプリを開くことはありません。システムがバックグラウンドで自律的に決済を実行します。 このように、決済はもはや単発のイベントではなくなりつつあります。お客さまの操作を伴わずに実行される オフセッション決済──サブスクリプション課金のように、継続的に行われる支払い形態が増えてきています。 そして、メルカリ特有の多様な支払い手段を組み合わせつつ、これらの継続決済を大規模に安全に実行するために、私たちは Mandate (マンデート) と呼ばれる新しい概念を導入しました。 Mandateとは 「Mandate」という言葉は日常では馴染みが薄いかもしれませんが、Fintech の領域では口座振替依頼や自動引き落としの同意を表す一般的な用語です。Stripe の SetupIntent や、インド UPI の AutoPay など、世界中の決済プラットフォームで同様の仕組みが提供されています。 身近な例として、動画配信サービスのサブスクリプションがあります。お客さまは初回登録時にクレジットカード情報を登録し、「毎月このカードから定額を引き落としてよい」という包括的な許可を与えます。この「将来の支払いに関する包括的な同意」こそが Mandate の本質です。これらは一般に「オフセッション決済」と呼ばれます。請求の瞬間にお客さまが操作を行わずに決済が実行される決済形態を指します。 メルカリにおける Mandate も同じ考え方で、お客さまがサービス(例:メルカリモバイルサービス)に対して「メルペイ残高やポイントなどを用いて、未来の支払いを行ってよい」と認可するデジタルな契約を示します。 一般的なMandateは、特定の「クレジットカード」や「銀行口座」に1対1で紐づきます。たとえば、あるサブスクAの支払いに対してクレジットカードBで契約するためのMandateを作成するという具合です。しかし、メルカリのお客さまは以下のような多様な決済手段を持っています。 売上金・残高 無償ポイント・有償ポイント メルペイのあと払い クレジットカード 「ポイントが余っていればポイントを優先し、足りなければ残高から、それでも足りなければあと払いで」――このような複合決済を毎回お客さまの操作なしに行うには、単一のカードへの紐付けでは不十分です。そのため、Payment Platformでは、複数の決済手段に対してMandateを作成できるようにしました。つまり Mandate は、「多様な支払い手段を組み合わせて継続的に課金する」というメルカリ特有の要件を、安全に実現するための基盤として設計されています。 メルペイにおける Mandate Mandate を構成する 3 つの基本要素 オフセッション決済では、誰が → 誰に → どう支払うか の三要素がそろって初めて正しい権限判定ができます。この三要素によって Mandate のスコープを定めます。 Customer(誰が支払うか):お客さま Partner(誰に支払うか):料金を受け取るサービス(例: メルカリモバイル) Payment Method(どう支払うか):ポイント、残高、あと払い、クレジットカードなどの組み合わせ Mandate をこの三軸の組み合わせで表すことで、余計な権限を持たせることなく、監査に耐える説明可能性を確保し、かつ決済実行時の判定を機械的に行えるようになります。 Mandateの管理はWallet Serviceによって行われます。Wallet Service は、あんしん支払い設定など、お客さま固有の設定や支払い権限を管理する基盤サービスです。 決済サービスによる必須の Mandate 検証 オフセッション決済はお客さまの操作を伴わないため、何よりも安全性が求められます。Mandate が存在しない、あるいは範囲外の決済が誤って実行されることは絶対に避けねばなりません。 私たちはその安全性を担保するため、決済作成 API( CreateCharge )に Mandate 検証ロジックを統合しました。 クライアントはオフセッションとして決済を実行する意図を示すために mode=off_session を指定して CreateCharge を呼び出し、Mandate の有無を事前に確認する必要はありません。 Payment Service は mode=off_session を受け取ると、Wallet Serviceの CheckMandateExistence APIを呼び出し同期的に検証を問い合わせます。Mandate が存在し、スコープ内で有効なものであれば決済を実行し、そうでなければ即座にエラーを返して処理を中断します。 こうしてプラットフォームが Gatekeeper として機能することで、サービス側の実装に依存しない堅牢な安全性を確保しています。 決済チェックアウトソリューションで Mandate-Free な開発者体験を実現する Off-session modeのCreateChargeでは、クライアントはMandateの存在を意識せず使用することができます。しかし、サービス契約時には Mandate関連のAPI 呼び出しを実装する必要があります。つまり、サービス側のエンジニアはmandateの作成・削除などライフサイクルに関する仕様を理解し、実装しなければいけません。 そこで Payment Platform 側では、 決済チェックアウトソリューション と組み合わせることで、クライアントが Mandate の API 仕様を意識せずに済む「Mandate-Free」な開発体験の実現を目指しました。 メルペイのチェックアウトソリューションは、決済向けの共通チェックアウト画面を提供する仕組みとして開発されました。決済 UI や 3DS 対応をプロダクトが個別に実装する必要がなくなり、基盤側で統一的に提供できるようになったものです。 さらに今回、このチェックアウトソリューションに setup mode を新設し、支払い手段の登録フローも一括で管理できるようにしました。setup mode のチェックアウトソリューションを通じてお客さまがサービス利用のための支払い手段を登録すると、内部でMandate関連APIを呼び出し、Wallet Serviceに Mandate を作成・更新します。 結果として、クライアントはお客さまに支払い方法を設定させるために チェックアウトソリューション を呼び出すだけでよく、以降の継続課金も mode=off_session の CreateCharge を呼ぶだけで完結します。Mandateの検証やスコープチェックは Payment Platform 側が強制するため、クライアントは決済実行時に必要となる細かな権限管理ロジックを扱う必要がなくなります。 メルカリモバイルにおける実践 Mandate と チェックアウトソリューションの統合は、現在メルカリモバイルのクレジットカード決済で本番運用されています。 お客さまはサービス契約時に一度だけ チェックアウトソリューション を通じてクレジットカードを支払い手段として登録します。登録後、クレジットカードは内部的に Mandate と紐づけられ、以降の毎月の料金請求はオフセッションで自動的に処理されます。お客さまが特別な操作をすることはありません。 メルカリモバイル開発者にとっても、複雑なカード登録フローや Mandate 管理といった重たい実装から解放され、「契約時に チェックアウトソリューション を使用する」「毎月 off_session で CreateCharge を呼ぶ」という最小限の実装だけで安全な継続課金が実現できています。 おわりに 本記事では、メルカリの多様な支払い手段と複雑なビジネス要件を背景に、将来の支払い権限を管理する基盤としての Mandate と、その Mandate をお客さまにも開発者にも意識させない形で統合した チェックアウトソリューション(setup mode) の実践を紹介しました。 明日の記事は@Minatoさんです。引き続きお楽しみください。
こんにちは。Mercari Ads teamのEngineering Managerの @ogataka50 です。 この記事は、 Mercari Advent Calendar 2025 の15日目の記事です。 1. はじめに Mercari Ads では、メインの DB として TiDB を採用しています。HTAP(Hybrid Transactional and Analytical Processing)により、広告設定などのオンライン処理(OLTP)と、impression・conversion の集計などのバッチ処理(OLAP)を単一クラスタで運用しています。しかし、ワークロードの多様化とトラフィック増加に伴い、リソース競合による性能劣化が次第に顕在化しました。具体的には次のような事象です。 バッチ処理や集計処理が TiDB に過度な負荷をかけると、広告管理ツール(Ads Manager)の操作のレスポンスが悪化し、サービス品質(UX)に悪化 逆に、オンライン処理が重くなったときにバックグラウンドのレポート集計やバッチ処理が大幅に遅延。これによりレポートの生成やデータ更新が遅れ、期待どおりの動作にならない このような異なるワークロード同士のリソース競合によって、システムの安定性やパフォーマンスが低下する課題に直面していました。 そこで、TiDB の Resource Group を使って、用途(オンライン/バッチ/レポートなど)ごとにリソースを論理分離し、干渉しないよう制御することにしました。 本記事では、Resource Group の概要と、どのように導入を進めたかを共有したいと思います。 2. Resource Control の概要 Resource Group は、TiDB におけるリソース制御機能のひとつで、論理的な「グループ 」に分け、各グループに対して CPU/I/O/ストレージへのアクセス量を制御できます。 この制御の単位となるのが Request Unit (RU)になります。RU は CPU 時間やディスク I/O、IOPS など複数リソースの消費量を統合して評価する抽象的な単位です。 TiDB の公式ドキュメントでは以下のように RU の消費定義 が示されています。 Resource Group を使い、複数のワークロードで必要なTiDBのリソースを論理的に分離して、リソース競合を防ぎつつ、必要に応じてリソースを割り当て直すことができます。具体的には以下のような処理です。 オンライン処理には高めの RU を割り当てる + 高優先度で処理させる バッチ/分析には必要最小限の RU を割り当て、低優先度で処理させる この仕組みによって、オンライン処理のレイテンシを維持しつつ、バッチや分析処理も同時に安定して実行できるようにすることを目指しました。 3. 実際の導入方法 3.1 サービスごとに SQL ユーザーと Resource Group を分離 Mercari Ads では、各マイクロサービス (広告管理 UI、レポート生成バッチ、分析サービスなど) ごとに SQL ユーザーを分け、それぞれに専用の Resource Group を割り当てるようにしました。 クエリ単位でヒントを使って Resource Group を設定することも可能ですが、多数のサービス/クエリが混在すると運用が煩雑になりやすいため、SQLユーザー単位での分離することから始めました。 3.2 Resource Group の定義例 まずは Resource Group を作り、それを SQLユーザーに紐付けるような流れになります。 次の SQL で Resource Group を作成します。 -- create Resource Group CREATE RESOURCE GROUP IF NOT EXISTS rg_online RU_PER_SEC = 2000 PRIORITY = HIGH BURSTABLE; CREATE RESOURCE GROUP IF NOT EXISTS rg_batch RU_PER_SEC = 500 PRIORITY = LOW; RU_PER_SEC: 1 秒あたりに補充される RU の量 (トークン・バケットの回復速度) を指定 PRIORITY: ストレージ層 (TiKV / TiFlash) 側でのタスク優先度 — 高優先度なら他より先に処理される BURSTABLE: 状況に応じて余剰リソースの利用を許可する設定。負荷が低いタイミングでは余裕を使える どの Resource Group をどの SQL ユーザーに割り当てるかを、以下の SQL で設定します。 -- bind Resource Group and SQL User ALTER USER 'ads_service_sql_user' RESOURCE GROUP rg_online; ALTER USER 'batch_job_sql_user' RESOURCE GROUP rg_batch; optimizer hintsを追加することでSQL単位でResource Group を指定することも可能です。 -- SQL 単位で Resource Group を指定する例 SELECT /*+ RESOURCE_GROUP(rg_name) */ * FROM table_name; 3.3 モニタリング → 設定の見直しサイクル 次のようなサイクルで適切な設定値の調整をしていきました。 まずは制限なしのRU_PER_SEC (default / unlimited 相当) での Resource Group を作成し、各サービスの「通常負荷時」のリソース消費を観測 CREATE RESOURCE GROUP IF NOT EXISTS rg_monitoring RU_PER_SEC = 2147483647; 1週間程度監視し、通常時のRU 使用量、I/O/CPU 負荷、クエリ数などを把握する 用途 (オンラインかバッチなど) に応じて RU_PER_SEC・PRIORITY・BURSTABLE を設定し直す オンライン処理: RU_PER_SEC 高め / HIGH 優先度、必要なら BURSTABLE を使うことを検討 バックグラウンド(バッチ/分析)処理: RU_PER_SEC 控えめ / LOW 優先度 このサイクルを継続し、各 Resource Group の RU_PER_SEC を徐々に適正化しました。 4. 平均値ベースだけではなく、瞬間的な RU の消費も考慮する Resource Group を SQL ユーザーごとに分け、RU_PER_SEC の設定を進めていく中で、意図通りにいかなかったケースがありました。あるサービスは、平常時はほとんど TiDB にアクセスしないのですが、ユーザが期間の長い広告レポートをリクエストした際にだけ、大量のデータを読み取る集計処理が発生し、瞬間的に多くの RU を必要とするケースがありました。このような場合平均値ベースの RU 消費は小さく見えます。しかし実際には、1 回のリクエストで数千〜数万 RU を消費するようなスパイクがあり、平均値を基準に RU_PER_SEC を設定していたため、このスパイク時に上限に達してしまい、必要な RU を確保できず著しくパフォーマンスが低下する状況が生まれてしまいました。 そのため、RU_PER_SEC の設定では平均値だけではなく、瞬間的なスパイクにも対応できるよう考慮する必要があります。こうしたユースケースでは、必要に応じて BURSTABLE を有効化し、一時的に余剰リソースを利用できるようにすることも効果的です。 5. Runaway Query の制御と管理 Resource Group によるリソース分離だけでなく、想定以上にリソースを消費するクエリを制御、管理する機能があります。 Resource Group 定義に、QUERY_LIMIT オプションを追加することで、あるクエリが長時間実行される、または過剰にリソースを消費するような クエリを検知し、強制終了 (KILL)、優先度変更といった制御が可能です。 TiDB では、このような予想以上にリソースを消費するクエリを Runaway Query と呼んでいます。 たとえば、以下のように定義することで、「30秒以上実行されるクエリは自動的にkillする」などの制御ができます。 -- configure QUERY_LIMIT on resource group CREATE RESOURCE GROUP rg_online_limited RU_PER_SEC = 10000 QUERY_LIMIT = (EXEC_ELAPSED = '30s', ACTION = KILL); こうした制御を入れることで、意図せぬ重たいクエリや無限ループ/誤った SQL による極端なリソース消費や、他ワークロードへの影響を防ぐ一助になります。 発生した Runaway Query は、次の SQL で確認できます。 -- select Runaway Query by Resource Group SELECT * FROM mysql.tidb_runaway_queries WHERE resource_group_name = 'rg_online_limited' ORDER BY start_time DESC LIMIT 100; 6. まとめ & 今後の展望 Resource Groupの導入によって、オンライン処理に必要な RU を安定して確保できるようになり、Ads Managerのレイテンシがバックグラウンド処理の影響を受けにくくなりました。また、バックグラウンド処理側も過剰にリソースを奪わない範囲で着実に実行されるようになり、ワークロード間の競合による性能劣化が減少しました。 実施した内容をまとめると、以下の通りです。 現状のワークロード把握 各SQL user(≒サービス)ごとにどのようなシステム要求があるかリストアップ (オンライン/バッチ/Background/分析用途など) 最初は制限なし (default / UNLIMITED 相当) で動かし、どれくらい RU を消費するか観測 モニタリング期間 (数日〜1週間) 通常時のRU 消費を計測 特にバッチや分析クエリのピーク時の消費量に注意する ワークロードごとに Resource Group の設定値を設定 オンライン処理: 応答性重視 → RU_PER_SEC を比較的高め、PRIORITY = HIGH、必要なら BURSTABLE を使うことを検討 バックグラウンド処理: RU_PER_SEC を控えめ、PRIORITY = LOWで必要以上のリソースを消費しないようにした 必要に応じて Runaway 制御 (QUERY_LIMIT) を設定 継続的な運用とレビューサイクル 定期 (週次/月次) で RU 消費状況、遅延・タイムアウト・失敗のログをチェック 発生したRunaway Queryの確認 必要に応じてResource Group 設定を調整 今後は引き続き下記のような改善、調整を行っていこうと考えています。 スパイクが発生するケースでは、一時的に割当を調整するなど、Resource Group と Runaway 制御を組み合わせた運用改善 定期レビューの自動化。どのサービスがどれくらい RU を使っているか/どの程度余裕があるかなどを可視化と調整 本記事が TiDB 運用の一助となれば幸いです。 明日の記事は @Snehaさんです。引き続きお楽しみください。 7. Refs Use Resource Control to Achieve Resource Group Limitation and Flow Control CREATE RESOURCE GROUP Manage Queries That Consume More Resources Than Expected (Runaway Queries) TiDB Resource Control: Stable Workload Consolidation of Transactional Apps Managing Resource Isolation: Optimizing Performance Stability in TiDB
はじめに こんにちは、Ads Servingチームでバックエンドエンジニアをしている@yanapです。 この記事は、 Mercari Advent Calendar 2025 の14日目の記事です。 メルカリは 2025年10月時点で 月間 2,305 万人 のお客さまに利用されており、検索や閲覧などの操作に合わせて広告が表示される「メルカリAds」にも、毎日非常に多くの広告リクエストが届きます。 広告候補は用途ごとに多数存在し、その中から最適な広告を選び出す必要があります。 しかし、どれだけ処理が複雑であっても、広告は 数百ミリ秒以内 に返さなければ検索体験を損ねてしまいます。 大規模なトラフィックの中で高速に広告を選定する仕組みを成立させるのは、簡単ではありません。 お客さまがメルカリアプリで「トマト」と検索したその瞬間、裏側では広告配信のための小さな通信が静かに動き始めています。 その通信を受け取り、複数の内部サービスと連携しながら最適な広告を短時間で選定して返す仕組みを、Ads Serving チームが担当しています。 本記事では、この広告配信フローの全体像と、低レイテンシを維持するための工夫について紹介します。 広告がどこに表示されるか まずは、メルカリAdsが実際にお客さまの目にどのように触れるのかを見てみましょう。この記事では、広告配信の仕組みそのものにスポットを当てていますが、こうした広告がメルカリアプリ内のどこに表示されるのかを具体的に知ることで、その仕組みをより身近に感じてもらえるかと思います。 メルカリAdsは、以下のような場所に表示される仕組みになっています。 検索結果画面 お客さまが検索したキーワードに関連する広告が、検索結果リストの上部や一定間隔ごとに表示されます。 検索体験を邪魔しないよう、通常の商品リストと自然に馴染むようにデザインされています。 商品詳細ページ 「この商品を見ている人におすすめ」ブロック内に、関連性の高い広告が表示されます。 閲覧中の商品や検索履歴などをもとに、興味を持ちそうな商品を提示しています。 メルカリアプリでは、このようにお客さまの操作や閲覧内容に合わせて、自然に広告が挿入されるように設計されています。 メルカリAdsの広告配信の仕組み ここからは、メルカリAdsで広告がどのように選ばれ、短時間で返却されているのかを紹介します。 まずは中核となる AdServer を軸に「広告が返ってくるまでの流れ」を見ていきます。 広告リクエストの開始 検索操作やページ遷移などをきっかけに、広告を表示するための通信が AdServer に送信されます。 広告枠の位置、検索キーワード、閲覧中の商品情報など、表示に必要な情報がここに含まれます。 AdServer の役割:最適な広告を選ぶ司令塔 AdServer は、広告配信フローにおける 中心的な役割を担うサーバー です。 操作に応じて送られた広告リクエストを受け取り、「このお客さまには今どの広告を出すべきか?」を判断します。 そのために、AdServer は裏側にある複数のサービスに問い合わせて「広告候補」を収集します。 収集された候補は、ターゲティング条件・配信設定・予算状況などにもとづいてフィルタリングされ、最終的にユーザーに適した少数の広告が選ばれて返却されます。 表示位置に合わせた広告の返却 検索結果画面では、広告の位置や頻度は画面や文脈に応じて細かく制御されています。 選ばれた広告は、その時点の仕様に基づき、適切な位置としてクライアントに返されます。 高速な広告配信を支える仕組み ここからは、メルカリAdsの広告配信が「なぜこれだけ複雑なのに高速なのか」を紹介します。 広告リクエストが AdServer に届くと、最適な広告を選ぶために複数の内部サービスと連携して処理が進みます。 このように重い処理ですが、実際には 多くの工夫によって数百ミリ秒で完了するように最適化されています。 数百ミリ秒、つまり0.数秒です。まばたき1回分の時間ですね。 この短い時間で、多数のサービスが連携し、広告が選ばれて返却されます。 多数の広告候補生成ロジックを"並列"で動かす AdServer の裏側では、用途ごとに分かれた「広告候補生成サービス」が 複数存在しています(この仕組みを社内では「Demand」と呼んでいます)。 なぜ複数のサービスに分かれているのでしょうか? それは、広告を探す「手がかり」が状況によって異なるからです。 たとえば、以下のようにそれぞれの広告製品ごとに専用の候補生成ロジックが動いています。 Product Ads(商品広告) : 広告主が商品データフィードを連携することで、多品目を効率的に広告配信できる製品 システム側では、メルカリの商品データベースと連携して広告候補を探索 Infeed Ads(インフィード広告) : 広告主が画像やテキストクリエイティブを入稿して配信する製品 ユーザーのデモグラフィック情報を用いたターゲティング配信が可能 システム側では、MLモデルを使って、ユーザーの興味に合った広告を推薦 Shops Ads(メルカリShops広告) : メルカリShopsの店舗向けに特化した広告製品 ショップ商品を効果的にプロモーション C2C Ads(メルカリC2C広告) : メルカリの個人間取引(フリマ出品)を対象とした広告 これらのサービスは、AdServer のリクエストを受けて すべて並列に処理を開始し、広告候補を生成します。 そして異なる広告抽出ロジックを持った各Demandを複数のmicroserviceに分割し、AdServerから並列で呼び出すことで、短時間で大量の広告候補を抽出することを実現しています。 配信設定や予算チェックの最適化 広告配信では広告を出稿している広告主が、配信の上限予算や期間を設定できます。AdServerで広告を返却する前に、これらが有効かどうか確認する必要があります。 ここで重要なのが、データの性質による使い分けです。 配信設定(ON/OFF) → 頻繁には変わらないデータ → キャッシュ(一度取得した情報を保存)を使って高速化 予算残高 → 刻一刻と変化するデータ → 毎回リアルタイムで確認(ただし、タイムアウト時は柔軟に対応) つまり、「変化の速度」に応じて最適な取得方法を選んでいるわけです。 重い判断処理であっても、データの性質に応じて最適な取得方法を使い分けることで、レイテンシを最小限に抑えています。 AIによる最終的な並び替え レスポンスを返す直前には、AIによるスコアリングによって広告を「どの順序で表示するか」を最適化します。 ここでも軽量なモデルやキャッシュが利用され、処理の高速化が図られています。 表示に必要な情報の組み立て 最終的なレスポンスを返すために、広告として表示するタイトル・画像などの詳細情報を取得し、クライアントがそのまま描画できる形に整えます。 ここでもキャッシュの活用によって、追加の遅延を抑えています。 まとめ 本記事では、メルカリAdsにおける広告配信の仕組みを紹介しました。 メルカリAdsが高速に動作しているのは、次のような設計思想のもと、複数のサービスが連携しているためです。 用途別に分かれた複数の広告候補生成ロジックを並列で呼び出せる構造 重い処理を複数のサービスに分散し、AdServer が統合して返すアーキテクチャ 配信設定・商品情報など、頻繁に参照されるデータはキャッシュ前提で高速化 予算情報はリアルタイム取得で正確性を保ちつつ、タイムアウト時は柔軟に対応 AIスコアリングも軽量化・キャッシュにより遅延を抑制 これらの工夫により、検索結果が表示されるころには最適な広告が数百ミリ秒以内に返却されているという体験が実現されています。 日々の開発を通してこの仕組みを理解していく中で、システムの設計思想や高速化の工夫に触れられたのは貴重な経験でした。 今後もメルカリAdsは進化を続けていくと思います。 この記事が、メルカリAdsの仕組みに興味を持つきっかけになればうれしいです。 最後までお読みいただき、ありがとうございました。 明日の記事は @ogataka50さんです。引き続きお楽しみください。
こんにちは。メルペイ Manager of Managers の @abcdefuji です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の14日目の記事です。 アドベントカレンダー内のn8nの連載企画の最終日です。 本記事では、メルカリにおける n8n Enterprise 導入の PoC(Proof of Concept)をどのように成功させたか を紹介します。 近年、生成 AI の爆発的な普及により、企業内には日々膨大な AI ツールが流れ込んでいます。コード生成、文章要約、データ連携、AI エージェント、自動化、その他 毎日どこかで新しいツールが誕生し利用されている状態 です。 この状況が生む課題はシンプルであり、深刻です。 PoC ツールが乱立する 情報アップデートに追いつけず、すべてを触り切る余裕がない PoC を始めても Adoption(定着)が進まない 評価するチームも、本業と並行では十分にリソースを割けない こうした課題は、多くの企業で共通しているはずです。 メルカリでもn8n の PoC は、このような“AIツールが溢れすぎる時代”の中で始まりました。 その中で、どのように導入まで至ったのか。 最初に結論を言うと n8n PoC の成功に必要だったのは、技術そのものではなく、“組織として AI をどう扱い、どう広げていくかを PoC で学んでいく姿勢” でした。 これは、ツールの良し悪しを評価する PoC ではなく、 “AIツールを組織に根づかせる方法を見つけるための PoC” だったのだと思います。 1. PoC を始める前に見えていた背景 メルカリでは、社内から100名を越えるメンバーが選出された横断組織「AI Task Force」 によって 約4,000の業務プロセスが可視化 されています。 その多くは AI や自動化によって効率化できる余地がありました。 AI Task Forceの詳細は下記の記事をご確認ください。 メルカリが本気で始めた「AI-Native」化。100名規模のタスクフォースが立ち上がるまで 「AI Task Force」で変化を加速する。CTO @kimurasが描くメルカリの成長戦略 しかし現場には次のような課題があり、どの組織でも PoC が途中で止まってしまう理由にもなっています。 部署によって使うツールがまったく違う 非エンジニアが参加しづらい セキュリティ観点の懸念が大きい PoC の担当者には本業があり、専念できない このままでは自動化が前に進みません。 そこで私たちは、組織全体の“共通基盤”となる自動化プラットフォームとして、n8n の PoC を開始しました。 2. PoC が始まってすぐに見えてきた“勢い” n8n を試した初期段階から、複数チームで自然にワークフローが作られはじめました。 気づくと次のような利用データが観測されていました metrics value 週あたりの実行数 約 13,000 回 月あたりの実行数 約 40,000 回 PoC の段階にも関わらず、すでに “実務の中に入り始めている” という手応えがありました。 ただし、数字だけで PoC がうまくいったわけではありません。 ここからは、 どのように PoC を設計し、実行したか の話になります。 3. PoC の最初の一歩は「とにかく触ること」 PoC の企画書を書くよりも先にやったことは、ただひたすら n8n を触る ことでした。 UI の特徴 JSON 構造がどう見えるか どこまで柔軟に作れるか LLM との相性 非エンジニアがつまずきそうなポイント これらは、仕様を読むだけでは分かりません。 自分で触ってみて初めて、ツールが“どう現場にハマるか”が見えてきます。 PoC 担当者が誰よりも詳しくなることで、現場と同じ目線で話せるようになります。 これが後の推進力につながりました。 4. n8n PoC は“やる気のある仲間探し”でもあった 実際に PoC を動かし始めて感じたのは、 PoC の成否はツールよりも人で決まる ということでした。 今回のコアメンバーは以下の通りです: abcdefuji(Backend Manager) :PoC 推進、ユースケース伴走 (当ブログ) hi120ki(AI Security) :脅威モデル、ガードレール設計 shuuk(AI Task Force) :全社プロセスとAdoptionの橋渡し ISSA(Director) :DevEx の視点での全体整理 理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない | メルカリエンジニアリング mewuto(Backend Eng) :静的解析 CLI(DAG / AST) n8nの静的解析CLIツールをOSS化 – JSON解析とDAGで実現するセキュリティチェックの自動化 | メルカリエンジニアリング T(SRE) :Enterprise 構成、Enginerringの全て Making n8n Enterprise-Ready: 企業向けn8nの導入と運用の取り組み | メルカリエンジニアリング 全員、 本業がありながら隙間時間で参加していました 。 にもかかわらず PoC が成立したのは、全員に共通して 「これはメルカリ全体にとって価値がある」 という強い思いがあったからです。 PoC は仲間が多いほど強くなります。 そして熱量は伝播します。 n8n の PoC はまさにその象徴でした。 5. 小さな成功を一緒につくる:ユースケース伴走 PoC を成功に導くためには、 早い段階で小さな成功を複数つくること が重要です。 n8n の PoC では、Marketing・QA・SRE・監査・HR などのチームに伴走し、次のような改善が生まれました: 部署 ユースケース 効果 Marketing KPI チェックの自動化 月 500 分削減 QA リリース作業の自動化 90 分/週 → 0 分 Eng AI Agent 開発工数削減 2 週間 → 2〜3 日 Audit 情報集約の効率化 属人性を大幅に改善 ここで重要なのは、PoC チームが「代わりに作ってあげる」のではなく、 一緒にワークフローを作り、一緒に成功する という姿勢でした。利用者がわからない時にサポートを行う。これは Adoption を加速する最も強力な手法です。 6. セキュリティとガバナンス:PoC で課題が出るほど良い n8n は自由度が高い分、いくつかのリスクもあります。 PoC のなかで実際に以下のような問題が見つかりました: credential の誤設定 HTTP Node での不正なアクセス 権限混同によるリスク Code Node 内の危険な処理 しかし、これはむしろ好材料です。 PoC は問題を見つけるための場所 です。ここで検知できれば、本番導入では安全に運用できます。 PoC 中に次の仕組みを整備しました: External Hook による保存前チェック 静的解析 CLI(JSON / DAG / AST) SSO(Okta) Task Runner によるコード実行の隔離 ガイドライン・テンプレート こうして、導入時に必要な安全性が PoC のなかで自然と整っていきました。 詳細はこちら Making n8n Enterprise-Ready: 企業向けn8nの導入と運用の取り組み | メルカリエンジニアリング n8nの静的解析CLIツールをOSS化 – JSON解析とDAGで実現するセキュリティチェックの自動化 | メルカリエンジニアリング 理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない | メルカリエンジニアリング 7. 「ROI は罠」 ── 短期回収で PoC を評価してはいけない PoC ではよく ROI が議論されますが、短期回収だけで判断すると誤った評価になりがちです。 AI や自動化は 「導入しない」 という選択肢のほうが損をする 世界に入っています。 参考: “BoldなAI活用”から1年。人間の限界を超えていく、メルカリの「AI-Native」な現在地 そのため私たちは、次のような前提で PoC を進めました: 導入するかどうか、ではなく、どう導入すれば成功するかだけを議論する。 数字の比較は必要ですが、まず見るべきは “成果ベースの数字” です。 再現性のある成功事例がどれだけあるか 非エンジニアでも価値を出せているか エラー削減、工数削減の実績 全社展開できる安全性があるか これらが確認できて初めて、年間コストや ROI 試算の意味が出てきます。 また、n8nにはEstimated time saved機能があります。 1 workflow毎にどの程度時間を削減できたかの見積もりを設定する機能です。 設定後、以下のようにDashboardから各Workflowで設定された削減時間を確認することができます。 中長期的にはこのようにn8nの効果測定をすることも可能です。 8. Adoption戦略:広げるには“雰囲気”も必要 PoC の後半では、次のような活動をして Adoption を広げていきました。 All Hands や社内勉強会で紹介 Slack チャンネルで成功事例を共有 非エンジニア向けテンプレートの提供 質問にはすぐ返す文化づくり 気づけば、周囲の人たちが 「ちょっと n8n 触ってみるね」 と自然に言うようになっていました。 ツールが広がるには“雰囲気づくり”もとても大事です。 9. PoC を成功に導く“再現可能な型” 最後に、今回の PoC から得られた成功パターンをまとめます。 まず自分が一番触る 効率より熱量のある仲間を見つける 小さな成功を早くつくる PoC 中に課題を見つけ、仕組みとして解決する ROI よりも成果ベースの数字を見る テンプレート・ガードレールで安全に広げる Adoption を広げる雰囲気をつくる PoC担当者の覚悟 これらは n8n に限らず、AI ツール全般の導入で応用できる考え方です。 PoC担当者には”覚悟”が必要です。達成するべきVisionを信じきる覚悟です。自身がなぜ必要・有効なのかのロジックを持ち、そしてそれを信じて突き進んでください。 おわりに n8n PoC を振り返ると、成功を分けたのは技術だけではありませんでした。 本業の合間に動きながら、皆でアイデアを出し合い、問題を潰し、成功を積み重ねていく。 その“姿勢”そのものが PoC の最大の価値であり、組織が AI を受け入れる力につながりました。 PoC はツールのテストではなく、 組織が AI をどう扱うかを学ぶフェーズ です。 これからも改善を続けながら、AI-Native な組織を目指して進んでいきます。 またどこかでメルカリがどのようなworkflowを構築し活用しているのか紹介できたらと思います。ここまで読んでいただきありがとうございました。 明日の記事は Timoさんです。引き続きお楽しみください。 n8n.io logo source: https://n8n.io/brandguidelines
こんにちは、メルカリEngineering Officeチームの@thiroiです この記事は、 Mercari Advent Calendar 2025 の13日目の記事です。 はじめに Engineering Office は、”Establish a Resilient Engineering Organization.”というミッションを元に、エンジニア組織を横断的に支える役割を担っています。 今日は組織を裏側で支える仕組みの一つとして、「AI時代のナレッジマネジメント」をテーマに書いていきます TL;DR 組織に情報、ノウハウを蓄積する仕組み、手法をナレッジマネジメントと言います ナレッジマネジメントはAIが存在する前、重要ではあるものの、コストパフォーマンスのバランスを取るのが難しいもので、メルカリでも課題を多く抱えていました AIの台頭により、ナレッジマネジメントの効率化が進んだことに加え、社内ナレッジAIのコンテキスト利用という新しいユースケースが生まれ、コストパフォーマンスが劇的に向上しました メルカリでは、この環境変化の元、AI-Nativeの推進にあたって、ナレッジマネジメントに投資を決定し、いくつかの施策を推進しています ナレッジマネジメントって何? ナレッジマネジメントとは、個人の持つ情報、ノウハウを、組織に蓄積、共有する仕組み、手法のことです 例えば、以下のようなことを感じたことはありませんか 過去に似たような障害対応をしたのに、どうやって対応したかがわからない 現在のAPIの仕様がコードを読まないとわからない そもそもなんでこんな設計になったのかわからない ちょっと前に可視化に使ったSQLがわからない これらの課題に対する解決策は一つではありませんが、ナレッジとして蓄積するということは一つの解決策になります 過去の学びや、情報を蓄積し、引き出せる状態にすることにより、学び直しや、情報の再構築を防ぐことができます。いわゆる「車輪の再発明を防ぐ」、「巨人の肩の上に立つ」をしよう、ということです。ナレッジを会社の資産として正しく蓄積し、後続するメンバーであったり、未来の自分自身がそれを利用できる状態にすることで、生産性を向上できます。これがナレッジマネジメントの目的です ナレッジマネジメントの課題 現場において、ナレッジマネジメントは非常に難しい課題です。程度の差はあれ、社会人として「この情報が見つからない」といった課題にぶつかったことがない人はいないのではないでしょうか。ナレッジマネジメントの課題の分け方はいくつかありますが、ここでは最もよく見られる4分類を利用します 記録されない(Create) 発見できない(Find) 活用できない(Use) 更新されない(Maintain) 上から順に、詳しく見ていきましょう 1. 「記録されない」 もっともわかりやすく、頻繁に見る課題です この発生原因は、そもそもナレッジの蓄積をするという習慣がなかったり、記録に対する時間を今取れない、記録のコストが高く感じる、といったものがあります。「後で書く」は私の感覚だと、「コードを後で書き直す」とほぼ同じく、90%以上行われません。書かないとほぼ同義です。特定の時間を取る仕組み(ドキュメントにチームで集中する日をとるなど)がない限りは非常に難しいです 2. 「発見できない」 問い合わせをしたら、「このページを参考にしてね」と言われた経験はありませんか。これは、該当するナレッジ自体は蓄積されているものの、発見ができないという状態です。 ドキュメントの検索性が低い、もしくは情報を調べるという文化、環境がないことが主な原因です。ツール自体の検索性能が低かったり、複数のツールにドキュメントが散らばっている場合によく発生します。 3. 「活用できない」 見つけたものの、それが役に立たない状態です。例えば、書いてある内容がハイコンテキストすぎたり、欲しい情報に対して情報量が多すぎて、読むコストを支払う気になれなかったり、何かしらのクオリティの問題で、読んでもわからないといったことはよく発生します。結果、活用に至らず、担当者に話を聞く、結局ソースコードを見て確認することになり、情報を探したこと自体が徒労に終わります 4. 「更新されない」 ドキュメントは実体に則してアップデートされる必要があります。例えば、プロダクト開発で画面仕様書やAPI仕様書がある場合、最新機能のアップデートに応じて、これらは更新する必要性があるでしょう。 明確なプロセスがあったり、APIを外部に公開しているといった事情がない限り、こういったドキュメントのアップデートを必要な時に行うという習慣をもっている人は非常に少ないです。特にドキュメントのオーナーが会社を去った場合などは、多くの場合、良いドキュメントですら管理されず、廃れていきます。 これはそもそも「ドキュメントやナレッジを資産として、管理対象とする」ということが組織として合意されて、チームが管理する仕事の一部になっていないためです。「更新されない」という問題が発生する際のコストは高くつくことがあります。読み手が問題に気づき、担当者に問い合わせるならまだいい方で、最悪の場合、適説でない情報を元に業務が行われる可能性があるからです。 AI時代以前のナレッジマネジメント これらの課題があることを踏まえ、ナレッジマネジメントではどのようなことが発生していたのか考えてみます AI時代以前は、情報が増えるほど整理や検索のコストが膨らみ、「一部の人しか知らない」状態が常態化しやすく、特に会社の規模の拡大や歴史の積み重ねによって情報やナレッジが増えていくと、検索難易度や管理難易度があがりやすい環境でした。 しかしこれらの課題が浮き彫りになっていても、なぜか解決に至らないケースが多く見受けられます。それはコストパフォーマンスの問題が大きかったのだろうと思います。 ナレッジを適切に管理、保管するのは非常にコストがかかりますし、標準的なプロセスを作るためには、現場の自由度を下げる可能性があるというトレードオフも存在します。多大なコストをかけて標準化を進め、コストを一定し払いそれらの課題を解決しても、ツールとしての検索性能がボトルネックになるかもしれません。 これらの環境を踏まえると、ナレッジマネジメントに対してどのくらいの投資が適切であるかという判断は難しく、どこまでコストをかけて、どこまでの標準化やメンテナンスを行うのかは、慎重に意思決定をする必要がありました メルカリでも、情報が複数のツールに分散し、管理されていないナレッジも多く存在しています。結果、最初にあげた4つの問題がかなり発生している状態で、社内のEngineer向けのSurveyでも、常にナレッジマネジメントは課題のTop3に入っています さらに、メルカリグループの大きな特徴として、メルペイ、メルコインをはじめとする金融関連のプロダクトを保有しているという点があげられます。これらは厳格なプロセス、ドキュメンテーションが求められるドメインです。一方マーケットプレイスであるメルカリアプリ自体は、そこまで細かい管理は必要とされません。また、新規事業立ち上げもあるため、これら全てを横断した細かいプロセスで縛る場合、最も厳しいところに合わせる必要があり、それにはデメリットが少なからず伴う上、メルカリの自由度が高い風土とはマッチしづらく、課題は認識しつつも、課題解決には慎重になっていたという現状がありました AIはこの環境においてまさにゲームチェンジャーとして現れました AIがナレッジマネジメントにもたらしたもの AIがまずもたらした変革は、ドキュメンテーションの既存業務の圧倒的な生産性向上です。例えば以下のようなものは明確に、様々な課題を低減してくれました。既に多くのエンジニアが体験しているのではないでしょうか 議事録を自動で作成してくれる(「記録されない」課題を低減) 概要を知りたい場合、全文を読まずとも、長い文章の要約をしてくれる(「活用できない」課題を低減) 検索性が圧倒的に向上し、必要な情報にリーチしやすく(「発見できない」課題を低減) 既存のコードから仕様の言語化を実行してくれる(「記録されない」課題を低減) 最新の議論を元に、Product Requirement Documentation (PRD)のアップデートが必要な箇所を見つける(「更新されない」課題を低減) これらの利便性の向上はまだしばらく続くでしょう。ナレッジマネジメントツールのAI特化の機能開発により、AIの恩恵をより幅広いユースケースで受けられるようになるはずです 二つめの変革は、ナレッジ自体の価値があがったということです。ナレッジが人が読むものだけでなく、AIがコンテキストとして使うものになったからです。新しい、非常に大きなナレッジ活用のユースケースが生まれたと言い換えても良いでしょう AIは学習元となっている情報、コンテキストに依存します。そのため、仮に会社特有の開発のお作法や、ドメインナレッジがある場合、それをコンテキストとして正しく注入しない限り、それらは考慮されません。「会社のやり方や、ドメイン特有の内容を考慮したいい感じ」のアウトプットが欲しいわけで、そのためにはコンテキストとして、社内のナレッジをきちんと管理し、それがAIに届く状態にする必要があります 総合すると、AIは ナレッジマネジメントを正しく行うコストを劇的に下げて ナレッジマネジメントのアウトプットの価値をあげた といえます 大事なことなので、別の表現で言います 非常にコストがかかって、まぁまぁな価値を出していたナレッジマネジメントは、AI時代において、コストがそこまでかからず、めちゃくちゃ大きな価値を生む業務になったのです つまり、ナレッジマネジメントは、AI時代において、コスとパフォーマンスが爆発的にあがったのです メルカリにおけるナレッジマネジメント戦略 メルカリでは、これらの背景を元に、ナレッジマネジメントをAI-Native時代において、重点的に投資すべき領域として定めています。 その中で推進していることがいくつかありますが、そのうち大きなものを3つほど紹介します 1. 全社員共通のAI ReadyなCentral Knowledge Baseの構築 まず一つめは、AIとの相性が良く、AIに特化した機能開発が盛んなKnowledge Baseを一つ選定し、そこにナレッジを集約することです。ここで言うKnowledge Baseとは、いわゆる社内Wikiで、社内における情報を集約するためのツールを指します メルカリでは、複数のツールを必要に応じて許容しつつも、特段の理由がなければ、全てCentral Knowledge Baseに集める、という方向性を現在進めています。なぜ一つのツール、Central Knowledge Baseに舵を切ったかというと、以下のようなメリットがあるためです AIの接続のためのコストを減らせる(ツールが増えるとセキュリティ、運用構築、それらの動作テストなどかなりコストがかかります) AI機能含め、ナレッジベースツールに対する習熟度を会社全体としてあげやすい メタデータを統一できる(メタデータが異なる=検索ロジックの複雑性や、管理の標準化の難易度があがる。メトリクスも統一しづらい。) この方向性を元に、現在、メルカリはNotionをCentral Knowledge Baseとして位置付け、ナレッジの中央管理型への移行を進めています。本記事の主旨と離れるので、細かくは記載しませんが、ツール選定に関しては、フロー情報(議事録など、メンテしない情報)とストック情報の両方に強いという点や、AIとの親和性の高さが大きなポイントでした 2. ドキュメントをオープンにする文化の推進 せっかく情報が蓄積され、検索性があがっても、それらがAIや、社員がリーチできる状態でないと意味がありません。メルカリでは、良くも悪くも最低限のアクセス権限を付与する文化が多かれ少なかれあり、そもそも知りたい情報へのアクセス権限がないということがあったのです。例として、重要な意思決定に関するミーティングは基本的に参加者以外は見れないという状態でした そのため、現在はドキュメントを出来るだけオープンな場所におくための文化作りや仕組み作りをしており、重要な意思決定に関しても、公開範囲の明確化、拡大を推進しています もちろんこれは、同時に秘匿性の高い情報の適切な管理が大事になってくるため、 Personally Identifiable Information (PII、いわゆる個人情報)を含む情報などに関しては、管理方法の厳格化、プロセス化を併せて推進しています 3. ナレッジ蓄積文化の推進 そもそも、ナレッジを蓄積する文化というのは一朝一夕でできるものではありません。なぜそれが今重要なのか、そしてどのように蓄積すべきか、ということを繰り返し発信、推進しています メルカリ内でも、ナレッジ蓄積する文化が元々あるという部署もあれば、ナレッジ蓄積自体がそもそもほとんど行われていない部署もあり、温度感はかなりまちまちです そのため各組織から一緒に推進してもらうメンバーをアサインしてもらい、現場の温度感に合わせた組織ごとにカスタマイズされたオンボーディングの実行をしたり、新入社員向けのオンボーディングコンテンツにナレッジマネジメントを追加したりといったことをしています 今後の展望 現状では、AIによる検索性の向上、ドキュメンテーション作成の補助などの恩恵は既に得られているものの、メルカリにおいて、AI-Nativeなナレッジマネジメント環境の整備は、まだまだ推進初期の段階です。課題は盛りだくさん。しかも移行によって、新たに解決すべき課題も発生しており、まだまだ最善の状態までは行き着いていません とはいえ、AI-Nativeな環境を作るにおいて、ナレッジマネジメントへの投資は必須だと考えています。このナレッジマネジメントの推進は、単なるナレッジ関連業務の効率化に留まらず、AIを最大限に活かすための土台作りだからです。今回は我々が今取り組んでいるもののベースとなっている考え方、目指している方向について記事にしましたが、来年の今頃には、もう少し結果や、そこからの学びなどを記事にできればと思います。ここまで読んでくださってありがとうございます。 明日の記事は@yanapさんです。引き続きお楽しみください。
こんにちは。メルペイのPayment & Customer PlatformのAccountingチームでBackend Engineerをしている @mewuto (みゅーと)です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の13日目の記事です。 要旨 メルカリでは、業務自動化プラットフォームとして n8n を導入していますが、ワークフローの自由度が高い反面、セキュリティリスクも懸念されます。特に「権限混同(Confused Deputy Problem)」は重大な脅威であり、個人の権限を不適切に共有することで、意図しないデータアクセスや操作が可能になってしまいます。 本記事では、n8nワークフローのセキュリティレビューを自動化するCLIツールの開発と、GitHub Actionsを活用した実際の運用例について解説します。このツールは GitHub でOSS公開しており、 npm からインストール可能です。 対象読者 : ワークフロー自動化ツール(n8n、Zapier等)を使用している開発者・セキュリティ担当者 JSONベースの静的解析やDAGを用いたフロー解析に興味のある方 業務自動化基盤のセキュリティ強化に取り組んでいる方 目次 背景:n8n導入とセキュリティ課題 セキュリティポリシーの定義 アーキテクチャ概要 ノードレベル検出の実装例:BigQuery本番環境アクセス検知 シナリオレベル検出の実装例 検出の例および社内での活用事例 成果と今後の展望 まとめ 1. 背景:n8n導入とセキュリティ課題 n8nとは n8n は、ノーコード/ローコードで業務自動化ワークフローを構築できるオープンソースのプラットフォームです。ZapierやMakeと同様に、異なるサービス間のデータ連携や処理の自動化が可能ですが、n8nはより自由度が高く、柔軟なカスタマイズや複雑なフローの構築ができる点が特徴です。 メルカリでは、開発者やビジネスチームの業務効率化のため、n8n Enterpriseを導入しました(参考: 理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない )。しかし、その自由度の高さゆえに、以下のセキュリティ課題が浮上しました。 Confused Deputy Problem(権限混同)の脅威 「Confused Deputy Problem」とはあるシステムにおいて、ユーザーが本来持つ権限よりも大きな権限が設定されているときに、アクセスできないはずのリソースにアクセスできたり変更できてしまう問題です。n8nワークフローでは、権限を持つメンバーのcredentialが組み込まれているため、以下のようなリスクが発生します。 データベースへの意図しないアクセス : 本来DBアクセス権を持たないメンバーが、Slackボットを実行することで、権限を持つメンバーのcredentialを通じて本番データベースにアクセスできてしまう 機密情報の意図しない漏洩 : ワークフローが出力した機密情報(スプレッドシート、Slack Publicチャンネルへの投稿等)を、本来アクセス権を持たないメンバーが閲覧できてしまう チーム間での権限境界の破綻 : 別チームのメンバーが作成したワークフローを実行することで、本来アクセスできない他チームのリソース(社内ドキュメント、ストレージなど)にアクセスできてしまう これらの問題を防ぐため、ワークフロー有効化前の手動レビューを実施していましたが、以下の課題がありました。 レビュー工数の増大と品質の不安定性 : 1ワークフローあたり、修正依頼や修正後の再レビューを含めて30分〜1時間のレビュー時間が必要。また、セキュリティガイドラインは整備されているものの、詳細な技術ドキュメントを参照しながらワークフローを作成することは容易ではなく、ユーザー自身での自己改善が難しい。加えて、人間によるレビューである以上、見落としのリスクも存在する 継続的監視の欠如 : 承認後もワークフローは自由に変更可能であり、変更後の安全性を保証できない これらの課題を解決するため、静的セキュリティ解析ツールの開発に至りました。 2. セキュリティポリシーの定義 ツール開発にあたり、セキュリティガイドラインを策定しました。主要な要件は以下の通りです。 2.1 権限混同の防止 入力ソースの制限 : ワークフローをトリガーできるユーザーを明示的に制限 出力先の制限 : 結果の出力先を閲覧権限を持つユーザー内に限定 2.2 Slack Bot特有の要件 実行可能メンバーのホワイトリスト化 : Slackトリガーには必ずユーザー検証を実装 エフェメラルメッセージの使用 : 機密情報は一時的なメッセージで返す Private Channel推奨 : Public Channelへの出力を制限 2.3 データの入出力制御 本番環境アクセスの明示 : 本番データへのアクセスは明示的に警告 動的クエリの検証 : 外部入力による動的クエリを検出。動的にSQLクエリを構築すると、本来意図していないテーブルやデータセットにアクセスできてしまうリスクがあるため、最小権限の原則に基づき使用範囲を制限 入力値のフィルタリング : 外部入力を使用する箇所での検証実装 出力先のアクセス権限制御 : Google Sheetsなどへの出力時は、適切なアクセス権限スコープ(閲覧可能ユーザーの制限)が設定されていることを確認 これらのポリシーを自動検証することで、手動レビューの負担を大幅に軽減できます。 3. アーキテクチャ概要 3.1 全体構成と処理の流れ ツールは以下の処理フローで動作します。 n8nワークフローJSON読み込み 静的解析による検証 : 2.1. ノードレベルチェック : 個別ノード(BigQuery、HTTP Request、JavaScript Code、Google Sheets/Drive等)の設定を検証 2.2 シナリオレベルチェック : 複数ノード間の関係性(Slackトリガー後の呼び出しユーザーのバリデーション実装、スプレッドシート出力先の権限範囲等)をDAG(有向非巡環グラフ)を用いて解析。 ※ なぜDAGが必要か : LLMによるチェックは再現性が保証されず、論理的なグラフ解析もまだ未発達です。DAGによる静的解析は、ノード間の実行順序や依存関係を確実に追跡でき、「バリデーション → 外部アクセス」といったセキュリティ上重要な順序関係を100%の精度で検証可能です。 検出結果の出力 : Console、JSON、PR Comment形式で結果を出力 3.2 ワークフローJSONの構造 n8nのワークフローは、以下のようなJSON形式で保存されます。 { "name": "My workflow", "nodes": [ { "id": "node-id-1", "type": "n8n-nodes-base.googleBigQuery", "parameters": { "projectId": "merpay-prod", "sqlQuery": "{{ $json.query }}", "operation": "executeQuery" } }, { "id": "node-id-2", "type": "n8n-nodes-base.code", "parameters": { "jsCode": "const data = $input.item.json;\nreturn { result: data.rows.length };" } }, { "id": "node-id-3", "type": "n8n-nodes-base.slack", "parameters": { "resource": "message", "operation": "post", "channel": "#general", "text": "{{ $json.result }}" } } ], "connections": { "node-id-1": { "main": [[{"node": "node-id-2", "type": "main", "index": 0}]] }, "node-id-2": { "main": [[{"node": "node-id-3", "type": "main", "index": 0}]] } } } このJSONから以下の情報を抽出できます。 ノード設定 : 各ノードのtype、parameters、credentials フロー構造 : connectionsによるノード間の依存関係 各ノードのparametersには、ノードタイプに応じた詳細情報が含まれます。 JavaScript Codeノードの実装内容 BigQueryのデータセット名やSQLクエリ HTTPリクエストのエンドポイント、メソッド、リクエストボディ この構造化された豊富な情報により、実行前の詳細な静的解析が可能になります。 3.3 2層検出アーキテクチャ セキュリティリスクは、個別ノードの設定だけでなく、ノード間の関係性によっても発生します。そのため、本ツールでは以下の2層アーキテクチャを採用しています。 ノードレベル検出 : 個別ノードの設定を検証 例:BigQueryの本番プロジェクトアクセス、Slackの投稿先チャンネル 各ノードが独立して安全な設定になっているかを確認 シナリオレベル検出 : 複数ノード間の関係性を検証 DAGを用いたフロー解析 例: (i) Slack Trigger → ユーザーバリデーション → 外部アクセスの順序検証 (ii) Google Sheets Create → Google Drive Share/HTTP Request Permissionsのスコープ設定検証 ワークフロー全体としてセキュリティ要件を満たしているかを確認 この2層アプローチにより、個別の設定ミスだけでなく、ワークフロー全体の設計上の問題も検出できます。 4. ノードレベル検出の実装例:BigQuery本番環境アクセス検知 ノードレベル検出の具体例として、BigQueryの本番環境アクセス検知を解説します。 4.1 検出対象 以下のようなワークフローを検出します。 { "parameters": { "projectId": "merpay-prod", "operation": "executeQuery" }, "type": "n8n-nodes-base.googleBigQuery" } この設定では、 projectId に prod 文字列を含む本番環境プロジェクトへのアクセスを検出します。 4.2 実装 検出ロジックは BigQueryChecker に実装されています。 // Typescript // パラメータからプロジェクトIDを取得 const projectId = node.parameters.projectId; // 本番環境へのアクセスを検出 if (projectId.includes('prod')) { // 警告を追加 addWarning('本番環境へのアクセスが検出されました'); } 詳細な実装は GitHubリポジトリ を参照してください。 4.3 検出結果 検出された問題は以下のような形式で報告されます。 ### ⚠️ 警告 (Warnings) (1) - **[Execute a SQL query]** (n8n-nodes-base.googleBigQuery) 本番環境プロジェクト `merpay-prod` へのアクセスが検出されました。本当にアクセスして良いか確認してください。 5. シナリオレベル検出の実装例 シナリオレベル検出では、複数ノード間の関係性を解析します。代表的な実装例として、Slack User Validationシナリオを解説します。 5.1 検出対象のリスク Slack Triggerを使用したワークフローでは、以下のリスクがあります。 想定されるリスクシナリオ : 本来アクセス権を持たないメンバーがSlackボットにコマンドを送信し、アクセス権を持つメンバーのcredentialを使用して本番データベース等の機密リソースにアクセス 必要な対策 : Slackトリガーの直後にユーザーバリデーションを実装 外部アクセス(BigQuery、HTTP Request等)の前にバリデーションを完了 バリデーションに失敗した場合はワークフローを停止 5.2 検出の流れ Slack User Validationの検出は、以下の3ステップで実施されます。 DAG解析でバリデーションノードを探索 (5.3節): Slack Triggerから下流のノードを辿り、Code/Ifノードを探索 バリデーション実装を検証 (5.4節): 見つかったノードの内容を解析し、適切なユーザー検証が実装されているか確認 フロー整合性をチェック : バリデーション前に外部アクセス(BigQuery等)が存在しないか検証 5.3 DAGによるフロー解析 n8nのループ対応 : n8nではワークフロー内でループ(循環参照)を作成できます。しかし、フロー解析にはDAG(有向非巡環グラフ)が必要です。そのため、SCC(強連結成分分解)を用いてループを検出し、各SCCを1つのノードとして扱うことでDAGに変換します。 実装 ( workflow-graph.ts ): // Typescript // 1. 強連結成分(SCC)を検出 const sccs = stronglyConnectedComponents(this.graph); // 2. 各SCCを1つのノードとして扱い、Condensed DAGを構築 const condensedDAG = this.createCondensedDAG(sccs); // 3. トポロジカルソートで実行順序を決定 const executionOrder = topologicalSort(condensedDAG); この変換により、ループを含むワークフローでも確実にフロー解析が可能になります。 e.g. Slack Triggerからのパス解析 ( slack-user-validation/checker.ts ): // Typescript // 1. Slack Triggerからの全ての下流ノードを取得 const allDownstreamNodes = this.getAllDownstreamNodes(slackTriggerId); // 2. バリデーションノード候補を抽出(Code, If nodes) const codeNodes = allDownstreamNodes.filter( node => node.type === NODE_TYPES.CODE ); const ifNodes = allDownstreamNodes.filter( node => node.type === NODE_TYPES.IF ); // 3. Slack Triggerからバリデーションノードまでのパスを取得 const pathToNode = this.graph.getPathFromNodeToNode( slackTriggerId, codeNode.id ); // 4. パス上に外部アクセスノードがないか検証 const pathValid = this.isPathValid(pathToNode); 5.4 バリデーションノードの検証 バリデーションノード候補が見つかったら、ノードタイプに応じて内容を解析します。 Codeノードによるバリデーション ( jscode-validator.ts ) BabelのAST解析で以下の4要素を検出: User ID抽出 : $input.item.json.user または $json.user 許可ユーザーのホワイトリスト定義 : const users = { 'U123': 'user1', 'U456': 'user2' } 検証ロジック : !users.hasOwnProperty(userId) 等 エラーハンドリング : 検証失敗時の return または throw 4要素すべて揃っている場合は完全な実装と判定します。 Ifノードによるバリデーション ( if-node-validator.ts ) Ifノードの条件式で以下をチェック: メールアドレスパターン : 右辺値が会社ドメイン(例: @example.com )のパターンと一致 ユーザーコンテキスト抽出 : 左辺値にユーザー情報の抽出が含まれる 等価演算子 : equals 演算子による厳密なチェック If nodeで「 {{ $json.user }} が @example.com メールと一致するか」をチェックするパターンを検出可能です。 5.5 検出結果の例 Codeノード(javascript) 完全なユーザーバリデーション実装 // Javascript // Slack Triggerの直後のCode node const userId = $input.item.json.user; const users = { 'U0123ABCD': 'authorized_user1', 'U4567EFGH': 'authorized_user2' }; if (!users.hasOwnProperty(userId)) { return; // バリデーション失敗時に停止 } // 以降の処理 検出結果: ### ✅ OK (1) Slack TriggerのCodeノードで適切なユーザー検証が実装されています。承認済みユーザー数: 2 不完全なユーザーバリデーション実装 // Javascript // User ID抽出のみ実装(ホワイトリストなし) const userId = $input.item.json.user; // 検証ロジックなし 検出結果: ### 🚨 重大な問題 (Critical Issues) (1) - **[Slack Trigger]** (n8n-nodes-base.slackTrigger) Slack Triggerに対するユーザー検証が不完全です。以下の「不足している要素と修正方法」を参考にCodeノード "User Validation" を設定してください: - 不足している要素と修正方法: - 1. 認証リスト: オブジェクトとして定義(変数名は "users" である必要があります): `const users = { "userId1": "userName1", "userId2": "userName2" }` - 2. 検証ロジック: `if (!users.hasOwnProperty(userId))`, `if (!(userId in users))` などのチェックを追加 - 3. エラーハンドリング: 検証のif文内に `return` または `throw` ステートメントを追加 Ifノード Ifノードで以下のような条件を設定: 左辺値: {{ $json.user }} (Slackユーザー情報の抽出) 演算子: equals (等価演算子) 右辺値: @example.com (会社ドメインパターン) 検出結果: ### ✅ OK (1) Slack TriggerのIfノードで適切なユーザー検証が実装されています。 6. 検出の例および社内での活用事例 メルカリでは、このツールをGitHub Actionsに組み込み、ワークフロー追加時のPRで自動的にセキュリティチェックを実行しています。 6.1 問題のあるワークフローの検出例 以下のワークフローには複数のセキュリティ上の問題および懸念があります。 検出された問題 : Slack Trigger直後のユーザーバリデーションが未実装 BigQueryで本番環境(prod)プロジェクトへのアクセスを実行 Google Sheets作成後のアクセス権限スコープ設定が未実装 これらの問題は、PRコメントとして以下のように報告されます: Slack TriggerとGoogle Sheetsでは重大な問題として、BigQueryの本番環境アクセスについては警告として検出されます。 6.2 修正後のワークフローの検証例 上記の問題を修正したワークフローでは、以下の対策が実装されています。 実装された対策 : Ifノードによるユーザーバリデーション(メールドメインチェック) Google Driveノードによる適切なアクセス権限スコープ制御 そして、修正後のPRコメントでは、問題が解消されたことが確認できます。 ユーザーバリデーションが正しく実装されていることを検出し、スコープ制御の設定内容も詳細に表示されます。これにより、レビュアーはセキュリティ観点を即座に確認できるようになっています。 7. 成果と今後の展望 ツール導入によって得られた定量的な成果に加え、開発を通じて得られた技術的な知見、そして今後の展望について整理します。 7.1 定量的成果 レビュー工数の削減 : 手動レビュー時間: 30-60分/ワークフロー → 5-10分/ワークフロー 削減率: 約80-85% 検出精度 : 重大セキュリティリスク(🚨 Error)の検出率: 100% 偽陽性率: 約15%(⚠️ Warningレベル) 7.2 技術的知見 JSONからの情報抽出 : n8nのワークフローJSONには、ノード設定、接続情報、コード内容など、豊富な情報が含まれる この構造化データにより、他のワークフローツール(Zapier、Make等)よりも詳細な静的解析が可能 DAGによるフロー解析 : graphology をベースとしたグラフ構造で、ノード間の依存関係をDAGとしてモデル化 graphology-components でSCC(強連結成分分解)を実行し、ループをDAGに変換 graphology-dag のトポロジカルソートで実行順序を決定し、複雑なワークフローパターンを検出可能 特に「ユーザーバリデーション → 外部アクセス」の順序検証など、セキュリティ上重要な関係性を自動検証できる AST解析の有用性 : 正規表現では検出が難しい複雑なJavaScriptパターンを、ASTによる構造的な解析で実現 Babelのtraverseを使うことで、コードの意味を理解した検証が可能 n8n公式との型定義同期 : n8nは個別ノードの型定義を公式に提供していないため、このツールでは独自に型定義を作成 公式パッケージには TypeScript型定義 ではなく、 ランタイムスキーマ定義 ( versionDescription.properties )が含まれており、各パラメータの名前、型、必須/任意などの情報を持つ このスキーマ定義を活用し、Schema Validator ( bigquery/schema-validator.ts ) で自作型定義のキーと公式スキーマのプロパティ名を照合 不一致が検出された場合は警告を出力することで、公式の破壊的変更(パラメータ名変更、削除等)にも迅速に対応可能 7.3 今後の展望 検出ノードの拡充 : 現在対応しているノードタイプの拡大 LLMとの連携 : 検出結果に対する修正提案の自動生成 ワークフローの意図を理解した高度なセキュリティ分析 動的情報を活用した検出の強化 : Slack APIを用いたチャンネル属性の検証:チャンネルIDからPrivate/Publicを判定し、Public Channelへの投稿を警告 Credential情報から取得可能な権限スコープの検証:設定されたCredentialの実際の権限を確認し、過剰な権限付与を検出 8. まとめ 本記事では、n8nワークフローの静的セキュリティ解析ツールの開発について解説しました。 技術的ポイント : ワークフローJSONの豊富な情報活用 : ノード設定、接続情報、コード内容を完全に抽出可能 DAGによるフロー解析 : 複数ノード間の関係性を解析し、セキュリティ上重要なパターンを検出 AST解析 : JavaScriptコードを構造的に理解し、ユーザーバリデーションロジックの完全性を検証 ビジネスインパクト : 手動レビュー工数を80%削減 継続的な自動監視により、変更後のワークフローも安全性を保証 セキュリティポリシーの標準化と一貫性のある適用 ワークフロー自動化ツールの導入が進む中、本ツールのような静的解析アプローチは、他のプラットフォームにも応用可能です。業務効率化とセキュリティ確保の両立を目指す組織の参考になれば幸いです。 明日の記事は abcdefujiさんです。引き続きお楽しみください。 n8n.io logo source: https://n8n.io/brandguidelines/
こんにちは。メルカリでエンジニアリングマネージャーとして働いています @tokkuu です。 この記事は、 Mercari Advent Calendar 2025 の12日目の記事です。 今回は、私たちのAds事業が急成長する過程で直面したシステム課題と、それを乗り越えるために立ち上げたプロジェクト「PJ-MARP」について紹介します。 「PJ-MARP」は “Make Ads Robust and Profitable” の略称で、Adsシステムを「堅牢(Robust)」かつ「収益性の高い(Profitable)」状態へと再構築することを目的とした社内プロジェクトです。 急成長する事業の足元を支えるため、技術的負債の解消からインフラ最適化、チーム運営の改善までを横断的に推進しました。 事業の急拡大に伴うシステム負荷の増大、それに伴うコストの増加やインシデントの多発は、多くのプロダクトが通る「成長痛」ではないでしょうか。 本記事では、私たちがどのようにして システムの信頼性 と 収益性 を取り戻したのか、技術的なアプローチと組織的な取り組みの両面からご紹介します。 急成長の裏で直面した「壁」 Ads事業はリリース当初から飛躍的に成長していきました。 具体的な数字で言うと、インプレッション数はローンチ前の4倍、コンバージョン数(CV)に至っては5〜10倍という規模にまで拡大しています。 初期フェーズでは、市場探索と仮説検証を最優先し、広告主や代理店が利用したいと思うようなプロダクトへと早く成長させる必要がありました。 このような スピードと成長 を重視する戦略をとった結果、広告在庫が十分に溜まり、アカウント数が増加していき、ビジネスとして順調にスケールしていきました。 しかし、その急成長の裏でシステムでは課題が徐々に露呈していきました。 直面していた3つの課題 特に2024年の年末から2025年の年始にかけて、私たちは以下のような深刻な課題に直面していました。 頻発するインシデント 我々のビジネスの成長が加速したことと相まって、広告業界として盛り上がりを見せる年末にかけてインシデントが多発しました。 インシデントの突発的な対応が増え、対処療法的なスケールアップなどの対応を繰り返し、結果としてチームとして徐々に疲弊していきました。 インフラコストの増大 売上は伸びていましたが、その分それと同じ勢いでクラウド(GCP)の利用費も増大していました。スピードを優先したため非効率にコストがかかっている部分も多く、 増えた売上がそのままGCP費用に消えていく ような状況になりつつありました。 広告表示機会の損失(Fill-rateの低下) Fill-rateとは広告リクエストに対して実際に広告を返せた割合のことです。 システム遅延により、タイムアウトが発生し、本来表示できるはずの広告が表示できないケースが増えていました。 具体的には改善前の1月の時点で、Worst caseでは iOSで51%、Androidで26% という危機的な状況でした。 急成長したことによりこのような問題が多発したため、このままでは事業の成長を阻害してしまいます。 そこで私たちは、収益性と堅牢性を確立することを目的としたプロジェクト、PJ-MARPを立ち上げました。 技術的アプローチ:アーキテクチャレベルでの見直し PJ-MARPでは、単なる対症療法的なバグ修正ではなく、根本的なアーキテクチャの見直しを行いました。リアーキテクチャ前のシステムでは非効率な処理やデータストアのアクセスが多く、処理速度の向上がそのままコスト最適化に結びつくケースが多々ありました。 また、広告においてログはとても重要な役割を持つため、ログを記録するパイプラインやデータストアについてはセンシティブに扱う必要があります。 私たちはこれらの理由から、 パフォーマンスとコストの最適化 と データストアとインフラの改善 の2つの観点を重点的に対応することにしました。 詳細はこの記事では紹介しませんが、主に効果が高かった施策について簡単に紹介します。 パフォーマンスとコストの最適化 まず着手したのは、リクエスト処理の効率化です。 Cache-chainingの導入 頻繁にアクセスされるデータに対して、多層的なキャッシュ戦略(Cache-chaining)を導入しました。これにより、DBへの直接的なアクセス数を大幅に減らし、レイテンシを短縮しました。 DBへのアクセス数を減らすことで、DB側のコストも削減することができました。 Redisコマンドの最適化 Adsでは検索結果のページング処理の過程で、Redisへ検索セッションのキャッシュを行っています。 このときのアクセスパターンを見直し、非効率なRedis操作コマンド実行を削減しました。 gRPCコールの削減 Adsのシステムは広告を絞り込む過程で様々なマイクロサービス間の通信が発生します。 スピード重視の開発によって整理されていなかったマイクロサービス間の通信においても、Profilerを用いて処理を解析し、不要なgRPCコールを洗い出すことによって、通信オーバーヘッドを削減しました。 データストアとインフラの改善 インシデントの問題となりやすい、データストア周りについても改善を行いました。 BigTableのホットスポット解消 特定のノードに負荷が集中していたBigTableのキー設計を見直し、ホットスポットを解消することでスループットを安定させました。 Elasticsearchのインデックス再構築 Adsではユーザーの検索語句に基づいて広告をマッチングするためにElasticsearchを使用しています。このマッチングパフォーマンスを向上と、オーバーヘッドを減らす目的でインデックスのマッピングやシャード設定を見直し、再構築を行いました。 データパイプラインのリファクタリング ログ収集やその後の集計処理を行うデータパイプラインを最適化し、遅延・欠損なくデータを処理できる基盤を整えました。 具体的にはDataflow上で動く各ジョブのコードレベル、フローレベルのリファクタリングや、失敗時の対処、モニタリングの強化を行いました。 プロセスと組織:チームをどう動かしたか これらの多数の取り組みを素早く完了させるために、技術的な修正と同じくらい重要だったのが、徹底的な可視化でした。 システム構造の可視化で現状を正しく捉える 複雑化したシステムを改善するには、まず現状を正しく理解することが欠かせません。 今回のプロジェクトは、Adsチームだけでなく他チームからの協力も多く得ながら進めたため、システム全体の理解を深め、共通認識を持つことが重要でした。 そこで私たちは、時間をかけてアーキテクチャ図や処理フロー図を詳細化することにしました。 あえて非同期で進めず、長い時間を確保してドキュメントを繰り返し更新しながら議論を重ねることで、ステークホルダー全員の認識を丁寧に揃えることを狙いました。 その結果、「現在のシステムがどうなっているのか」「どこがボトルネックなのか」という点について、徹底的に共通理解を築くことができました。 KPIの可視化で改善効果をリアルタイムに把握 また、DataDogやLooker Studioを活用し、主要KPIを継続的にトラッキングできるダッシュボードを構築しました。 これにより、改善施策の効果をリアルタイムに可視化し、意思決定のスピードと精度を高めることができました。 たとえば、「この施策を導入した結果、コストがどれだけ削減されたのか」「Fill-rateがどの程度改善したのか」といった成果を即座に確認できます。 数値として成果が見えることで、チーム全体の達成感や次の改善への意欲が自然と高まり、モチベーション維持にも大きく貢献したと感じています。 成果 PJ-MARPの取り組みの結果、劇的な改善が見られました。 改善前は危機的状況だったFill-rateは、施策適用後の直近1ヶ月では、プラットフォームを問わず 一貫して95%以上の水準 を維持できるようになりました。 またコスト面でも大きな成果が出ました。1月と比べて、3月は約28%のコスト削減を実現しました。 ビジネスの成長を止めずに、利益率を大きく改善することに成功しました。 まとめ 今回のプロジェクトを通じて、私たちは多くの学びを得ました。 コストダッシュボードは初期に作る : 何かが起きてから見るのではなく、常にモニタリングできる状態にしておくことで、異常検知やトリアージが容易になります。 キャッシュは最初から考える : パフォーマンス向上のため、特に広告プロダクトのような高トラフィック低レイテンシが求められるシステムでは、キャッシュ機構はサービス開発の初期段階で組み込んでおくのがよいと感じました。 定期的な見直しと外部の視点 : アーキテクチャは一度作って終わりではなく、効率化のために定期的に見直す必要があります。その際、チーム外のエキスパートの視点を入れることが非常に有効です。 本番投入後の再評価 : 本番環境での挙動を確認し、設定値を再評価・チューニングするプロセスは考慮しておくべきだと感じました。この再評価とチューニングを継続的に行うことがシステムを堅牢にするうえで重要だと思います。 PJ-MARPを通じて、システムが堅牢になっただけでなく、Adsチームの結束力が強まり、特定の個人のスキルに依存しない体制が整いました。 今後も、システムの信頼性と収益性のバランスを取りながら、Ads事業のさらなる成長を支えていきたいと思います。 明日の記事は @t-hiroi さんです。引き続きお楽しみください。
こんにちは。JB SREの @T です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の12日目の記事です。 弊社ではAI Workflow Platformとして n8n を導入しました。 概要や背景についての詳細は、前日のISSAさんによる「 理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない 」の記事で紹介されていますので、そちらもご覧ください。 本記事では、n8n Enterprise Edition を社内導入するにあたり、システム面で対応した内容について、いくつかご紹介します。 n8nを弊社基準のSecurity、ガバナンスに適合させるための挑戦 チームで、あるいは全社規模で自動化ツールを展開しようとするとき、ScalabilityやSecurity、ガバナンスといった壁に突き当たった経験はありませんか? n8nは非常に柔軟で強力なAI Workflow Engineですが、それを数百人規模の組織で安全かつ安定して運用するためには、デフォルトの状態から一歩進んだ設計と工夫が必要です。 本記事では、私たちがn8nを単なる便利ツールから、信頼性の高い「AI Workflow Platform」へと進化させるために実施した具体的な取り組みをご紹介します。 もしかすると、「n8nはここまで拡張・カスタマイズできるのか」という、Enterprise利用における柔軟性の高さを再発見していただけるかもしれません。 なぜEnterprise Editionだったのか? まず、私たちが直面した最初の選択は「 Community Edition(CE) か、 Enterprise Edition か」でした。 「Workflow Engine自体は同じコードベースなのだから、無償のCEで十分ではないか?」 そう考える方も多いでしょう。実際、私たちも最初はそう考えました。しかし、組織で利用するPlatformとしてn8nを捉え直したとき、Enterprise Editionを選択する必然性が見えてきました。 CEとEnterprise Editionの決定的な違いは、Workflow機能そのものではなく、 Team collaboration 、 Security 、 Scalability といった「周辺機能」にあります。 Team collaboration : CEではWorkflowはOwner1人が属人的に運用し、個々のユーザーが限られた権限の中、パーソナルな空間で使用する設計思想が感じ取れます。一方、Enterpriseでは Projects 機能により、チーム単位でのWorkflow共有や、 RBAC (Role-Based Access Control) によるきめ細かな権限管理が可能になります Security : 企業導入の必須要件となりがちな SSO (SAML/OIDC)や、HashiCorp Vaultなどの外部Secret store連携はEnterpriseのみの機能です Scalability : 大規模なトラフィックを捌くための Multi-main mode や、Databaseを肥大化させないための Binary data の外部ストレージ保存(S3等)もEnterpriseがサポートしています 単にWorkflowを動かすだけならCEで十分です。しかし、私たちが目指したのは、属人化を防ぎ、監査可能性を担保し、全社規模で安心して利用できる「基盤」でした。その実現には、Enterprise Editionが提供する、特にSecurityとガバナンスに関わる機能群が不可欠だったのです Enterprise導入を支えるn8nのシステム構成 私たちはn8nをGoogle Cloud上にSelf-hostingしています。 Enterprise水準のScalabilityとSecurityを確保するために、以下のような構成を採用しました。 ComputeとNetwork Compute : Cloud Run を採用し、Serverlessならではの運用負荷の低さとScalabilityを享受しています Network : Direct VPC Egress と Serverless NEG を利用し、private IPのみを持つセキュアな構成を実現しています データ永続化とGCSの活用 Cloud Runの一時ディスクはインスタンス再起動で消えてしまうため、GCS (Google Cloud Storage) をマウントし、以下のデータを永続化しています。 Binary data : n8nの external-storage 機能を使い、Workflowで扱うファイルの実体をDBではなくGCSに保存しています Community node : 通常、n8nのCommunity nodeはGUIから簡単にインストールできますが、Cloud Run環境では永続化のためにマウントしたGCSへインストールする必要があります。 しかし、GUI上で直接インストールを試みると、GCSのマウントに使用しているgcsfuseの制約により、下記のエラーが発生し、正常に完了しませんでした。 writes will fall back to staged writes due to err: cant allocate any block as global max blocks limit is reached 本来であれば --write-global-max-blocks optionでブロック制限を緩和すべきところですが、 Cloud Runのvolume mount ではこのoptionがサポートされていませんでした。 そこで回避策として、GitHub Actions上で npm install を実行し、生成されたfile群をGCSにsyncさせ、n8n起動時にそれを読み込ませるという運用フローを採用しています External Hook : 後述するガバナンス用のスクリプトもGCSに配置しています Cloud Run External Metrics Autoscaling (CREMA) によるWorkerのAutoscale 特筆すべき工夫として、 Worker pools のScalability確保があります。 「Cloud RunのAutoscale」といえば、CPU使用率やリクエスト数を基準にするのが一般的でしょう。しかし、n8nのWorkerのような非同期jobを処理するworkloadにおいては、それらの指標だけでは負荷(jobの滞留状況)を正確に反映できない場合があります。 そこで私たちは、 Cloud Run External Metrics Autoscaling (CREMA) の導入を検討しています。これは KEDA を利用してDataDogなどの外部metricsを元にCloud Runをスケールさせる仕組みです。 これにより、n8nのQueueの深さなど、アプリケーション固有のmetricsに基づいた最適なAutoscalingを実現しようとしています。 n8nを統制のとれたPlatformへ昇華させる機能群 システム構成に加え、n8nの機能をフル活用して、ガバナンスと利便性を両立させています。 1. TerraformによるProject機能のコード管理 みなさんの組織では、誰がどのWorkflowをメンテナンスしているか、一目で追えているでしょうか? 複数人での利用において、Personal spaceの共有機能はSecurity的な懸念(誰にでも全権限で共有されてしまう等)がありました。 解決策として Projects機能 を採用しましたが、GUI操作での管理は属人化の温床です。 そこで私たちは、 n8n Terraform provider を内製化し、Projectの作成からメンバーのアサインまでをすべてTerraformでコード管理しています。Terraform providerを内製化した理由は、公式でのTerraform providerの提供がなかったのと、OSSで開発されているTerraform providerがProjects機能に対応していなかったためです。 resource "n8n_project" "sre_team" { name = "SRE Team" } resource "n8n_project_member" "sato" { project_id = n8n_project.sre_team.id user_id = data.n8n_user.sato.id role = "project:editor" } e.g.: ProjectのTerraform resource設定 これにより、権限設定の変更履歴がGitに残り、レビュープロセスを通すことが可能になりました。 2. Oktaを活用したSSOとUser-Provisioning 認証は全社標準のOktaとSAML連携し、SSOを実現しています。 さらに、v1.122.2から導入された SAML経由でのrole-provisioning を活用予定です。これは、Okta側のuser属性( n8n_instance_role , n8n_projects )に応じて、n8nログイン時に自動的にロールや所属Projectを割り当てる機能です。 これにより、入退社や異動に伴う権限変更をOkta側で一元管理でき、n8n側の運用負荷を大幅に削減できます。 3. Audit Logの監査とプロアクティブな自動化 監査ログの取得は必須要件ですが、私たちは Log Streaming 機能を使い、ログを単なる記録以上のものとして活用しています。 以前は「監査ログは何かあった時に後から見るもの」という受け身の運用でしたが、現在ではログを起点に次のアクションを自動化する運用へと変化しています。 n8nから出力されるeventログ(user登録、Workflow保存など)をトリガーに、Cloud RunのSidecarを利用し、以下のような自動化を行っています。 初期設定の自動化 : ユーザーが新規登録( User signed up )されたら、自動的に共通のprojectへ招待し、Onboardingなどの手間を省いています セキュリティ通知 : WorkflowやCredentialが不適切に共有( User credentials shared 等)されたら、Slackで通知し、Projects機能の利用を促します e.g.: Slackでの通知メッセージ このように、ログを単なる記録として扱うだけでなく、次のアクションを自動実行するトリガーとして活用することで、プロアクティブなガバナンスを実現しています。 4. External Hookによる徹底したガバナンス n8nの自由度は魅力ですが、統制の観点からはリスクにもなり得ます。 私たちは External Hook 機能を活用し、Workflowが保存( workflow.update , workflow.create )される直前に独自のバリデーション処理を挟み込んでいます。 機密情報のチェック : Workflow内にAPI keyやパスワードなどが直書きされていないか確認し、検出された場合はWorkflowの保存をブロックして修正を促します 通信先の制限 : n8nのHTTP Request nodeで、localhostや社内ネットワークの特定セグメントなど、アクセスしてほしくない宛先が設定されていないか検証します 承認フローの強制 : WorkflowをActiveにする際、所定の承認プロセスを経ていない場合は有効化できないように制御します。これにより、管理されていない野良Workflowの乱立や、意図しないデータ漏洩を防いでいます これらのExternal Hook処理により、n8nは「自由なツール」から「ガードレールの効いたWorkflow Platform」へと変化しました。 5. Code node実行環境の分離と堅牢化 n8nのCode nodeは、ユーザーがJavaScriptコードを実行できる非常に強力な機能ですが、複数人が利用する環境においては、その実行環境の管理が課題となります。 そこで私たちは、コード実行を安全に行うための Task runners という仕組みを使用しました。 Task runnersには2つの動作モードがあります。デフォルトの Internal モードはn8nのメインプロセス内でコードを実行しますが、私たちはより堅牢性を高めるため、 External モードを採用しました。 Source: n8n Docs – Task runners External mode これは、コードの実行環境をn8n本体から切り離された独立したプロセス(CloudRun Sidecarコンテナ)で動作させるモードです。この構成により、万が一Code node内で意図しない挙動のスクリプトが実行されたとしても、その影響範囲をコンテナ内に封じ込め、n8n本体の安定性を維持することができます。 さらに、私たちは以下の環境変数を設定し、セキュリティの強化を図っています。 Pythonの無効化 : N8N_PYTHON_ENABLED 環境変数をfalseに設定し、Pythonの実行を無効化しました。これは、PythonのTask runnerがまだBeta版である点を考慮したためです 一部Nodeの無効化 : NODES_EXCLUDE 環境変数を利用し、 Execute Command Node と Read/Write Files from Disk Node を無効化しました。これらのNodeは、envコマンドによる環境変数の閲覧や、サーバー上のファイルへの意図しないアクセスを許容してしまう可能性があるため、リスクを未然に防ぐ目的で利用を制限しています これらの設定を通じて、Code nodeの自由度を活かしつつも、より安心して利用できる環境を目指しました。 まとめ n8n Enterprise Editionが提供するProjects、SSO、Log Streaming、External Hookといった機能群は、私たちが求めるセキュリティとガバナンス要件を満たす上で非常に大きな助けとなりました。 単にツールを導入するだけでなく、これらの機能を組み合わせ、自社の運用に合わせてカスタマイズすることで、n8nは真に「Enterprise-Ready」な基盤となり得ます。 私たちがここまでSecurityやガバナンスの強化にこだわった理由は、 エンジニアの皆さんもエンジニア以外の皆さんも、誰もが安心して使える環境を作りたかったから です。 エンジニア以外の皆さんは、 AI Workflow Builder のようなChat-baseの機能を活用することで、Non-Programmingで容易にAI-Agentを作成し、業務を効率化でき、「プログラミングができないと自動化は難しい」というこれまでの考えは、AIの力によって覆されつつあります。 Source: n8n Docs – AI Workflow Builder 一方でエンジニアの皆さんは、n8nを使用してプロトタイプを爆速で作成・検証し、高速なPDCAサイクルを回すことが可能になるでしょう。 更には人間が作成したWorkflowをAIが深く理解し、より堅牢でスケーラブルなアプリケーションとして自動で実装してくれる日がくるかもしれません。 n8nはほぼ週次でマイナーリリースが行われるなど開発スピードが非常に速く、日々新しい機能が追加され、直近だと v2.0へのメジャーアップデート も控えています。 n8nの進化と、私たちが取り組んできたSecurityやガバナンスの強化が組み合わさることで、社内の「AI-Native」な活動を少しでも後押しできるような環境になれれば幸いです。 明日の記事は mewutoさんによる、「n8nの静的解析CLIツールをOSS化 – JSON解析とDAGで実現するセキュリティチェックの自動化」についてです。n8nの承認フローの詳細が伺えるかと思います。引き続きお楽しみください。 n8n.io logo source: https://n8n.io/brandguidelines/
はじめに こんにちは。メルカリのバックエンドエンジニアの @kg0r0 です。 この記事は、 Mercari Advent Calendar 2025 の11日目の記事です。 本記事では OpenID Connect Core 1.0 で定義されている Claims パラメーターについて説明し、最後にメルカリでの利用例について紹介します。 認証認可の領域を担当されているエンジニアの方であれば OpenID Connect Core 1.0 仕様に目を通したことがあるかもしれません。一方で、Claims パラメーターは比較的マイナーなパラメーターであり、利用を実際に検討したケースは多くないのではないかと想定しています。もしここで紹介する内容が OpenID Connect (OIDC) で属性情報を扱ううえでの何かしらの気づきになれば幸いです。 Claims パラメーターについて Claims パラメーター は OpenID Connect Core 1.0 に定義されており、Authentication Request で指定可能なパラメーターの一つです。なお、 OpenID Connect for Identity Assurance 1.0 などの一部の拡張仕様内でも利用されています。 Claims パラメーターは JSON オブジェクトとして表現され、以下の例のように userinfo や id_token といったメンバーによって UserInfo Endpoint や ID Token に含めて返却して欲しい属性情報を指定することができます。 { "userinfo": { "given_name": {"essential": true}, "nickname": null, "email": {"essential": true}, "email_verified": {"essential": true}, "picture": null, "http://example.info/claims/groups": null }, "id_token": { "auth_time": {"essential": true}, "acr": {"values": ["urn:mace:incommon:iap:silver"] } } } Claims パラメーターは OPTIONAL であり、通常、Relying Party が取得したい属性情報を指定する場合は scope パラメーターが使われます。一方で、 claims パラメーターでは scope パラメーターでは指定することができない特定の組み合わせを要求することができます。また、 essential や value 、 values などのメンバーによってより詳細な条件を追加することもできます。 例えば、 5.5.1. Individual Claims Requests に記載されている以下の例では essential を true にすることで auth_time が必要であることを示すことができます。 "auth_time": {"essential": true} また、 value を以下のように利用し sub として特定の値を指定することによって、 login_hint よりも強制力のある処理をしたいユースケースなどに利用することもできそうです。 "sub": {"value": "248289761001"} 一部の Identity Provider (IdP) 実装 では Authentication Context Class Reference (ACR) を指定する方法として acr_values だけでなく claims がサポートされており、Level of Assurance (LoA) に基づいた処理に利用することも検討できます。 "acr": {"essential": true, "values": ["urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:bronze"]} 前述のように Claims パラメーターは、要求する属性情報に追加の条件を付与することを可能とし、Relying Party などが属性情報に基づいた特殊なユースケースを実現する際に利用できる可能性があります。 一方で、OpenID Connect Core 1.0 中では、Claims パラメーターの扱いに関する要件が複数記載されており、IdP 側の実装には少し複雑な箇所があります。 例えば、Claims パラメーターの定義において JSON オブジェクトの他に明示的に null を指定することができます。この場合、given_name はデフォルトの形式で要求されていることを示します。このため、IdP 側の実装によっては、Claims パラメーターをパースする際に明示的に null が指定されたのか値が存在しないため null になっているのか判断が必要な場合が考えられます。 "given_name": null また、各メンバーのデフォルトの動作を正しく適用する必要があり、例えば、 essential は OPTIONAL であるため明示的に true または false が指定される以外にも、デフォルトの動作として false が指定された通りに Voluntary Claim として扱う必要があります。 上記の通り、 Claims パラメーターは JSON オブジェクトで表現され、良くも悪くも自由度が高い構造になっています。また、OP 側の各メンバーの処理に関する要件が仕様中に複数記載されています。このため、実装が複雑になることを避けるためには、一度 Claims パラメーター以外のシンプルな方法がないか検討した方が良いかもしれません。 メルカリでの利用例 現在、メルカリでは RFC 9470 OAuth 2.0 Step Up Authentication Challenge Protocol で定義されているような LoA に基づく Step Up Authentication のメカニズムが導入されています。 例えば、Client が Protected Resource にリクエストする際に Protected Resource が要求する LoA を満たしていなければ、Client はその LoA を満たすことができるように acr_values を指定して Authorization Request をおこなうといった流れになります。 ここで、Protected Resource が要求する ACR が mf (multi factor) だとします。このとき、Password のみで認証されていた場合は、MFA を実施するように誘導され、必要に応じて電話番号などの登録をおこないます。なお、Password + SMS などで認証済みであれば要求を満たすことができ、FIDO といった phishing resistant な MFA で認証済みであっても同様に要求が満たすことができます。 しかし、もし Protected Resource が電話番号を必要とするサービスであった場合、FIDO によって認証されたユーザーは要求された LoA は満たしますが必要な属性情報は登録済みでない可能性があります。ここで、Claims パラメーターを利用して以下のように検証済みの電話番号が必要であることを伝えます。これにより、IdP に対して一度の Authorization Request の中で電話番号の登録が必要であることも伝達して誘導することができます。 "phone_number_verified": {"value": true} おわりに 本記事では OpenID Connect Core 1.0 で定義されている Claims パラメーターについて説明し、メルカリでの利用例についても紹介しました。Claims パラメーターは OpenID Connect Core 中に記載されている仕様ですが、あまり触れられていることが少ないと感じたためこの度記事にしてみました。実現したい処理をまずは標準仕様の中で行うことができないか検討することは、独自仕様の追加を避けて処理の透明性などを担保することに繋がると考えています。また、OpenID Connect Core など何度も読んだ仕様だとしても改めて読むと新しい気づきがあったりするのでオススメです。 明日の記事は @tokkuさんです。引き続きお楽しみください。
こんにちわ。AI Task Force の @ISSA です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の11日目の記事です。 概要 ワークフローや業務自動化のツールは以前から数多く存在していますが、 Zapier や Make のようなノーコード・ローコードツール、Workato のようにエンタープライズ寄りの iPaaS、そして MuleSoft、Talend、Informatica といった本格的な統合基盤まで、選択肢は幅広く揃っています。 しかし、ノーコード・ローコードツールは構築スピードが速い一方で複雑なロジックや AI 活用には限界があり、エンタープライズ向け ETL は強力である反面、開発・運用コストが大きくスピードを求める現場では扱いづらい側面があります。 さらに AWS / GCP / Azure が提供する workflow engine は強力である一方で、業務部門とエンジニアが同じ環境で協働するにはハードルが高いという課題もあります。 コーポレート IT ではノーコード・ローコードツールを活用して業務を最適化し、プロダクト開発ではフルコードで機能を磨き込む——そんな二つの世界を行き来する中で私は気づいたことがあります。 それは、「スピード」「柔軟性」「協働しやすさ」を同時に満たす基盤なしに、モダンな業務自動化は成り立たないということです。 こうした要件から、今回私たちは n8n を業務自動化基盤として導入しました。 n8nとは、ノーコード・ローコードで多様なアプリやサービスを連携させ、業務プロセスを自動化できるオープンソースのワークフロー自動化ツールです。 GUI とコードのバランス、イベント駆動とバッチ処理の両立、LLM との自然な統合など、現代の Developer Experience に求められる要素が揃っていたからです。本記事では、n8n を導入する上で Developer Experience がどのような観点で重要だったのか、その点を中心にお話しします。 背景 私はこれまで、常に “業務と開発のあいだ” に身を置いてきました。 B2B SaaS のソフトウェアエンジニアとしてキャリアをスタートした後、事業会社の SE として業務システム導入とサービス開発の双方に携わりました。 Salesforce を中心とする PaaS 上でのアプリ構築や ETL / iPaaS ツールの導入を経験し、ノーコード・ローコードツールによる業務システム開発の世界を本格的に知ったのもこの頃です。 前職 では、Salesforce Platform(PaaS)上での 数十万行規模の SaaS プロダクト開発 を担当しました。その中で Salesforce DX(現 Salesforce CLI)に出会い、手作業による設定移行(いわゆるチェンジセット中心の開発)から脱却し、ソース主導の開発・バージョン管理・CI/CD といった “あるべき開発者体験” を強烈に実感しました。PaaS でありながらフルコードのように扱える世界が存在する——そんな手応えを得た転機でした。 現在はメルカリの IT 部門で、業務プロセス、SaaS、プロダクト開発が交差する環境の中、さまざまなシステムの開発・導入を担当しています。ノーコードとフルコード、業務と開発の双方を理解する立場として、「組織としてどのような Developer Experience を設計すべきか」を考え続けています。こうした背景から、長年追い求めてきたワークフロー基盤の“聖杯”とも言える理想像に最も近い存在として、n8n にたどり着きました。なぜ n8n が“聖杯に近い”と感じたのか 多くのツールを触ってきましたが、それぞれ強みと弱みがあり、業務と開発の双方が求める要件を“ちょうどよく”満たすものにはなかなか出会えませんでした。 n8n に触れたときに感じたのは、ノーコード・ローコードツールでありながら、ソフトウェア開発の当たり前をそのまま再現できるという稀有さです。これは単なる便利ツールではなく、業務オペレーションとプロダクト開発が協働できる “基盤” になり得るという感覚に近いものでした。ここからは、私が特に評価した n8n の Developer Experience の 3 つの特徴を紹介します。 1. 安全な開発とデプロイを支える環境分離 n8n の一つ目の特徴は、ノーコード・ローコードツールでありながら、開発(dev)と本番(prod)の環境分離を前提としている 点です。多くのツールでは、ワークフローの編集と本番実行が同じ環境で行われるため、「本番が壊れるのでは?」という不安が常につきまといます。n8n では、dev・stg・prod のように 明確に独立した環境を持てる ため、業務に影響を与えずに新しいワークフローを試し、改善することができます。環境ごとに設定や変数も独立しており、たとえば開発用の API キーやテストデータを安心して利用できます。このように、prod と dev を安全に分離して扱えること自体が、業務自動化基盤として非常に大きな価値 です。 2. 変更管理と協働を支える Source Control(GitOps) 環境分離が「どこで安全に実行するか」を決める仕組みだとすれば、 Source Control は “何をどの状態で管理し、どう協働するか” を担う仕組みです。 n8n の Source Control は単なる Git 連携ではなく、 ワークフローを JSON として push/pull し、その時点の状態を正確に再現できる GitOps 基盤になっています。これにより、変更履歴の可視化、ロールバック、環境間の同期など、ソフトウェア開発で一般的なプロセスを低コード環境にも適用できます。PR レビュー自体は GitHub/GitLab 側で行いますが、ワークフローの更新を Git に集約することで、ノーコード・ローコードツールでは珍しいレベルの協働性と透明性を実現します。n8n が提供するこの GitOps 体験こそ、複数人で安全にワークフローを進化させるための大きな強みです。 3. Infra as Code を可能にする JSON ベースの構造 n8n のワークフローはすべて JSON で定義されています。これにより、CLI や API を使った自動デプロイ、CI/CD への統合が容易になり、ワークフローをコードと同じプロセスで管理できる 点が大きな特徴です。一般的なノーコードツールは UI 操作に依存するため、変更の再現性やテスト、自動化が難しいという課題があります。一方 n8n はワークフローそのものが構造化データとして扱えるため、 IaC と同様にバージョン管理・レビュー・自動デプロイを実現できます。 これにより、ワークフローは“便利なツール”の域を超え、組織全体の運用基盤として耐えうる堅牢さと拡張性を備えるようになります。 ノンエンジニアにとっての課題と、組織としての解決策 ここまで主に Developer Experience の観点から n8n を紹介してきましたが、一方で「ノーコード・ローコードだから誰でも簡単に使える」というわかりやすいストーリーだけでは語れない側面も存在します。特に、業務部門のメンバーを含む ノンエンジニアが n8nを扱う際の難易度は、正直に向き合うべきテーマだと考えています。 なぜノンエンジニアにとって難しいのか n8n は柔軟な分、ワークフロー構築時に求められる選択肢が多く、設定項目も細かいのが特徴です。たとえば以下のようなポイントは、非エンジニアにとって大きなハードルになります。 HTTP ノードの設定 どのエンドポイントに、どの HTTP メソッドで、どの認証方式を使うのか。API の仕様理解そのものが前提となります。 認証情報・スコープ管理 API キーをどこに保持するか、どの権限が必要か、最小権限設計が適切かなど、IAM の基礎知識が求められます。 MCP(Model Context Protocol)の拡張 MCP に独自の機能を追加する際、どのように設定し、どの認証情報を与えるか。これはもはやアプリケーションアーキテクチャの理解が必要です。 コードノード(Python / JavaScript)の存在 柔軟性が高い一方で、自由度を活かそうとするとコーディング能力そのものが要求されます。 このように、n8n の“できることの広さ”は魅力である一方、ノンエンジニアにとっては 情報リテラシーの要求水準が決して低くない、という現実があります。 メルカリのスケールで展開するために実施した3つのソリューション こうした課題に対して、私たちは「個々のスキル差に依存しない、安全で再現性のある運用」を実現するために、3 つの仕組みを導入しました。 1.セキュリティガードレールの整備 詳細は別の記事で紹介されますが、n8n の柔軟性ゆえに発生し得るリスク(権限の混同、過剰な API 権限、危険な分岐など)を自動検出するため、静的解析・DAG 解析を組み合わせた独自ツールを構築しました。これにより、セキュリティレビュー工数を大幅に削減し、安全性を担保しています。 2.MCP による自然言語開発体験の導入 メルカリでは、MCP は「会社として認可されたもののみ使用可能」というルールがありますが、n8n においては一般公開されている OSS の MCPを利用可能にすることで、個々人が自然言語を使ってワークフロー構築をサポートできるようにしました。これにより、ノンエンジニアでも“どこから手を付ければよいか分からない”という状況を減らし、最初の一歩を踏み出しやすい環境が整備されています。 3.社内レビュー体制と Slackbot による補助 n8n ワークフローは、社内のレビュー体制によって品質・セキュリティが担保されています。さらに、Slackbot がレビューや申請のフローを補助することで、 レビュー依頼 → 自動チェック → 承認までをスムーズに進められるようになっています。 この仕組みによって、ノンエンジニアが構築したワークフローでも安心して本番運用できる環境が整いました。 まとめ n8n がもたらす価値は、ワークフロー自動化を“業務ツール”ではなく“技術基盤”として扱えるようにする点にあります。環境の分離、変更管理の明確さ、そしてコードと同等の扱いやすさ。これらが組み合わさることで、業務自動化が持続的に改善できるプロセスへと変わります。 一方で、導入には学習コストがかかったり、PR レビュー機能が組み込まれていないなど、改善の余地も存在します。それでも、ワークフローをエンジニアリングの文脈で扱えるツールは多くなく、n8n はその中でも際立った選択肢だと感じています。 今回の記事では Developer Experience に焦点を当てましたが、n8n には他にもセキュリティ、拡張性、運用性など、多くの語るべきポイントがあります。 今後、シリーズとしてさまざまな視点から n8n の実像を紹介していく予定です。 明日の記事は Tさんによる、「Making n8n Enterprise-Ready: 企業向けn8nの導入と運用の取り組み」です。お楽しみください。 n8n.io logo source: https://n8n.io/brandguidelines/
こんにちは。メルカリでソフトウェアエンジニアをやっている @sters です。 この記事は、 Mercari Advent Calendar 2025 の10日目の記事です。 LiveContactTool LiveContactToolとは、カスタマーサービスのオペレーターがチャット応対に使用するシステムです。 “Effortless Customer Experience Project” というプロジェクトで、“世界中のメルカリのお客さまのお困りごとを 5 分で解決する” というゴールへ向かうために開発しているシステムです。 先日行われた、Mercari Gears 2025 でも関連するセッションがありました。こちらもぜひご覧ください! https://speakerdeck.com/mercari/mercari-gears-2025-transforming-customer-engagement-with-google-customer-engagement-suite 機微情報 機微情報とは「個人情報のうち、特に取り扱いに注意すべき情報」として取り扱いが定められている情報です。「 金融分野における個人情報保護に関するガイドライン 」では次のように書かれています。 法第2条第3項に定める要配慮個人情報並びに労働組合への加盟、門地、本籍地、保健医療及び性生活(これらのうち要配慮個人情報に該当するものを除く。)に関する情報(本人、国の機関、地方公共団体、学術研究機関等、法第57条第1項各号に掲げる者若しくは施行規則第6条各号に掲げる者により公開されているもの、又は、本人を目視し、若しくは撮影することにより取得するその外形上明らかなものを除く。 LiveContactTool以前のお問い合わせ応対の中でも、そういった情報が添付された場合には、該当の情報を削除できるような仕組み・フローが整備されています。ただしオペレーターによる手動での対応がメインのため、オペレーターの作業工数もかかっていました。 Cloud DLP Cloud DLPとは、GCPのプロダクトのひとつで、Data Loss Prevention (DLP)を行うためのソリューションです。 DLP とは 機密データを監視、保護をして、流出や悪用されることから守りましょう、というツール、プロセス、あるいは総称のソリューションです。保護の方法として、マスキング、難読化、匿名化をしてリスクの軽減を行うことができます。 Cloud DLPはGCPの様々なプロダクトと連携して、自動的にDLPの処理を行うことができます。 詳しくは公式のドキュメントを読んでいただくとよいでしょう。 https://cloud.google.com/security/products/dlp?hl=ja LiveContactTool上での機微情報の取り扱いにおいて、このCloud DLPを含めて様々な方法を検討しました。 前提として、手動での機微情報削除はオペレータの工数を取ってしまうため、リアルタイムなオペレーションが必要なチャット応対ではあまりやりたくありませんでした。また、手動での機微情報削除のためのリッチなシステムを構築する必要があることも採用しにくい理由でした。 結果として、Cloud DLPを自動化された機微情報削除のシステムとして採用することにしました。この理由はいくつかあります。 LiveContactTool上だけでなく、チャット応対として利用するCCaaSやConversational AgentsともCloud DLPと連携できる 様々な情報がデフォルトでサポートされている 国ごとに固有の情報もサポートされている 辞書や正規表現でカスタマイズ可能 “マイナンバー” が前後10文字以内にあること、のようなホットワードを設定できる 画像ファイルのDLPを行うことができる APIでの即時実行と、ジョブによる非同期実行がサポートされる ジョブトリガーを設定して永続的に実行しつづけることも可能 マネージドサービスのため、コンピューティングに関する管理が不要 PoCしたところ、マスキングの精度が期待を満たしていた LiveContactToolでCloud DLPをどのように使うか 簡略化したものですが、Cloud DLPまわりの全体像がこちらです。 現状では、チャットが終わり次第DLPを実施するようにしています。 テキストのマスキング テキストデータを送るとDLP処理をしたテキストデータを返すAPIがあります。これをつかってチャットメッセージをマスキングしています。 https://docs.cloud.google.com/sensitive-data-protection/docs/reference/rest/v2/projects.locations.content/deidentify 1つのチャットメッセージは1つのSpanner上のレコードで管理しています。1つのセッションで複数のチャットメッセージが入ります。これをそのままCloud DLPのAPIで1つメッセージずつ処理しようとした場合、Cloud DLPのAPI呼び出しクオータの制限に引っかかってしまいます。 そこでチャットメッセージを結合し、一定の長さの文字列にまとめてCloud DLPのAPIを呼び出すようにしています。実際にはctxや、API呼び出しのエラーハンドリング等諸々がありますが、やっていることはこんな感じです。 type deidentifyTarget struct { index int content string } func deidentifyMessages(messages []string) []string { result := make([]string, len(messages)) copy(result, messages) separator := generateSeparator() var batches [][]deidentifyTarget var currentBatch []deidentifyTarget var currentSize int // バッチ処理のために文字列長を計算してまとめる for i, content := range messages { size := len(content) if len(currentBatch) > 0 { size += len(separator) } if currentSize+size > maxBatchSizeBytes && len(currentBatch) > 0 { batches = append(batches, currentBatch) currentBatch = nil currentSize = 0 } currentBatch = append(currentBatch, deidentifyTarget{index: i, content: content}) currentSize += size } if len(currentBatch) > 0 { batches = append(batches, currentBatch) } // 各バッチについてCloudDLPのAPIを呼び出す for _, batch := range batches { var parts []string for _, t := range batch { parts = append(parts, t.content) } combined := strings.Join(parts, separator) apiResult := callCloudDLP(combined) deidentified := strings.Split(apiResult, separator) for j, t := range batch { result[t.index] = deidentified[j] } } return result } 画像のマスキング 画像のバイナリを送るとDLP処理をしたバイナリを返すAPIがあります。 https://docs.cloud.google.com/sensitive-data-protection/docs/reference/rest/v2/projects.locations.image/redact しかし、アプリケーションのメモリやネットワーク帯域を使うことになります。またCloud DLP側のファイルサイズの制限もあり、4MBとなっています( 参考 )。この都合のためにジョブを使うようにしています。実装としては、アプリケーションはCloud DLPのジョブを作成するAPIを呼び出しています。 https://docs.cloud.google.com/sensitive-data-protection/docs/reference/rest/v2/projects.locations.dlpJobs/create ジョブを使うことで非同期にCloud DLP内で処理を行います。ファイルサイズや画像の量にも依存すると思いますが、今のところ、数秒〜30秒くらいで処理が終わっているようで、そこまで大きな遅延や問題は起きていません。 Cloud DLP終了時のイベントをアプリケーション側で受け取れるので、それを利用してSpanner上のデータを更新しています。 Cloud DLPの設定管理 DLP処理を実施するAPIでは、どのように検査やマスキングを行うかを直接指定することができます。しかし、これでは設定がアプリケーションに閉じてしまい、他のアプリケーションやシステム全体から利用することが困難になります。 そこでTerraformを使い、CustomInfoType、InspectTemplate、DeidentifyTemplateを定義するようにしました。このようなterraformを書いてリソースを作成しています。 resource "google_data_loss_prevention_stored_info_type" "custom_regexp_infotype" { parent = "projects/gcp-project-id-production/locations/us" stored_info_type_id = "CUSTOM_REGEXP_INFOTYPE" display_name = "CUSTOM_REGEXP_INFOTYPE" description = "Custom InfoType using regexp" regex { pattern = "(?:${join("|", local.dlp_phrases.phrases)})" } } resource "google_data_loss_prevention_inspect_template" "dlp_inspect_template_for_text_content" { parent = "projects/gcp-project-id-production/locations/us" template_id = "inspect_template_for_text" display_name = "Inspect template for text" description = "DLP Inspect Template for text content with built-in and custom InfoTypes" depends_on = [ google_data_loss_prevention_stored_info_type.custom_regexp_infotype, ] inspect_config { min_likelihood = "UNLIKELY" info_types { name = "LOCATION" } info_types { name = "CUSTOM_REGXP_INFOTYPE" } } } resource "google_data_loss_prevention_deidentify_template" "replace_with_detected_infotype_for_text" { parent = "projects/gcp-project-id-production/locations/us" template_id = "replace_with_detected_infotype_for_text" display_name = "Mercari Contact Replace with detected InfoType for text" description = "Deidentify template that mask data with detected InfoType for text" deidentify_config { info_type_transformations { transformations { primitive_transformation { replace_with_info_type_config = true } } } } } terraformで作ったリソースを、APIの呼び出し時のパラメータに指定することができます。これで、terraformで管理するDLP設定を使った、検査・マスキングを行います。 func (c *client) DeidentifyStringWithTemplate( ctx context.Context, input string, inspectTemplateName string, deidentifyTemplateName string, ) (string, error) { req := &dlppb.DeidentifyContentRequest{ Parent: c.parentResource, InspectTemplateName: inspectTemplateName, DeidentifyTemplateName: deidentifyTemplateName, Item: &dlppb.ContentItem{ DataItem: &dlppb.ContentItem_Value{ Value: input, }, }, } res, err := c.dlpClient.DeidentifyContent(traceCtx, req) if err != nil { return "", err } return res.GetItem().GetValue(), nil } 精度をあげるために 組み込みのInfoTypeではうまく判定されないものがあったり、正規表現や辞書での定義をすり抜けたり、あるいは間違ってマスキングされたり、といったことが起こります。そういったものをチャット応対のオペレーション中に見つけた場合、即時に対応する方針はありますが、それだけでは不十分です。 そこで定期的に内容をサンプリングし、おかしなものが無いかをチェックすることにしました。 ランダムにチャットメッセージを抽出し、マスキング漏れがないかを確認 ランダムにマスキングされたチャットメッセージを抽出し、過剰なマスキングがないかを確認 先日のサンプリング調査では、配送番号がクレジットカードやマイナンバーとして判定されてしまい、マスキングされるケースが見つかりました。配送番号がわからなくなってしまうとオペレーションに不都合が出てしまうため、このマスキングはされないように調整することが必要です。これについては、Cloud DLPのテンプレート、InfoTypeの辞書や正規表現、ホットワードを見直すことで改善をしました。 また、検討レベルの段階ですが、システム面でモニタリングの実現可能性も探っています。Cloud DLPでは様々なメトリクスを取ることができ、何をどのくらいの一致の可能性で検出したのか、結局マスキングをしたのかしてないのか、といった情報がわかります。これらを使ったとしても、マスキング漏れや過剰なマスキングがなされていないか?を正確に知ることは難しいと思いますが、全体の傾向はわかるはずです。機微情報のモニタリングをするという点で、有用な情報になりそうだと思っています。 おわりに LiveContactToolではCloud DLPを使って機微情報をマスキングしています、という説明をしました。 Cloud DLPを使うことは初めてだったのですが、技術的な設計・実装以上に「機微情報をシステムとしてどう定義し、運用していくか」に難しさを感じました。これにはエンジニアメンバーだけではなく、関連するチームも巻き込んで、モニタリングしていく体制もセットで整備が必要でしょう。 このように書くと大変そうではありますが、Cloud DLPはAPIやデータ構造の仕組みが使いやすく、様々なGCPプロダクトと連携できるのが魅力的です。フルマネージドサービスのため、細かいメンテナンスが不要なのも大きなメリットだと思います。このようにシステム面から、機微情報の取り扱いを簡単にできるようにしてくれるプロダクトだと思います。機会があればぜひ使ってみてください。 そして明日12/11の記事は @kgoro です。引き続きお楽しみください。
はじめに こんにちは。 @panorama です。 この記事は Merpay & Mercoin Advent Calendar 2025 の 10 日目の記事です。 私は現在、7月に新設されたAI Task ForceというチームでEnablerを務めています。 AI Task Forceは、メルカリをAI Nativeな組織へと変革するために立ち上がった100名規模のチームで、Enablerは「各領域でAI Nativeな業務変革を主導する役割」とされています。 私の担当領域はFT Engineering(メルペイ・メルコインのエンジニアリング組織)です。 AI Task Forceについては以下の記事が詳しいです。 メルカリが本気で始めた「AI-Native」化。100名規模のタスクフォースが立ち上がるまで 「AI Task Force」で変化を加速する。CTO @kimurasが描くメルカリの成長戦略 また、 2025/12/02のAdvent Calendar で@nnaakkaaiiさんから発表されたpj-doubleというプロジェクトでは、QA領域のTech Leadを担当しています。 ▲上記図の「Double QA」のTech Lead AIを前提とした、仕様解析、テスト観点やテストケースの設計、自動テスト、そしてQA全体のワークフロー再設計に取り組みながら、「人間が何を行い、どのような業務体験を目指し、品質をどう担保するか」を日々議論しています。そして複数のパイロットチームとともに手法やツールの改善ループを回しています。 最初は「AI Task Forceとpj-doubleの話」(※1) や「なぜQA領域をやることになったか」、「どのような手法を試しているのか」を記事にしようと思っていました。しかしここ数ヶ月を振り返って、一番私に取って重大だったのは マインドセットの変化 だと気付きました。 そこで本記事では、AI Task Forceとpj-doubleで組織と業務のAI Native化を進める中で得られた、「不確実なものへの向き合い方」にフォーカスして書いていきます。 ※1 簡潔に補足しておくとAI Task Forceとpj-doubleは現在は統合状態に近く、双方の文脈で自然に登場します。実は紆余曲折あってそのようになっているのですが、今回は本題ではないので割愛します。技術書典19にメルカリとして出版した「 Unleash Mercari Tech! vol.7 」の第6章に「AI Task Forceに異動してから何をしてきたのか、pj-doubleがどのように拡大してAI Task Forceと統合状態になったか」などを書いていますので、もし気になる方はお手に取っていただけると。 不確実なものへの向き合い方 AI Task Forceで最初に直面したのは、答えのない領域でどう判断し、どう動くかという問いでした。 AI時代における組織や業務のあり方も、AI技術の未来も、誰も明確な答えを持っていません。 そのため「失敗したらどうしよう」「確証がないままこれだけの人を巻き込んで進めて良いのか」と不安を抱く日々が続きました。 しかし私は次第に ​ 不確実な中でも挑戦する意義 不確実な中で物事を決める、進める価値 という2つの考え方に行き着き、迷いを生じずに動けるようになりました。 不確実な中でも挑戦する意義 私は当初、AI Task Forceの目指す壮大な目標と答えのない挑戦にどう向き合えば良いかわからずいました。 一時は「社内固有の最適化だけ自分たちで行う。しかし一般的な大部分はソリューションプロバイダに任せてしまう方が、自分たちで挑戦するより合理的ではないか」という消極的な考え方になりました。 しかしその考え方はAI Task Forceの挑戦を根本的に否定するものでした。 たとえば、pj-doubleの取り組みではSDD (spec-driven development)に近い方法論が含まれており、こうした手法を検証することも活動の一部でした。 一方で、KiroのようにSDDの実現を支援するための外部ソリューションも存在しており、そうしたツールの進化に期待すれば、自分たちで手法を磨いたり検証したりする挑戦は不要なのでは、と感じる瞬間もありました。 しかし最終的には 「誰も正解がわからない。でも、手探りで失敗を繰り返したとしても進める価値がある」 のがAI Native化だと考え直しました。 自分たちで解を見つけられるかもしれないし、他社の成功例を参考にすることになるかもしれない。結局ソリューションプロバイダのサービスを採用することになるかもしれない。 AI Task Forceでは最初に自分たちが真にAI Nativeな組織に辿り着くことを目指していますが、たとえそうでなかったとしても、築き上げた下地や知見があればすぐに最適解に追従することができます。 答えが出るまで待っていてはいつまで経っても変化が起きませんし、答えが出た頃にはまた新しい問いが生まれます。だから取り残されないために、挑戦し続けることが大切だと気付いたのです。 実はこの考え方は、メルカリが10年以上かけて育ててきたカルチャー、Valueの一つ” Go Bold ”にも含まれていました。 Go Bold 大胆にやろう 大きな成功のためには、思考のリミッターを外して、試行回数を増やすことが必要です。誰かと同じことをすれば普通の成果しか得られません。失敗や変化を恐れず、普通ではない大胆なチャレンジをし続けます。 失敗自体を責めることはしません。ナイストライを賞賛します。 ミッション達成のために一人一人がありたい姿を描き、周囲に示すことを重要視します。 成功・失敗に関わらず振り返りを言語化して共有することで、組織全体で素早く学び、次のチャレンジの糧にします。 私はこのバリューのことはとっくに理解したつもりになっていました。 ですが今回の経験でバリューを再解釈し、メルカリはずっとこのスタンスだったのだと気付いたとき、とても背中を押されたような気持ちになりました。 不確実な中で物事を決める・進める価値 AI Task Forceは活動を開始して間もないチームです。 そのため ​ 何を対象にAI Native化を進めるか 誰を巻き込み、どの規模でやるか どの手法を採用するか など、無数に決めることがあります。 私はAI Task Forceへ異動する前、メルカードを作るバックエンドチームに所属していました。 そのチームではすでにある程度業務フローが確立しており、決定に関してはTech LeadやPM、 EM、 Directorと議論して決めていました。 そのためAI Task Forceに来てしばらくはどんな選択肢に対しても「これを私が決めて良いのだろうか」「とりあえず自分の意見をまとめて”偉い人”(※2)に持っていこう」みたいな考え方をしていました。 ※2 メルカリではTech LeadやEMも1つのRoleとしてフラットに考えるカルチャーがあるため、”偉い”という概念は本来ありません。この”偉い”という言葉は私が当時持っていた考え方を言語化しただけです。 しかしAI Task Forceで物事を決めようと思ったとき、 それを決める人は自分 でした。 というより本当は以前からずっとそうだったのです。 「権限がないから決められない」と思い込み、判断を他者に委ねていただけでした。 そこで初めて 「決める」こと自体に大きな価値がある ことに気付きました。 特にそれが誰にもわからないこと、決められないこと、自分で決めたくないことであればあるほどです。 AI Task Forceが向き合う不確実性もまさにそうです。 答えがないからこそ、決断して実行に移せることに大きな価値があります。 もちろんその決断には自分なりの根拠があり、関係者から納得が得られる必要があります。 ですがそういった細かいところは差し置いて、 「決める」「進める」ことが不確実な領域に向き合う上でとても大切 だとわかりました。 (言葉の上でも”不確実”と言っているのだから、論理的に考えても当然ですよね。ですがそれをきちんと仕事上で実感したのはAI Task Forceに入ってからでした。) また可能であればその専門性をもって「決める」前に「主張する」ができるとなお良いことにも気付きました。 決めることも重要ですが、決めるためには自分なりの意見が必要で、「主張する」「主張を持つ」という前段階があることも重要だと感じました。 たとえばpj-doubleのBackend QAでは、Scenarigo(※3)というテストツールを使うべきかという議論がありました。 使わない理由としては、Scenarigoがyamlベースで独自文法を含むため、AIがその構造と意味を十分に理解できず、AIの効果を最大限発揮できない懸念があったことです。 一方で使う理由としては、社内に既存の利用実績と知見があることと、仮に将来別ツールへ移行する場合でも、Backend QA全体フローから見れば置き換えの影響は限定的だと見積もれた点がありました。 これらを踏まえ、最終的には Scenarigo を採用することに決めました。 どちらが最適かは現時点でも確定していません。 しかし、自分なりの主張を持ち、決断し、言語化したことで、停滞していた状況が一気に動き始めたのを感じました。 これが「決める」「進める」ことが大切だと感じた場面の一例です。 ※3 Go製のテストツール。 https://github.com/scenarigo/scenarigo AI Nativeな未来での人間の価値 先ほどの「決める力」や「進める力」ですが、おそらくAIに関わらずTech LeadやEMといった方向性を示す必要がある役割を担う方に取っては、従来から求められてきた力だったと思います。 しかし私は最近、これらは単なる役職上のスキルを超えて、 AI Nativeな未来において人間が持つ本質的な価値になる のではないかと感じています。 というのも、先ほどの私の例を思い出すと、「いくつもの選択肢を検討し、それぞれのパターンで起こることを予測し、意見を言う (ただし決めない)」というのはもはやAIができていることだからです。 一方で責任と自信をもってして「決める」というのは以前の私と同じく、AIにもまだできていないことです。 今年の春頃「AIは意思や欲望を持たない。だからそれを持つことが人間の価値だ。」という言葉をよく耳にしましたが、今ならその意味がよくわかります。 終わりに 私は上記のようなマインドセットの変遷を経て、不確実への不安(「失敗したらどうしよう。確証がないまま進めて良いのか」という気持ち)を払拭し、この挑戦をすること自体に大きな価値を見出せるようになりました。 またAIというもの自体が不確実性をはらんでおり、時代も含めて不確実が大きいからこそ、決める行為そのものが価値になるということに気が付きました。 決める力こそが人間に残る最後のクリエイティビティなのかもしれません。 この記事では、ここ数ヶ月で私の中で起こった考え方の変化を赤裸々に説明してみました。 この気付きが、誰かの背中を押せていたら嬉しいと思います。 明日の記事はISSAさんです。引き続きお楽しみください。
こんにちは。メルカリの検索領域で Software Engineer をしている @otter です。 この記事は、 Mercari Advent Calendar 2025 の9日目の記事です。 メルカリの商品検索とその品質管理 メルカリの商品検索は、膨大な商品のなかからお客さまの意図を的確に汲み取り、本当に探している商品を検索結果に表示することが重要です。そのため、検索キーワードと検索結果との関連性や妥当性を日々チェックし、品質を維持・向上させることは不可欠と言えます。 この記事では、検索結果の品質チェックフローをどのようにLLM(大規模言語モデル)を活用して改善してきたかをご紹介します。 検索結果の品質レビューにおける課題と要件 これまで、プロダクトマネージャーやエンジニアがサンプリングした検索キーワードごとに、検索結果アイテムを一つ一つ目視で確認し、無関係なアイテムの表示率を計算してきました。ただ、この作業は非常に時間がかかる上、複数人で行うと評価基準にばらつきが生じ、評価結果が安定しないという課題がありました。 こうした課題を受け、品質レビューには、日次や週次で自動化されダッシュボードで監視できることに加えて、十分なレビュー数を安定して確保できること、明確な評価基準があること、そして検索者のコンテキストや意図を正確に汲み取れることが求められるようになりました。 LLMと評価基準による客観的かつ安定したモニタリングの実現 上記の要件を満たすため、私たちはLLMを用いた検索結果品質レビューに取り組みました。 いくつかモデルを比較した結果、Gemini 2.5 Proがユーザーの意図を最も正確に把握できていたため、採用しています。 当初は、検索者の目線に近い形で検索結果画面のスクリーンショットのみをLLMに入力して評価を行っていました。しかし、この方法では複雑な商品情報まで踏み込んだ判断が難しく、例えば商品の仕様やカテゴリの違いによる誤判定が生じるなど、十分な精度が得られないケースがありました。そこで、評価精度を高めるために、各商品の詳細な情報、商品名、商品種別、価格、カテゴリ、サムネイル画像などもあわせてLLMへ入力するよう改良しました。 評価基準 LLMには各商品について「Relevance Score (0.0–1.0)」と、その理由を返答するように指示しています。スコアはAmazonの ESCI relevance judgements (Exact, Substitute, Complement, Irrelevant) に基づく関連性判定を用い、分類ごとにスコアを設定しています。   Exact (1.0): 指定クエリと完全に一致する商品(例:「iPhone 14 Pro Max 256GB」→完全に同じモデルと仕様) Substitute (0.75): 機能的に代替可能な商品(例:「iPhone 14」→iPhone 13など、世代違いだが似た仕様) Complement (0.5): 補完的な商品やアクセサリー(例:「iPhone」→iPhoneケース、充電器) Irrelevant (0.0): 全く無関係、または条件を満たさない商品(例:「望遠鏡」→靴下) 従来の目視評価では判断基準が属人的になりやすく、評価結果にばらつきが生じがちでしたが、このような明確なスコア定義とLLMを組み合わせることで、評価結果の安定性や客観性が大きく向上しました。 品質モニタリングツールの仕組み 検索チームにとってSearch Relevancyの品質チェックには、現在大きく2つのユースケースがあります。 Online Monitoring 本番環境の検索クエリログからランダムに抽出したキーワードで検索結果の関連性を評価します。週に1回、約1,000件の検索キーワードについて、それぞれの検索結果上位120件の商品が対象です。 レビュー結果はBigQueryテーブルに出力され、モニタリングダッシュボード等から継続的に確認できます。また、検索品質改善のためのA/Bテストや新機能リリース時に、Average Relevance ScoreやIrrelevant Items Rateへの影響を監視できます。 Offline Evaluation 新機能をA/Bテストする前のオフライン評価や、改善検証などに使われています。検証したいキーワードをエンジニアやプロダクトマネージャーが入力することで、その検索結果・カテゴリ・ブランド・価格帯の分布、さらにLLMによる関連性評価を即時にツール上で確認できます。また、あらかじめ用意したキーワードセットによる大量一括レビューも可能です。 これらの2ユースケースは異なるシステム上で稼働していますが、LLMのプロンプトを共通化することで、評価基準と結果の一貫性を保っています。 今後の拡張の可能性 画像データとテキストデータを組み合わせることで評価精度が向上しましたが、まだ人の目による判断が必要な難しいケースも残っています。とはいえ、モデルの精度は年々大きく向上しており、今後さらに自動化できると期待しています。 また、評価・監視用途だけでなく、LLMによる評価データそのものを学習用データとして活用し、検索機能のモデル精度を高めていく、といった応用も視野に入れています。 まとめ メルカリの検索品質向上のために、これまで人手のみで行ってきた検索結果の関連性評価を、LLMを活用して自動化・安定化する取り組みを紹介しました。 LLMの導入によって、レビュー作業の効率化だけでなく、より客観的な評価軸をもとに継続的な品質モニタリングが実現できました。 今後は、評価データを活用したさらなる検索機能の改善や、より難易度の高いケースへの対応にもチャレンジしていく予定です。 検索や推薦システムの品質評価に悩んでいる方、またLLMの活用に興味がある方の参考になれば幸いです。 明日の記事は @task さんです。引き続きお楽しみください。
こんにちは。メルカリモバイル Tech Leadの @_seitau です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の9日目の記事です。 先日公開された記事『 pj-double: メルカリの開発生産性向上に向けた挑戦 』では、メルカリグループ全体として取り組んでいる ASDD (Agent Spec Driven Development) という新たな開発手法の概念や、AIネイティブな開発への展望について解説されました。 この記事では、その実践編として、より現場の実務に焦点を当てます。 2025年3月にリリースしてからさまざまな機能追加をしてきたメルカリモバイルの開発現場において、私がどのように ASDD を運用・実践してきたか、実際のプロジェクト事例を交えて紹介します。 実践フロー:PRDから実装まで Coding Agentの登場により、開発者は複数のタスクを並列で進めることが可能になりました。このメリットを最大化するための具体的なワークフローについて、最近リリースした メルカリモバイルの 複数回線対応プロジェクト を例に解説します。図示しているタスク名は仮のものを使用しています。 Step 1: PRDをPdMが作成し、リポジトリにコミットする まず、情報の管理方法から見直します。 PdM(プロダクトマネージャー)は、PRD(プロダクト要件定義書)をドキュメントツールではなく、Markdown形式でGitHubリポジトリに直接コミットします。 リポジトリ内に仕様が存在することで、後続のCoding Agent(Claude CodeやCursorなど)にコンテキストとして読み込ませやすくなり、エンジニアが素早く開発に着手しやすくなります。 Step 2: Coding Agentを活用してAgent Specを生成する 次に、エンジニアはCoding Agentを使用して Agent Spec(AIへの実装指示書) を生成します。 この際、単にPRDを読ませるだけでなく、Slackでの議論ログやNotion上のメモなど、散らばっている関連コンテキストにも接続して読み込ませます。これにより、人間がゼロから仕様を書き起こすことなく、文脈を考慮した精度の高いSpecドラフトをAgenticに生成することができます(この仕組みの詳細は 先述の記事 を参照)。 Step 3: タスクリストを作成し、依存関係を可視化する 生成されたSpecを元に、具体的な実装タスクを洗い出し、「どのタスクが独立しており、どのタスクが依存関係にあるか」 を整理します。 私は実装に着手する前に、以下のようなタスクリストを作成し、依存関係を可視化しています。 タスクID 機能項目 タスク名 説明 規模 依存関係 T001 申し込み 回線申し込み情報取得API修正 フィールド追加 S – T002 申し込み 回線申し込みAPI修正 バリデーション追加 S T001 T003 申し込み 回線数制限バリデーション 上限追加 M T001, T002 T006 Top画面 回線一覧API実装 回線一覧取得 M – T009 Default回線 インデックス追加 インデックス追加 S – T008 Default回線 回線切り替えロジック実装 申込・開通 L T009 この表において最も重要なのが 依存関係 の項目です。これを図解すると、以下のように並列実行可能なラインが見えてきます。 これにより、「Stream A, B, Cは依存関係がないため、3つのAgentを同時に稼働させて並列実装が可能である」といった判断を的確に行えるようになります。 Step 4: チームレビューとSpecの磨き込み 整理されたタスクとAgent Specは、実装を開始する前にチームメンバーによるレビューを受けます。ここで仕様の抜け漏れや認識のズレを解消しておきます。 現場で機能させる原則 このフローを運用する上で、私が重視している原則が3点あります。 粒度を小さく保つ 1タスク = 1PR を基本とします。 AIに一度に大量の変更を指示すると、意図しない挙動やバグが発生するリスクが高まります。「バリデーション追加のみ」「DBカラム追加のみ」といったレベルまで粒度を細分化することで、AIの実装精度が安定し、人間側のレビューコストも低減されます。 依存関係を明示する 前述の通り、依存関係の定義は並列化における要です。 自分が設計(Spec作成)を行っている間に、バックグラウンドで複数のエージェントが独立したタスクの実装を進めているという状態を作り出すことが重要です。 レビューの対話をコンテキストとして活用する Agent SpecのレビューMTGは、録画または文字起こしをしておくことを強くおすすめします。 最初のSpecだけでは表現しきれていなかった背景やニュアンスが、レビュー時の対話には多く含まれています。 この文字起こしログをCoding Agentに読ませてSpecを改善させる ことで、当初の記述では表現できていなかった文脈や設計の抜け漏れを補足し、より精度の高いSpecへと昇華させることができます。 例えば、チームメンバーに設計を説明する中で、 同じ時期に走っている別PJで追加される機能との整合性は? なぜこのカラムにIndexを追加する必要があるのか? どのデータが回線に紐付き、どれがアカウントに紐づくのか? など、チームメンバーから設計をみた時の疑問点が浮き上がります。レビューMTG内でこれらの質問に対して説明し、そのログをSpecに反映することで、設計者だけでなくチーム全体で納得感のあるSpecになります。設計の意図を適切にSpecに反映するのは、Coding Agentの脱線を防ぐうえでも重要です。 導入に向けたステップ もし、この手法を個人のタスクから試してみたい場合は、以下のステップから始めることをおすすめします。 PRDをMarkdownで記述する: AIがコンテキストを理解しやすくなります。 Coding Agentにドラフトを書かせる: 自分でゼロから書かず、PRDを読ませたAgentにSpecの叩き台を作らせてください。 依存関係リストを作成する: 実装に着手する前に、タスク一覧と依存関係を整理してください。これが並列化の指針となります。 おわりに あらかじめSpecを書いてから実装に着手する手法は目新しいものではありませんが、この記事に書いた原則やワークフローを意識することで、Coding Agentの力をさらに引き出し、並列に素早く実装を進めることができます。 実際にAgent Specを用いて並列で開発するワークフローを実践するようになってから、1ヶ月かかる想定だったプロジェクトを1週間で終えることができた事例もあり、確実に開発速度が向上しています。 これらの定量的な開発速度の変化や、AI Native化へ向けたメルカリの開発組織の取り組みについては先日開催したmercari GEARS 2025で発表しています。ぜひ併せてご覧ください。 メルカリでは、ASDD (Agent Spec Driven Development) を含め、日々よりよい開発手法を試行錯誤しています。この記事がAIネイティブな開発スタイルについて考えるきっかけとなれば幸いです。 明日の記事はpanoramaさんです。引き続きお楽しみください。
こんにちは。メルカリ ハロでQAエンジニアをしている @Yu-ga です。 この記事は、 Mercari Advent Calendar 2025 の7日目の記事です。 概要 「この作業、AIで効率化できないかな?」そんな模索をしていた時、人手不足という現実的な課題に直面しました。本記事では、QAエンジニアとして1〜2日で作れる小さなプロトタイプから始め、チームのフィードバックを受けながら実用的なツールに育ててきた実体験を紹介します。 きっかけと課題 メルカリが「AI Native」への転換を発表する中、QAチームでも「私たちの業務にもAIを取り入れられないか?」という議論が活発になっていました。 限られたリソースの中で品質を維持するには、これまでのやり方を見直す必要があります。そこでチーム全体で集まり、 「今後のQAプロセスをどう進化させるか」「AIでどの部分を効率化できるか」 を議論しました。 話し合いを重ねる中で、「まずは課題解決ツールのプロトタイプを作ってみよう」という共通の方向性が生まれ、限られた時間とリソースの中でQA業務全体の効率化を目指し、AIを活用した新しいQAプロセスの検証が始まりました。 QAエンジニア以外でもACを書けるようにしたい メルカリ ハロではQAエンジニアがAcceptance Criteria(以下AC)をJiraのストーリーチケットで作成していましたが、これをQAエンジニア以外でも簡単かつQAエンジニアと同程度の品質で作成できるようにする必要があり「AC Generator」というWebツールを開発しました。 ここでいうAcceptance Criteria(受け入れ基準)とは、 その機能が「完成した」と判断するための具体的な条件 のことです。 ※以前のAC活用に関する取り組みについては こちらの記事 もご覧ください。 「AC Generator」の開発にあたっては、「PMやエンジニア全員が使えること」と「人間によるレビュー・修正を必須とすること」を設計の核としました。 誰もが使えるようにしたのは、単なるQAのリソース不足解消だけが目的ではありません。QAエンジニア以外でもACを作成できるようになれば、開発の初期段階から全員が品質について同じ解像度で議論しやすくなると考えたためです。 また、レビューを必須としたのは、AIに「ある程度の土台」を作ってもらうことで作成のハードルを下げつつ、AI特有の誤り(ハルシネーション)を人間の目で排除するためです。これにより、誰でも効率的に、かつ一定品質以上のACを作成できるようになると考えました。 また、「過去にQAエンジニアが作成したACから厳選した学習データ」をAIに分析させ、AC作成ルールを生成しました。このルールには、AC作成の際の書き方や構造、仕様書からACを抽出する際の観点などが含まれています。 最初のプロトタイプでは、ユーザーがConfluenceの仕様書URLを入力すると、AC作成ルールを元にAIが自動でACを生成し、Web画面で結果を表示、それを人間がレビュー・修正してJiraチケットに起票するという一連のフローを構築しました。このプロトタイプは、AIを用いて1日程度で作成することができました。 実際にPMやエンジニア、QAエンジニアにツールを使用してもらい、SlackやJiraでフィードバックを収集し、それらに一件一件対応しました。例えば「成果物の精度がばらつく」という意見に対してはAIレビュー機能を追加し、「AIに再度修正して欲しい」という要望にはAIフィードバック機能を追加するなど、20件を超えるフィードバックに対応して機能を拡張しました。 その結果、PMやエンジニアがACを作成できるようになり、個人差はあるものの 最低限のACが誰でも作成可能な状態 となりました。AC Generatorが一時的に使用できなくなった際に、エンジニアから「AC GeneratorがないとAC作れないよ」という声をいただき、実用的なツールに成長できたことを実感しました。 現在は仕様書とルールベースでの生成に限定されているため、仕様書に記載されていない影響範囲の分析ができませんが、将来的には仕様書全体の取り込みやコードベースを考慮した生成にも発展させていきたいと考えています。 リグレッションテストケース作成を効率化したい AC Generatorと同様のアプローチで、リグレッションテストケース作成の効率化にも取り組みました。「ACから重要なものをリグレッションテストに追加する」というプロセスがありましたが、その作業は後回しにされることが多く、 リグレッションテストが増えない という課題がありました。 そこで、テスト管理ツールの TestRail に登録されている既存のリグレッションテストとその階層構造をAIに分析させました。これにより、どのようなテストがどのフォルダに分類されるべきかという分類ルールや粒度を学習し、現状の運用に即したリグレッションテスト作成ルールを策定しました。 先ほど紹介したAC Generatorのアーキテクチャをベースに、誰でも簡単に使えることを重視し、ACとルールからリグレッションテストを生成、Web画面で人間がレビュー・修正してTestRailに起票するというフローをAIを用いて1〜2日で構築しました。 このツールにより生成されたテストケースの精度が高く、そのまま使えるレベルのものも多かったため リグレッションテスト作成の工数は体感で8割程度削減 されました。しかし、作成コストは削減できたものの、リグレッションテスト作成のきっかけ作りには至らず、テスト数の増加効果は限定的でした。 根本的な解決には、作業のトリガーとタイミングの自動化が必要でした。そこで、ACのJiraチケット完了時に自動でリグレッションテスト生成・PR作成を行うワークフローの開発にも着手しましたが、現状まだ実用段階にはなっておりません。 E2Eテスト作成を効率化したい E2Eテスト作成においては、テストコードの書き方やTestRailの構造に合わせたファイル設計、Page Object Model、コーディング規約など習得すべき知識が多く、 特定のメンバーに依存してスケールしにくい という課題がありました。 この課題に対し、既存のE2EテストをAIに分析させてコーディング規約を自動生成し、さらに Playwrightのベストプラクティス も組み込んだルールを作成しました。 開発用エディタでの動作を前提としたE2E作成ルールと、TestRailのケースIDからテスト手順などの情報を取得するスクリプトを組み合わせることで、 AIがTestRail情報を基にE2Eテストコードを自動生成できる ようにしました。このプロトタイプもAIを用いて2日程度で作成することができました。 Page Objectの書き方統一やAPI利用ルールの追加、AIレビュールールの追加など、フィードバックを受けて改善を行った結果、AIによるE2Eテスト作成の精度が向上し、以前よりも効率化されました。 E2E作成ルールの整備により、チームメンバー誰もが一定品質のE2Eテストを作成できるようになりました。 ただし、生成されたコードには人間によるレビューと修正が必要で、チームメンバーにも基礎知識が求められるため、完全な自動化には至らず、人的コストは残存しています。 テスト前にコードベースからIssueを検出したい コードレビューによる問題特定は従来から行っていましたが、得意・不得意な技術領域があり、レビューの質に偏りがあるという課題がありました。 そこで、AIを活用してPRの修正内容(差分)を分析し、仕様書やACと照合してコードベースからバグを検出する取り組みを行いました。 QAエンジニアがテストを開始する前にこのチェックを行い、問題点だけでなく解決策もセットでエンジニアにフィードバックするようにしました。その結果、手動テストでは発見困難な問題(動作には影響しない型定義のミスなど)や仕様との齟齬をテスト前に検出できるようになりました。 この取り組みにより、 手動テスト前のIssue発見とコード理解 の両面で大きな効果を得られました。一方で、AIの回答には誤りも多く含まれており、AIの提案を鵜呑みにせず、人間による適切な判断が不可欠であることを学びました。 学び 今回の取り組みを通じて、AIは単なる作業自動化の手段にとどまらず、QAエンジニアが抱える課題を自らの手で解決するための強力な武器になることを実感しました。一連のツール開発とチームでの運用を通じて得られた、AI活用における学びを以下にまとめます。 まずはやってみる 技術的な難易度を事前に考えすぎるより、まず手を動かして動くものを作ることの重要性を実感しました。どの機能も最初のプロトタイプは1〜2日で作成でき、AIを活用すれば想像以上に多くのことが実現可能であることがわかりました。 フィードバックループを早く回す 最小プロトタイプを1〜2日で作成 みんなに使ってもらう フィードバックを収集 改善する このサイクルを早く回すことで、実用的なツールに育ちました。 また、 小さく始めることで失敗のリスクを下げる ことができました。1〜2日で作れるプロトタイプなら、仮に使われなかったり方向性が間違っていても、投資した時間やコストは最小限で済みます。大規模な開発を始める前に、本当にニーズがあるのか、アプローチが正しいのかを低コストで検証できたことは大きなメリットでした。 多くの人に使ってもらいフィードバックを得ることは簡単ではありませんが、最初のツール紹介の反応をきっかけに、発信の場や伝え方を工夫するようになりました。その結果、より多くのメンバーの目に留まり、さまざまなフィードバックを得ることができました。 プロトタイプは使われなければ成長しないため、開発前から「どうやって使ってもらうか」「どうフィードバックを集めるか」を戦略的に考えることの重要性を学びました。 半分の効率化でも十分価値がある AIが頭出し → 人間がレビュー・修正 AIが下書き → 人間が仕上げ AIが指摘 → 人間が判断 この協働パターンが最も効果的でした。AIが最初に生成するものには品質のばらつきがあるため、人間によるレビューと修正は必須のプロセスだと思います。 100%の効率化を目指すと、完璧を求めるあまり開発に時間がかかったり、そもそも実現できなかったりします。しかし、50%程度の効率化であれば、短期間で実現でき、すぐに効果を実感できます。 そして重要なのは、削減できた時間で新たな改善に取り組めることです。50%削減で生まれた時間を使って次のツールを作り、さらに効率化を進める。この積み重ねによって、結果的に大きな効率化を実現できました。 テストを事前に書いておく AIは想定外の動きをします。フィードバックを元に機能を追加する際に、既存機能を壊してしまうということが散見されました。それを防ぐために、コア機能についてテストを事前に実装し、AIが修正を行う際には必ずテストをクリアすることを必須としました。 例えば、AC Generatorでは以下のようなテストを実装しました。 基本表示テスト : フォーム要素が正しく表示されるか バリデーションテスト : 必須フィールドやURL形式のチェック 完了フローテスト : フォーム入力からチケット作成完了まで これらのテストは品質保証だけでなく、 AIに対する仕様の明確化 という重要な役割も果たしました。テストコードが「期待する動作」を具体的に定義することで、AIが修正を行う際の指針となり、意図しない変更を防ぐことができました。 しかし、AIにコード修正を依頼すると、 「テストが通らないから」という理由で、AIが勝手にテストコード側を修正してしまう という問題も発生しました。これを防ぐために、「テストコードは修正禁止」というルールを明確にAIに与えることで解決しました。 まとめ 今回の取り組みを通じて、 「経験がないからできない」という時代は終わった と強く感じました。 AIの登場により、未経験の領域でも成果を出せる可能性が劇的に広がりました。実際、今回のツール開発では、これまで使ったことのない技術やアーキテクチャを容易に取り入れることができました。「やったことがないから無理だ」と諦めていたアイデアも、AIというパートナーがいれば、わずか1〜2日で形にできる時代です。 今回の取り組みで最も強く感じたのは、AI活用において重要なのはプログラミングスキルそのものよりも、「この課題を解決したい」という強い当事者意識だということです。 「誰かが解決してくれるのを待つ」のではなく、「AIを活用して自分で解決する」。 このマインドセットの変化こそが、業務効率化以上の価値を私自身にもたらしてくれました。AIですべてを100%解決できるとは思いませんが、自らの手で課題を解決し、より本質的な品質保証活動に注力するための強力な武器になると思います。 今後もAIの力を借りながら、品質保証の新しい形を模索し続けていきたいと思います。 明日の記事は @Antony Chane-Hive さんです。引き続きお楽しみください。