TECH PLAY

株式会社メルカリ

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

261

こんにちは、メルカリ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 さんです。引き続きお楽しみください。
アバター
こんにちは。Backend Enablement Team のエンジニアの @goccy です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の7日目の記事です。 時は今から4ヶ月ほど前の8月下旬、とても暑い日でした。珍しくチームの On Caller の PagerDuty がアラートを上げました。内容は Gateway からあるマイクロサービスへの呼び出しが突然 Timeout するようになり、gRPC の Deadline Exceeded が急増しているというものでした。対象のマイクロサービスは gRPC Federation を利用した BFF ( Backend for Frontend ) で、基本的にはさらに後ろのマイクロサービスにプロキシするだけの単純なサービスです。少し調査すると、該当の gRPC メソッドには Timeout が設定されていなかったため、Gateway 側で設定した Timeout ( 30秒 ) を超過した際に、Gateway 側でリクエストをキャンセルするタイミングで出ているエラーだとわかりました。この間、BFF 側でエラーが発生していないにもかかわらず Goroutines は増え続けていることから、リクエストが処理できずにサーバー上で滞留しているような状況だと推測できました。しかし、それ以上の調査が困難なことと、問題が起きているのが一部の Kubernetes Pod だけだったため、該当の Pod を再起動することを試したところ、無事解決に向かいました。その日は営業時間が終了間際だったこともあり、もやもやしたものを抱えつつ、いったん解散して帰途につくことになりました。 このときはまだ、これから長い闘いが待っていることをチームの誰も知る由もありませんでした。 サービス間の関係図 それから二日後、先日の問題の原因究明もろくにできていないような状況で、再びアラートが上がりました。しかし今回は前回とは別のサービスで、かつ gRPC Federation を利用していました。現象は前回同様、突然該当のサービスの特定の Pod だけ Goroutines が急増し応答しなくなるという問題で、Pod を作り直すと改善するという現象も同様でした。 立て続けに gRPC Federation を利用する複数のサービスで同様の問題が起こったため、原因が gRPC Federation 側にありそうだという線が濃厚になってきました。これはとてもまずい状況です。gRPC Federation を利用するサービスは 20 を超えます。 突如として暗雲が垂れ込めます。この日から、チーム総出でこの問題に対処するようになりました。 Lock 範囲の最小化 まず、該当のサービスの直近のリリースに含まれる gRPC Federation の変更点などを調査しましたが、こちらは特に怪しいものはないという結論に至りました。次に、リクエストが滞留し続けることから、gRPC Federation 側で取得している Lock の待ち時間が長すぎたり、Dead Lock しているケースがないかを疑いました。該当のサービスでは Cloud Profiler を導入しており、その結果から Lock 待ちをしているスレッドが大量にあることがわかりました。Lock 箇所として複数の候補がありましたが、それらを Claude Code を使ってプロファイリング結果を参照しながら議論したところ、いずれも Dead Lock でないこともわかりました。次の可能性として、Lock を解放するまでの時間が長く、Lock を解放するよりも Lock の待ち行列に加わるスレッドの方が多くなり、結果 Goroutines が増え続けている可能性を考えました。そこで、まずは Cloud Profiler が挙げた Lock 箇所のうち、Lock 範囲を最小化することで不必要に長く Lock を取得している箇所をなくしました。( 該当のPR ) しかし数日後、また Goroutines が急増しアラートが上がりました。この時点から、一日に3〜 5回程度不定期にアラートが鳴り続けるようになってしまい、Pod を再起動するだけとはいえ、On Caller の負担が顕著に増えました。ただ、Lock 範囲を狭めたことで、Lock 待ちをしている箇所のほとんどが gRPC Federation のプラグインを利用している箇所であるとわかりました。 gRPC Federation のプラグインアーキテクチャ ここで、以降の説明を理解しやすくするために gRPC Federation のプラグインアーキテクチャについて解説します。gRPC Federation 自体の詳しい説明は 私が以前書いた別記事 をご参照ください。 簡単に説明すると、gRPC Federation は Protocol Buffers 上で記述できる型推論付き静的型付け言語であり、式の評価には CEL( Common Expression Language ) を利用しています。 OSS として開発しているため、Protocol Buffers 上で記述する CEL 式の中でドメイン固有のロジックを利用することは通常できません。例えば認証処理を例にとると、gRPC の metadata から 認証用の JWT を取り出してパースし、得られた値をバリデーションしたり、さらに別の値を取得するような処理は CEL の表現力の範囲を超えています。ですが、こういった処理は gRPC Federation を利用するすべてのサービスで必要になる共通処理でもあります。 gRPC Federation ではこのような要件のために、CEL 式の中で利用できる API を独自に定義し、その実装を Go で書いて WebAssembly に変換して利用する プラグインシステムを用意しています 。 Go でプラグインシステムを提供する場合、WebAssembly を利用するのが最善であることは、先日 Go Conference 2025 で発表してきた ( Go で WebAssembly を利用した実用的なプラグインシステムの構築方法 ) ので、詳しくはそちらを参照して欲しいのですが、重要なこととして、WebAssembly を利用する場合に次のような制約があることを知っておく必要があります。 WASI P1 という仕様に沿って動作するが、P1 の段階ではスレッドの仕様は存在しない スレッドの仕様がないため、必ずシングルスレッドで動作する WebAssembly を動作させるために作るインスタンスは、起動するだけで数十MBのメモリを消費するため、インスタンスを無制限に作るようなアーキテクチャは採用できない これらの制約のため、gRPC Federation ではプラグインを評価する際、gRPC Service ごとにプラグインに対する WebAssembly インスタンスを一つだけ作成し、評価の前後で Lock を取得して直列に処理します。これはつまり、サーバーに対してどんなに並行にアクセスしても、プラグインの処理の段階では直列実行になることを意味しています。 Cloud Profiler の結果、このプラグイン評価時に取得する Lock 待ちが大量に発生していることが原因だとわかりました。 また、以降ではプラグインの評価に際して、WebAssembly ランタイムを動かすGo 側をホスト、プラグイン側をゲストと呼称して説明します。 問題の再現に挑戦 具体的な原因がわかってきたところで、プラグインの評価を高頻度かつ大量に行うことで問題が再現するかを試し始めました。ローカルで立ち上げたサーバーに対して高負荷な状況を作ったり、開発環境にデプロイしているサーバーに対してリクエストするなどさまざまなアプローチをチーム総出で試しましたが、問題が起こった gRPC メソッドに対して 1000rps を超える負荷を 30分近くかけ続けても再現には至りませんでした。 ここで、問題を再現することが難しいことが分かったため、問題発生時に本番環境で取れる情報を増やす方向も考え始めました。具体的には対象のサービスに pprof による計測用のエンドポイントを仕込んでおき、問題が起きたときに対象の Pod のサーバーに対してプロファイリングを実行することで、 Cloud Profiler ではわからない情報をとれるようにしました。 pprof による調査 pprof を仕込んだ翌日、問題が再発しました。今回は対象の Pod を再起動するのではなく、Pod を Kubernetes Service から切り離し、切り離したサーバーの pprof のエンドポイントにリクエストを送ることで各種データを取得しました。 取得した結果を対象に、例えば go tool pprof -traces を実行すると、計測時に動いていたすべての Goroutines をスタックトレース付きで把握することができるのですが、プラグイン関連のスタックトレースを見ると、プラグイン評価に際してホストとゲストそれぞれが相手からのリクエストを待っている状態になっていました。これはありえない状況です。ホストは正しくゲストに要求を伝えたと思っているが伝わっていない、もしくはその逆の現象が発生している可能性があります。 それぞれが同時に相手からの要求を待ち受けているということは、永久にその要求は満たされません。そのため、デッドロックのような状況になってプラグイン評価が永遠に行われず、プラグイン評価は直列に動作するため、サーバーの処理が停止してしまう事態になっていることがわかりました。 しかしここから調査が難航します。そもそもなぜこのような事態になっているかがわからなかったのと、問題が起こるたびにプロファイリングをしても結果が毎回同じ内容だったため、新しい情報が得られませんでした。ローカルでも引き続き再現しません。 そこで、プラグインインスタンスに何らかの不調が起きて、突然ホストとゲスト間の疎通ができなくなると仮定し、ホストとゲスト間のメッセージのやりとりに利用するメモリの読み書き処理でエラーになった場合は、あらかじめ作っておいたバックアップインスタンスにフォールバックするような仕組みを作成しました ( https://github.com/mercari/grpc-federation/pull/327 )。もしどこかでエラーが返るようであれば、インスタンスをリフレッシュすることで復帰できるはずです。 しかし結果は期待とは異なり、エラーが発生することもなく、予兆なしに突然問題が発生してしまいました。 GC を疑い始める プラグインを評価する際、ホストからゲストへのリクエスト方法は大きく2通り存在します。 一つは認証処理の例で説明したような通常の API リクエストです。もう一つはGCの実行でした。 WebAssembly ランタイムではアロケーション戦略としてリニアメモリを採用しており、メモリが足りなくなったら倍のメモリを確保して対処します。ですが、メモリが十分でも確保した範囲を縮小することはしません。つまり、GC が走るタイミングが遅れるほど、メモリが不必要に多く確保されてしまうことになります。これを防ぐため、プラグイン側で細かく GC を実行する必要があり、gRPC Federation では定期的にホストからゲストに対して、強制的に runtime.GC() を実行するように要求する戦略をリリース初期から1年以上行ってきました。 このアーキテクチャをふまえ、本番環境でしばらく運用した際にエラーなく突然問題が起こることから、GC をトリガーした際に問題が起こるのではないかという仮説を立て、ホストからの GC 呼び出し処理をなくすことで、事態が好転するのではないかと考え始めました。仮に GC が関係なかったとしても、ホストからゲストにリクエストを投げる回数自体が減るため、問題が発生する頻度が下がるのではないかという期待もありました。 トレードオフとして、プラグイン側で GC が走る頻度が減るため OOM ( Out Of Memory ) になるリスクは上がりますが、まずはホストからゲストへの GC 要求をやめて様子を見てみることにしました。 事態が好転し始める GC の経路を削除した後、アラートが上がる頻度が目立って減りました。しかし、予想通り OOM が発生してしまったのと、頻度がゼロにはなっていないため、根本対策にもなっていません。 プロファイリングしても相変わらず同じ結果でした。 しかし、事態が今までと変わったことから、問題の切り分けのためにプラグイン側で自動で走る GC を疑ってみることにしました。プラグイン側で走る自動GCを GOGC=off 環境変数を指定することで止め、強制 GC のみで運用する方法です。このとき、より実装をシンプルにするため、今までホスト側で GC の終了を待っていなかったのを同期的に待つようにしました。補足として、今まで GC の終了を待っていなくて大丈夫なのかと疑問に思った方がいるかもしれませんが、ゲスト側で強制GCの場合を特別扱いして処理しているので非同期にすること自体は問題ありません。 この状態でしばらく運用したところ、問題が発生する時は、スタックトレースから必ず強制GCの経路になっていることがわかりました。つまり、ホストから強制GCを実行したところ、その結果が返らずに停止しているということになります。 ホスト・ゲスト間の通信方法の改善 突然 runtime.GC() から処理が返ってこなくなるとは考えにくかったので、GC が走った影響で、ホストとゲスト間の通信に利用する何かが壊れたと推測しました。そこで、両者の通信方式をよりシンプルなものに変更することで GC の影響を減らすことができるのではないかと考え、今まで利用していた STDIO を使った通信方法をやめ、ホスト関数と Go のチャネルを利用した通信方式に変更し、すべてをホスト上で行う標準的な Go のチャネルを介した処理に変更しました。 https://github.com/mercari/grpc-federation/pull/331 https://github.com/mercari/grpc-federation/pull/333 この処理は Go Conference 2025 で紹介した際のホスト・ゲスト間の通信処理を改善したものなので、興味のある方は見てみていただければと思います。 別件で、プラグイン上でネットワークソケットを扱うために導入した wasi-go というライブラリも疑い、ネットワークソケット関連の syscall 以外を wazero が提供する枯れた実装に置き換えようともしましたが、wazero がファイルシステムまわりの API を private にしているために、 wasi-go 側で作った ソケットディスクリプタ を wazero 側に伝える方法がなく断念しました。 さて、この改善を行ったことで事態がより進展すると思われましたが、問題は収束せず、プロファイリングすると、ホストとゲストそれぞれでチャネルの受信処理でブロックしているという結果になりました。ただし、いずれも強制GCの結果待ちでした。 ログを仕込む 今回の問題は本番環境で長時間運用しなければ発生しなかったため、コストの面からデバッグログを仕込むことを躊躇っていました。しかし、これ以上原因をしぼりこむことができなかったため、遂にログを追加することにしました。 あわせて Trace も追加しました 気をつけたこととして、問題が発生したらできるだけすぐにデバッグログを無効にしたかったため、環境変数を有効にしたときだけログを有効にするような Feature Flag 方式を採用し、環境変数の変更を巻き戻せばすぐに元に戻せるようにして運用しました。 ログを有効にして計測した結果、ゲストがGC 実行の要求を受け取ったというログは見つかりましたが、結果を送信するというログがなかったため、ないだろうと考えていたGC実行中にハングしている可能性が濃厚になりました。 また、ログを仕込んだ結果、ホストとゲストでどちらも受信待ちになっていたと思っていた不可思議な現象は、実は別のプラグインインスタンスだったことがわかりました。つまり、ホスト側とゲスト側のスタックトレースは、それぞれ別のインスタンスのものを表示していたのです。先に説明した通り、gRPC Federation は gRPC Service ごとに作るプラグインインスタンスは一つですが、 同一プロセス上で動く gRPC Service が複数ある場合はインスタンスも複数になります 。このことを考慮できていなかったのが悔やまれます。しかしこれで、問題がデッドロックではなくGC実行中のハングに収束したためとてもシンプルになりました。 インスタンスの自動復旧対策 気づくと10月も半ばに差し掛かっていました。振り返ると8月下旬から数えて2ヶ月近く格闘していたことになります。 ですが問題が明確になったことで、取れる策も具体的になりました。GC実行中に処理が返らなくなることから、まだその原因はわからないにせよ、ワークアラウンドを実装することはできます。強制GCにタイムアウトを設け、タイムアウトした場合はインスタンスが壊れたと判断し、前に実装していたインスタンスをバックアップに切り替える仕組みを使って自動的に復旧することができるはずです。 ついでに、インスタンスを切り替える際にログも出力してみます。無事復旧しているならばログが出るはずです。 この対応を行ってから、数日間様子を見ましたが、仕込んだログは定期的に出力されつつ、Goroutines が急増することもなくなりました。 ひとまず、ワークアラウンドはうまく動いていそうで、障害対応が落ち着いたといえる状態になりました。 このとき行った実装は こちらです 根本原因が判明 障害対応が落ち着いたので、今まで得られた情報からより再現しやすい環境で調査を続けたところ、長時間動作させる中でローカルで再現できるようになりました。依然として再現することは難しかったですが、それでも再現できたことは大きく、詳細なデバッグログを仕込んで複数回実行する中で、 ネットワークソケットを利用するために導入した wasi-go というライブラリの PollOneOff syscall 内で呼び出している unix.Poll から処理が返ってこなくなることがわかりました。デバッグログの結果から、どうやら通常は 0 になるはずの unix.Poll に渡している timeout 値が異常値(負数)になっており、永久にくることのないイベントを待ってブロッキングする挙動になっていることがわかりました。また、 PollOneOff と GC に因果関係があるかを調べるために Go 本体のソースコードを追ってみると、GC のシーケンスで PollOneOff が呼び出される経路があることもわかりました。 この問題を修正したときのPRは こちらです 。 ※ ライブラリは私が作ったもののように見えますが、実際には fork したものを使っています。 この修正を適用したあと、仕込んだインスタンス切り替え時のログは出力されなくなり、無事問題が解決しました。問題発生から2ヶ月ほどのことで、あの暑かった夏から少し肌寒くなってきていました。 反省点 あらためてプロファイリング結果を思い返すと、実は PollOneOff のスタックトレースは一番最初に計測したプロファイリング結果に含まれていました。勘がよければ、最初の時点で PollOneOff が原因だと気づくこともできたのです。 PollOneOff は epoll のようにファイルディスクリプタに対するイベントを監視する WASI 用の syscall で、スタックトレースは最初から目に入っていましたが、初期の実装ではホストとゲスト間の通信に STDIO 方式を採用していたため、通信時に利用する標準入出力のイベント待ちで呼び出されているのかと勘違いしていました。また、wazero ( WebAssembly ランタイム ) 側で特別に別スレッドでイベント待ちをしているのかとも予想しており、あまり気にしていなかったのが悔やまれます。 冷静に考えると、あとで Go のチャネルを利用した通信方式に切り替えてもスタックトレースに現れていたのはおかしいし、シングルスレッドアーキテクチャなのに別スレッドでイベント監視しているのもおかしいので、先入観で判断してはいけないと改めて気付かされました。 逆に、最初からプロファイリング結果に答えは書かれていたので、pprof が大事だとも言えます。 振り返り 本番環境でしか問題が起こらない場合、問題のある Pod を単に再起動するのではなく、サービスから切り離してプロファイリングするのは非常に重要なフローでした。 pprof は実行時にしか負荷がかからないので、本番用のビルドでも関係なく pprof の計測エンドポイントを持っておいた方がいいという気づきも得ました。 また、ちょっとしたことですが、デバッグログを無効化するために実装を巻き戻して image を作り直し、 hotfix を deploy する方法だと時間がかかってしまうので、環境変数によってデバッグログの出し分けを制御できたのは良かったです。 今回の対応では、問題のあるPodをサービスから切り離した後、pprof によるプロファイリングをメインに対応してきましたが、実はもうひとつ試したかったこととして、 Delve でのサーバープロセスへのアタッチによるデバッグがありました。しかし、いくつかの制約により実施には至らず、もし実行できていれば unix.Poll 時に timeout 値が変な値になっていることに気付いたかもしれないため、今後の課題です。 メルカリグループ全体の課題として、プロファイリング結果や Core ファイルなどを永続化する手段を標準化していないため、仮にそれらの情報をディスクに書き出したとしても、Pod が消えると情報が消えてしまう問題があります。Go 1.25 から入った Flight Recorder などを効果的に利用するためにも、解析情報を Pod の外に退避させ、デバッグ時に参照できるような仕組みを構築することが重要だと考えています。 今回修正を加えた https://github.com/goccy/wasi-go は、 https://github.com/dispatchrun/wasi-go を fork したものなのですが、fork 元のメンテナンスが停止していること、自分の方のライブラリでは今回の修正対応以外にも TLS 対応や exec.Command 対応など、実際のユースケースに応じたさまざまな改善が入っているので、これから Go が WASI P2 に対応するまでの間で WebAssembly を使いたいと思っている方には強い味方になってくれるはずです。ぜひご利用ください。 最後に、今回 Claude Code にプロファイリング結果の分析などの壁打ち相手になってもらいましたが、AI は答えを出そうとするあまり誤った答えを平気で出してくるので、嘘を嘘だと見抜く力が必要だと強く感じました。 明日の記事は fenomasさんです。引き続きお楽しみください。
アバター
Merpay KYC チームの Sakabe です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の6日目の記事です。 2025年11月14日〜15日に開催された「 YAPC::Fukuoka 2025 」へメルカリグループはスポンサーとして参加していました。今回はその参加レポートをお届けします! YAPC::Fukuoka 2025 について YAPC は Yet Another Perl Conference の略で、Perl を軸にしながらも、今では Web・インフラ・AI・SRE・フロントエンドなど幅広い技術者が集まる技術カンファレンスです。 今回のテーマは「きゅう」。探”求” や ”急”激な成長体験など、”九”州開催の YAPC らしいキーワードでした。 開催概要 日程: 2日開催 2025年11月14日(金) 2025年11月15日(土) 会場: 福岡工業大学 会場入口には YAPC の装飾が並び、到着した瞬間からイベントの熱気に包まれました。 福岡工業大学は駅を降りるとすぐに広いグラウンドが目に入り、学生の活気も合わさって “技術のお祭り” らしい雰囲気が一気に高まります。 Mercari メンバーの登壇 今回は Mercari から 「 iPhone のマイナンバーカードを使った本人確認の実装 」と題しeKYC に関するトークが採択されました。 iPhone のマイナンバーカードを使った本人確認の機能について、Verifier における技術的な観点や開発経緯などをkgoroさんに話していただきました。 最新の eKYC 手法ということもあり質問が非常に多く、Q&A 後も聴講者が集まって議論が続くほどの盛り上がりでした。 現地ならではの熱量を強く感じたセッションでした。 発表資料はこちらをご確認ください。 セッションの見どころ DBからエンジニアリング、AI まで幅広い発表があり、個人的に特に印象に残ったのはこちらです。 今、MySQLのバックアップを作り直すとしたら何がどう良いのかを考える旅 競馬で学ぶ機械学習の基本と実践 旧から新へ: 大規模ウェブクローラのPerlからGoへの置き換え YAPC の特徴として、特定の技術に閉じすぎず、技術と向き合う姿勢を語るセッションが多いのも魅力でした。 参加メンバーの感想 (kgoro) 今回は「iPhone のマイナンバーカードを使った本人確認」というテーマで採択いただき発表させていただきました。発表について多くのご質問をいただき、個人的にも iPhone のマイナンバーカードについて改めて深く考える貴重な機会になりました。 聴講した発表に関しては、「 クレジットカードの不正を防止する技術 」という発表が特に印象に残りました。こちらは普段扱う領域に少なからず関連する技術ですが、直接的に扱う機会が少なかったため、非常に学びになる内容でした。また、今後これらの技術を考えるうえでの後押しにもなったと感じています。 全体を通して、この度のカンファレンスは遠方かつトークの内容もさまざまなトピックがあったことから自分にとって新鮮な経験となりました。 (myuto) 普段は機械学習まわりの開発をしていますが、分野関係なくいろんなバックグラウンドの人が集まるイベントなので、終始ゆるく楽しく過ごせました。技術系のカンファレンスって専門領域に寄りがちな印象があったけど、YAPCは良い意味で雑多で、それがすごく心地よかったです。 最新の生成AIの動向に対しても、いかにAIを使った上で開発や制作をうまく回すかといった見解や価値観もいろいろ発表や懇親会を通して伺うことができたのが楽しかったです。 イベント全体の雰囲気が明るくて、参加者同士で気軽に話せるのが良かったです。普段あまり接点のない人たちとも技術の話で盛り上がれるのは、やっぱり現地イベントならではですね。スポンサーエリアも盛り上がっていて、各社の工夫が感じられて見て回るだけで楽しかったです。 まとめ YAPC は コミュニティ × 技術 × 熱量 が心地よく交わる、独特の魅力を持つイベントです。 福岡開催ということもあり、主催者の気合いの入り方も違いましたね。 運営・スポンサー・スピーカー・参加者のみなさん、本当にありがとうございました! 次回の YAPC も楽しみにしています! 明日の記事はgoccyさんです。引き続きお楽しみください。
アバター
こんにちは。メルカリのフロントエンドエンジニアの @mattsuu です。 この記事は、 Mercari Advent Calendar 2025 の5日目の記事です。 はじめに メルカリ ハロ Webフロントエンドの開発スピードと品質両立の取り組み という記事を公開してから約1年が経過しました。 前回の記事では、Next.js、Apollo Client、Tailwind CSS、そして monorepo 構成を採用し、MSW を使用したモック駆動開発などの技術スタックと開発手法について紹介しています。 この1年間、私たちは初回の技術選定を基盤としながら、パッケージ管理、テスト戦略、開発体験の3つの領域に焦点を当てて継続的な改善を進めてきました。 本記事ではそれらの取り組みと効果について紹介します。 パッケージ管理の改善 monorepo 環境での依存関係管理とセキュリティ対策として、pnpm catalog による一元管理、minimumReleaseAge によるサプライチェーン攻撃対策、Knip による未使用パッケージの検出を導入しました。これらにより、依存関係の更新作業の効率化とセキュリティリスクの低減を実現しています。 pnpm catalog による一元管理 monorepo 環境では、同じライブラリの異なるバージョンを管理する場合があります。バージョンによって I/F が異なったり、セキュリティ update の対応が複数箇所で必要になるなど、対応コストが発生します。これを防ぐために各ライブラリで単一のバージョンを利用する方針を採用しました。 pnpm の catalog 機能を使うことで、依存関係のバージョンを一箇所で管理することができます。 # pnpm-workspace.yaml catalog: '@apollo/client': 3.13.4 'next': 15.3.2 'react': 19.0.0 # ... その他の依存関係 pnpm-workspace.yaml にカタログを定義し、各パッケージの package.json で catalog: を参照する運用により、同一ライブラリのバージョン分散を防いでいます。 { "dependencies": { "@apollo/client": "catalog:", "next": "catalog:", "react": "catalog:" } } この仕組みにより、依存関係のバージョン更新は catalog 側を更新するだけで monorepo 全体へ反映されるため、個別のパッケージに対して複数 PR が発生する問題を解消できました。 導入手順については次の記事を参考にしました。 monorepo内でのパッケージのバージョンを1つだけに統一するOne Version Ruleをpnpm catalogで実装する Dependabot の活用 Dependabot を catalog と組み合わせることで、依存関係更新のワークフローが改善しました。Dependabot が catalog のバージョンを更新する PR を作成し、それをマージするだけで monorepo の依存関係を一括更新できます。 運用面では導入して間もなかったこともあり、ナレッジ共有も踏まえて週1で担当をローテーションしていました。CI で自動テストが通り、PR に対して E2E テスト手動でトリガーして通過したらマージするようにしています。major update など破壊的変更があるものは別途チケットを切って対応していました。 また、Dependabot の cooldown 機能 を利用して、既存ライブラリの更新に一定の待機期間を設けていました。これにより、公開直後の不具合やセキュリティリスクを回避しやすくなりました。 minimumReleaseAge によるセキュリティ対策 前述の Dependabot の cooldown 機能は既存のライブラリアップデートに対するセキュリティ対策ですが、手元で新規ライブラリのインストールやアップデートを行う際には pnpm の minimumReleaseAge が役立ちます。 # pnpm-workspace.yaml minimumReleaseAge: 10080 # 1 week in minutes minimumReleaseAgeExclude: - '@example/*' この設定により、公開されてから一定期間経過していないパッケージはインストールされません。 社内パッケージは minimumReleaseAgeExclude で除外し、開発の妨げにならないようにしています。また未導入ではありますが、pnpm の trustPolicy オプションを有効にすると、 サプライチェーン攻撃への対策 をさらに強化できます。 Knip による未使用パッケージの検出 プロジェクトが成長する中で、使われなくなった依存関係が残り続ける問題がありました。未使用のパッケージはバンドルサイズの増加やセキュリティリスクの原因になります。 Knip を導入することで、未使用のパッケージを自動検出できるようになりました。 CI では knip-reporter を利用していますが、現在は builtin の reporter が実装された ため、そちらを使うと良いかもしれません。また、Knip は未使用のファイルや、export しているがどこからも import されていない型や関数も検出できるため、コードベースを健全に保つ上で効果的です。 詳しくは次の記事が参考になります。 TypeScript/JavaScriptの不要なコードを削除するツール「Knip」の紹介 – ベースマキナ エンジニアブログ テスト基盤の整備 テスト戦略として、全画面への Playwright による Integration Test の導入、Component Test の最小化、VRT の活用を進めました。Code Coverage よりも Use Case Coverage を重視し、テストの種類を絞ることでメンテナンスコストを削減しながら、安定したテスト基盤を整えることができました。 Playwright を用いた Integration Test と VRT の導入 1年前の時点では Integration Test の基盤が十分に整っておらず、軽微な機能改修でも QA エンジニアに詳細な確認を依頼する必要がありました。また、機能自体に問題がなくとも UI のリグレッションが発生した際に気づきにくい体制でした。 この課題を解消するため、画面全体を対象とした Playwright による Integration Test を導入し、UI の差分検出には Playwright の VRT を活用しました。 Integration Test の方針は、メルペイでのテスト自動化方針を参考に次の考えをベースとしています。 基本的に、仕様書をベースにアプリケーションの振る舞いが期待通りかをテストします。実装の詳細は考慮しません。インテグレーションテストが仕様書と対応し、テストコードを見るとアプリケーション挙動がわかるように管理されると理想的です。 参考: メルペイフロントエンドのテスト自動化方針 仕様書とテストコードを完全に一致させることは難しいものの、基本方針として Code Coverage よりも Use Case Coverage を重視する姿勢で運用していました。 テスト構成 テストコードの大枠とトップレベルの describe について次のような構成にしています。 /** * Specs: * https://example.com/specs/feature-abc // ① */ test.beforeEach(async ({ page }) => { await mockLogin(page); await mockAllGraphQL(page); // ② }); // 画面のパス const url = '/sample-page'; test.describe('Rendering', () => { test('renders correctly', async ({ page }) => { await mockGraphQL( // ③ page, SampleQueryDocument, produce(mockSampleQueryResponse(), (draft) => { // ... 必要に応じてレスポンスを調整 }) ); await page.goto(url); // ④ // ... レンダリング確認 }); }); test.describe('Validation', () => {}); test.describe('Actions', () => {}); ① 仕様へのリンクを記述する ② mockAllGraphQL を利用してすべての API リクエストをモックする ③ 個別ケースは mockGraphQL でレスポンスを上書きする トップレベルの beforeEach 内で mockGraphQL を利用することは避け、対象のテストと近い場所で呼び出す 多少冗長でもモック内容とテスト内容の関連が明確になることを優先する ④ モック設定完了後に page.goto を実行し、テスト対象の画面に遷移する トップレベルの describe については次の3つに分類しています。 Rendering : レスポンスに基づいて情報を正しくレンダリングできるか Validation : 無効なリクエストが送信されるのを防げるか Actions : 利用者の操作に応じて、正しいリクエストを生成し、レスポンスを適切に処理できるか(更新、エラー、ページ遷移を含む) 記述ルールを明確にしたことで一貫した実装が可能になり、monorepo で複数メンバーがそれぞれのアプリケーションや機能を担当する場合でも、テストを書く負荷を減らすことができました。また、これらのルールを Claude.md にまとめておくことで、AI によるセルフレビューやテスト生成の精度も向上し、開発体験の改善につながりました。 Component Test 最小化戦略 テストの種類とバランスを考えたとき、私たちは次の方針を採用しました。 Integration Test (Playwright) : ブラウザ上で実際のユースケースを検証し、全画面をカバーする Component Test (React Testing Library) : DatePicker など複雑な UI コンポーネントのみに限定する Unit Test (Jest) : ロジック部分を対象にカバレッジ 80% 程度を維持する テストの種類を絞ることで、チームメンバーが学ぶべき範囲を限定し、テストの重複排除などメンテナンスを削減することができました。 Integration Test に寄せた構成では CI のパフォーマンスが気になるところですが、 Playwright の sharding で4並列にテストを実行させることで実行時間が10分程度に抑えられるようにしています。 また、Playwright にはテスト計画生成・テスト生成・自動修復を行う Test Agents が用意されており、将来的にテスト運用をより効率化できる可能性があります。QA チームが管理する E2E テストでも Playwright を採用していたため、チーム間でツールが統一されている点もメリットでした。 VRT (Visual Regression Testing) の活用 モバイル向け Web UI は事業者向け管理画面と比べて画面あたりの情報量が少なく、UI の変化を見つけやすいという特徴があります。また、UI 検知に必要なモックも少なくて済むため、実装やメンテナンスの負担を抑えられます。そのため、モバイル向け UI では Rendering のテストを VRT 中心にし、事業者向け管理画面では text や element の assertion を用いる構成にしていました。VRT には Playwright の screenshot 機能 を利用して全画面実装しました。 test('should render page correctly', { tag: '@vrt' }, async ({ page }) => { await mockGraphQL( page, ExampleDocument, produce(mockExampleResponse(), (draft) => { // UI の確認に使うデータは immer で上書きする draft.data.item.name = 'テストアイテム1'; }) ); await page.goto('/example'); await expect(page).toHaveScreenshot({ fullPage: true }); }); これまでは開発環境でデザイナーに UI を直接確認してもらう必要がありましたが、VRT の導入により特定条件の UI を Snapshot で比較・確認できるようになり、確認作業の工数を削減できました。 開発体験の向上 開発効率と品質を向上させるため、ESLint カスタムルールによる品質担保、Mock の自動生成、Claude Code Action によるセルフレビュー、Apollo MCP によるテストデータ作成の効率化といった取り組みを行いました。これらにより、レビュー負荷の軽減や開発スピードの向上につながりました。 ESLint カスタムルールによる品質担保 実装漏れが原因で QA 段階で不具合が見つかった場合には、修正と合わせて ESLint のカスタムルールを追加していました。チーム固有のルールをドキュメントや Claude.md / Agents.md にまとめる方法もありますが、静的解析によってルールを強制するほうがヒューマンエラーを防ぎやすく、品質担保に効果的です。 カスタムルールの追加には、次の記事が参考になります。 no-restricted-syntax でお手軽にカスタムルールを追加する – Zenn Ubie における ESLint 活用 – Zenn Mock 自動生成の仕組み GraphQL スキーマが更新されるたびに手動で Mock データを更新するのは負担が大きいため、 @graphql-tools/mock を利用した Mock データの自動生成を導入しました。スキーマが更新されたら、pnpm でスクリプトを実行するだけで Mock データを更新できます。 # GraphQL スキーマから型と Mock データを生成 pnpm generate:graphql GraphQL Code Generator のカスタムプラグインを作成し、 @graphql-tools/mock の addMocksToSchema を使って Mock データを生成しています。 import { addMocksToSchema } from '@graphql-tools/mock'; const mockedSchema = addMocksToSchema({ schema: graphqlSchema, mocks: { // 型に応じて初期値を設定 ID: () => 'mock-id-0001', String: () => 'Hello World', Int: () => 10, Boolean: () => true, Time: () => '2021-01-01T00:00:00.000Z', }, }); // スキーマに対してクエリを実行し、Mock データを生成 const result = executeSync({ schema: mockedSchema, document: queryDocument, }); この result を使って、次のように mock 関数を自動生成します: const mockFn = ` export function mockExampleResponse(): ExecutionResult<ExampleQuery> { return ${JSON.stringify(result, null, 2)}; } `; この仕組みにより、GraphQL スキーマから適切な Mock データが自動生成されるため、手動で値の整合性を確認する手間が大きく削減されました。 また、特定のケースを再現したい場合は、immer を使って必要な部分のみ上書きできます。 import { produce } from 'immer'; import { mockExampleResponse } from 'example-mockdata/base/gen/query'; const customMock = produce(mockExampleResponse(), (draft) => { // 特定の条件を再現するためにデータを上書き draft.data.items[0].status = 'active'; draft.data.items[0].count = 10; }); このように、意図的に上書きした箇所がそのまま Storybook やテストで確認したい条件として明確に残るため、Mock の管理や UI・テスト確認がシンプルになりました。 Claude Code Action によるセルフレビュー claude-code-action を導入することで、PR レビューを依頼する前の draft の段階で実装者自身が抜け漏れに気づけるようになりました。レビュワーが指摘する前に基本的なミスを修正できるため、レビュワーの負担削減や品質の向上につながりました。 Apollo MCP によるテストデータ作成の効率化 ローカルで開発する際は、API が未実装の場合を除き、基本的にバックエンドと実際に疎通しながら進めていました。モックで開発する手もありますが、実際の API と通信するほうが挙動の再現性や信頼性が高いためです。一方で、特定の条件を再現するためのテストデータ作成には課題がありました。開発環境の管理画面を操作したり、場合によっては DB を直接触る必要があり、準備に手間がかかりがちでした。 そこで Apollo MCP を導入し、テストデータを口語ベースで簡単に作成できるようにしました。Apollo MCP は GraphQL のスキーマやクエリを操作して実行できるため、次のようなことが可能になります。 「今日の18:00 ~ 20:00 で時給1200円の募集を北海道で作って」のような指示をすると、該当する mutation を実行しテストデータを作成 GraphQL スキーマを自動で読み取り、どんなクエリや mutation が使えるかをで一覧・検索 特定のクエリに必要な引数を口語形式で検索 これにより、ローカルやテスト環境でのデータ準備が大幅に効率化され、手動テストの工数削減や開発スピードの向上につながりました。 おわりに この1年間、日々の開発の中で出てきた課題に向き合いながら、少しずつ仕組みを整えてきました。All for One のカルチャーのもと、メンバーがそれぞれの専門性を活かしつつ、知見を共有しながら開発できたことが改善を進める上で良い循環になっていたと思います。本記事で紹介した取り組みが参考になれば幸いです。 明日の記事は sathiya さんです。引き続きお楽しみください。
アバター
はじめに こんにちは。 @Sakamoto です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の4日目の記事です。 2025年9月からメルコインでフロントエンドのインターンを始め、12月初めでちょうど3か月になりました。期間中はメルコインに加え、メルカリNFTの開発にも参加しました。 本記事では、インターン期間中に取り組んだタスクを振り返り、そこで得た学びや気づきをまとめます。 チームについて 今回のインターンではメルコインとメルカリNFTでフロントエンドエンジニアとして参加しました。それぞれ扱うプロダクトの特性が大きく異なるため、求められる視点や進め方にも違いがありました。 メルコイン 暗号資産交換業としてガバナンスやリーガルと関わりが深く、単に機能を実装するだけでなく、背景となる法律や制約を理解したうえで開発を進める必要があります。 メルコインのフロントエンドチームはお客さま対応のための社内ツールのフロントエンド開発、LP(ランディングページ) の作成などを担当しています。 メルカリNFT NFTの売買を可能にするプロダクトを持つチームで、2025年1月に提供を開始し、現在も毎週のように新機能が追加されています。 そのため、よりスピード感のある開発が求められる環境でした。 メルカリNFTのフロントエンドチームは主にメルカリNFTのフロントエンド開発を担当しています。 取り組み概要 メルコイン 社内ツールのフロントエンド改修 メルカリNFT 買取機能の実装・改善 メルカリNFTのくじをメルカリアプリ内から購入できるようにするための対応 リリース後のメモリ監視 その他 Web3領域のドメイン知識の習得 社内の多職種の方とのコミュニケーション エンジニア業務でのAI活用 ここでは太字の6つについてまとめようと思います。 社内ツールのフロントエンドの改修 背景 メルコインで進行しているプロジェクトの一部として、口座の状況、暗号通貨の配信価格など、お客さまからのお問い合わせに必要な情報を確認できる社内ツールの改修を担当し、フロントエンドの仕様整理から実装、QAチームとのテストケースの調整まで、一連の開発プロセスに関わりました。 やったこと 社内ツールの改修にあたって、 表記揺れがないか バリデーションに過不足はないか エラーハンドリングが適切か UIの整合性があるか API連携が正しいか 変更に柔軟に対応できる設計か といったポイントを確認し、後工程での手戻りが起きにくい状態まで仕様書のレビューを行いました。 その上で自分の実装スピードや改修範囲を考慮し、適切なバッファを含めて工数見積もりを行いました。 実装では、各マイクロサービス間の通信にはgRPCが使われているため、Protocol Buffersの定義からモックを作り、UIとの接続を進めると同時にCypressでのE2Eテストも作成・改修に取り組むことができました。 またQAチームとはテストケースや指摘内容を確認しながら、仕様と実装の齟齬をなくすことに努めました。 学び gRPCやProtocol Buffers、Cypressといったこれまで触れる機会の少なかった技術に取り組んだことで、フロントエンド以外の領域にも一部理解が広がりました。 あわせて、仕様書の精度をどう高めるか、どの程度のバッファを見込んで工数を組み立てるか、レビューされやすいコードにするにはどんな書き方がよいかなど、プロダクト開発全体を俯瞰する視点も身についたと感じています。 買取機能の実装・改善 背景 メルカリNFT内のGMV(流通取引総額)を伸ばすための施策として、NFTくじを購入したお客さまが買取依頼を出せる機能の追加をしました。 それに伴い、商品詳細ページに新しいUIや状態管理が必要となり、この部分の改修を担当しました。 また、このタスクがメルカリNFTのコードを触る最初の機能開発だったため、既存コードや周辺仕様のキャッチアップを素早く進める必要もありました。 やったこと 仕様書を確認し、商品詳細ページのどのタイミングで買取ボタンを表示するか、どの状態をどう見せるかといったUIの出し分けを整理しました。 その上で、実装した内容の品質を担保するためにVRT(Visual Regression Test)を活用し、Playwrightを用いたインテグレーションテストも追加・修正しました。 学び Playwrightを使ったインテグレーションテストやVRTの活用など、これまで触れる機会のなかったテスト手法を実際に使いながら理解を深められました。 また、買取前後でUIが複雑に変化する仕様だったこともあり、コンポーネントの責務をどう分けるか、保守性と可読性をどこまで両立できるか、といった設計面の学びも多かったです。 スピードが求められる環境の中でも、既存実装の調査や理解を並行して進めることの重要性を実感しました。 リリース後のメモリ監視 背景 メルカリNFTでは、サービスを安定して提供し続けるために、リリース後のリソース状況の監視や挙動の確認を継続的に行っています。 特にメモリ使用量は、トラフィックの増減や特定機能の利用状況によって変動しやすいため、定期的に観測し、必要に応じて挙動を調査する運用を行っています。 今回の取り組みでは、本番環境のメモリ使用状況をより細かく把握し、どのタイミングでどう増減しているのかを整理しながら、今後の安定した運用につなげるための調査を進めています。 やったこと まずは「いつ・どんな状況でメモリが増えるのか」を把握するために、各Podのメトリクスを確認し、傾向を調査しました。 その上で、なぜそのような増加が起こるのか仮説を立て、順に検証を進めました。 Datadogからログやメトリクスを細かく確認しつつ、他チームにも相談し、Node.jsやNext.jsの内部挙動、キャッシュやSSR周りの可能性など、さまざまな観点から情報を集めることで検証の方向性を調整しています。 学び 日々の調査の中で得られる学びは非常に多いと感じています。 Node.jsやNext.jsが内部でどのようにメモリを使うのか、CPU・GCの仕組み、キャッシュの扱いなど、技術的な理解が大きく深まりました。 同時に、進行中の問題を扱う際のコミュニケーションの取り方や調査内容のまとめ方、仮説の立て方と検証の進め方、進捗が見えにくいタスクをどうやって周囲と共有するかなど、機能開発とは異なるスキルも求められることを実感しています。 Web3領域のドメイン知識の習得 メルコインとメルカリNFTの両方に関わる中で、Web3に関する基礎知識を継続的に身につけることを意識してきました。 暗号資産やNFTの基本概念をはじめ、法規制やコンプライアンス、チェーンの仕組み、トランザクションの流れ、ウォレットの挙動など、業務に必要な知識は社内ドキュメントとして多く整理されています。 日々の開発と並行して、それらの資料を自分から探して読み、背景となるドメイン理解を深めるようにしてきました。 Web3は学ぶ範囲も広く、継続して取り組みたい領域です。 社内の多職種の方とのコミュニケーション もう一つ意識的に取り組んでいたのが、社内の多職種の方とのコミュニケーションです。 プロダクト開発では、さまざまな職種と連携しながら仕様の確定やリリースに向けた調整を進める必要があります。 そのため、日々のやり取りだけでなく、会社の施策として実施されているチービルやメンターランチの機会を活用しつつ、自分からも声をかけて1on1やランチの機会をつくるようにしていました。 チーム全体が話しかけやすい雰囲気を持っていたこともあり、積極的にコミュニケーションを取ることができました。 また、普段どのように技術をキャッチアップしているのか、どんな観点で仕様を見ているのか、なぜ今のキャリアを選んだのかといった話を聞くことで、自分の知らなかった考え方や知識に触れる機会も多くありました。 開発スキルだけでは得られない学びが多く、視野を広げるきっかけになったと感じています。 エンジニア業務でのAI活用 CursorなどのAIツールも積極的に活用し、コード全体の構造を素早く把握したり、関連部分の洗い出しを効率よく進めるようにしていました。 開発・実装では、メンターの方からのアドバイスも参考にしながら、どの情報を渡すと意図が正確に伝わるかといった点を意識するようにしました。 仕様を明確にまとめてコンテキストとしてAIに渡すことで、実装の方向性がぶれにくくなり、提案されるコードの品質も安定するようになりました。 また、AIから返ってくるコードはそのまま使うのではなく、自分の考えとの違いを確認したり、なぜその書き方になるのかを読み解くことで理解を深めるきっかけにもなりました。 結果として、実装を効率化するだけでなく、コードの設計方針を比較しながら学べる良い教材のような存在にもなっていたと思います。 まとめ 3か月を通して、メルコインとメルカリNFTのそれぞれ異なる特徴を持つプロダクトの開発に携わり、多くの学びを得ることができました。 機能実装だけでなく、仕様の詰め方、テストの考え方、多職種とのコミュニケーションなど、エンジニアとして必要なスキルを幅広く経験できた期間だったと感じています。 残りの1か月も、これまでの学びを活かしながら、より深い理解と確かなアウトプットを積み重ねていきたいと思います。 明日の記事は @Fabさんです。引き続きお楽しみください。
アバター
はじめに こんにちは。メルペイのGrowth PlatformでML Engineerをしている @hokao です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の3日目の記事です。 背景 Growth Platformでは、新たにGrowth MLというMLチームを立ち上げました。私は主にMLOpsを担当しつつ、チームの生産性向上に向けた基盤づくりにも取り組んでいます。 Growth MLチームでは、 Kubeflow Pipelines(KFP) でMLバッチ処理を実装し、 Vertex AI Pipelines 上で運用しています。KFPは、Kubernetes上でコンテナを使ってMLワークフローを構築・実行するためのプラットフォームであり、Vertex AI PipelinesはそれらをGoogle Cloud上でサーバーレスに運用・管理できるサービスです。KFPには Python SDK が用意されており、パイプラインをPythonコードとして記述できます。 KFPはPythonでMLパイプラインを実装できる一方で、コンポーネントの設計・実装の自由度が高く、適切に使いこなすのは容易ではありません。そこで、メンテナンス性と可読性を高め、チームの開発生産性を向上させるために、KFPを用いた実装パターンを整備しました。本稿では、その概要とポイントを紹介します。 KFPのコンポーネント実装パターン コンポーネントはパイプラインを構築する基本的な構成要素です。執筆時点では、コンポーネントの実装方法として次の3パターンが提供されています。 Lightweight Python Components Containerized Python Components Container Components Lightweight Python Components Lightweight Python Componentsは最も手早く実装できますが、関数スコープに完全に閉じた実装が求められるという制約があります。実装例は次のとおりです。 from kfp import dsl @dsl.component( base_image='python:3.14', packages_to_install=['numpy==2.3.0'], ) def sin(val: float = 3.14) -> float: import numpy as np return np.sin(val).item() packages_to_install で指定したライブラリはパイプライン実行時にインストールされ、その後に関数のコードが実行されます。依存ライブラリは関数ごとに管理する必要があり、MLバッチ全体で用いる特定ライブラリのバージョンを統一することが困難です。また、ライブラリのインポートや使用するシンボルの定義を関数内に閉じ込める必要があるため、関数の責務が容易に肥大化し、単体テストの記述も難しくなります。さらに、KFPが担うコンポーネント定義などのパイプラインのオーケストレーションと、ドメイン固有のビジネスロジックが密結合になりやすく、コードの見通しも損なわれます。これらの特性から、プロダクション用途には向きません。 Containerized Python Components Containerized Python Componentsは前述のLightweight Python Componentsに近い書き方ですが、関数スコープに閉じるという制約が一部緩和されます。コードと依存関係を含むコンテナイメージを事前にビルドすることで、関数外で定義したシンボルも参照できます。 しかし、関数ごとに依存ライブラリを定義する必要がある点は同じです。さらに、KFPのCLIが特定のディレクトリ構造に依存して自動生成する runtime-requirements.txt と Dockerfile を前提とするため、柔軟性に欠けます。したがって、これらの制約を踏まえた上で利用する必要があります。 Container Components 3つ目のContainer Componentsは、 docker run のようにイメージ、コマンド、引数を指定してコンポーネントを定義する方式です。 from kfp import dsl @dsl.container_component def say_hello(name: str) -> dsl.ContainerSpec: return dsl.ContainerSpec( image='gcr.io/my-project/my-base-image:latest', command=['python', 'main.py'], args=['--name', name], ) パイプラインで用いるコードとライブラリを含んだイメージを用意する必要がありますが、依存関係の管理がはるかに容易になります。実行時にライブラリをインストールする必要もありません。また、実行するPythonスクリプトはKFP特有の制約を受けない通常のスクリプトとして実装できるため、テストも書きやすくなります。 こうした理由から、Growth MLチームでは原則としてContainer Componentsでコンポーネントを実装することとしました。 Container ComponentsでのCLI実装とその課題 Container Componentsで実行コマンドを指定するため、処理ロジックはPythonのCLIとして実装するのが自然です。CLIコマンドは docker run のように実行されるため、引数としてリストや辞書などの複雑な引数を渡すときは注意が必要です。 たとえば、次のようなパイプラインを用意したとします。実行するコンポーネントは1つだけで、CLI引数としてPythonのリストを渡します。 from kfp import dsl, local @dsl.container_component def argparse_component( list_var: list, # KFP does not support the `list[str]` type annotation. ) -> dsl.ContainerSpec: return dsl.ContainerSpec( image='kfp-demo:latest', command=['python', '-m', 'cli.argparse_cli'], args=['--list-var', list_var], ) @dsl.pipeline def pipeline() -> None: argparse_component(list_var=['a', 'b', 'c']) def main() -> None: # Using local Docker runner for demonstration local.init(runner=local.DockerRunner()) pipeline() if __name__ == '__main__': main() 実行するCLIは次のとおりです。標準ライブラリの argparse で実装しており、リストを受け取り、 print 関数でそのまま出力するだけのシンプルなものです。 import argparse def main() -> None: parser = argparse.ArgumentParser() parser.add_argument('--list-var', nargs='+', required=True) args = parser.parse_args() print(args.list_var) if __name__ == '__main__': main() CLI引数として渡しているのは ['a', 'b', 'c'] なので、 print の結果も ['a', 'b', 'c'] となってほしいところです。しかし実際には、 ['["a", "b", "c"]'] と出力されます。 Container Componentsはほぼ docker run と同じ仕組みで動作するため、引数を受け渡す際に変数の値がシリアライズ(文字列化)されます。そのため、実際の docker run コマンドで渡される引数は文字列 '["a", "b", "c"]' になります。また、先ほどの argparse の実装では、スペース区切りで渡された複数の値をリストとして扱う仕様になっています。結果として、この文字列全体が1要素として扱われ、 ['["a", "b", "c"]'] というリストになってしまいます。この挙動は、 Click や Typer など他のCLIライブラリでも同様です。 この問題を単純に解決しようとすると、ひとまず引数を文字列( str )として受け取り、CLI側でデシリアライズするという実装が考えられます。ただ、それだと本質ではない処理が増えてコードの見通しが悪くなり、なにより型の安全性が担保されません。 引数が int や bool だけで済むならシンプルで理想的ですが、Pythonだけでパイプラインを書く以上、 list や dict といった構造化データを引数にしたいケースはどうしても発生します。 Pydantic Settingsで型安全かつシンプルにCLI引数を扱う いくつかのアプローチを検討・試行した結果、 Pydantic Settings を使う方法が、これらの課題を最もシンプルに解決できることが分かりました。 Pydantic Settingsは、 Pydantic の型定義を使って環境変数や .env などから設定を型安全に読み込めるライブラリです。さらに、 SettingsConfigDict(cli_parse_args=True) を有効にすればCLI引数もPydanticモデルでパースできます。 先ほど argparse で実装したCLIをPydantic Settingsで書き直すと次のようになります。 from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(cli_parse_args=True) list_var: list[str] = Field(alias='list-var') def main() -> None: settings = Settings() print(settings.list_var) if __name__ == '__main__': main() Pydanticは入力値を指定した型にパースし、その出力がその型に一致することを保証します。Pydantic SettingsでCLI引数をパースする場合、 list や dict などはJSON形式をサポートしているため、KFPのContainer ComponentsでシリアライズされたCLI引数(JSON文字列)も適切にパースしてくれます。その結果、わざわざデシリアライズ処理を書く必要がなく、型安全性も自然に確保されます。 複雑な構造体もPydantic Settingsで型安全に扱う argparse など標準的なCLIライブラリでは dict などをそのまま引数型として扱えませんが、Pydantic SettingsならJSON文字列をモデルにマッピングできるので、簡単かつ型安全に扱えます。 ここでは少し応用的な例として、KFPの dsl.PipelineTaskFinalStatus を使って実行タスクの最終状態を受け取る方法を紹介します。使い方はシンプルで、コンポーネントに PipelineTaskFinalStatus の型アノテーションを付けた引数を追加するだけです。変数に自動的に値が代入されるようになっているため、コンポーネントの関数を呼び出す際にこの引数を指定する必要はありません。公式ドキュメントにはContainer Componentsでの具体例はありませんが、Pydantic Settingsを使えば問題なく扱えます。 from kfp import dsl @dsl.container_component def pipeline_task_final_status_component( base_image: str, pipeline_task_final_status: dsl.PipelineTaskFinalStatus, ) -> dsl.ContainerSpec: return dsl.ContainerSpec( # The `image` argument must be cast to string. # ref: https://github.com/kubeflow/pipelines/issues/4433#issuecomment-2959874538 image=str(base_image), command=[ 'python', '-m', 'cli.pydantic_settings_cli', ], args=[ '--pipeline-task-final-status', pipeline_task_final_status, ], ) PipelineTaskFinalStatus の変数をCLIの引数に指定すると、他の値と同様にJSON文字列としてシリアライズされてコンテナに渡されます。シリアライズ後の実体は単なるJSON文字列なので、対応するPydanticのモデルを定義しておけば、CLI側でそのまま型付きオブジェクトとして安全に扱うことができます。 from typing import Literal from pydantic import BaseModel, Field from pydantic_settings import BaseSettings, SettingsConfigDict class Error(BaseModel): code: int | None = None message: str | None = None class PipelineTaskFinalStatus(BaseModel): error: Error pipelineJobResourceName: str pipelineTaskName: str state: Literal['SUCCEEDED', 'FAILED', 'CANCELLED'] class Settings(BaseSettings): model_config = SettingsConfigDict(cli_parse_args=True) pipeline_task_final_status: PipelineTaskFinalStatus = Field( alias='pipeline-task-final-status', description='Pipeline task execution status (JSON format)', ) def main() -> None: settings = Settings() print(settings.pipeline_task_final_status) if __name__ == '__main__': main() 想定と異なる形式のデータが渡された場合、Pydanticは入力時点で検証し即座にバリデーションエラーを返します。そのため、ネストした dict や list などのやや複雑な構造のデータでも、Pydantic Settingsを使えば型に沿って安全に扱えます。 Pydantic Settingsを併用することで、KFPのContainer Componentsの開発体験が大きく向上します。 おわりに 本稿では、KFPのコンポーネント実装パターンを整理し、Container ComponentsとPydantic Settingsを組み合わせることで、開発者がより簡単かつ型安全にパイプラインを構築できるアプローチを紹介しました。 他にも、パイプラインの実行スケジュールを宣言的に記述し、 terraform apply のように差分を確認・適用できる仕組みなど、運用を支えるさまざまな取り組みをしています。これらについても、また別の機会に紹介できればと思います。 今回紹介した内容が、KFPを使った開発における設計で悩んでいる方の参考になれば幸いです。 明日の記事は @Sakamoto さんです。引き続きお楽しみください。
アバター
こんにちは。メルカリのAI Securityエンジニアの @hi120ki です。 この記事は、 Mercari Advent Calendar 2025 の2日目の記事です。 メルカリでは社内でのAI/LLM活用の拡大を目指し、様々な取り組みが行われています。これらの動きを後押しするために AI Securityチーム はLLM APIへのアクセスを安全でありながら便利に提供するためのサービスLLM Key Serverを開発しました。 これによりLLM APIへのアクセス申請を受け担当者が手作業でユーザーを登録していたプロセスがLLM Key Serverによって置き換えられ、LLM APIユーザーは申請の手間なく自身の社内アカウント経由で有効期限付きのLLM APIキーを取得できるようになりました。 また、GitHub ActionsやGoogle Apps Script上でLLM APIを利用するための共通テンプレートも提供し、ローカル環境のみならずCIやクラウドやノーコードツール等の複数のサービス上でのLLM利用促進にも貢献しました。 本記事ではLLM APIにおけるセキュリティ課題・プロセスの改善・LLM Key Serverのアーキテクチャ・実装におけるポイントについて解説します。 LLM APIにおけるセキュリティ課題 現在様々なLLMが各社から展開されており、メルカリでは複数のLLMをタスクごとの要求や従業員の好みごとに使い分けています。しかし、LLMへのアクセスを提供するAPIには多くの場合APIキーが必要となっています。 主要なLLMベンダーが提供するAPIへのアクセスに使われるAPIキーは、有効期限がなく、流出しそれが検知されなかった場合、長い期間にわたって情報漏洩・経済的損失を受ける恐れがあります。さらに現在のAI/LLM活用の流れの中で、多くのAPIキーが発行されており、管理があいまいになってしまうことも懸念されています。また、複数社のLLMを利用する中でユーザー・チーム・権限管理が複雑になることが想定されます。このような複雑な管理体制は定期的なアクセス権の監査を難しくします。 もちろんAPIキーを用いずWorkload Identityおよびクラウド間連携を用いてGoogle CloudやAzure経由でLLM APIにアクセスする方法が最も安全であり、推奨される方法として社内で提示しています。しかし一方で、設定の複雑さや、様々な外部のAI/LLMプロダクトがそれらをサポートせずリリースされることもあり、多くのLLM関連ツールを評価している状況下では別の方法が求められていました。 また追加の要件として、セキュリティポリシーによって煩雑なフローを強制することはかえってポリシーの逸脱を助長しかねないため、安全性を保ちながら利便性も追求することが必要でした。 安全かつ便利なLLM APIへのアクセス提供 安全かつ便利なLLM APIへのアクセス手段を提供するため、複数のモデルを一つのAPIから利用可能にするOSSの LiteLLM と、Google WorkspaceおよびGoogle Cloudの OIDC IDトークン発行機能 を用いることにしました。 LiteLLMは様々なモデルプロバイダーから提供されるLLMを一つのAPIから利用可能にするOSSで、基本的なLLM API呼び出しの他にClaude CodeなどのCoding Agentツールにも対応しています。 また、OIDC IDトークン発行機能はGoogleのOAuthやサービスアカウントの権限を利用することでGoogleによって署名されたIDトークンを取得できるというもので、これを利用することでユーザーの身元を確認することができます。 メルカリでは Google CloudからGitHubへのアクセスを有効期限が短い認証情報へ切り替えるためのToken Server を運用していますが、LLM Key ServerはこのアーキテクチャをベースにLLMへのアクセスへと発展させたものになります。 LLM Key Serverのアーキテクチャ LLM Key Serverの認証フローは以下のようになっています。 LLM Key Serverの認証フロー まず、Claude Codeを利用したいユーザーやLLMを利用したいアプリケーションは、GoogleのAPIから自身を証明するためのOIDC IDトークンを取得します。ここではGoogle Workspaceアカウントによる認証や、Compute metadataサーバーからのサービスアカウント認証などが利用されます。 次に、取得したOIDC IDトークンをLLM Key Serverに送信すると、LLM Key Serverはトークンの署名を検証し、トークン内の情報をもとにLiteLLMへアクセスするための一時的なAPIキーを発行します。このAPIキーは有効期限が短く設定されており、ユーザーはこのキーを用いてLiteLLM経由で様々なLLMへアクセスすることができます。 ローカル環境での利用においてGoogle Workspaceアカウントによる認証を利用する際は、社内公開のCLIツールを利用することで1つのコマンドでOAuth認可フローを開始し、OIDC IDトークンの取得からLLM APIキーの取得までを行うことができます。 またAPIキーはサービスアカウントに対しては1時間という短い有効期限が設定されています。しかし、クラウド上で動作しLLMを利用するアプリケーションが長時間動作することを想定し、自動的にキーを更新する仕組みも提供しています。これはGo言語のライブラリとして整備されており、自動的にキーの更新を行い、継続してLLM APIを利用することができます。 このようにしてGoogle Workspaceアカウントによる認証やGoogle Cloud上のサービスアカウント認証を利用することで、安全にLLM APIへのアクセスを提供しつつ、有効期限付きのキーを発行することで情報漏洩リスクを低減し、さらに自動更新ライブラリを提供することで利便性も確保しました。 LLM Key Serverの利用形態の拡張 LLM Key Serverはローカル環境やクラウド上で動作するアプリケーションの利用だけでなく、様々な社内ツールやサービス上での利用も想定しています。特に以下の2つの形態での利用をサポートしています。 GitHub Actions GitHub Actions上でLLM APIを利用するための共通テンプレートを提供しています。GitHub Actions上では GitHubから提供されるOIDC IDトークン を用いてLLM Key ServerからLLM APIキーを取得し、LiteLLM経由で様々なLLMへアクセスすることができます。これにより、CI/CDパイプライン上でのLLM活用が促進されており、Claude Codeを用いたコードレビューが自動化されたりしています。 - name: Get LiteLLM Key id: litellm uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: script: | const oidc_request_token = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN; const oidc_request_url = process.env.ACTIONS_ID_TOKEN_REQUEST_URL; const oidc_resp = await fetch(`${oidc_request_url}&audience=https://key-server.example.com`, { headers: {Authorization: `bearer ${oidc_request_token}`}, }); const oidc_token = (await oidc_resp.json()).value; if (!oidc_token) { core.setFailed('Failed to retrieve OIDC token from GitHub Actions'); } const res = await fetch('https://key-server.example.com/llm-key', { method: 'GET', headers: { 'Authorization': `Bearer ${oidc_token}`, 'Content-Type': 'application/json', } }); if (res.status !== 200) { core.setFailed(`LiteLLM API Error: HTTP ${res.status}`); } const body = await res.json(); core.setSecret(body.key); core.setOutput('token', body.key); このようなGitHub Actionsのactionを用意することで、開発者が直接LLM APIキーを管理することなく、CI/CDパイプライン上で安全にLLM APIを利用できるようになりました。 Google Apps Script Google Apps Script上でもLLM APIを利用するための共通テンプレートを提供しています。Google Apps Script上では OAuth scope設定 を用いてユーザーの認証を行い、OIDC IDトークンを取得することができます。 Google Apps Scriptの設定ページから appsscript.json ファイルを表示するようにした後、以下のようにOAuth scopeを追加します。 "oauthScopes": [ "openid", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/script.external_request" ], その後、Google Apps Script上で以下のようにOIDC IDトークンを取得し、LLM Key ServerからLLM APIキーを取得し、LiteLLM経由で様々なLLMへアクセスすることができます。 function getLLMToken() { try { const cache = CacheService.getUserCache(); const cacheKey = "llm_token"; const cachedToken = cache.get(cacheKey); if (cachedToken) { return cachedToken; } console.log("[+] Fetching new LLM token"); const token = ScriptApp.getIdentityToken(); const options = { method: "GET", headers: { Authorization: "Bearer " + token, }, }; const response = UrlFetchApp.fetch( "https://key-server.example.com/llm-key", options, ); const statusCode = response.getResponseCode(); if (statusCode !== 200) { throw new Error( `HTTP request failed with status ${statusCode}: ${response.getContentText()}`, ); } const responseText = response.getContentText(); const responseData = JSON.parse(responseText); if (!responseData.key) { throw new Error("Key not found in response"); } cache.put(cacheKey, responseData.key, 50 * 60); // Cache for 50 minutes return responseData.key; } catch (e) { console.error("Error getting LLM token: " + e.toString()); return null; } } OIDCのIDトークンを検証する際には、ユーザーのメールアドレスだけでなく、Apps Scriptのバックエンドとして動作しているGoogle Cloudプロジェクトが、Google Cloud組織内の system-gsuite/apps-script フォルダに所属しているかどうかも確認しています。 これにより、信頼できるスクリプトからのアクセスのみを許可するようにしています。 これによりノーコードツール上での平文でのLLM APIキーの管理を避け、安全にLLM APIを利用することができます。 この仕組みにより社内でのLLM活用が促進されており、社内ドキュメントの要約や翻訳などに利用されています。 さいごに 組織としてLLM APIキーを安全に扱うための仕組みとしてLLM Key Serverを開発しました。これにより安全性と利便性の両立を実現し、社内でのLLM活用をポジティブにSecurityチームから促進することができました。今後もAI Securityチームは安全で便利なAI/LLM活用のための取り組みを続けていきます。 このようなメルカリでのAI/LLM活用やセキュリティに関する取り組みに興味がある方は、ぜひ メルカリの採用ページ をご覧ください。 明日の記事は@Jazzさんです。引き続きお楽しみください。
アバター
こんにちは。メルペイ VPoE室マネージャーの @nnaakkaaii です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の2日目の記事です。 はじめに この記事では、メルカリで2025年7月からスタートした取り組みであるProject Double(以下、pj-double)についてご紹介します。 「AIと協業する開発体験」「開発プロセスの再設計」「AI‑Nativeな組織づくり」に関心のあるエンジニア・PM・マネージャーの方にとって何かしらの参考になればと思い、本記事を執筆しました。 pj-doubleは、プロダクト開発における生産性を2倍に向上させることをミッションに掲げ、開発体制・プロセスをAI-Nativeに再設計する挑戦です。しかしその背景には、私たちがAIの導入を試みる中で経験した数々の試行錯誤と、そこから得た気づきがありました。 まずは、その前段として社内におけるAI活用の始まりと、初期フェーズでの課題から振り返ってみたいと思います。 2025年初頭: Vibe Codingの台頭と課題 2025年初頭は、AIを活用したコーディング手法の黎明期でした。社内でもさまざまなAIツールの活用、MCPサーバーの整備などがボトムアップに推進されました。それぞれの開発者が多種多様な方法論を模索する、いわゆる “Divergence” のフェーズでした。 このAI活用の初期段階において、私たちは大きな可能性を確認しました。設計・実装・テスト環境上の動作確認までを全てAIで行い、開発工数の見積もりに対して5分の1まで効率化を実現した事例も生まれました。 このようにAIを活用した開発手法が、一部の開発者の生産性を格段に向上した一方で、AIを使いこなしている開発者とそうでない開発者の間の溝も顕在化し始めていました。当時のAIを活用した開発手法の模索は、個人ごとに異なる個別最適化の域を出ず、組織全体としての生産性を向上するための組織的ムーブメントには至っていないという課題がありました。 実際に、こうした開発手法に再現性を与え、組織全体で共有知化するための試みもいくつか立ち上がったものの、局所的なプラクティスの共有・CLAUDE.mdやCursor Rulesの共有知化などに留まっていました。その理由として、当時主流であったVibe Codingに構造的な要因があったと振り返っています。Vibe Codingは、開発の状態に合わせてAIに都度指示を送って、その出力を都度確認しながら方向修正を行い、AIと並走しながら実装の完了まで導くような開発手法です。この同期的でインタラクティブなプロセスの性質上、開発者が直感的・経験的に行なっている状況判断の質が生産性を大きく左右します。このように、個々の状況判断はAIとの対話ログに閉じてしまって透明性が低く、また場面ごとの判断根拠を事後的に説明することも難しいため再利用性も低かったことが、再現可能な共有知に昇華する上での決定的な課題となっていました。 またベイエリアの新興企業を中心として、AIを前提とした開発プロセスや開発体制によって、数十人でユニコーン級のサービスを作る事例が知られるようになりました。このようなAI-Nativeな組織では、開発のプロセスも、そこにおける1人1人の役割や職能も、既存のそれらとは異なる形を取ると思います。社内においても、一部の開発者は「開発体験そのものが根底から変わる実感」を覚えていたものの、ボトムアップなAI-Native化の推進体制では、既存の開発プロセスや体制の上での局所最適なAI活用に留まってしまうという限界が見えていました。AIがない時代に最適化された開発プロセスや開発体制を、AI-Nativeな開発プロセスや開発体制へと変革することが、プロダクトデリバリーにおける持続的な競争力を有するための至上命題でした。 2025年7月: pj-doubleの発足 このような背景を受けて、AI-Nativeな開発プロセス・開発体制へのシフトを本格的に推進させるため、2025年の7月頃にメルペイのVP of Engineering Officeにて “pj-double” が発足されました。これは、さまざまな手法を模索して個人の個別最適化を達成する “Divergence” のフェーズから、全社として高生産性を再現度高く実現可能な開発プロセスのスタンダードを確立する “Convergence” のフェーズへの移行のスタートでした。私たちはこのスタンダードの確立により、社内のあらゆる知見やフィードバックがそこに集積され、持続的に組織として共有知を育てていけるような体制を実現することを目指しました。 時期を同じくして、技術環境のトレンドも変化しました。Prompt EngineeringからContext Engineeringへ、そしてVibe CodingからAgentic Codingへの変化です。Prompt Engineeringのもとでは「いかに良いプロンプトを与えるか」が重要でしたが、Context Engineeringのもとでは「いかに多くのデータソースと繋いで、いかに必要な一次情報を直接インプットするか」が重要視されるようになりました。また、同期的かつインタラクティブにAIを導く必要があったVibe Codingに対して、Agentic Codingでは、「最初に目的・ステップ・完了条件さえ与えれば、Agentが自律的にタスクを完遂するため、開発者はその結果のみを評価すれば良い」という非同期的かつスケーラブルな開発体験を可能にしました。 このようにAI活用は、個人のアートから、再現性のあるサイエンスへと変化し、pj-doubleが掲げる再現可能なAI-Native開発への挑戦を後押しする鍵となりました。 2025年7-9月: AIを活用した開発手法の効果の実証 pj-doubleではこの開発手法のスタンダードを確立することを、最初の3ヶ月の目標に置きました。メルペイやメルカリモバイルにおける30以上のバックエンド開発のプロジェクトと連携を開始し、社内で蓄積されたさまざまな開発手法に関する知見を集積することから始めました。 この連携を通して、「どのようなプロジェクトのどのような開発フェーズではどのようなAI活用が行われているのか」、「それらの手法には定性的にどのような利点・欠点があるのか」、「それらの手法が定量的にどのくらいの開発生産性向上の効果を出しているのか」、「それらの手法は他のプロジェクトにおいても同程度の効果を再現可能なのか」、などの観点について、各プロジェクトとの週例のミーティングを通して解像度高くトラックしていきました。 ここにおいて特に、開発生産性の測定のためにどのような指標を設定すべきかが議論に上がりました。 DX (メルカリで導入している開発者体験を可視化するツール)などで定量的に確認可能なメトリクスは既に複数あったものの、どれも一長一短で、また3ヶ月という期間の短さを踏まえて即効性も重視されました。結果として私たちは、プロジェクト開始時点における従来通りの開発工数の見積もりと、最終的な開発工数の実績値との間で、どれほどの開発速度向上の効果があったかを主観ベースで統計化することにしました。なるべくノイズや主観によるボラティリティを減らすため、週例のミーティングにおいて細かく開発フェーズごとに行なった作業と要した時間を報告してもらいました。これには、どの工程においてどのような課題があるのかを顕在化させる副次的な効果もありました。 そしてこの連携を通した定量的な調査の中で、最も開発生産性向上の効果が高かった手法が、Specドリブン開発(Spec-Driven Development; SDD)でした。SDDを採用した複数のプロジェクトは、見積比で平均して150%以上の開発速度向上の効果を実現しました。またSDD以外のAIを活用した開発手法を採用したプロジェクト群と比較しても、80%以上の開発速度向上の効果を実現しました。 また更に定性的な調査の中で、AIを活用した開発手法の効果を高く実感している開発者とそうでない開発者の間には、次のような観点で差があることがわかりました。 (1) AIのコンテキストに適切な一次情報を与えられているか 最初にAIに開発時の前提となる全ての一次情報を明示的に渡すことによって、AIの作業中に開発者がプロンプトから情報を補完する必要がなくなります。これにより、AIが要件を無視して実装したり、Hallucinationを起こすことを防止できます。 (2) AIにタスクの明確な目的・ステップ・完了条件などを明示することができているか 最初に開発のステップや完了条件などを明示することによって、AIの作業中に開発者が介入して方向修正する必要性が減ります。これにより、AIが全く意図しない方針で実装を進めてしまったり、タスクのゴールを都合良く解釈してしまうといったことを防止できます。また実装が終わるまでどのようなアウトプットになるのかわからないといった不確実性も解消されます。 (3) AIが作業中にコンテキストに保持する情報を最適化できているか AIが単一のコンテキストで「資料を読み、技術調査を行い、コードベースを解析し、実装を生成し、テストを実行し、不具合を修正し、…」というステップを全て進めようとすると、不要なドキュメントやコードの情報、テスト時のログなどが全てコンテキストに含まれることになってしまい、すぐに会話がcompactされてしまいます。このcompactionによって、AI自身が何のタスクを行なっていたのか忘れてしまうこともあります。実装を複数のステップで異なる会話のコンテキストに分離したり、Claude CodeのSubagent機能のようなコンテキストを部分的に分離するような機能を用いることが、これに対する解決策となります。 2025年9月: スタンダードとしてのAgent-Spec Driven Development(ASDD)の提案 このような結果を踏まえて、SDDの高い開発生産性への効果と、上記3つの観点に代表されるAIを活用した開発手法の効果を左右する不確実性や属人性を低減するために、メルカリ流に最適化された手法が、Agent Specドリブン開発(Agent-Spec Driven Development; ASDD)でした。 ASDDは、「資料の読解・技術調査・コードベースの解析を通して、実装計画(Agent Spec)を生成する」ステップと、「その策定されたAgent Specに基づいて実装を行う」ステップの、2つのステップから構成されます。このAgent Specには、タスクの一覧、タスクごとの詳細設計、タスクごとに「どの実装を参考に」・「どのファイルに」・「どのような変更を加えるか」などの詳細に記載されており、SDDのSpecにおいてAIにとっての明瞭性と人間にとっての可読性を両立したものになっています。ASDDは、SDDのSpecすらも自動生成することを目指した開発手法であるとも言えます。 1つ目のAgent Spec生成のステップでは、メルカリのナレッジ基盤に最適化されたエージェントが自動的に一次情報にアクセスし、詳細な実装計画を生成します。また別のエージェントが、実装計画がサービスのコーディング規約に沿っているか、計画が所定のセキュリティ観点をクリアしているかなどのさまざまな調査を行います。この2つのエージェントが交互に修正と評価を繰り返し、最後に要件や仕様における不確実要素が残った場合には、開発者に追加の質問を行います。このように、「AIのコンテキストに適切な一次情報を与える」ことをプロセスによって保証しました。 2つ目のAgent Specから実装を生成するステップでは、実装を行うエージェントと、テストや静的解析を行うエージェント、実装結果のAgent Specとの整合性を検証するエージェントが互いに協調しながら、タスクの完了まで自律的に実行されます。このように、「AIにタスクの明確な目的・ステップ・完了条件などを明示する」ことをプロセスによって保証しました。 このASDDには、次のような利点がありました。 AIは計画時・実装時のそれぞれで必要な情報のみをコンテキストに保持することができるため、コンテキスト中の情報密度が高い AIの実装計画をレビューできるため、実装結果の予測可能性が高い 生成される実装は実装計画に基づく宣言的なアプローチであるため、再利用性や透明性が高い 各ステップへのcontributionを通して、全社横断で知見の集約と持続的改善を行うことができる そして、ASDDによって実装全体がAIに移譲して非同期的に実行可能なプロセスになったため、高いスケーラビリティの実現が可能になりました。このスケーラビリティこそが、高い開発生産性の実現を可能にしたと考えられます。 2025年10月から現在: 開発プロセス全体のAI-Native化へ 2025年10月からは、1人から始まったpj-doubleも10人超のチームに拡大し、またその対象領域もメルペイの一部プロジェクトから、全社へとスケールしています。対象範囲も、これまでのバックエンドの詳細設計と実装のAI-Native化に加えて、要件定義や設計、iOS/Androidのクライアント開発やQAを含めた、デリバリーサイクル全体のプロセス再設計へと拡大しています。 要件定義・設計領域では、プロダクト仕様書と技術設計書を作成・合意するプロセスを再設計しています。そこでは、PMや開発者が簡単な要件や技術方針を伝えるだけで、AIが一次情報や過去のプロジェクトを参考にプロダクト仕様書と技術設計書を自動生成することを目指してきました。 クライアント実装の領域では、バックエンドの実装と同様にASDD開発の有効性を調査してきました。既にiOS開発では効果が検証され始めており、バックエンドとクライアントの一貫した実装体験として、ASDDの整備をさらに続けていく予定です。 QA領域では、バックエンドQAとクライアントQAのそれぞれにおいて、またテストケースの自動設計と自動実装のそれぞれについて、ASDDと同様にQAのワークフロー全体をAgenticに実行するための手法を模索しています。テストケース自動設計においては、Claude Code Commandによって、マイクロサービスの依存関係やテストケースのもとになる仕様書の解析・テストケースの設計などを自動化する試みが模索されており、現場のQAエンジニアからはポジティブなフィードバックをいただいています。またテストケース自動実装においては、ASDDと同様にテストの実装と評価のループをAgentが自律的に行うことによって、高い精度で正しいテストケースが実装されることを確認しています。 これらの各領域における取り組みは現在進行形で、現場のプロジェクトとの連携の中で、地に足つけた持続的な検証と改善を行いつつも、トップダウンで「プロセスがどう変化するか」「体制がどう変化するか」についてビジョンを打ち立てて、着実に一歩ずつ前進しています。 pj-doubleを通して得た学び 2025年7月から現在に至るまでのpj-doubleの道のりは、決して順風満帆なものではありませんでした。技術的な課題、組織的な摩擦、そしていわゆる「ビルドトラップ」など、多くの壁に直面しました。 しかし、それらを乗り越える過程で得られた学びこそが、pj-doubleの道筋や目指す開発プロセスの礎となっています。ここでは、特に重要だった4つの学びを共有します。 学び1: AI活用は品質・保守性とのトレードオフではない プロジェクト発足当初、多くの開発者が懸念していたのが、「開発速度を上げることで、品質や保守性が犠牲になるのではないか」というトレードオフでした。しかし、検証はこの直感に反する結果を示しました。 まず機能的な品質についてです。pj-doubleでは、QAワークフローの自動化などのGuardrail整備を進めることで、「デグレが発生していないか」「意図通りに動作するか」といった機能的な品質保証をプロセスとして組み込んでいます。 重要な視点は、これが「AIと開発者の責任分界点の再定義」であるということです。AIに実装を任せる(非同期化する)ことで、開発者はそこで浮いたリソースを、より本質的な品質担保や、開発者がリソースを責任を持つべき高度な検証作業に充てることができます。実際に、メルカリモバイル開発における事例では、実装の高速化によって創出された時間を動作検証やQAに充てることで、設計段階でのインシデントの未然防止に寄与するという成果が確認されました。 次に保守性の観点です。「AIが書いたコードが読みにくい」「拡張性が低い」といった懸念もよく聞かれます。しかし、これらの問題はAIの能力不足ではなく、コンテキストの欠如に起因することが多いです。「社内の共通ライブラリを使って欲しい」「特定のコーディング規約に従って欲しい」といった意図があるならば、そのドメイン知識や規約をコンテキストとしてAIに与えることが効果的です。実際にASDDでは、Agent Specの生成時に、社内フレームワークの参考実装などをコンテキストに与えることで、社内の実装における暗黙的なパターンを反映させており、開発者からも「自分で書くのと同じかそれ以上の品質の実装計画が生成される」というポジティブなフィードバックを受けています。 さらに、こうしたASDDなどの共通基盤を整備することで、組織として推奨する実装パターンや規約を誰もがコンテキストに注入できるようになります。個人のスキルに依存してバラバラに行われていたAIコーディングに対して、組織全体で品質のベースラインを引き上げることが可能になると期待しています。 もちろん、短期的な速度向上が長期的な技術的負債につながらないよう、私たちはDXツールを用いてRevert Rate(手戻り率)やMTTR(平均復旧時間)などの指標を常にモニタリングしています。定性的な感覚と定量的な計測の両軸で確認を続けていますが、現時点ではAI活用が品質を低下させるというシグナルは出ておらず、むしろ標準化の強力な武器になり得ると確信しています。 学び2: レビューのボトルネックはPull Requestの粒度で解消できる 「AI時代の開発は、人間によるコードレビューがボトルネックになる」。これはよく耳にする懸念であり、実際に社内のDX指標を見ても、レビューのリードタイムが開発生産性の向上を阻害している傾向が一部で見受けられました。しかし、現場への定性的なヒアリングと分析を通して、これは「AIコード特有の問題」ではなく、「プロセスの運用」によって解決可能な課題であることが見えてきました。 レビューが辛くなる原因を調査したところ、単に「AIが書いたコードだから」ではなく、AIの圧倒的な生産速度によって「1つのPR(Pull Request)に含まれる差分が膨大になっていたから」であることが判明しました。人間はインクリメンタルな情報の処理には長けていますが、一気に大量の情報を受け取ることで、認知負荷が限界を超えてしまったとも言い換えられます。 pj-doubleではこの課題に対して、仕組みによって解決することを目指しました。Agent Specを生成する段階でタスクを「合理的かつ動作可能な最小単位」に細かく区切り、そのタスクごとにPRを作成するという運用を徹底したのです。 実際にこのプロセスを採用した開発者からは、「PRが小さく保たれることで、AIが書いたコードであってもレビューのストレスを感じなくなった」などのポジティブなフィードバックが寄せられています。AIと人間のコードの間に、レビュー工数上の有意な差は生じないというのが、私たちの現在の結論です。 さらに私たちは、AI-Nativeな開発プロセスの再設計において、コードレビューという行為そのものの再定義も必要だと考えています。 一口にコードレビューと言っても、品質や保守性を守る側面、踏襲的なプロセスとして習慣化して手段と目的が逆転した側面、監査上の要求を満たすための側面など、その役割は複数の意味を持ちます。これら全てを「人間が目視で保証する」という形で行う必要があるのでしょうか。 pj-doubleでは、単に全ての生成コードに人間が目を通すという既存の形を超えて、より意義のある、本質的な価値提供にフォーカスした新しいコードレビューの体験を模索し続けています。 学び3: 現場に地に足つけたアジャイルな検証と改善のサイクルの難しさ 私たちがpj-doubleを通して度々実感したのは、現場での検証と改善のサイクル、いわゆるフィードバックループを回すことの難しさです。特にpj-doubleのQA領域での取り組みにおいて、私たちは典型的な「ビルドトラップ」を経験しました。 QA領域でのAgent活用において、私たちは当初、QAワークフローを自動化するためのリッチなUI付きのツールを開発・提供しました。プロンプトだけを配布するよりも、画面を提供したほうがデモがしやすく、配布も容易で、何より入力を制限することでAgentの挙動の再現性を担保できると考えたからです。 しかし、これは結果として検証の足枷となりました。本来、検証フェーズで最も重要なのは「プロンプトや手法の改善」です。しかし、アプリという堅牢な形にしてしまったことで、改善のためにはアプリ側のコード修正まで必要になり、コントリビューションのハードルを上げてしまいました。ASDDのように最小構成で始めていれば、アーリーアダプターたちが直接プロンプトを改善できたはずが、アプリのメンテナンスに時間を取られ、本来の目的である「課題解決の手法の確立」がおざなりになるという本末転倒な事態を招いたのです。 また、アジャイルな改善ループの実現も、想像以上にハードルが高いものでした。 当初は週に2〜3回程度の頻度でフィードバックをもらい、高速に改善を回す想定でした。しかし、現場の開発者にとって、検証内容を言語化してフィードバックを送る行為自体が大きなコストです。実際に届くフィードバックは非常に質が高く丁寧なものでしたが、それを高頻度で要求することは現実的ではありませんでした。 これらの経験から、私たちはアプローチを修正しています。 まずツールありきで考えるのではなく、最小限の構成で「手法の洗練」に集中すること。そして、週に1度などのミーティングにおけるカジュアルなフィードバック収集や、Slack上のフィードバックの自動収集、DXの機能を活用した集計値の自動算出などの仕組みを導入しています。 この学びを踏まえたアプローチの修正によって、pj-doubleのQA領域では提供する手法の洗練と現場からのフィードバックの収集のアジャイルなサイクルが実現されるようになりました。この2025年10月から始まったQA領域のAgentic化の取り組みは、今や急速なスピードで各領域におけるQAエンジニアの体験を塗り替えていっています。 この取り組みを通した最も重要な教訓は、現場に対して「良いツールがあるから使ってフィードバックして」という姿勢ではうまくいかないということです。 「開発プロセスを変える必要があり、そのために協力してほしい」というスタンスで巻き込み、ツールはそのプロセス変革を支援するための手段に過ぎないという合意形成を行うこと。ツールに依存しないプロセスを現場と共に見出し、泥臭く検証し続けることこそが、遠回りのようでいて最短の道であると痛感しています。 学び4: 混沌とした過渡期こそ、明確なビジョンが旗となる 上のビルドトラップの話とも重複しますが、pj-doubleにおいて私たちが最も警戒したのは、手段の目的化、すなわち「AIを使った便利ツールの開発」に陥ることでした。 日々新しい生成AIモデルやツールが生まれては消えるカオスの渦中で、今の技術スタックに合わせてツールを作り込んでも、それは数ヶ月後には陳腐化してしまいます。pj-doubleの模索においても、目前の課題解決に最適化したソリューションが、数ヶ月後には時代遅れになっているリスクと常に隣り合わせでした。取り組み自体を陳腐化させないためには、特定のツールや局所的な最適化ではなく、ツールに依存しない「プロセスの変革」に向き合う必要があります。しかし、私たちが今向き合っているものが、本当にツールに依存しない普遍的なプロセスなのか、それとも現在のツールの限界を補うための過渡的な対処療法に過ぎないのか、その見極めは非常に困難です。 このような「標準化と陳腐化のジレンマ」の中で、組織として価値あるアセットを積み上げ続けるために最も重要だった思考の転換こそが、現在の手元に見える課題から積み上げ式に考えるのではなく、来たる未来から逆算する「バックキャスティング」でした。 今のツールの限界を度外視し、技術のポテンシャルを前提に置くことから始めます。「Agentが自律的にコンテキストを収集し、コーディングルールに従って実装を完遂し、QAもAgenticに自動で行われる」。これらが当たり前に実現されたとき、プロダクトデリバリーはどのような形態を取るべきか? ボトルネックはどこに移るのか? この理想的な未来の体験から逆算して初めて、今なすべき投資が見えてくるものかと思います。 私たちが進めているナレッジフロー整備、非同期のエージェント実行基盤開発、リグレッションQAの自動化、テスト環境への自動デプロイを含むCI/CDの改善などは、単なるインフラ整備ではなく、この描いた世界観へシームレスに移行するための戦略的な布石です。 メディア理論家のマーシャル・マクルーハンはかつて、「私たちはバックミラー越しに現在を見ながら、未来に向かって後ろ向きに進んでいる("We look at the present through a rear-view mirror. We march backwards into the future.")」と言いました(出典:The Medium is the Massage by Marshall McLuhan)。 これは、私たちの「常識」や「思考様式」そのものが過去の技術環境によって形作られているという、構造的な限界への指摘です。私たちは今の常識(バックミラー)を通してしか世界を見ることができないため、Agentが当たり前になった新しいパラダイムにおいて、私たちがどのような思考や行動の原理を持つべきかを、現在の延長線上で想像することはできません。ゆえに、過去の成功体験や現在の思考の常識だけで将来を設計しようとすれば、それは局所最適化に留まり、本当に目指すべき世界観には辿り着けません。明確なプロセスが見えない過渡期だからこそ、ともすれば「ビルドトラップ」に陥りがちです。 だからこそ、pj-doubleでは既存の常識を意図的に崩し、統一された未来の体験を鮮明にイメージとして描き出し、それを旗として掲げ続ける能動的な態度が不可欠であると強く意識してきました。実際に、pj-doubleでは積極的に目指す世界観やその先の開発体験を社内で発信することで、先の見えない変革の中でも、組織全体が同じ方向を向いて進むことができてきたのだと振り返っています。 pj-doubleが上流プロセスで直面した課題 ASDDでは、Agent SpecというAIにとっての中間表現を統一することで、AIの実行計画への介入が可能になりました。すなわち、開発標準・ドメイン知識・職能の専門知識を強制的に注入することに成功しました。このようにASDDは、pj-doubleが当初目指していた集合知の共通基盤としての効果を発揮しています。 ASDDはこのように下流の「仕様が決まった後の実装」において大きな効果を発揮しているのと同時に、上流の「仕様が決まるまでのプロセス」において深刻な課題に直面しています。私たちは、開発現場における継続的な検証とフィードバックの中で、「コードを書く」速度は劇的に向上したものの、その前段である「何を作るか(What)」と「どう設計するか(How)」の合意形成プロセスにおいて、深刻なボトルネックに直面しました。 より具体的には、これまで開発者からはASDDについて次のようなフィードバックを受けました。 (1) Agent Spec以前に必要な合意形成が無視されている 「Agent Specは仕様や設計方針が確定していることを前提にしているが、そもそも関係者間の合意形成に最も多くの時間がかかっている」 「Agent Specのように自動生成されたドキュメントは判断根拠の説明が伴わないため、合意形成に用いる媒体としても適切ではない」 (2) Agent Specの生成とレビューのコストが高く、開発サイクルの速度が落ちる 「生成されたAgent Specや実装の意図が分からず、情報源を探すために技術設計書を遡り、プロダクト仕様書を遡り、Slackの議論を遡らないといけない」 「毎回0から書き換わるAgent Specを一行一行レビューして修正するくらいなら、自分で書いたほうが早い」 1つ目について、今のASDDでは、たとえ合意のない確度の低い要求でも、そのまま実装の自動生成まで行うことができてしまいます。結果として、AIは曖昧な部分を勝手に補完し、「技術的には正しいが、誰も合意していない成果物」が自動生成されます。立ち返って考えてみると、従来の上流から下流までの開発フローにおいては、仕様・設計・方針に関する段階的な合意の積み重ねこそが、開発フローのチェックポイントとなって手戻りを防止する機能を果たしていたはずです。またこのプロセスの本当のボトルネックは、これらの合意形成を同じ時間・同じ場所に集まって同期的に行うステップです。pj-doubleは要件定義段階で、プロダクト仕様書や技術設計書を自動で生成することを目指していましたが、このような一次情報が創出される創造的な議論の場や、そこにおける合意形成などの本質的なプロセスがそのスコープから抜け落ちてしまっていました。つまり、上流工程において私たちがフォーカスすべきは、一次情報を組み合わせてプロダクト仕様書や技術設計書に変換するプロセスではなく、その一次情報そのものを生成するプロセスだったという反省です。 また2つ目について、ASDDによって生成された長大なAgent Specや実装は開発者の認知負荷を遥かに凌駕し、真面目にレビューしようと思うと、膨大なレビューコストを強いることになってしまいます。さらにAgent Specには、一次情報と自動生成された真偽不明な情報とが入り混じることもしばしばありました。開発者は、生成された情報に心当たりがない場合、その情報の出自を遡って調査する必要があります。本質的に人間の認知モデルは、インクリメンタルな認識の更新を基本としていて、今の認識との差分として追加情報を期待するはずです。他方でpj-doubleで取ってきたドキュメント自動生成のアプローチは、膨大な情報源からかき集めた新情報を既知の情報と一緒くたにして提示するため、認知過負荷を引き起こしてしまっていました。 課題解決への系統的分析とアプローチ このような課題に対して、私たちはその原因が、「『AIと考える・AIと決める』べきことを、『AIに任せる』体験として設計してしまったこと」であったと総括しました。 AIと開発者の協業モデルは、大きく同期プロセス・非同期プロセスに分類できると考えています。(ここで暗黙知とは言語化されていない情報、形式知とは言語化・文書化された情報を指しています。) 同期的協業 — AIと考える・決める 非同期的協業 — AIに任せる 活動の様態 AIが情報収集・開発者が意思決定・AIが生成・開発者が評価 AIが自律的に情報収集・意思決定・生成・評価を行う 活動の役割 暗黙知と形式知の交換 / 一次情報の生成を伴う 形式知から形式知への転換 / 情報の変換に過ぎない 活動の意義 選択・意思決定・合意 アウトプットの機能的側面、実利的な要求の実現 機能やテストの実装は、動作するという機能的な要求を満足することが目標であるため、非同期的協業によって認知負荷を超えてスケールさせることが重要で、その点でAIへの移譲を促進することがAI-Native化の促進を意味します。 一方で上流プロセスにおいては、作成される仕様や設計そのものの価値は小さく、むしろそこに至るまでの比較検討・意思決定・合意などの、人間による承認の記録としての側面が大きいです。そして承認を要するという性質上、誰かの認知負荷の範囲内で運用される必要があり、その意味で同期的協業を必要とします。すなわち、上流フェーズにおける最重要事項は、生成されるアウトプットではなく、仕様や設計が検討され、レビューされ、合意され、それらが新たな一次情報として管理されるようになることです。 しかしpj-doubleでは、同期プロセスの意義を過小評価し、全てを非同期プロセスとして処理しようとしていました。一次情報は既知で既出の前提として、それらを用いた情報の変換(プロダクト仕様書・技術設計書・Agent Spec・実装生成)のAgentic化にフォーカスしてしまっていました。私たちは、「AIが最初から90%の完成度の設計を作る」ことと、「AIと一緒に考え、機微な方向性を決定しながら同じ設計に辿り着く」ことが、上流プロセスにおいては全く異なる意味を持つという観点を見落としていたということです。 すなわち、上流プロセスを非同期的協業とは区別された同期的協業として再設計することがpj-doubleにおける次の命題となるわけですが、このAIとの同期的協業も更に、「AIと考える」体験と「AIと決める」体験の2つに区別することで、更に見通しが良くなると考えています。 同期的協業 — AIと考える 同期的協業 — AIと決める 活動の様態 AIが情報を収集・提示し、開発者の思考を拡張する 思考や意思決定を通して新たな一次情報を生み出す 活動の役割 形式知の提示による暗黙知の拡大 一次情報の形式知への登録・参照 活動の意義 対話を通した認知の範囲・思考の深度・選択の質の向上 合意が形成され、蓄積されること 「AIと考える」プロセスは、開発者が1人では見つけられなかった情報にアクセスし、1人では実現し得なかった深度で検討することを可能にします。ここでは、AIとの自然な対話の中で、AIが設計・実装を前進させるためのガイドをしてくれることが理想的な体験です。例えば、ChatGPTやClaudeとの対話を通して、設計や実装の方針について議論するような体験です。ここでは、開発者自身への知の内面化(Internalization)の側面に価値があります。 そのためには、人の認知モデルに基づいて、開発者の現状理解からの差分として新たな情報が補完的に提示されるようなインタラクションの設計であるべきです。これは例えば、エンジニアがテックリードとの会話を通して、「こういう設計もあったか」「この設計はこの考慮が漏れていた」などの発見を伴うプロセスに対応します。AIと開発者の関係性は、AIが生成した情報を開発者が受動的に消費するという関係性ではなく、開発者が次に必要とする情報をAIが補完的に提示するという関係性を目指すべきであると考えます。 またこのような開発者とAIの思考の模索は、完全なフリーフォーマットで行うことも、完全なテンプレートに基づいて行うことも、どちらも適切ではないと考えています。過度な自由度は選択麻痺を引き起こし、過剰な制約は模索の余地を奪うためです。pj-doubleのプロセス再設計においても、プロセスを標準化することが何度か試みられましたが、これは思考の幅を制限してしまう過度な制約の典型例だったと言えそうです。AIが全体プロセスやそこにおけるステータスを見据えつつ、開発者を自然な対話の中で誘導できるような思考フローが理想的でしょう。 「AIと決める」プロセスは、開発者によるさまざまな選択肢の採用・不採用の意思決定とその理由を、新たな一次情報として蓄積するためのプロセスです。ここでは、建設的なプロセスのチェックポイントとしての意思決定やその蓄積、一次情報の生成という、知の表出化的側面に価値があります。 このように、AIと決めたことが蓄積し、それがAIと考えるために使われるという循環をデザインすることが、同期的協業の条件であると考えています。 私たちpj-doubleは現在、このような考えに基づいた課題解決方針の模索とその検証を実施するフェーズにあります。もしその取り組みを通して更なる発見や学びがあれば、またいつか別のテックブログなどで共有させてください。 pj-doubleが描く世界観 そしてこの体験が実現した世界観は、次のようにイメージできるかもしれません。 (1) AIと考える 開発者はAIとの話し合いを通じて、仕様や設計を考え進めていきます。そこにおいてAIは既存のサービスやドメイン知識を駆使しながら、思索や意思決定をサポートします。このような能力の拡張により、上流工程において開発者1人が担える役割も拡大します。 (2) AIと決める AIは対話の中で追加で発生する一次情報を蓄積します。PMの「なぜこの仕様は却下されたのか」という質問や、テックリードの「なぜこの技術選定を行なったのか」という質問に対して、蓄積された一次情報に基づいて回答します。 このような合意の形成と参照という側面は、これまで必要だった「開発者↔開発者」の同期的なコミュニケーションを、「開発者↔Agent」+「Agent↔開発者」のコミュニケーションへと分解し、合意の形成・参照を非同期的に実現可能にします。すなわち、各職能の人が同じ時間・同じ場所に集まらなくても、合意の確認や意思決定をできるようになります。 (3) AIに任せる このように合意形成が行われたら、あとはプロジェクトの機能要求を実現するための仕事をAIに移譲するだけです。例えばここで生成されるプロダクト仕様書や技術設計書は、全て過去の意思決定に基づくため、開発者がその生成意図を吟味したり、Hallucinationを精査する雑務が発生しません。またASDDの本質的な付加価値であるAgenticな実装生成も、高い確度で任せることができます。QAも自動で実行されるため、デグレの心配もありません。あとは、安心して実装の完了を待つだけです。 この世界観のもとで、PMや開発者はAIによって拡張された思考や調査能力を存分に使いながらプロダクトのアイデアを考え進めていき、アイデアが煮詰まったと思ったら即座にそれが形になります。試行と学習のサイクルが圧倒的な速度で回り、より多くのアイデアを試し、より速く学ぶことができるようになるでしょう。 また職能の拡張は一人一人の役割やプロジェクトの体制にも変化を与えるはずです。開発者がAIによって拡張されることで、専門知識が必要な調査や業務が自動化され、PM、アナリスト、Designer、エンジニア、QAといった個人間の職能の差異が薄れていき、結果として1人が担う役割も大きくなるはずです。またそのもとで、プロダクトデリバリーは少人数の自律的なチームによってEnd-to-Endに実施されるようになります。これまではOutputに目が向きがちだった開発者も、よりお客さまへの価値提供との距離が近づくことで、OutcomeやImpactに責任を持つようになっていくでしょう。 さらに、薄れるのは職能の境界だけでなく、サービスの境界にも広がりそうです。ドメイン知識が形式知化されることで、プロダクト開発のために要求されるドメイン知識の総量も減少し、サービスの境界と認知負荷の境界が等価ではなくなるかもしれません。すなわち、逆コンウェイの法則に基づくような組織体制ではなく、よりビジネスを加速させるために最適な組織構造へと変化するかもしれません。例えば、1つの少人数開発チームが複数のサービスをまたいで1つの施策を実現するような体制はその候補の1つです。 こうした要件定義からQAまでをAI-Nativeに再設計するための取り組みの効果は、開発生産性の向上だけに留まらず、本質的なビジネスの強度向上に貢献するとも考えています。これまでは各工程で担当者が異なるため知識の分断が、各工程でプラットフォームが違うためデータの分断が発生することがありました。プロジェクトを通したより多くの知識が、またより多くの職能ごとの専門知が集積されるようになることで、市場やお客さまのOutcome・Impactがフィードバックされて、それをもとにプロダクトを改善・拡張するというループが自己完結化するようになれば、施策の精度や戦略の練度の向上にも寄与できると信じています。 pj-doubleの取り組みやそこで得た学びは、知的活動を中心とする他のあらゆる領域にも拡大できるかもしれません。「AIと考える」「AIと決める」「AIに任せる」の3つを使い分けながらプロセスを再設計することが、プロダクト開発に留まらない「AI-Nativeな働き方」の重要な要素になると考えています。 おわりに 以上、メルカリにおけるpj-doubleを通した、AI-Native化への挑戦とその学びについての紹介でした。本記事が、Human CentricからAgent Centricへと向かうこの過渡期において、AIとの新しい協業の形を模索する一助となれば幸いです。長文にお付き合いいただき、ありがとうございました。 明日の記事は hokaoさんによる「Kubeflow PipelinesとPydantic Settingsを活用してMLパイプラインを型安全かつシンプルに実装する」です。引き続きお楽しみください。
アバター
はじめに こんにちは。メルペイVPoEの @keigow です。 この記事は、 Merpay & Mercoin Advent Calendar 2025 の1日目の記事です。 2025年も色々なことがありました。年末ということで、2025年にメルペイとして、或いはメルカリグループとして取り組んだことをこの機会に振り返られればと思います。 mercari GEARS 2025 11月13日に「メルカリエンジニアリングの今」をテーマにテックカンファレンスを開催しました。リアルな場でのイベントは実に7年ぶりの開催となりましたが、社内外含め沢山の方にご来場いただき、盛り上がることができました。キーノートを始めとして各発表の スライド や 動画 も公開されておりますので、ぜひ御覧ください。 AIによって進化する開発 ちょうど半年前に同じくブログの連載企画で メルペイにおけるAI活用の取り組み についてご紹介しました。当時は Claude Code が流行り始めたぐらいだったことを考えると、凄い速度の変化が起きていると改めて感じます。当時はエンジニアのAI Coding Toolの利用率を追いかけていましたが、今ではほぼ全てのエンジニアが何らかのAI Coding Toolを利用するようになりました。 グループ全体として大きな取り組みとなったのは、 こちらの記事 でも取り上げているAI Task Forceです。目標としてAI Nativeな会社を目指し、エンジニアの部署に限らず、カスタマーサービスやマーケティングなど全社33の部署を対象に、専任のエンジニアを含めた100名規模の組織を組成し、業務の棚卸しと、AI Nativeなワークフローとはそもそもどうあるべきかを考え、見直しを行っています。まだ道半ばではあるものの、各部署でロードマップを作成し、着実に成果が出てきています。 Project Double AI Task Forceの活動と並行して、メルペイにVPoE Officeという新しい箱を作り、AIによる生産性向上の取り組みとしてProject Doubleをスタートしました。その名の通り生産性を2倍にしようという取り組みで、当時一部のエンジニアたちが行っていたAgenticなCoding手法を、Agent Spec Driven Development(ASDD)として標準化することで誰もがそれを利用可能にしようとする試みです。 初期はBackendのみを対象としたProjectでしたが、7-9月の四半期で一定の成果を出すことができたため、その範囲をClientの開発、Backend/ClientそれぞれのQA領域、仕様書や設計などのPlanning領域へと広げることを決めました。また対象となるCompanyもFinTechの一部のPilot Projectから、グループ全体へと広がり、現在はEngineeringの中でも最優先の取り組みとして進行しています。 内容の詳細や得られた学びについてはこのProjectをリードしているnakai-sanから明日の記事で詳しく紹介していただく予定です。 Project Doubleが実現した世界で Agentic Codingの推進において、乗り越えるべき課題は精度やイテレーションにかかる時間などたくさんありますが、日々起きるモデルの進化やツール、環境の整備によっていずれ問題としては解決されていくと思っています。 一方で、1人のエンジニアが企画から設計、開発、テスト、リリースまでを一貫して行える世界が来たときに備えて、いくつか解決しなければいけない部分もあります。オンコール体制の整備、問い合わせ対応などの運用業務の最適化など、Agentic Codingが中心となった時代に最適な組織設計です。こちらについては一定の仮説はあるものの、まだ明確な応えが見えていない領域であり、Project Doubleの推進と合わせてトライアルをしていきたいと思っています。 しゃべるおさいふ AIの活用はEngineeringだけでなく、プロダクトへの活用もPoCという形でトライしています。メルペイで現在取り組んでいるのがこちらの「しゃべるおさいふ」という機能で一部のお客さまを対象に試験的に導入しています。 内容は非常にシンプルで、利用データを元にAIがコメントをしてくれるという機能です。ちょっとした日常の利用に対してコメントを貰えるというのは思ったよりも嬉しい体験で社内テストは想定していたよりも好評でした。そこまで複雑な機能では無いものの、コストの観点や安全性、倫理的な観点も含め、AIをプロダクトで活用するとはどういうことなのか、という学びを得ることができました。12/20のkobaryo-sanの記事ではこちらの機能のバックエンドの設計について紹介する予定なのでお楽しみに。 開発合宿 1月の事になりますが、数年ぶりにメルペイで一泊二日の開発合宿を行いました。普段業務に追われて取り組めないような新しい技術へのチャレンジ、OSSの開発、やりたいと思っていた新機能の開発などに取り組みました。この開発合宿をきっかけとして、 有志による開発プロジェクト がスタートし、実際にリリースまでつながることもありました。コロナ禍で普段直接顔を合わせることが少なくなっていたエンジニア同士のコミュニケーションの機会にもなり、参加してくださった皆様の満足度もかなり高かったので、また来年もトライできればと思っています。 開発合宿の様子はMercari GearsのYouTubeに 動画 も上がっているので、気になった方はぜひご覧いただければと思います。 おわりに 今年は全社のテーマとしてAI Nativeを掲げ、Engineering組織としてもAI中心の一年となりました。半年前も同じようなことを書いた気がしますが、目まぐるしい変化の中で働けることを楽しんでいます。 明日の記事はその中でも中心的な取り組みとなる、nakaiさんによる「pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌」です。引き続きMerpay & Mercoin Advent Calendarをお楽しみください。
アバター
こんにちは。メルペイ Engineering Engagement チームの mikichin です。 Advent Calendarの季節がやってきます!今年も、メルカリグループは Advent Calendar を実施します! ▶ Mercari Advent Calendar 2025 はこちら Merpay & Mercoin Advent Calendar とは? Advent Calendar の習慣にもとづいて、メルペイ・メルコインのエンジニアがプロダクトや会社で利用している技術、興味のある技術分野やちょっとしたテクニックなど知見をアウトプットしていきます。このAdvent Calendarを通じてクリスマスまでの毎日を楽しく過ごしていただければと思っています。 2024年のMercari / Merpay & Mercoin Advent Calendar はこちら Mercari Advent Calendar 2024 Merpay & Mercaoin Advent Calendar 2024 公開予定表 (こちらは、後日、各記事へのリンク集になります) Date Theme / Title Author 12/1 TBD @keigow 12/2 pj-double: メルカリにおけるEngineeringのAI-Native化の取り組みと学び @nakai 12/3 Kubeflow PipelinesとPydantic Settingsを活用してMLパイプラインを型安全かつシンプルに実装する @hokao 12/4 TBD: mercoin internshipでの経験 @Sakamoto 12/5 TBD: Using AI for areas it is strong at. How we automated some aspects of technical documentation generation. @Fab 12/6 YAPC::Fukuoka参加しました @Sakabe 12/7 TBD: gRPC Federation の Incidentについて @goccy 12/8 Finally, Mercari in English! Our road to cross-platform i18n @fenomas 12/9 TBD: vibecheckerというlocalでreviewするtoolの話 + reviewのreviewの自動化? @seitau 12/10 TBD: AI関連技術 or AI Task Force/pj-doubleでの学び or 何か @panorama 12/11 TBD: n8n関連 @ISSA 12/12 Making n8n Enterprise-Ready: 企業向けn8nの導入と運用の取り組み @T 12/13 n8nの静的解析CLIツールをOSS化 – セキュリティチェックの自動化 @mewuto 12/14 TBD: n8n関連と生産性 @abcdefuji 12/15 Extending the Balance Service: Challenges in Implementing Multi-Currency @Timo 12/16 "Supercharging User Engagement: Use Server-Driven UI to reduce Time-to-Market" @Stefan_droid 12/17 Mandates for Recurring Payments @tomo 12/18 DebtAccountAsyncUpdateの設計 @Minato 12/19 2B 与信の世界 @komatsu 12/20 LLMを用いたおしゃべり機能「しゃべるおさいふ」のバックエンド設計 @kobaryo 12/21 getDX の活動・連携周りで何か @ntk 12/22 加盟店管理画面へのOAuth2.0の導入 @taki 12/23 障害振り返りにCAST(システム理論に基づく因果分析)を試してみた @pooh 12/24 AI駆動学習サイクル @kubomi 12/25 TBD @kimuras 初日の記事は keigowさんです。引き続きお楽しみください。
アバター