<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>メルカリエンジニアリングブログ</title><description>メルカリのエンジニアブログです。技術情報やエンジニアリング組織などの情報を日々発信していきます。</description><link>https://engineering.mercari.com/</link><language>ja</language><copyright>© 2023 Mercari, Inc.</copyright><category>blog</category><item><title>決済プラットフォームに常駐する自律AIエージェントの設計と運用</title><link>https://engineering.mercari.com/blog/entry/20260630-28a5eee688/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260630-28a5eee688/</guid><description>&lt;p&gt;こんにちは。メルペイ Director の @abcdefuji です。この記事は「Merpay &amp;amp; Mercoin Tech Openness Month 2026」の 20日目の記事です。 はじめに 私たちは [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 30 Jun 2026 11:00:38 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイ Director の @abcdefuji です。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay &amp;amp; Mercoin Tech Openness Month 2026&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の 20日目の記事です。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;私たちは、決済プラットフォームチームに Slack メンションで起動し Google Compute Engine（GCE） 上に常駐する AI エージェント pcp-agent を作り、本番運用しています。狙いは「人間が AI をキックする」ツールではなく、トリガーベースで自律実行する Ambient Agent を作ることでした。&lt;/p&gt;
&lt;p&gt;本記事では、この取り組みで本当に難しかった2点、決済ドメインで自律エージェントを安全に動かすセキュリティと、その効果を計測する仕組みについて実コードと図で解説します。さらに、これが単一のbotではなく、SREやn8nなど複数ドメインへ展開する再利用可能なエージェント基盤（remote-claude）であることも紹介します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/1fc54940-blog1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;背景：なぜ「常駐エージェント」なのか&lt;/h2&gt;
&lt;p&gt;PCP（Payment &amp;amp; Customer Platform）は数十のマイクロサービスを抱え、社内の大量のユースケースを支えています。障害調査、アラートのトリアージ、システム間の金額検証、問い合わせ対応。これらはいずれも「決まった手順だが人手を食う」作業です。私たちはこうした運用を AI に肩代わりさせたいと考えました。&lt;/p&gt;
&lt;p&gt;掲げたビジョンはシンプルです。AI があらゆるオペレーションをトリガーベースで自律実行し、人間のアクションを必要としない Platform 体制を構築する、というものです。「人間が AI をキックする」世界観は前提から外しました。&lt;/p&gt;
&lt;p&gt;だからこそ私たちは、チャット UI に個人が質問を投げるツールではなく、チームに常駐し、トリガーで自律的に動く Ambient Agent として pcp-agent を位置づけました。人で言えば「決済プラットフォームチームに運用担当が常駐している」イメージです。&lt;/p&gt;
&lt;h2&gt;全体アーキテクチャ&lt;/h2&gt;
&lt;p&gt;pcp-agent は Slack 統合型の Claude Code bot で、隔離された Docker コンテナ内で動きます。セッションごとに copy-on-write ファイルシステムで独立したコンテナが立ち上がり、影響範囲をセッション単位に閉じ込めます。共通インフラ（server / scheduler / auth-proxy）が Slack メンションやスケジューラーからの起動を受け、コンテナを起動します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/2be2089d-diagram4-1024x961.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;エージェントの権限管理関しては、決定的な層を導入しています。書き込み可能なリポジトリは、社内の集中管理された ACL と、そこから発行される scope を絞った短命の GitHub token によって リポジトリ単位で機械的に制限しています。それ以外はすべて read-only（調査・参照用）です。CLAUDE.md には writable repo を明示してエージェントの振る舞いを誘導しますが、強制するのは上記の決定的な層です。リスクが高い領域への書き込みはエージェントに許さない、という境界をまず引きました。&lt;/p&gt;
&lt;h2&gt;仕組み：Claude Code を核に remote-claude で包む&lt;/h2&gt;
&lt;p&gt;中身を一段掘り下げます。pcp-agent の心臓部は Claude Code そのものです。私たちは Claude Code を「コマンドラインで動くエージェントランタイム」として使い、その周りを remote-claude という薄いラッパーで包んでいます。remote-claude は Slack やスケジューラーからのトリガーを受け取り、セッションごとに Docker コンテナを立て、その中で Claude Code を起動し、標準出力を Slack に橋渡しする役割を担います。&lt;/p&gt;
&lt;p&gt;Claude Code は単体では汎用のコーディングエージェントです。これを「決済プラットフォームドメインの運用担当」に仕立てているのは、ワークスペースに置いた .claude ディレクトリの中身です。ここには振る舞いを決める CLAUDE.md、ドメイン知識と手順をコード化した Skill / Plugin、そして実行のたびに介入する Hook が含まれます。能力を増やしたいときはモデルを差し替えるのではなく、スキルやルールを足す、つまり「プロンプトとファイル」で拡張するのが設計の基本方針です。&lt;/p&gt;
&lt;p&gt;一回の実行（セッション）の流れはこうです。トリガーが来ると remote-claude が新しいコンテナを copy-on-write で起こし、その中の Claude Code が Slack メッセージやスケジュールされたプロンプトを入力として受け取ります。エージェントがツールを呼ぶたびに、まず PreToolUse hook がセキュリティ検査をかけて危険なら止め、ツール実行後に PostToolUse hook が 開発者体験の計測プラットフォームであるDX へ利用イベントを送ります。同じセッション（Slack スレッド）への追加リクエストは、起動中の Claude Code プロセスを再利用するため高速で、コールドスタート約30秒に対してウォームは約2〜3秒です。セッションはアイドルが約30分続くとクライアントを切断し、約1週間（session_ttl）で破棄します。各セッションは copy-on-write で独立した workspace を持つので、影響範囲をセッション単位に閉じ込めつつ再現性を確保できます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/5790b763-diagram-1024x578.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;設計上の要点は「LLM を信用しすぎない」ことです。エージェントの判断（自然言語）は本質的に非決定的なので、安全性や監査が要る部分は決定的な Hook、ネットワークレベルの auth-proxy、コンテナ隔離、IaC 管理のシークレットなど、LLMの外側に寄せています。LLM には「何をやりたいか」を任せ、「やってよいか」「どう繋ぐか」はコードと基盤で固める、という責務分離です。&lt;/p&gt;
&lt;h2&gt;認証設計：auth-proxy でクレデンシャル注入を一点集中&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/921e95a2-blog3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;エージェントは Notion / Jira / DX / PagerDuty / Datadog / Slack など多数の外部 API を横断します。認証方式はサービスごとにバラバラで、これを LLM に直接持たせるのは危険です。そこで私たちは auth-proxy をネットワークレベル（iptables DNAT）に置き、宛先ごとに適切な認証情報を自動注入する構成にしました。注入するヘッダや取得方法はサービスごとに異なり（環境変数、keyless な GitHub トークン、GCP サービスアカウントのインパーソネーションなど）、その差異を auth-proxy が吸収します。エージェント側の curl はトークンを一切知りません。&lt;/p&gt;
&lt;p&gt;この設計のもう一つの利点は、シークレットをエージェントのコードから完全に分離できることです。実トークンはすべて IaC（microservices-terraform）で管理し、auth-proxy 側にだけ環境変数として渡します。ランナー（エージェント実行コンテナ）に埋め込まれるのは非機能のダミー値だけで、万が一漏れても無効です。エージェントのワークスペースにクレデンシャルは存在しません。&lt;/p&gt;
&lt;h2&gt;セキュリティ：決済ドメインで自律エージェントを動かすための多層防御&lt;/h2&gt;
&lt;p&gt;決済ドメインで本番権限を持つ自律エージェントは、最高レベルの安全要求を満たす必要があります。pcp-agent はdeny rules、PreToolUse hook、behavioral rules、auth-proxy、コンテナ隔離の5層で防御しています。重要なのは、単一の層に依存しないことです。どれか一つが破られても、別の層が止める設計にしています。&lt;/p&gt;
&lt;p&gt;主防御線に据えたのは PreToolUse hook です。個々のツールごとの許可・拒否設定に頬り切るのではなく、どのツール経由でも危険な操作を止められる決定的なフックを最終的な強制層に置く方が確実だと考えたからです。後述するように、このフックは想定外の終了コードで落ちた場合に「黙って許可」ではなく「ブロック」を返す fail-closed 設計にしてあります。フックがクラッシュした隙にコマンドが通る事故を防ぐためです。&lt;/p&gt;
&lt;p&gt;シークレットファイルの検出には現実的な難しさがあります。jq のフィールドアクセスをしてissue key を取得するような正当な操作と、秘密ファイルの読み取りを区別しなければなりません。私たちのパターンはファイルパスとして現れる鍵ファイルのみを止め、クエリ構文は通すよう練られています。&lt;/p&gt;
&lt;p&gt;この手の防御は「イタチごっこ」です。セキュリティレビューを重ねるたびに想定外の抜け道が見つかり、その都度検出パターンを強化してきました。正当な操作は通しつつ、秘密情報の読み取りだけを確実に止める。その考え方を、次の小節でコード例とともに示します。&lt;/p&gt;
&lt;p&gt;設計判断（ADR）も明文化しています。たとえば WebSearch と WebFetch はドメイン許可リストの対象外としました。許可リストは「LLM が Bash コマンドで URL をハルシネートする」ことを防ぐためのもので、Web 閲覧（読み取り）とは脅威モデルが異なるからです。ただし取得 URL は秘密パターンをスキャンし、クエリ経由のデータ漏洩を防いでいます。また Codex MCP は本エージェントでは有効化していません。Codex は独自のシェル実行エンジンを持ち Claude Code の deny rule を迂回するため、human-in-the-loop のない Slack bot では受け入れ不可と判断し、フックでブロックしています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PreToolUse hook：fail-closed の安全網&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
# PreToolUse security hook — deterministic enforcement layer
# Exit codes: 0 = allow, 2 = block
set -euo pipefail

# 0=allow / 2=block 以外で落ちたら block を返す（クラッシュ時に許可しない）
trap &amp;#039;ec=$?; if [[ $ec -ne 0 &amp;amp;&amp;amp; $ec -ne 2 ]]; then
    echo &amp;quot;{ decision: block, reason: hook failed (exit $ec) — blocking for safety }&amp;quot;
    exit 2
fi&amp;#039; EXIT&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注：上記の echo は可読性のため簡略表記にしています。実装では &lt;code&gt;decision&lt;/code&gt; / &lt;code&gt;reason&lt;/code&gt; を含む JSON を出力します。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;シークレットファイル検出のパターン&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;正当な jq フィールドアクセスは通しつつ、鍵ファイルの読み取りだけを止めます（一部のパスは可読性のため簡略表記）。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;secret_file_patterns=(
    &amp;#039;.env(boundary)&amp;#039;
    &amp;#039;.env.&amp;#039;
    &amp;#039;&amp;lt;pathchar&amp;gt;.pem(boundary)&amp;#039;
    &amp;#039;&amp;lt;pathchar&amp;gt;.key(boundary)&amp;#039;
    &amp;#039;credentials.*.json&amp;#039;
    &amp;#039;/proc/&amp;lt;pid&amp;gt;/environ&amp;#039;
)
# .env.example / process.env.X / jq の .fields.parent.key は許可（ファイル読みではない）&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ポイントは boundary（境界）と pathchar（パス文字）の条件で「ファイルパスとして現れる鍵ファイル」だけを対象にし、&lt;code&gt;{ key: .key }&lt;/code&gt; のようなオブジェクトアクセスを誤検知しないことです。&lt;/p&gt;
&lt;h2&gt;計測：PostToolUse hook × DX で効果を測る&lt;/h2&gt;
&lt;p&gt;「導入したが効果は？」に答えられなければ本番運用は続きません。私たちは、エージェントのツール実行を PostToolUse hook で捕捉し、DX にイベント送信しています。これにより「どのスキルやツールが、誰に、どれだけ使われたか」を継続的に計測できます。&lt;/p&gt;
&lt;p&gt;設計には三つのポイントがあります。第一に fail-open です。計測はエージェントの動作を妨げてはいけないので、どんなエラーでも黙って正常終了します。セキュリティの PreToolUse が fail-closed なのとちょうど対照的で、層ごとに設計思想を逆にしているのが要点です。第二にセッション内重複排除で、同じイベント型は1セッションに1回だけ送ります。第三にメール解決で、Slack コンテキストからユーザーを特定し、bot やスケジュール実行ではフォールバックの共通アドレスに振り替えます。&lt;/p&gt;
&lt;p&gt;興味深いのは、送信するだけでなくエージェント自身が DX のメトリクスを読むことです。チームスコアは MCP 経由で直接クエリし、Core4（Effectiveness / Impact / Quality / Speed）や sentiment を参照します。これにより、エージェントが自分の置かれたチームの開発者体験データを踏まえて振る舞える素地ができます。なお、コメント API は個人のメールを含むため、リポジトリに保存せず Slack にも出さず、必要時にオンデマンドで取得して 1on1 準備などの参照にのみ使う、という PII 取り扱いを徹底しています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;フロー：PostToolUse hook の処理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/285bd51b-diagram1-466x1024.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これによりダッシュボード側でスキル別・ツール別の利用状況を集計できます。&lt;/p&gt;
&lt;h2&gt;エージェントに「記憶」を持たせる：ナレッジの自動メンテナンス&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/a9873223--2026-06-29-8.39.16-1024x216.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ドメイン知識は Slack と Notion に散在し、放置すればエージェントは古い情報で答えてしまいます。私たちはこれをスケジュールジョブで継続的に更新しています。日次のチャンネルナレッジ抽出ジョブ（対象チャンネルの整備後に本格有効化予定）は、前日の人間同士の会話を読み、ドメインに関わる知識をスキルやナレッジとして抽出・提案します。&lt;/p&gt;
&lt;p&gt;さらに、エージェントが対応した Slack スレッドを見直して有用な知識を登録し、既存エントリの更新・削除を整理する LangMem ベースの自動メンテナンスも回しています。RAG で都度検索するだけでなく、記憶そのものを継続的に手入れする発想です。&lt;/p&gt;
&lt;p&gt;加えて「AI が読みやすいドキュメント構造」への投資も行いました。リポジトリ一覧や監視チャンネル一覧を機械可読な形で同期し、エージェントが参照しやすくしています。PCPにはリポジトリだけで約50個ほどあるのでこのような情報を整理することがスタートでした。知識の鮮度・ノイズ・ハルシネーションは依然として課題ですが、人手の更新に頼らない仕組みを基盤として持てたことが大きな前進でした。&lt;/p&gt;
&lt;h2&gt;自律スケジューラー：何が毎日・毎週動いているか&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/949eed46-blog2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;pcp-agent は Slack メンションでの起動だけでなく、cron ベースのスケジューラーで自律的にも動きます。これが「トリガーベースで人間のアクションを必要としない」というビジョンの実装そのものです。週次のダッシュボード要約やアラートダイジェストは、誰かが依頼しなくても毎週決まった時刻に投稿されます。&lt;/p&gt;
&lt;p&gt;ジョブ定義は宣言的な YAML で、タイムゾーン・タイムアウト（active_deadline_seconds）・リトライ（backoff_limit）・多重起動抑止（concurrency_policy: forbid）・出力先 Slack チャンネルを指定できます。中心は「PCP Daily Schedule」で、平日毎朝9時(JST)に４つのルーティンを順番に実行し、各完了後に中間結果を Slack に投稿、最後に「本日のまとめ」を出します。当初は別々のジョブだったものを、一つの統合ワークフローに集約しました。このループを回すこと自体で、日々の会話からナレッジが溜まり、スキルも更新されていく、そうした自己更新的なループとして設計しています。&lt;/p&gt;
&lt;p&gt;スケジュール例: &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ジョブ&lt;/th&gt;
&lt;th&gt;スケジュール&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pcp-daily-schedule&lt;/td&gt;
&lt;td&gt;平日 9:00&lt;/td&gt;
&lt;td&gt;４タスク統合：①リリースノート生成 ②対象リポジトリ追跡（新スキル取込提案）③既存スキル改善提案 ④LangMem ナレッジ前日分メンテ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;xxxx-weekly-dashboard-summary&lt;/td&gt;
&lt;td&gt;毎週月 10:30&lt;/td&gt;
&lt;td&gt;各チームダッシュボードの週次サマリ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yyyy-weekly-alert-digest&lt;/td&gt;
&lt;td&gt;毎週月 11:00&lt;/td&gt;
&lt;td&gt;各チームアラートの週次ダイジェスト&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: xxxx-weekly-alert-digest
  enabled: true
  cron: &amp;quot;0 11 * * 1&amp;quot;               # Every Monday 11:00 (JST)
  prompt_file: &amp;quot;config/prompts/xxxx-weekly-alert-digest.md&amp;quot;
  timezone: &amp;quot;Asia/Tokyo&amp;quot;
  active_deadline_seconds: 600     # 10分でタイムアウト
  backoff_limit: 1                 # 失敗時に1回リトライ
  concurrency_policy: forbid       # 前回実行中ならスキップ&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ポイントは、スケジューラーが単なる cron 以上であることです。タイムアウト、リトライ、多重起動抑止といったジョブ制御を持たせることで、自律実行を本番で安全に回せるようにしています。&lt;/p&gt;
&lt;h2&gt;実際のユースケース&lt;/h2&gt;
&lt;p&gt;pcp-agent の能力はスキルとしてファイル化され、実運用で次のようなユースケースに使われています。いずれも「決まった手順だが人手を食う」調査・集計・トリアージ作業です。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ユースケース&lt;/th&gt;
&lt;th&gt;スキル&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;障害・アラート調査&lt;/td&gt;
&lt;td&gt;investigate-alert&lt;/td&gt;
&lt;td&gt;アラートを起点に関連ログ・メトリクスをたどり一次切り分け&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;日次トリアージ&lt;/td&gt;
&lt;td&gt;xxxx-daily-triage&lt;/td&gt;
&lt;td&gt;XXXXチームの毎日の異常検知とトリアージ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;問い合わせ調査&lt;/td&gt;
&lt;td&gt;investigate-inquiry&lt;/td&gt;
&lt;td&gt;問い合わせをドメイン知識付きで調査&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;週次サマリ&lt;/td&gt;
&lt;td&gt;yyyy-weekly-alert-digest&lt;/td&gt;
&lt;td&gt;各チームのアラート・ダッシュボードの週次要約&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PR監視・Issue起票&lt;/td&gt;
&lt;td&gt;watch-pr / issue / jira&lt;/td&gt;
&lt;td&gt;レビュー補助と起票&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;この中でも最初のわかりやすいユースケースは調査のユースケースでした。&lt;/p&gt;
&lt;p&gt;参考例として、Alert（トリガー） → Datadog（データ集約） → Slack / Notion等 (データ可視化・レポート) という流れです。すでに多くのエンジニアのローカル環境にはデータ調査スキルが存在すると思います。そのスキルをリモートに持ってきてトリガーさせるだけで一定の時間削減が実現でき、そしてローカルに存在するスキル（属人性）をリモートに持ってくることで誰でも同じ体験を実現することができます。&lt;/p&gt;
&lt;p&gt;そして、これらのユースケースが「実際にどれだけ使われているか」を DX で可視化しています。どのユースケース（スキル）が、どのチームで、どれくらい使われたかをダッシュボードで追跡できます。エージェントの価値を「何ができるか（能力）」ではなく「何に使われているか（採用）」で測るのが狙いです。&lt;/p&gt;
&lt;h2&gt;プラットフォームとしての強み：1つのbotではなく、エージェント基盤&lt;/h2&gt;
&lt;p&gt;ここまで pcp-agent という1つのエージェントを見てきましたが、本当の狙いは個別のbotを作ることではありません。私たちが作ったのは remote-claude という再利用可能なプラットフォームで、pcp-agent はその上で動く1テナントにすぎません。同じ基盤の上に、SRE 向けの remote-claude-sre、ワークフロー自動化(n8n)向けの remote-claude-n8n が並んで動いており、data platform 領域へも展開しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/bf88e1a3-diagram2-1024x298.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;プラットフォームと各エージェントの責務は明確に分かれています。共有基盤(remote-claude)が提供するのは、Slack統合、セッション隔離(copy-on-write)、cronスケジューラー、auth-proxy、PreToolUse/PostToolUseフックの仕組み、PIIリダクション、ランナーのリソース制限(メモリ・PID・CPU)といった「どのエージェントにも共通して必要な土台」です。各テナントが持つのは、自分のドメインに固有の workspace/.claude、つまりCLAUDE.md、スキル、ルールだけです。&lt;/p&gt;
&lt;p&gt;この設計の効きどころは、新しいエージェントを立ち上げるコストの小ささです。共有設定は base.yaml に集約され、各エージェントは差分だけを上書きします。テンプレート(remote-claude/template)をコピーし、ドメイン知識をスキルとして足せば、Slack起動・隔離・スケジューラー・セキュリティ・計測がすべて最初から揃った状態で新しいテナントが立ち上がります。「決済の常駐エージェント」で作った仕組みが、そのまま他のチームにも転用できるわけです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# remote-claude/config/base.yaml （全エージェント共通、各エージェントが上書き）
default_model: sonnet
use_proxy_network: true              # ランナーに権限を与えずオーバーレイで経路制御
session_policy:
  session_ttl_minutes: 10080         # 7 days
  runner_memory_limit: &amp;quot;4g&amp;quot;
  runner_pids_limit: 256
  runner_idle_timeout_minutes: 30
slack_output_redaction: false        # PIIリダクションをデプロイ単位でONにできる&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;セキュリティや計測のような「外側で固める」部分をプラットフォーム側に持たせることで、各チームはドメイン知識の記述に集中でき、かつ全社的に一貫した安全性・可観測性を担保できます。これがエージェントを「点」ではなく「基盤」として広げる強みです。ちなみに remote-claude はもともと SRE 向け（cc-sre）に生まれ、そこから複数ドメインをホストする汎用プラットフォームへと一般化されていきました。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;半年運用して、いくつか確かな手応えがありました。まずスキル化は効きます。ドメイン知識を SKILL.md としてコード管理すると、エージェントの振る舞いが再現可能になり、レビューやテストの対象にできます。エージェント自身に新しいスキルを作らせるループも回り始めました。&lt;/p&gt;
&lt;p&gt;もっとも、正直なところ最大の課題はユースケースの深掘りだと感じています。問い合わせのための調査のように、各所のパーツは AI に代替できる部分が出てきましたが、まだ人間がトリガーしている場面が多く、真に AI を軸に置いたフローになっているとは言えません。エージェントが受け取れるトリガーをさらに増やし、ユースケース全体を「AI 前提」のプロセスへ置き換えていきたいと考えています。&lt;/p&gt;
&lt;p&gt;今後は、このエージェントを一つに統合するか、ドメインごとに分散させるかをユースケース次第で見極めていきます。LLM のコンテキストは有限なので、独自のワークフローが必要なユースケースが増えれば分散させる方針です。&lt;/p&gt;
&lt;p&gt;加えて、社内でこうしたエージェントが増えていくほど、個々の精度以上に「フリート全体をどう統治し、どう観測するか」が重要になります。エージェントごとに権限・ポリシー・監査がバラバラだと、事故も非効率も増えていきます。そこで、ガバナンスと各エージェントの振る舞い・コスト・利用状況などを横断的に把握する仕組みを、今後プラットフォーム側で整えていきたいと考えています。&lt;/p&gt;
&lt;p&gt;「AI エージェントを作ってみた」記事は数多くありますが、決済ドメインの本番チームに常駐させ、セキュリティを多層で固め、効果を計測しながら回すところまでやると、考えるべきことは一気に増えます。私たちが半年で取り組んだのは、その「面倒な部分」を実コードで埋めてきた記録です。そして、その面倒な部分を共有基盤（remote-claude）に寄せたことで、決済で作った仕組みを SRE や n8n へ横展開できる「エージェント基盤」に育ちつつあります。これから業務にエージェントを組み込む方にとって、安全性と計測をプラットフォーム側でどう設計するかの一例になれば幸いです。&lt;/p&gt;
</content:encoded></item><item><title>AI-Native な開発の実践に向けて</title><link>https://engineering.mercari.com/blog/entry/20260630-b22667b4d6/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260630-b22667b4d6/</guid><description>&lt;p&gt;こんにちは becosuke です。メルカリ NFT と、その上で立ち上げている新規サービスの Backend を担当しています。この記事は「Merpay &amp;amp; Mercoin Tech Openness Mont [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 30 Jun 2026 10:00:26 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは becosuke です。メルカリ NFT と、その上で立ち上げている新規サービスの Backend を担当しています。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay &amp;amp; Mercoin Tech Openness Month 2026&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の 20日目の記事です。&lt;/p&gt;
&lt;p&gt;この記事では、私たちメルカリ NFT チームがこの4ヶ月ほどで取り組んできた AI-Native な開発について書きます。1月末から、開発のやり方そのものを大きく作り変えてきていて、最終的には人の手をできるだけ介さずにサービスを作り続けられる状態を目指しています。その過程で得た学びを、似たことに取り組もうとしている方に持ち帰ってもらえたらと思っています。&lt;/p&gt;
&lt;p&gt;先に結論だけ書いておきます。私たちがリソースを集中させるべきだと判断したのは「正解」そのものではなく、何が正しく何が誤りかの「判断基準」(&lt;a href=&quot;#anno1&quot; title=&quot;注1&quot;&gt;注1&lt;/a&gt;)でした。AI を動かすための機構は、これからどんどん外から手に入るようになります。けれど判断基準だけは、その案件ならではの価値観そのものなので、外注できません。だから私たちは、機構を作ることよりも、その上に載せる判断基準を残すことに時間をかけました。なぜそう考えたのか、そして実際に何をやったのかを、順を追って書いていきます。&lt;/p&gt;
&lt;h2&gt;この案件の位置付け&lt;/h2&gt;
&lt;p&gt;私たちは、Non-Fungible Token（NFT）の売買を行うマーケットプレイスである メルカリ NFT を既存サービスとして運営しています。いま新しく立ち上げているのは、この既存の メルカリ NFT のコードベースを土台にした新規サービスです。&lt;/p&gt;
&lt;p&gt;この案件には、2つの顔があります。1つは、新しいプロダクトの立ち上げであること。もうひとつは、AI-Native な開発のあり方そのものを試す Proof of Concept（PoC）の場であることです。&lt;/p&gt;
&lt;p&gt;メルカリには会社全体として AI-Native 化を進める方針があり、社内基盤もすでにしっかり整っています。その方向性には私たちも強く共感しているのですが、今回はあえて一度そこを離れて、前提から組み直すとどうなるかを試してみることにしました。理由は3つあります。&lt;/p&gt;
&lt;p&gt;1つ目は、AI に spec から開発を駆動させるには、その spec を解釈するための価値基準や判断の根拠が前提として必要だと考えたからです。これがいちばん大きな理由でした。社内基盤の上に乗ったとしても、AI が「何を基準に判断すべきか」を持たないうちは自走できません。基盤の良し悪しの問題ではなく、その手前で価値基準と過去の判断の根拠を溜める前工程が、私たちにはまだ必要だったということです。だからまずはそこを溜め込む段階を踏んでから、成熟した先で既存の仕組みに接続する、という順序を選びました。&lt;/p&gt;
&lt;p&gt;2つ目は、既存の正解の上に乗ると、その枠内での最適化しかできないからです。この案件は新しいプロダクトの立ち上げであると同時に AI-Native 開発の PoC でもあるので、いったん既成の枠を外して前提から組み直すことに意味があります。AI-Native として本当は何が必要なのかを検証したかったので、別のアプローチで組み立て直すことで見えてくるものを優先しました。社内基盤を使うプロジェクトと、別解を試すプロジェクトが並走すれば、会社全体として AI-Native 開発の幅も広がります。&lt;/p&gt;
&lt;p&gt;3つ目は、Claude Code 本体の進化に身軽についていきたかったからです。Claude Code には plugin や Agent Teams のような、開発の組み立て方そのものに関わる機能追加が数ヶ月単位で入っていて、AI に開発をさせる足場の作り方が次々に変わっていきます。こうした新機能をすぐ取り込めるよう、&lt;code&gt;CLAUDE.md&lt;/code&gt; と rules、skill、hooks だけの軽い構成で試したかったという事情もあります。標準機能だけで素朴に組むことで、何が効果を生んでいるのかの切り分けもしやすくなります。&lt;/p&gt;
&lt;p&gt;念のため書いておくと、この独自路線は社内基盤と対立するものではありません。あくまで今の段階での選択であって、蓄積が成熟したら、社内基盤の考え方を取り込んだり、逆にこの案件で得たやり方を社内のほかの案件でも使えるようにしたり、という双方向の合流を視野に入れています。&lt;/p&gt;
&lt;h2&gt;この記事でいちばん伝えたいこと：機構は外から、判断基準は自前で&lt;/h2&gt;
&lt;p&gt;具体例に入る前に、この記事を貫く中心の考え方から書きます。&lt;/p&gt;
&lt;p&gt;AI agent は、モデルと、その周りを取り囲む機構（ハーネス）でできていると言われています（Agent = Model + Harness）。このモデルを取り囲む環境・制約・フィードバックを設計する規律は、2026年2月に Mitchell Hashimoto 氏がブログ(&lt;a href=&quot;#anno2&quot; title=&quot;注2&quot;&gt;注2&lt;/a&gt;)で &amp;quot;engineer the harness&amp;quot; と名付けて以降、ハーネスエンジニアリングと呼ばれて急速に広まりました。prompt engineering から context engineering、そして harness engineering へ、という発展の系譜です。&lt;/p&gt;
&lt;p&gt;ハーネスにはさらに、モデル提供元が出荷する内側ハーネス（Agent SDK など）と、その上に私たちが組み立てる外側ハーネスがあります。Thoughtworks の Birgitta Böckeler は、外側ハーネスを agent の前に指針や制約を渡す Guide / Guardrail（feed-forward）と、agent の出力を観測してフィードバックを返す Sensor（feedback）に分けたうえで、ハーネスの議論は禁止やガードレールの側に偏りがちで、観測の側はむしろ手薄になりやすいと指摘しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/3e1db6c1-fig01-harness-structure.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ハーネスには大きく3つの役割があります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正解を示す こと&lt;/li&gt;
&lt;li&gt;結果を観測する こと&lt;/li&gt;
&lt;li&gt;危ない操作を止める こと&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このどれもが、AI に安心して任せるために欠かせません。私たちはこの偏りに違和感を持ち、3つの役割を等しく支える機構がどこにあるのかを考えました。&lt;/p&gt;
&lt;p&gt;ここで私たちが立てた仮説はこうです。この3つを支える機構は、これからプラットフォーム側が整えてくれる。評価ループ、hooks、権限のプロンプト、サンドボックスといった機構は、汎用部品として外から提供される方向に向かっている。実際、危ない操作を事前に止める hooks や権限のプロンプトは Claude Code 本体に組み込まれていて、自前で実装するものから既製品として使うものへと、徐々に移ってきています。だとすれば、その機構づくりは私たちのチームでは「できるだけやらない部分」として切り分けてよい、と考えました。&lt;/p&gt;
&lt;p&gt;一方で、3つの役割すべてに共通して必要になるものがあります。何を正解とし、どこからを合格とし、何を危険とするか、それぞれの判断基準です。これは、この案件ならではの価値観そのものなので、外からは提供されません。観測エンジンや停止機構は買えても、その基準は各案件固有の判断の産物で、外注できない。整理すると、三本柱のすべてについて「機構は買える／判断は自前」という構図になります。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;機構&lt;/th&gt;
&lt;th&gt;判断基準&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;正解を示す&lt;/td&gt;
&lt;td&gt;context を供給する足場&lt;/td&gt;
&lt;td&gt;何を正解とするか&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;結果を観測する&lt;/td&gt;
&lt;td&gt;評価ループ・eval 基盤&lt;/td&gt;
&lt;td&gt;どこからを合格とするか&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;危険を止める&lt;/td&gt;
&lt;td&gt;hooks・権限・サンドボックス&lt;/td&gt;
&lt;td&gt;何を危険とするか&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;出どころ&lt;/td&gt;
&lt;td&gt;プラットフォームが提供（買える）&lt;/td&gt;
&lt;td&gt;案件固有（自前で持つしかない）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;なぜ「判断基準」を集めるのか&lt;/h3&gt;
&lt;p&gt;個別の正解を一つひとつ集めていくより、判断基準を与えるほうが、応用が効きます。1つの正解は点にすぎませんが、判断基準はその点を生み出す規則です。だから、まだ出会っていない状況に遭遇しても、基準さえあれば AI は自ら考えることができます。&lt;/p&gt;
&lt;p&gt;例えばデータの書き換えについて、私たちは「InsertOrUpdate（UPSERT）は使わず、Read で存在を確かめてから INSERT か UPDATE に分岐する」という方針を残しています。UPSERT は全カラムを指定しないと意図しないリセットが起きるためです。この1つの方針が、AI が新しい書き込み処理を書くときの指針にもなり、reviewer が UPSERT の紛れ込みを一次チェックする観点にもなります。Anthropic の言葉を借りれば(&lt;a href=&quot;#anno3&quot; title=&quot;注3&quot;&gt;注3&lt;/a&gt;)、正解を示すというのは「モデルの望ましい挙動を最も生みやすいコンテキストを与えること」で、観測するときの基準もそこから派生します。同じ判断基準が、正解を示す側にも、結果を観測する側にも、両方の基盤になっているわけです。&lt;/p&gt;
&lt;p&gt;機構と判断基準には、もうひとつ大事な違いがあります。「寿命」が違うということです。Anthropic 自身も、ハーネスの各構成要素は「いまのモデルにこれができない」という前提のうえに置かれているので、その前提は stress test して、要らなくなったら外していく対象だと述べています。(&lt;a href=&quot;#anno4&quot; title=&quot;注4&quot;&gt;注4&lt;/a&gt;)実際、ある世代のモデルで必要だった構成要素が、次の世代では不要になったり、別の組合せに置き換わったりしていく例も出てきています。もちろん、test のようにモデルがどれだけ進化しても外せない観測の機構もあるので、すべての機構が一律に失効していくわけではありません。それでも、機構の側には入れ替わりがある一方で、目標と正しい判断基準を与える側は廃れずに残ります。だからこそ判断基準は、特定のツールの設定ファイルに埋め込むのではなく、自分たちのリポジトリに、可搬な形で持っておくべきだと考えました。&lt;/p&gt;
&lt;p&gt;ここからは、この考え方を実際の開発でどう形にしたのか、4つの具体例と、それによる変化、それらを社内のほかの案件で共有する方法について書いていきます。どの取り組みも、機構を作り込むことよりも、その上流にある判断基準を残すことに重点を置いた話として読んでもらえると、つながりが見えてくると思います。&lt;/p&gt;
&lt;h2&gt;1. コードの一貫性：言葉の意味や正しいやり方を1つに固定する&lt;/h2&gt;
&lt;p&gt;1つ目は、コードの一貫性です。&lt;/p&gt;
&lt;p&gt;既存のコードベースを途中から AI-Native に切り替えるために、最初にやったことは大量のリファクタリングでした。人間ならあまり気にしないようなちょっとした言葉のゆれが、AI の判断を狂わせてしまうので、意味を1つに揃えておく必要があります。同じ概念が場所によって違う名前で呼ばれていたり、書き方が場所ごとに違ったりすると、AI はそのたびに周辺から「この案件ではどう書いているか」を推測することになり、出力がぶれます。&lt;/p&gt;
&lt;p&gt;そこで、二段階で揃えました。&lt;/p&gt;
&lt;p&gt;まず、同じ言葉が複数の意味を指している状態や、その逆をなくして、「1つの概念には1つの名前、1つの名前には1つの意味」というところまで徹底しました。命名は AI がコードベースの全体像を掴むときに最初に頼る手がかりなので、ここがぶれていると、その先のどんな指示や設計書を渡しても精度が落ちてしまいます(&lt;a href=&quot;#anno5&quot; title=&quot;注5&quot;&gt;注5&lt;/a&gt;)。&lt;/p&gt;
&lt;p&gt;次に、正しいやり方が何通りもあるものについて、どれでやるかを決めてルールとして書き残しました。例えばエラーの扱い、ページネーションの実装、enum の持ち方のように、「正解は複数あるが、案件としてはこの形に揃える」というものをルール化しておく。こうしておくと、今後同じような課題にぶつかったとき、AI は解決の方法を迷わず選べます。前の章で書いた「1つの正解（点）」ではなく「判断基準（規則）」を残す、というのを地でいく作業でした。&lt;/p&gt;
&lt;p&gt;この期間、構造を整えながら、差し引きで万単位の行数のコードを減らしました。機能を削ったのではなく、重複や不要な抽象を整理して、同じことをより少ないコードで表すようにした結果です。AI が読まされる量が減り、1つの概念にあたる場所が一箇所に集まることで、AI に渡す文脈はノイズの少ないものになります。コードベースを整えること、それ自体が、AI のための環境整備の土台になる作業だったと考えています。&lt;/p&gt;
&lt;h2&gt;2. 判断を残す仕組み：spec / design と RDR / ADR&lt;/h2&gt;
&lt;p&gt;2つ目は、判断を残す仕組みです。&lt;/p&gt;
&lt;p&gt;私たちは、仕様書（spec）には「いまどうなっているか」だけを書いて、「なぜそう決めたのか」「どの案を捨てたのか」という判断は別のファイルに分けて残しています。判断の経緯を残しておかないと、しばらく経ってから「なぜここをこう決めたんだっけ」と人も agent も何度も悩み直すことになります。一度悩んだことを文字にしておけば、次の人や AI はその上から考え始められます。&lt;/p&gt;
&lt;p&gt;設計（design）の判断には、もともと ADR（Architecture Decision Records）という考え方があり、社内基盤でも使われています。私たちはこれを仕様の側にも持ち込んで、なぜその仕様にしたのかという判断を RDR（Requirements Decision Records）という専用のファイルに残す方針にしました。spec 本体は「現時点の仕様」を表し、RDR は「そこに至るまでの判断の積み重ね」を表す、という役割分担です。&lt;/p&gt;
&lt;p&gt;ADR や RDR を「どう書くか」「どんなときに新しいレコードを作るか」といった作法は、&lt;code&gt;decision-record&lt;/code&gt; という skill にまとめてあります。記録そのものは各案件に固有のものなので持ち運べませんが、「記録の作り方」のほうは共通の手順として配ることができます。&lt;/p&gt;
&lt;p&gt;判断の経緯や却下した代替案を本文に混ぜると、「今の仕様はどれか」を読み取りにくくなってしまいます。現時点の要求だけを spec に残し、判断の層を RDR に切り出すことで、レビュー対象をノイズから守っています。&lt;/p&gt;
&lt;p&gt;AI 側から見ても、この分離には意味があります。エージェントは普段は現状（spec / design）だけを読み、仕様を変えるときにだけ、紐づく RDR / ADR を必要に応じて引きにいきます。こうすると、AI に渡す context には「いまどうあるか」だけが入り、過去の議論や却下案がノイズとして混ざらない状態を保てます。&lt;/p&gt;
&lt;h3&gt;Git を origin に、Notion を読む場所に&lt;/h3&gt;
&lt;p&gt;ドキュメントの原本はすべて Git に置いていて、Notion には自動で同期したコピーを置いて、読む場所やコメントをつける場所として使っています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/5601286f-fig02-git-notion.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Notion をそのまま origin にすると、ドキュメントがチームの同意なしに誰でも編集できる状態になり、設計や実装との整合性が破綻します。逆に Git を origin にすると、Product Manager（PdM）や Designer に GitHub の Pull Request（PR）レビューを強いることになり、レビュー参加の敷居が上がってしまう。そこで、origin は Git に固定したうえで、Notion 側は「読む場所」「コメントをつける場所」と割り切りました。これで、非エンジニアのレビュアーは使い慣れた Notion でレビューに参加できますし、変更履歴は git log に残ります。&lt;/p&gt;
&lt;p&gt;Git から Notion への同期は私たちでスクリプトを書いていますが、両者をつなぐ構成自体はありふれたもので、技術的には難しい話ではありません。むしろ難しかったのは、どちらを origin にするかという方針を最初に決めて、運用のなかでぶれずに守ることのほうでした。&lt;/p&gt;
&lt;h3&gt;レビューを skill に蓄積する&lt;/h3&gt;
&lt;p&gt;そして、集まったコメントは職種ごとのレビュー用 skill に反映していきます。プロダクトの観点もデザインの観点も、一度もらった指摘によって、次からは AI による一次チェックができるようになります。&lt;/p&gt;
&lt;p&gt;コメントを集めるところには、1つ実装上の落とし穴がありました。Notion のコメントは、解決済み（resolved）にするとコメント API からは取得できなくなります。レビューのコメントは指摘が反映されれば解決済みにしていくものなので、コメント API だけを見ていると、いちばん学びになる「対応済みの指摘」がごっそり抜け落ちてしまう。そこで私たちは、コメントを集めるときはコメント API ではなく履歴 API のほうから取得するようにしています。&lt;/p&gt;
&lt;p&gt;例えばプロダクトのレビュー用 skill は、いつも的確な指摘をくれるメンバーのコメントから育てています。他の案件からはなかなかレビューをお願いしづらい立場の方でも、その観点を AI がいつでも一次チェックとして返してくれるようになるので、価値が大きいと感じています。レビューがその場限りで消えずに AI の中へ積み上がっていくのも、判断基準を残すことの1つの形です。&lt;/p&gt;
&lt;h3&gt;spec → design → plan → issue のパイプライン&lt;/h3&gt;
&lt;p&gt;ここまで書いてきた仕様書や設計書、それにレビューの skill は、つなげて連続的に動かすこともできます。一つひとつを手で進めても十分機能しますが、つなげておくと、仕様書の変更がトリガーとなって実装まで自走させることもできます。&lt;/p&gt;
&lt;p&gt;私たちは3つのドキュメントを使い分けています。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ドキュメント&lt;/th&gt;
&lt;th&gt;役割&lt;/th&gt;
&lt;th&gt;対応する skill&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;spec&lt;/td&gt;
&lt;td&gt;仕様書&lt;/td&gt;
&lt;td&gt;spec-review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;design&lt;/td&gt;
&lt;td&gt;設計書&lt;/td&gt;
&lt;td&gt;design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;plan&lt;/td&gt;
&lt;td&gt;手順書&lt;/td&gt;
&lt;td&gt;plan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;それぞれに専用の skill があって、新しい仕様書が入ってくると、まず spec-review skill がその内容をレビューします。良さそうだと判断されたら、次に design skill で設計書をつくり、そこから plan skill で実装計画を立てます。plan skill は、もともとある plan モードを少し拡張したもので、立てた計画をファイルとしてリポジトリの中に残し、その手順の一つひとつに進捗のチェックがつくようになっています。リポジトリにファイルとして残しておくことで、session をまたいでも、別のメンバーが見ても、その計画と進捗をそのまま読める状態になります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/1d7e3fb9-fig03-pipeline.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最後の issue skill だけは少し毛色が違っていて、ドキュメントというより、できあがった計画を実装処理へ渡すための受け渡し役です。issue skill で GitHub issue を用意し、そこに plan ファイルへの参照を入れておくと、GitHub Actions がそれを拾って実装を進め、終わると PR を出してくれます。つまり、仕様書の変更がトリガーとなって、ここまで一気通貫で進められるというわけです。&lt;/p&gt;
&lt;p&gt;このパイプラインの土台になっているのは、それぞれの skill のなかに書き留めてきた判断基準です。spec-review が見るべき観点も、design や plan の組み立て方も、すべて私たちが少しずつ言葉にしてきたものです。パイプラインの組み立て自体は Claude Code の標準機能と GitHub Actions の組み合わせなので、目新しさはありません。新しいのは、機構の側ではなく、その上に積み上げた基準のほうです。&lt;/p&gt;
&lt;h2&gt;3. 検証ループ：推論ではなく、決定論を用意する&lt;/h2&gt;
&lt;p&gt;3つ目は、テストの話です。AI に開発を任せるとき、「ちゃんと動いていることをどう確かめるか」は、前章で挙げた三本柱のうち観測の役割にあたります。&lt;/p&gt;
&lt;p&gt;ブラウザを動かして操作をひと通り確かめる、通しの End-to-End（E2E）テストを思い浮かべてください。AI にこれをやらせる方法として、Claude のように、その場でブラウザを直接操作してもらうやり方があります。ただし、この方法は毎回 AI の推論が入るので、結果がゆれますし、実行のたびにトークンも消費します。テストの本来の目的は「同じ入力なら同じ結果になること」を確かめることなのに、確かめる側がゆれてしまうと、本末転倒です。&lt;/p&gt;
&lt;p&gt;そこで私たちは、AI を活用するのはテストの定義を作るところまでにして、実行のほうは推論を挟まない確定的なやり方にしました。AI に新しい機能の操作手順と期待される結果を書いてもらい、書き終わったら、その後の実行ループからは AI を抜く、という分け方です。こうすると、ゆれる可能性のある推論から決定論に変わって、同じ入力なら必ず同じ結果になります。Continuous Integration（CI）でも手元でも同じスクリプトが同じように走るので、差分が出れば、それは AI のゆらぎではなく、プロダクトに本当の変化があったサインだと、はっきり切り分けられます。&lt;/p&gt;
&lt;p&gt;実際に、メルカリ NFT の主要なフローを Playwright で組みました。出品、購入、オファー、承諾、買取、配送といった一連の流れを、売り手と買い手を切り替えながら通しで実行します。これを「regression test を実施して」の一言で AI が全シナリオを走らせる状態にしてあるので、コマンドを覚える必要はありません。&lt;/p&gt;
&lt;p&gt;確かめ方そのものを「正解」として固定するというのも、判断基準を残すことの1つの形です。AI に任せる範囲を広げるほど、人の代わりに結果を観測する機構は大事になります。そのときに、観測する側まで AI に任せきってしまうと、ゆらぐ判定でゆらぐ実装を確かめるという不安定な構造になってしまいます。確定的に動く部品を観測の側に据えることで、AI に任せる範囲を安心して広げることができるようになりました。&lt;/p&gt;
&lt;h2&gt;4. 個人情報（PII）の多層防御&lt;/h2&gt;
&lt;p&gt;4つ目は、セキュリティ、とくに個人情報の扱いです。社内でも注目されているテーマです。&lt;/p&gt;
&lt;p&gt;commit や PR にうっかり個人情報が混入することは、人でも AI でも起こりえます。私たちは commit も PR も、それに Jira チケットの作成も AI にやってもらっているので、AI に開発を広く任せるのであれば、個人情報の保護にも AI を活用するのは自然な流れだと考えました。&lt;/p&gt;
&lt;p&gt;そこで、一連の流れに対して個人情報を保護する機構を、三段の防御として載せることにしました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/b99b7497-fig04-pii-defense.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;三段にした意図は、どこか一段が落ちても別の段がカバーする構成にしたかったからです。一段目をすり抜けても二段目で止まり、それも越えてしまったら三段目で後始末できる。完全にミスをなくすことを期待するのではなく、ミスは起こる前提で複数の網を重ねる、というスタンスです。&lt;/p&gt;
&lt;p&gt;もうひとつ、skill によるコメントなどで個人情報を引用するときは、先頭の数文字だけ残してあとはマスキングするルールにしています。AI のコメントは、PR の本文に残ったり、コミットメッセージや別ドキュメントに引用されたりと、思わぬ場所に流れていきます。該当箇所が特定できる程度に頭を残してそれ以外を伏せておけば、AI があとでどこかに書き残すときにも、本物の個人情報が混入することはありません。&lt;/p&gt;
&lt;p&gt;この例では、機構（hooks や CI のスキャン、復旧手順）も判断基準（何を個人情報とみなすか）も、どちらもプラットフォームでなく私たち側で用意したものです。それでも、機構と判断基準を別物として扱い、判断基準を機構の上流に置く、という構図は変わりません。両者を区別して別々に扱う、という構図自体は崩れないということを、この例はあらためて思い出させてくれました。&lt;/p&gt;
&lt;h2&gt;どれくらい変わったか&lt;/h2&gt;
&lt;p&gt;ここまでに紹介した4つの取り組みは、実際にチームの開発にどんな変化をもたらしたのか。指標としては定量的に見られるものと、日々の運用感覚として感じる定性的なものの両面で、いくつかわかりやすい変化が出てきました。&lt;/p&gt;
&lt;p&gt;コードの一貫性を整え、開発の効率を上げる skill を入れていった結果、チーム全体の PR の数そのものが増えました。しかも、この案件は1月末にいったん開発チームを解散していて、それ以降はエンジニアが2人、プロダクトとデザインが1人ずつ、という少人数の体制です。チーム全体の流量が増えているうえに人数が少ないので、1人あたりで見れば何倍にもなっている計算です。&lt;/p&gt;
&lt;p&gt;これは AI を導入したから自動的に増えた、というよりは、AI が力を出せるようにコードや判断基準を先に整えてきた結果だと考えています。漫然と AI を導入するだけでは、同じ結果にはならなかったはずです。&lt;/p&gt;
&lt;p&gt;もうひとつ、PR の中身についても触れておきます。開発全体の PR 流量が増えている一方で、修正系の PR の割合は抑えられています。新機能や改善の PR の伸びが、修正系の伸びを上回っている、ということです。これは PR の乱造ではなく、品質を上げた上で流量も増えているという、非常に大きな成果だと思っています。&lt;/p&gt;
&lt;p&gt;それとは別に、アラートの調査を AI に任せるための skill（監視基盤を直接触れるようにしたもの）も整備しました。アラートの URL を渡すだけで原因を調べて修正までまとめてやってくれるようになりました。日々の運用感覚としても大きい変化でした。&lt;/p&gt;
&lt;h2&gt;共通と固有を分ける：ほかの案件に持っていくために&lt;/h2&gt;
&lt;p&gt;ここまで積み上げてきた仕組みは、この案件だけで閉じてしまうのはもったいないので、社内のほかの案件でも使えるようにしておきたいと考えました。そのためにやったのは、どこが共通で、どこがこの案件固有のものかを、はっきり分けておくことです。&lt;/p&gt;
&lt;p&gt;この「共通と固有を分ける」やり方は、たった1つの小さな判断から始まりました。社内共通のフレームワークである monorail をアップグレードしたときのことです。monorail のチームは、アップグレードの共通手順を &lt;code&gt;monorail-upgrade&lt;/code&gt; という skill で提供してくれています。ただ、私たちの案件には、それだけでは足りない固有の手順がありました。そこで、その名前のうしろに &lt;code&gt;-local&lt;/code&gt; をつけた &lt;code&gt;monorail-upgrade-local&lt;/code&gt; という skill を作って、固有の手順をそちらに置きました。この小さな切り分けが、いまの形の原型になりました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/520ff615-fig05-plugin-distribution.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;私たちのプロダクトは、Backend と Frontend が別々のリポジトリに分かれていますが、共通で使える skill は、1つの plugin にまとめて配っています。そして、それぞれのリポジトリに固有の部分は、その plugin を上書きする &lt;code&gt;-local&lt;/code&gt; な skill に入れています。こうしておくと、片方のリポジトリで skill を改善するともう片方にも横展開しやすくなりますし、skill の名前を見ただけで「どこからが共通で、どこからが案件固有か」が判別できます。&lt;/p&gt;
&lt;p&gt;このやり方は、Claude Code の &lt;code&gt;CLAUDE.md&lt;/code&gt; と &lt;code&gt;CLAUDE.local.md&lt;/code&gt; の考え方を応用したものです。plugin を案件ごとに上書きする仕組みは、Claude Code に機能として用意されているわけではなく、Feature Request として GitHub に issue が出ている段階です。そこで私たちは、&lt;code&gt;-local&lt;/code&gt; という名前のつけ方を決めて、その規約だけで共通と固有の境界を表すことにしました。人によってはすでに自然にやっていることかもしれませんが、これを暗黙のままにせず、命名ルールとして明示しています。&lt;/p&gt;
&lt;p&gt;大きな仕組みを最初から設計する必要はありませんでした。小さな判断を、次の人がそのまま使える形で残していけば、それが積み重なって、いつでも広げられる土台になります。&lt;/p&gt;
&lt;h2&gt;補：個人的な思想&lt;/h2&gt;
&lt;p&gt;ここからの話は、これまでの章とは少し性質が違います。この案件の特性から導かれた話というよりは、私個人の運用観の話だと思って読んでもらえると、はずれが少ないと思います。同じことをやろうとしている人が、これと違うやり方を選んでもなんら問題ありません。今回の記事に紛れ込ませているのは、この4ヶ月のなかで自分の中にまとまってきた考え方を、せっかくなので書き残しておきたかったからです。&lt;/p&gt;
&lt;h3&gt;専門知識は skill に集め、agent は薄いラッパーに保つ&lt;/h3&gt;
&lt;p&gt;Claude Code には、知識や手順をまとめておく skill と、独立した context を持って動かせる subagent という2つの仕組みがあります。コミュニティの best practice を眺めていると、機能領域ごとに専用の subagent を作って、そこに専門知識を持たせていくやり方をよく見かけます。私が選んでいるのはこれとは違うルートで、専門知識は skill にまとめて、agent はそれを呼ぶための薄いラッパーに留める という分け方です。&lt;/p&gt;
&lt;p&gt;agent と skill は対立する選択肢ではないと思っています。agent を作ってもよいのですが、agent には「この場面ではこの skill を、次にこの skill を呼ぶ」というルーティング情報だけ書く、と考えています。判断基準や手順、ドメイン知識といった本体は、すべて skill 側に書く。こうしておくと、同じ判断基準が複数の agent に重複して埋め込まれることがなくなりますし、skill を更新すれば、それを呼ぶすべての経路に反映されます。&lt;/p&gt;
&lt;p&gt;例えば Anthropic は、長時間動く agent harness の設計について、実装する generator agent とテストする evaluator agent を別の agent として分離することを推奨(注4)しています。生成する agent に自分の仕事を批判させるよりも、別の agent を評価役として厳しくチューンするほうがやりやすい、という理屈です。agent を切るとしたら、こういうメイン context からの隔離と独立した判断、それに伴う権限スコープが単独で必要なときが良さそうです。その場合も専門知識を埋め込まずに、必要な skill 群を呼ぶ薄いラッパーとして組みます。agent の固有価値は隔離・独立した判断・権限スコープであって専門知識そのものではない、というのが私の見立てです。専門知識は skill 側に集めたほうが、再利用しやすく、知識の重複も起こりません。&lt;/p&gt;
&lt;h3&gt;rewind ではなく、新しい session を fork する&lt;/h3&gt;
&lt;p&gt;Claude Code には、過去のやり取りを巻き戻して途中の状態から再開できる rewind という機能があります。便利な機能なのですが、私は意識的にあまり使わないようにしています。理由は単純で、rewind は context の rebase に近いところがあって、やってきた流れの一部が history から消える形になるからです。&lt;/p&gt;
&lt;p&gt;これは好みの問題で、git の rebase が悪いと言いたいわけではありません。git log がきれいになるメリットと、流れがそのまま残るメリットには、どちらにも価値があると思います。ただ私自身は流れを残しておきたいほうで、後から「あのとき何を試して、何で失敗したか」を辿れるほうに価値を感じます。&lt;/p&gt;
&lt;p&gt;やり直したいときは、rewind の代わりに新しい session を fork するようにしています。レビューしたいときも、進捗の区切りごとに分けたいときも、その都度別 session を立てます。session を分けたほうが、それぞれの context を小さく保てるので、結果的に動きも安定すると感じています。&lt;/p&gt;
&lt;h3&gt;plan ファイルと &lt;code&gt;TODO.local.md&lt;/code&gt; が session 間のバトンになる&lt;/h3&gt;
&lt;p&gt;session を多めに切る運用には、1つ前提があります。進捗を別 session に渡せる形で残しておく必要があるということです。これを担っているのが plan ファイルと、それからもうひとつ、&lt;code&gt;TODO.local.md&lt;/code&gt; というファイルです。&lt;/p&gt;
&lt;p&gt;plan ファイルのほうは、前章で書いた spec → design → plan → issue のパイプラインで出てくる、あの plan です。手順の一つひとつに進捗のチェックがついていて、ファイルとしてリポジトリに残っているので、別の session を立てても、最後にどこまで終わっていたかをそのファイル 1 枚で引き継げます(&lt;a href=&quot;#anno6&quot; title=&quot;注6&quot;&gt;注6&lt;/a&gt;)。同じことが git worktree を切って並列で動かしているときにも当てはまります。worktree A で進めていた作業を worktree B から続けることもできますし、agent 間でタスクを引き渡すときも、plan ファイルの該当箇所を指せば、お互いのなかで同じ進捗の理解が再現できます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TODO.local.md&lt;/code&gt; のほうは、個人レベルの todo を扱うためのファイルです。チケット化するほどではない、あるいはチケット化する前段階のもの、例えば「あとで気が向いたら直したい」「明日の朝いちで手を付ける」といったレベルの個人 todo を、ここに置いています。これを管理するための todo skill も用意していて、追加、完了マーク、一覧表示、コード内 TODO の集約(&lt;a href=&quot;#anno7&quot; title=&quot;注7&quot;&gt;注7&lt;/a&gt;)などができます。チケットほど重くなく、その場の独り言で消えてしまわない、中間地点のタスク管理に意味があります。&lt;/p&gt;
&lt;p&gt;plan ファイルと &lt;code&gt;TODO.local.md&lt;/code&gt; の2つで、チケットに上げるほどではない作業の進捗が、session や worktree、agent をまたいで運べるようになっています。これが、session を多めに切る私の運用を支える土台です。&lt;/p&gt;
&lt;h3&gt;中心の軸とのつながり&lt;/h3&gt;
&lt;p&gt;ここまで書いたのは私の運用観なので、章の頭で断ったとおり、違うやり方を選んでもなんら問題ありません。ただ、最初の章で書いた「機構は外から、判断基準は自前で」という軸を頭に置きながら振り返ると、1つ気付くことがあります。&lt;/p&gt;
&lt;p&gt;skill と plan ファイルと &lt;code&gt;TODO.local.md&lt;/code&gt; は、判断基準が宿る側にあるということです。判断基準や手順、進捗の文脈を、可搬なファイルとして残しているからです。一方の agent や rewind、session の制御は機構の側で、判断基準そのものを抱え込ませる対象ではない、と私は考えています。&lt;/p&gt;
&lt;p&gt;機構と判断基準を分けて、判断基準を機構の上流に置く、という構図を貫こうとすると、自然にこういう運用に寄っていくのではないかと思います。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;AI を動かす機構はこれからどんどん外から手に入るようになるので、私たちの仕組み化は、その上に載せる判断基準を残すことに集中してきました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;何を正解とし、どこからを合格とし、何を危険とするか&lt;/li&gt;
&lt;li&gt;コードの一貫性も、判断の記録も、テストの固定も、個人情報の保護も、すべて「判断基準を、価値観と理由ごと残していく」という同じ考え方でつながっている&lt;/li&gt;
&lt;li&gt;観測や停止の機構はモデルが進化するにつれて入れ替わったり、外せたりする部分がある。一方、判断基準は廃れにくい資産なので、特定のツールの設定ファイルに埋めるのではなく、自分たちのリポジトリに可搬な形で持っておく&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらが、この4ヶ月の試行錯誤から得た学びでした。&lt;/p&gt;
&lt;p&gt;結果が出るに越したことはないのですが、まだ誰も正解が分かっていない領域なので、いまは結果そのものよりも、改善していける過程に重きを置く段階だと思っています。同じようなことに取り組んでいる方、真似してみたい方がいれば、ぜひお話ししたいです。最後まで読んでいただき、ありがとうございました。&lt;/p&gt;
&lt;h2&gt;脚注&lt;/h2&gt;
&lt;h3 id=&quot;anno1&quot;&gt;注1&lt;/h3&gt;
&lt;p&gt;ここでいう「判断基準」とは、ドメイン知識と開発ルールを併せたものに近いと、いまは捉えています。ドメイン知識のほうには、その業務を成り立たせるための知識だけでなく、そのドメインで何に重きを置くかという価値観も含まれます。&lt;/p&gt;
&lt;h3 id=&quot;anno2&quot;&gt;注2&lt;/h3&gt;
&lt;p&gt;Mitchell Hashimoto, &amp;quot;My AI Adoption Journey&amp;quot;（2026-02）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey&quot;&gt;https://mitchellh.com/writing/my-ai-adoption-journey&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;anno3&quot;&gt;注3&lt;/h3&gt;
&lt;p&gt;Anthropic, &amp;quot;Effective context engineering for AI agents&amp;quot;（2025-09）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents&quot;&gt;https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;anno4&quot;&gt;注4&lt;/h3&gt;
&lt;p&gt;Anthropic, &amp;quot;Harness design for long-running application development&amp;quot;（2026-03, Prithvi Rajasekaran）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/harness-design-long-running-apps&quot;&gt;https://www.anthropic.com/engineering/harness-design-long-running-apps&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;anno5&quot;&gt;注5&lt;/h3&gt;
&lt;p&gt;これは人間も間違える原因になるので、AI を使わないとしても徹底したほうが良いと思っています。今回は AI-Native の話なので、本筋ではありませんが。&lt;/p&gt;
&lt;h3 id=&quot;anno6&quot;&gt;注6&lt;/h3&gt;
&lt;p&gt;Justin Young, &amp;quot;Effective harnesses for long-running agents&amp;quot;（2025-11）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&quot;&gt;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;session をまたぐ継続性を artifact（この場合はテキストファイル）で確保するという趣旨が該当します。&lt;/p&gt;
&lt;h3 id=&quot;anno7&quot;&gt;注7&lt;/h3&gt;
&lt;p&gt;これは案件内で共通のものなので &lt;code&gt;TODO.md&lt;/code&gt; にまとめられています。IDE を常に開いていた頃なら、こういった TODO は自動的に収集してきてくれましたが、最近は Claude Code だけで済んでしまうので、こういったものが必要になりました。&lt;/p&gt;
</content:encoded></item><item><title>Growth Platform ― メルカリグループのグロースを支えるチームの5年間の歩み</title><link>https://engineering.mercari.com/blog/entry/20260626-6e9484f480/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260626-6e9484f480/</guid><description>&lt;p&gt;こんにちは。メルペイ Growth Platform チームでエンジニアリングマネージャーをしている @yo-gawa です。この記事は Merpay &amp;amp; Mercoin Tech Openness Month  [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 29 Jun 2026 10:00:20 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイ Growth Platform チームでエンジニアリングマネージャーをしている @yo-gawa です。この記事は &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay &amp;amp; Mercoin Tech Openness Month 2026&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt; の 19 日目の記事です。&lt;/p&gt;
&lt;p&gt;私は 2018 年にメルペイに入社し、メルペイクーポンや決済に応じたポイント還元の仕組みなど、メルペイのキャンペーンを支える基盤の開発・運用に携わってきました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20210928-mtf2021-day4-2/&quot; title=&quot;【書き起こし】メルペイのキャンペーンの舞台裏 〜Growthを支える仕組み〜 – 小川 芳樹【Merpay Tech Fest 2021】 | メルカリエンジニアリング&quot;&gt;【書き起こし】メルペイのキャンペーンの舞台裏 〜Growthを支える仕組み〜 – 小川 芳樹【Merpay Tech Fest 2021】 | メルカリエンジニアリング&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;現在は、メルカリグループ全体のグロース基盤のエンジニアリングを統括しています。&lt;/p&gt;
&lt;p&gt;本記事では、私の視点からメルカリグループのグロース基盤の歴史と、どのようにチームが拡大してきたのかをお伝えします。&lt;/p&gt;
&lt;h2&gt;Growth Platform の立ち上げ&lt;/h2&gt;
&lt;p&gt;メルカリグループでは、ポイントやクーポンを用いたマーケティング／キャンペーンが非常に活発に行われています。既存のお客さま向けの施策に加え、メルカリハロやメルカリモバイルといった新規事業のグロースにも活用されています。また、お客さまとのコミュニケーションチャネルとして、アプリ内バナー、メール、Push 通知、「あなたへのお知らせ」等も併せて使われています。Growth Platform チームは、こうしたグロース活動を支える基盤・仕組みづくりを担うチームとして機能しています。&lt;/p&gt;
&lt;p&gt;「Growth Platform」というチーム名称が生まれたのは、ちょうど 5 年前の 2021 年 7 月でした。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://careers.mercari.com/mercan/articles/31347/&quot; title=&quot;「Growth UXチーム」が「Growth Platformチーム」に名称変更！　組織改編で見えたPMの役割やチームの変化 | mercan (メルカン)&quot;&gt;「Growth UXチーム」が「Growth Platformチーム」に名称変更！　組織改編で見えたPMの役割やチームの変化 | mercan (メルカン)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;上記の記事にもある通り、「共通する機能・基盤を横断的に見て、システム開発や運用の観点から下支えする」というミッションのもと、さまざまな取り組みを進めてきました。&lt;/p&gt;
&lt;p&gt;Growth Platform というチームが生まれる前、2019 年のメルペイサービス開始直後は、より多くのお客さまにメルペイを使っていただくため、決済に絡めたクーポンやポイント還元キャンペーン施策を次々と打ち出していました。そのスピード感を支えるべく、独自にキャンペーン基盤を開発してきました。&lt;/p&gt;
&lt;p&gt;一方で、マーケットプレイス事業にも同様にキャンペーンや Customer Relationship Management（CRM）を実行する基盤が存在していましたが、長いメルカリの歴史の中で生み出されてきた PHP 製ツールには多くの負債があり、メンテナンス性の観点からも、オペレーション事故が起きやすい構造になっていました。メルペイ側でも、それらのツールを利用することにリスクを感じていました。&lt;/p&gt;
&lt;p&gt;そこで、メルカリとメルペイのエンジニア間で実務的なコラボレーションをスタートし、古いツールを廃止して、新たに Engagement Platform (EGP) という内製マーケティングエンジンを共同開発する方向性で合意しました。&lt;/p&gt;
&lt;p&gt;その後、2023 年 1 月よりメルペイ内に統合組織を作り、「JR (Japan Region) Growth Platform」として、一丸となった開発体制をスタートさせました。詳細は当時のエンジニアリング統括であった keigoand の記事にまとまっています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20231201-merging-teams-for-a-growth-platform/&quot; title=&quot;Merging Teams for a Growth Platform | Mercari Engineering&quot;&gt;Merging Teams for a Growth Platform | Mercari Engineering&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;記事にもありますが、当初はそれぞれのチームで開発の進め方が大きく異なりました。メルペイが比較的小規模で立ち上げを進めた背景もあり、独自のオペレーションツールやミドルウェアがあり、エンジニアリングカルチャーも異なっていました。また、メルペイは金融事業を担っていることもあり、システム品質の担保も重要です。リリースプロセスの厳格化や、QAなどの各種基準への準拠などについて認識を合わせながら開発を進めることは苦労も伴いましたが、チームのケイパビリティは格段に向上しました。&lt;/p&gt;
&lt;h2&gt;グループの拡大とともに&lt;/h2&gt;
&lt;p&gt;先述した通り、Growth Platform チームはメルペイに所属しながらも、メルカリグループ全体を支えるチームです。&lt;/p&gt;
&lt;p&gt;この数年、メルカリでは複数の新規事業が立ち上がりました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;メルカリShops&lt;/li&gt;
&lt;li&gt;メルコイン&lt;/li&gt;
&lt;li&gt;メルカリNFT&lt;/li&gt;
&lt;li&gt;メルカリハロ&lt;/li&gt;
&lt;li&gt;メルカリモバイル&lt;/li&gt;
&lt;li&gt;越境ビジネス&lt;/li&gt;
&lt;li&gt;メルカリグローバルアプリ&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらのビジネスやプロダクトの Product-Market Fit を支えるべく、Growth Platform ではさまざまな開発を行ってきました。例えば、新しいサービス画面上でのキャンペーンバナー表示や訴求、新規のお客さまへのクーポン／ポイント進呈、利用実績に応じたロイヤリティプログラムなどは、一見共通でありつつも、それぞれのサービスのデータを基盤に統合し、マーケティングチームが柔軟かつ高速にキャンペーンを打ち出せる仕組みが必要です。また、グローバル展開においては、各地域の言語や、クーポンにおける通貨の違いへの対応などもスコープになります。&lt;/p&gt;
&lt;p&gt;さらに（一部例外はありますが）、メルカリのサービスは 1 つのメルカリアプリ上で動いています。起動直後に表示される「ホーム画面」や、マーケットプレイスにおける「商品詳細画面」「取引画面」でのキャンペーン訴求は、お客さまにサービス認知を獲得するうえで非常に重要です。そのような訴求エリアが限られる中で、どのサービスがそれぞれのお客さまにとって有用なキャンペーンであるのか、コミュニケーションチャネルがノイジーになりすぎないのか、といった最適化にも Machine Learning チームが取り組んでいます。&lt;/p&gt;
&lt;h2&gt;組織の拡大&lt;/h2&gt;
&lt;p&gt;事業の拡大に対応するため、Growth Platform 組織も拡大を続けてきました。&lt;/p&gt;
&lt;p&gt;2022 年 6 月にインド・ベンガルールに開発拠点として Mercari India を設立して以降、設立当初から一緒に開発を進めています。当初はクーポンドメインを共同で開発するところからスタートしました。新たなビジネスに対応したクーポン機能の開発を進めながら、mercari-api という PHP 製モノリスからマイクロサービスへのマイグレーションという大規模プロジェクトも自律的に推進しています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://about.in.mercari.com/news/mercari-india/migrating-coupons-from-monolith-to-microservice/&quot; title=&quot;Migrating Coupons from Monolith to Microservice - Mercari India&quot;&gt;Migrating Coupons from Monolith to Microservice &amp;#8211; Mercari India&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;近年ではクーポンドメインだけでなく CRM ドメインでも開発体制を拡大し、Backend、Frontend、Mobileのエンジニアが一体となってインド拠点のみで機能開発を完結するような事例も出てきています。&lt;/p&gt;
&lt;p&gt;現在は日本とインドのエンジニアが約2:1の割合で協働しています。また、日本とインドのエンジニアリングマネージャー同士、密に連携しながら開発を推進しています。&lt;/p&gt;
&lt;p&gt;Growth Platform の全体ミーティングでは、知見共有を含めた交流をエンジニア同士で積極的に行っています。加えて、定期的にインドオフィスへ出張したり、インドのエンジニアが東京オフィスを訪問するタイミングに合わせて交流を深めたりしています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/facb482d-image-from-ios-1024x768.jpg&quot; alt=&quot;インドオフィスに設置されているオブジェへのサイン&quot; /&gt;&lt;br /&gt;
(インドオフィスに設置されているオブジェへのサイン)&lt;/p&gt;
&lt;p&gt;一方で、使用言語の壁は依然として課題です。メルペイは従来、日本語中心でコミュニケーションしてきましたが、元マーケットプレイスのメンバーやインドオフィスのメンバーは英語中心です。Growth Platform チームの全体ミーティングは英語で行う一方、メルペイ全体のミーティングは日本語で実施しているといった状況下で、GOT（通訳）チームのサポートも借りながら、日本語話者・英語話者の双方が歩み寄ってコミュニケーションを進めています。&lt;/p&gt;
&lt;h2&gt;次なるチャレンジへ&lt;/h2&gt;
&lt;p&gt;Growth Platform チームの成熟は、メルカリグループのグロースとともにありました。&lt;/p&gt;
&lt;p&gt;EGP は、さまざまな機能開発を経て大きな進化を遂げています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gears.mercari.com/session/mechanism-6&quot; title=&quot;EGP - Mercari’s CRM Platform: Built Once, Powering Many | mercari GEARS 2025&quot;&gt;EGP &amp;#8211; Mercari’s CRM Platform: Built Once, Powering Many | mercari GEARS 2025&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260603-why-we-built-egp-code/&quot; title=&quot;AI と作る HTML ベースの LP エディタ EGP Code を内製した理由 | メルカリエンジニアリング&quot;&gt;AI と作る HTML ベースの LP エディタ EGP Code を内製した理由 | メルカリエンジニアリング&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;また、メルペイに特化した基盤である Santa サービスも進化を続けています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260617-d943dec64a/&quot; title=&quot;メルペイのキャンペーン基盤をルールベース汎用システムに書き直し、Otoku Revolutionするまでの話 | メルカリエンジニアリング&quot;&gt;メルペイのキャンペーン基盤をルールベース汎用システムに書き直し、Otoku Revolutionするまでの話 | メルカリエンジニアリング&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;基盤開発フェーズは一巡したと捉えていますが、ビジネスを支える基盤としてはまだ道半ばにあります。特に、ビジネスの要求に対するアジリティとシステムの信頼性向上は継続的な課題です。&lt;/p&gt;
&lt;p&gt;そこで今後は、グループそれぞれのビジネスグロースをより直接的に支えつつ、守りも固められる開発体制をつくるために、Growth Platform 体制としては一区切りとし、組織を CRMチーム と Incentiveチーム に分け、それぞれのミッションを担っていきます。&lt;/p&gt;
&lt;p&gt;CRM チームは、マーケットプレイスをはじめとする各事業の成長を後押しするため、プロダクトと密に連携しながら、EGP を活用したお客さま向けコミュニケーション機能の開発・改善を進めていきます。&lt;/p&gt;
&lt;p&gt;Incentive チームは、メルペイ Payment &amp;amp; Customer Platform の一員として、インセンティブを汎用的かつ安全に取り扱える仕組みづくりを推進していきます。&lt;/p&gt;
&lt;p&gt;チームは分かれますが、メルカリグループ全体で活用される共通 Foundation として、連携は継続していきます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/e288c98c-background.jpg&quot; alt=&quot;Growth Platform memberの集合写真&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;ここまで、Growth Platform のプロダクトやチームの拡大について簡単にご紹介しました。プロダクトや技術の詳細は、本エンジニアリングブログやカンファレンスでメンバーが紹介していますので、そちらもぜひご覧ください。&lt;/p&gt;
&lt;p&gt;これからもメルカリグループの可能性を広げるための仕組みづくりを追求していきます。&lt;/p&gt;
&lt;p&gt;次の記事は abcdefujiさん と becosukeさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>DebtAccountView: 柔軟な与信の分割と請求を実現する債権管理の仕組み</title><link>https://engineering.mercari.com/blog/entry/20260624-e6965edab9/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260624-e6965edab9/</guid><description>&lt;p&gt;こんにちは。メルペイの Balance Team で Tech Lead をしている @kobaryo です。この記事は「Merpay &amp;amp; Mercoin Tech Openness Month 2026」の18 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 25 Jun 2026 10:00:35 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイの Balance Team で Tech Lead をしている &lt;a href=&quot;https://x.com/artoy5884&quot;&gt;@kobaryo&lt;/a&gt; です。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の18日目の記事です。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Balance Team では お客さまの残高やポイントを扱うマイクロサービスをはじめ、債権を取り扱う Debt Service も管理しています。先日公開された @imamu さんの「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260608-partner-credit-service/&quot;&gt;事業者請求払いのための与信管理マイクロサービスの設計&lt;/a&gt;」で、与信を扱う Credit Service を中心に事業者請求払いの設計が紹介されましたが、本記事ではその中で取り上げられた「債権を任意の軸で集計する仕組み」を Debt Service 上でどのように表現したのかについて紹介します。&lt;/p&gt;
&lt;p&gt;事業者請求払いでは、与信管理のために「与信枠ごとの未返済債権」を高速に取り出したい、また請求のために「請求先ごとの債権の一覧」を把握したい、という2つのニーズが生まれます。お客さまや事業者ごとの残債を管理する既存のテーブル DebtAccount はお客さまや事業者を表す ID という単一の軸に縛られており、この2つの独立した軸を同時に管理できませんでした。この壁を越えるために、「DebtAccount は債権の操作ログ DebtLog を集計したビューである」という発想から &lt;strong&gt;DebtAccountView&lt;/strong&gt; を設計しました。1つの DebtLog を異なる軸で集計された複数のビューに畳み込むことで、与信枠軸と請求先軸を独立して管理でき、将来の新しい集計軸も最小限の変更で追加できます。&lt;/p&gt;
&lt;h2&gt;Debt Service が管理する「債権」とはなにか&lt;/h2&gt;
&lt;p&gt;事業者請求払いでは、我々が取引のたびに代金を立て替え、後からまとめて事業者に請求します。この立替金の記録が&lt;strong&gt;債権（Debt）&lt;/strong&gt;です。Debt Service はメルペイの中で債権を一元管理するマイクロサービスで、事業者向けのみでなくお客さま向けのメルペイのクレジットなど、複数のサービスから呼ばれる下位レイヤーとして機能します。&lt;/p&gt;
&lt;p&gt;各 Debt は「誰の（CustomerId）」「何の種別の（DebtType）」「いくらの（Amount）」立て替えかを保持します。DebtType は決済の種別や精算方法を区別するための分類で、お客さま向けの月次清算を表すものや事業者向けの請求書払いを表すものなど複数の種別が存在します。&lt;/p&gt;
&lt;p&gt;個別の Debt が増えていくと、「ある事業者の現時点での残債はいくらか」を素早く知りたい場面が増えます。Debt を1件ずつスキャンして合算するアプローチは、Debt の件数に比例して計算コストが大きくなるため、取引のたびに現在の残債が与信上限を超えないかをリアルタイムで確認するには向きません。そのために &lt;strong&gt;DebtAccount&lt;/strong&gt; というテーブルでお客さまや事業者ごと、かつ種別ごとに残債を保持しています。Debt の作成や返済が発生するたびに &lt;strong&gt;DebtLog&lt;/strong&gt;（債権の操作ログ）が記録され、それに応じて DebtAccount の Amount が更新される仕組みです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/91b69ae3-debt-structure.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;「1口座1軸」という壁&lt;/h2&gt;
&lt;p&gt;DebtAccount の設計はシンプルです。Unique key &lt;code&gt;(CustomerId, DebtType)&lt;/code&gt; によって、「この事業者の、この種別の残債はいくらか」という問いに即座に答えられます。お客さま向けのメルペイのクレジットなど既存のユースケースではこれで十分対応できました。&lt;/p&gt;
&lt;p&gt;請求書払いプロジェクトでは、この設計に2つの新しい要件が加わりました。1つは与信管理の要件です。詳細な説明は上述の &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260608-partner-credit-service/&quot;&gt;@imamu さんの記事&lt;/a&gt;に委ねるのですが、今回店舗や部門ごとなど1事業者が複数の与信枠を持てる設計を目指しています。そのため、与信枠を扱う Credit Service は決済フローの中で「ある与信枠に紐づく残債」をリアルタイムに取得できなければなりません。もう1つは請求の要件です。Invoice Service は請求先の軸でまとめた債権から請求書を発行するため、「ある請求先に紐づく債権の一覧」を取得できなければなりません。与信の軸（与信枠）と請求の軸（請求先）は完全に独立しており、1つの債権が両方に同時に紐づきます。またこれらの軸はどちらも CustomerId すなわち既存の事業者IDと独立しているため、既存の DebtAccount を用いて集計することはできません。&lt;/p&gt;
&lt;p&gt;2つの要件はそれぞれ性質が異なります。与信計算は決済フローの中でリアルタイムで行われ、応答時間が Debt の件数に依存してはならないため、与信枠の軸でも既存の DebtAccount と同じ「1行参照で残債がわかる」事前集計が必要です。請求の要件は残債の集計ではなく、明細の作成や債権の返済のためにどの債権がどの請求先に属するかという紐づけを保持しておくことです。ただし後々事業者向けに請求額を確認できるダッシュボードを提供したいなど、請求先軸でも事前集計する必要が出てくるかもしれません。またこれらに追加して3軸目がこの先登場しないとも限りません。このように考えた場合、我々に必要なものは多軸で残債を集計でき、かつその軸と債権を紐付けておく汎用的な仕組みでした。&lt;/p&gt;
&lt;h2&gt;1つの操作を複数の軸で同時に集計する DebtAccountView&lt;/h2&gt;
&lt;p&gt;この仕組みを実現するヒントは、DebtAccount の性質そのものにありました。DebtAccount は「口座」という名前のとおり実体を持つ台帳のように見えます。しかし、すべての事実は DebtLog に記録されており、DebtAccount はその DebtLog を &lt;code&gt;(CustomerId, DebtType)&lt;/code&gt; 軸で集計し、現時点の残債を1行に集約したビューです。この捉え方に立つと、「同じ DebtLog を別の軸で畳み込んだビューが複数あってもよい」という発想が生まれます。これが DebtAccountView の設計の直感的な出発点です。&lt;/p&gt;
&lt;p&gt;口座を分割するのではなく、集計の軸を並列に増やすというのが、設計の方向です。DebtLog は従来 DebtAccount という1つの集計ビューだけを更新していました。ここで、1つの DebtLog が既存の DebtAccount に加えて任意の数のビューを同時に更新できるようにしました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/b241fa1b-debt-account-view-structure.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;各 DebtAccountView は既存の DebtAccount と同じ物理構造を持つ集計テーブルです。DebtAccount の設計思想をそのまま踏襲しているため、DebtAccountView 専用の新しい運用パターンを考える必要はありません。従来「1 DebtLog が1 DebtAccount を更新する」ところを「1 DebtLog が1 DebtAccount + N 個の DebtAccountView を更新する」に拡張した形です。&lt;/p&gt;
&lt;h2&gt;3つのテーブルが作る台帳の構造&lt;/h2&gt;
&lt;p&gt;この仕組みを支えるのが3つのテーブルです。&lt;code&gt;DebtAccountViews&lt;/code&gt;（集計した残債）、&lt;code&gt;DebtAccountViewItems&lt;/code&gt;（Debt と DebtAccountView の所属関係）、&lt;code&gt;DebtAccountViewLogs&lt;/code&gt;（Debt Account View の変動履歴）がそれぞれの役割を担います。この機能に関わる主要な3テーブルのスキーマを示します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- 集計した現在の残債
CREATE TABLE DebtAccountViews (
  DebtAccountViewId STRING(100) NOT NULL,
  ViewKeyType       INT64       NOT NULL,  -- 1=請求先 ID, 2=与信付与先 ID, ...
  ViewKeyId         STRING(MAX) NOT NULL,  -- 指定した ViewKeyType の中の ID
  CustomerId        INT64       NOT NULL,
  DebtType          INT64       NOT NULL,
  Amount            INT64       NOT NULL,
) PRIMARY KEY(DebtAccountViewId);

CREATE UNIQUE INDEX DebtAccountViewsByViewKey
  ON DebtAccountViews(ViewKeyType, ViewKeyId, DebtType, CustomerId);

-- どの Debt がどの DebtAccountView に属するか（請求書作成時に使用）
CREATE TABLE DebtAccountViewItems (
  DebtAccountViewId STRING(100) NOT NULL,
  DebtId            STRING(100) NOT NULL,
) PRIMARY KEY(DebtAccountViewId, DebtId);

-- 残高変動の履歴
CREATE TABLE DebtAccountViewLogs (
  DebtAccountViewLogId STRING(100) NOT NULL,
  DebtAccountViewId    STRING(100) NOT NULL,
  DebtAccountLogId     STRING(100) NOT NULL,  -- DebtLog に対応する DebtAccountLog（DebtAccount の変動ログ）を DebtAccountView に紐付けることで、間接的に DebtLog と紐付けている
) PRIMARY KEY(DebtAccountViewLogId);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上流マイクロサービスは、債権を作成する際に &lt;code&gt;ViewKeyType&lt;/code&gt;（集計の軸）と &lt;code&gt;ViewKeyId&lt;/code&gt;（ある集計軸における集計ID）を指定すれば、あとはその債権が返済・取消など操作されるたびに自動的に DebtAccountView が更新されます。また軸の種類を増やす際は単に ViewKeyType の種類を増やすだけで十分です。&lt;/p&gt;
&lt;h2&gt;今後の展望&lt;/h2&gt;
&lt;p&gt;現状の実装では、一度の債権の操作で更新する DebtAccountView が多くなる、すなわち集計軸が増えていくと、債権操作のレイテンシが上昇してしまいます。そこで考えられる拡張が ViewKeyType ごとに更新の同期・非同期を切り替えられる設計です。初期実装では全 ViewKeyType をデータ書き込みと同じトランザクションで同期的に更新していますが、将来同期的に反映しなければならない与信枠などの軸は同期的に、債権の変動と集計する間にタイムラグがある請求先の軸は非同期で反映するという拡張を予定しています。&lt;/p&gt;
&lt;p&gt;また今回事業者請求払いのために DebtAccountView を設計しましたが、toB toC 問わず与信を分割したり与信を付ける先と請求する先が異なる任意のビジネスに適用できます。また、設計の本質は変動ログをピックアップし集計する汎用的なものであるため、債権に限らず残高やポイントを管理する Balance Service に導入して利用することも考えられます。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;DebtAccountView は、「1つの債権に複数の集計軸を持たせたい」という要件から生まれた機能です。既存の DebtAccount 設計をベースに、「1 DebtLog → N View を更新する」という拡張を汎用的かつシンプルに実現することにこだわりました。また将来のユースケースや拡張も考え抜けたことは、実装を通じて得た達成感の一つです。&lt;/p&gt;
&lt;p&gt;事業者請求払いはメルペイの Payment Platform としてまだ進化の途中にあります。与信管理、債権管理、請求、精算という一気通貫のプラットフォームを、各サービスの責務を保ちながら積み上げていくことは、技術的にもドメイン的にも自信を持って面白いと言えます。もしこうした課題に興味を持っていただけたら、ぜひ Balance Team やメルペイの採用・インターン情報も覗いてみてください。この記事が、Fintech や決済基盤のドメイン設計に興味を持つエンジニアにとって何らかのヒントになれば幸いです。&lt;/p&gt;
&lt;p&gt;次の記事は yogawaさん と haoyuさん です。引き続きお楽しみください。&lt;/p&gt;
&lt;h2&gt;Appendix: 採用しなかったその他の選択肢&lt;/h2&gt;
&lt;p&gt;既存の事業者IDのみでなく与信枠軸や請求先軸など任意の単位での集計を実現する手段として、DebtAccountView 以外にもいくつかの案を検討しました。&lt;/p&gt;
&lt;p&gt;1つ目は与信枠ごとに DebtAccount を分割し、請求先の情報は Debt に持たせる方法です。現状のスキーマの延長線上で実装することができとてもシンプルですが、請求先情報という Debt ドメインに関わりが深くない情報を列に直接追加するのはあまりに汎用的でありません。&lt;/p&gt;
&lt;p&gt;2つ目は DebtAccount を親子関係で分割する案です。事業者全体の DebtAccount を親として、その子は与信枠の軸で分割、さらにその子は請求先の軸で分割するといったものです。DebtAccount を分割するための軸という概念を追加しそこに与信枠軸や請求先軸を入れるので、1つ目の案よりは汎用的です。しかし与信枠の軸と請求先の軸は互いに独立しているので、与信枠の軸での集計は効率よく行えるものの、請求先の軸での集計は依然効率よく行えません。加えて、与信枠軸や請求先軸のみでなく効率良く集計する必要のある3軸目が登場した場合に、1つ目の案とこの2つ目の案は対応することができません。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/802afac7-parent-child-failure.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;3つ目は Debt Service 内でなく、上流サービス側で集計を持つ方法です。Credit Service が与信枠ごとの残債を管理し、Invoice Service が請求先ごとの集計を管理する設計です。しかし Debt Service を使うサービスが増えるたびに、同じ集計ロジックを各サービスが個別に実装することになります。Debt Service 内で集計済みの情報を管理すれば、債権の元データを唯一の情報源として、各サービスへの変更通知なしに信頼性の高い集計を一元的に提供できます。この仕組みを Debt Service に置くことは、将来債権を取り扱う新しいビジネスが高パフォーマンスな集計を必要としたとき、プラットフォームとして即座に応えられる基盤への投資でもあります。&lt;/p&gt;
</content:encoded></item><item><title>Otoku Revolution ~ 楽しい自動値引き体験の裏側</title><link>https://engineering.mercari.com/blog/entry/20260623-ff72f126be/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260623-ff72f126be/</guid><description>&lt;p&gt;はじめに こんにちは。メルペイの @yutaro です。 「Merpay &amp;amp; Mercoin Tech Openness Month 2026」17日目として、メルペイの決済をもっと楽しく、繰り返し使いたくなる体 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 24 Jun 2026 10:00:35 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。メルペイの &lt;a href=&quot;https://x.com/yutaroboy&quot; title=&quot;@yutaro&quot;&gt;@yutaro&lt;/a&gt; です。&lt;br /&gt;
「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay &amp;amp; Mercoin Tech Openness Month 2026&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」17日目として、メルペイの決済をもっと楽しく、繰り返し使いたくなる体験にするための取り組み（Otoku Revolution）について紹介します。&lt;/p&gt;
&lt;p&gt;私は普段、Payment Platform の Balance Team で、メルペイ残高やポイントの状態管理、債権管理、会計連携、法定帳簿（法律上必要な帳簿）等の開発・運用をおこなっています。&lt;/p&gt;
&lt;p&gt;この記事でシェアしたいのは、新鮮な体験を作るときほど、早い段階で「決済では何が起きるか」「返金や失効ではどう扱うか」「お金の流れはどうなるか」のような具体的な部分のイメージも煮詰めておくと、実装も追加施策も速く進められる、という実例です。&lt;/p&gt;
&lt;h2&gt;Otoku Revolution とは&lt;/h2&gt;
&lt;p&gt;Otoku Revolution は、一言でいうと「決済するほど次の決済がおトクになる」体験を、メルペイの既存の決済基盤の上で実現するプロジェクトです。&lt;/p&gt;
&lt;p&gt;実際の施策は、コード決済するたびにスタンプが貯まり、3回決済すると値引き特典を受け取り、次回決済時に自動で値引きされる新しい還元プログラムになりました。&lt;/p&gt;
&lt;div style=&quot;max-width:1080px !important; margin:32px auto !important; font-size:0 !important; line-height:0 !important; white-space:nowrap !important; overflow:hidden !important;&quot;&gt;
  &lt;span style=&quot;display:inline-block !important; width:49% !important; margin-right:2% !important; vertical-align:top !important;&quot;&gt;&lt;br /&gt;
    &lt;img
      src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/74ef024f--2026-06-24-5.10.41.png&quot;
      alt=&quot;画面イメージ1&quot;
      loading=&quot;lazy&quot;
      decoding=&quot;async&quot;
      style=&quot;display:block !important; width:100% !important; max-width:100% !important; height:auto !important; border-radius:10px !important;&quot;
    /&gt;&lt;br /&gt;
  &lt;/span&gt;&lt;span style=&quot;display:inline-block !important; width:49% !important; vertical-align:top !important;&quot;&gt;&lt;br /&gt;
    &lt;img
      src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/640a18e6--2026-06-24-5.10.55.png&quot;
      alt=&quot;画面イメージ2&quot;
      loading=&quot;lazy&quot;
      decoding=&quot;async&quot;
      style=&quot;display:block !important; width:100% !important; max-width:100% !important; height:auto !important; border-radius:10px !important;&quot;
    /&gt;&lt;br /&gt;
  &lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;(実際の決済後の画面)&lt;/p&gt;
&lt;p&gt;決済する、スタンプがたまる、値引き特典を受け取る、受け取った特典が次回の決済で自動的に使われる。というような流れで、クーポンを選んだり、後日ポイントを受け取ったりするのではなく、次の決済で自然におトクになる体験を目指しました。&lt;/p&gt;
&lt;p&gt;2026年5月中旬から、メルペイをご利用のお客さまの約半数を対象に試験的な導入を進めています。&lt;/p&gt;
&lt;p&gt;決済基盤の上でこの体験（即時値引き）を作るには、キャンペーン条件、値引きを実現するリワード、決済処理、履歴、会計上の扱いを矛盾なくつなぐ必要があります。&lt;/p&gt;
&lt;p&gt;構想初期には、周辺リポジトリをすべてcloneし、Claude Codeに読ませながら、関係しそうなサービスとデータの流れを図に起こしていました。&lt;br /&gt;
次のスクリーンショットは、そのときの初期アーキテクチャ図です。細部はその後整理していますが、議論のたたき台としては十分に現実に近く、どのサービスで何を扱うべきかを早い段階でそろえる助けになりました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/4fd97fe1--2026-06-03-15.44.06.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ここからは、この還元プログラムを実現するために値引きポイント &lt;code&gt;DISCOUNT_POINT&lt;/code&gt; をなぜ選んだのかを紹介します。&lt;/p&gt;
&lt;div style=&quot;max-width:1080px; margin:32px auto; font-size:0; line-height:0; white-space:nowrap;&quot;&gt;
  &lt;span style=&quot;display:inline-block; width:49%; margin-right:2%; vertical-align:top;&quot;&gt;&lt;br /&gt;
    &lt;img
      src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/292c4d63--2026-06-24-5.28.49.png&quot;
      alt=&quot;画面イメージ1&quot;
      loading=&quot;lazy&quot;
      decoding=&quot;async&quot;
      style=&quot;display:block; width:100%; height:600px; object-fit:cover; object-position:top center; border-radius:12px;&quot;
    /&gt;&lt;br /&gt;
  &lt;/span&gt;&lt;span style=&quot;display:inline-block; width:49%; vertical-align:top;&quot;&gt;&lt;br /&gt;
    &lt;img
      src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/5fcb1ec1--2026-06-24-5.24.14.png&quot;
      alt=&quot;画面イメージ2&quot;
      loading=&quot;lazy&quot;
      decoding=&quot;async&quot;
      style=&quot;display:block; width:100%; height:600px; object-fit:cover; object-position:top center; border-radius:12px;&quot;
    /&gt;&lt;br /&gt;
  &lt;/span&gt;
&lt;/div&gt;
&lt;h2&gt;なぜ DISCOUNT_POINT を作ったのか&lt;/h2&gt;
&lt;p&gt;最初の大きな判断は、この特典をどのような形で実現するかでした。&lt;/p&gt;
&lt;p&gt;検討した選択肢は、大きく3つありました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ポイントバック: 既存のポイント付与の仕組みで実現しやすい。一方、即時ではなく後日ポイントを受け取る体験になる&lt;/li&gt;
&lt;li&gt;クーポン: 特典として理解しやすい。一方、お客さまが選んで使うものになりやすく、既存の仕組みで即時反映を実現しにくかった&lt;/li&gt;
&lt;li&gt;値引き専用の残高（DISCOUNT POINT）: 完全に新しい即時値引き専用の残高を作成し、対象の決済で最優先で使われるようにする&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;3つ比較した結果、即時値引きという体験を実現するためには、値引き専用の残高を作成する必要があるという判断になりました。&lt;/p&gt;
&lt;p&gt;これは通常のポイントとは別の、値引きにだけ使えるお客さま向けの残高です。&lt;br /&gt;
内部的には残高のように扱えますが、お客さま向けにはすぐに次の決済に使えるクーポンのように振る舞います。次回の決済の値引きを実現したいので、決済金額が値引き額より小さい場合は、使い切れなかった分を失効させます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;をスピーディーに実装するために役立ったのが、約1年半前から稼働している新しい残高管理システムである Balance V2 です。&lt;br /&gt;
Balance V2 では、残高の種類を後から柔軟に追加できる設計になっており、種類ごとに使われ方や会計連携を宣言的に定義できます。また、残高の付与や消費、失効に伴う変動があった場合に自動で会計連携されるようになっています。&lt;br /&gt;
このプロジェクトではその汎用性を活かし、施策専用のサービスを立ち上げるのではなく、&lt;code&gt;DISCOUNT_POINT&lt;/code&gt; という新しい残高の一種として即時値引き特典を扱う設計にしました。&lt;/p&gt;
&lt;p&gt;結果的に、全く新しい種類のクーポンであるはずの即時値引き特典を、決済基盤上では残高の延長線上で扱い実装できるようになりました。&lt;/p&gt;
&lt;h2&gt;DISCOUNT_POINT の全体像&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;DISCOUNT_POINT&lt;/code&gt; は、決済完了後にスタンプが３回溜まっていれば付与されます。付与後、次の決済前に利用可能な残高として参照され、決済手段に最優先で使われる残高として組み込まれ、通常の残高やポイント・メルペイのクレジットより先に使われます。&lt;/p&gt;
&lt;p&gt;例えば、&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;を100円分もっているお客さまが300円分の商品を購入しようとすると、100円分が優先的に&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;から消費され、他の残高等の決済手段から200円分が消費されます。&lt;br /&gt;
このように、お客さま目線では200円分の負担で300円分の決済ができた。という値引き体験を実現できています。&lt;/p&gt;
&lt;p&gt;このような体験を実現するために、このプロジェクトではサービスの役割を分けました。冒頭の初期アーキテクチャ図は細かい実装寄りの図なので、ここでは大きく「決済前」「決済時」「決済後」の3つに整理しています。実際の構成にはさらに細かい処理や例外がありますが、大きな役割だけに絞ると次のようになります。括弧内は、社内で使っているサービス名です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/4ef27cbb-otoku_revolution_backend_simple.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;service&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;主な役割&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;特典付与サービス (santa-service)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;スタンプの管理、付与額の決定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;支払い方法設定サービス (payment-config-service)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;お客さまの設定や状況ごとの決済手段を決定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;決済サービス（payment-service）&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;渡された決済手段どおりに支払いを実施する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;残高管理サービス（balance-service）&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;DISCOUNT_POINT の残高管理、消費、失効、会計連携&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;このように役割を分けると、santa-serviceは条件判定、balance-serviceは残高の増減、payment-serviceは渡された内訳どおりに決済を成立させることに集中できます。&lt;/p&gt;
&lt;p&gt;お客さまにはひとつの施策に見えるものを、裏側ではスタンプ、決済に使う残高やポイント、履歴表示、会計上の記録に分けて考えられるようになり、それぞれの影響範囲について議論や意思決定がしやすくなりました。&lt;/p&gt;
&lt;p&gt;実際のキャンペーンの条件や例外の多くは、santa-serviceで扱っています。@hasegway さんの「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260617-d943dec64a/&quot; title=&quot;メルペイのキャンペーン基盤をルールベース汎用システムに書き直し、Otoku Revolutionするまでの話&quot;&gt;メルペイのキャンペーン基盤をルールベース汎用システムに書き直し、Otoku Revolutionするまでの話&lt;/a&gt;」 では、このような複雑なキャンペーンをルール組み合わせとして扱えるようにした Rulebase 移行について紹介されています。&lt;/p&gt;
&lt;p&gt;次の章から、付与、利用、返金・失効、会計 についての詳細をそれぞれ紹介していきます。&lt;/p&gt;
&lt;h3&gt;付与: 還元で決済を止めない&lt;/h3&gt;
&lt;p&gt;Otoku Revolution、は決済完了後のスタンプ更新から始まります。&lt;/p&gt;
&lt;p&gt;決済が完了すると、payment-serviceから非同期のeventが発行されます。santa-serviceはそのeventを受け取り、キャンペーンの対象のユーザーかどうかを確認してスタンプの付与を行います。ここで、スタンプが３回分に到達した場合は特典額を決め、balance-serviceに &lt;code&gt;DISCOUNT_POINT&lt;/code&gt;の付与を行います。&lt;/p&gt;
&lt;p&gt;ここで大事だったのは、&lt;strong&gt;スタンプ更新や値引きポイント付与を、決済そのものの成功条件にしないこと&lt;/strong&gt;です。&lt;/p&gt;
&lt;p&gt;この還元プログラムの体験としては「決済後に即時でスタンプ・&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;の付与が行われる」が理想です。一方で、スタンプ更新や&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;の付与までを決済処理の中で完了させようとすると、キャンペーン側の遅延や障害が決済成功率に影響してしまいます。&lt;/p&gt;
&lt;p&gt;決済基盤では、決済そのものをキャンペーン側の都合で不安定にしないことが最優先です。そのため、決済完了後にあとからスタンプを更新し、条件達成時に&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;を付与する設計にしました。&lt;/p&gt;
&lt;h3&gt;利用: 最優先で使われるポイントとしてのDISCOUNT POINT&lt;/h3&gt;
&lt;p&gt;付与された &lt;code&gt;DISCOUNT_POINT&lt;/code&gt; は、次の決済で自動的に使われます。&lt;/p&gt;
&lt;p&gt;ここで設計上大事だったのは、payment-serviceの役割を広げすぎないことです。&lt;br /&gt;
直感的には、payment-serviceが残高を見て「&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;があるなら使う」と決める設計も考えられます。この形にすると、値引きの判断を1か所に集めやすいという良さがあります。&lt;/p&gt;
&lt;p&gt;しかし、payment-serviceは単に外部向けの決済を担当しているだけではなく、社内のありとあらゆるお金の流れに関与しており、できるだけ&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;に関係する専用のロジックは入りこまないようにしたいです。&lt;/p&gt;
&lt;p&gt;そのため、決済前に支払い方法と金額を組み立てる payment-config-serviceが、支払い方法と&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;残高を見て、&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;を優先的に消費する決済内訳を作る設計にしました。payment-serviceはその内訳どおりに決済を進め、残高管理サービスが実際の残高消費を担います。&lt;/p&gt;
&lt;p&gt;このような責務分担にすることで、コード決済等の決済手段ごとに値引きを適用するかどうかのルールを段階的決めることができるようにもなります。&lt;/p&gt;
&lt;h3&gt;返金・失効: クーポンとして振る舞うように設計&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DISCOUNT_POINT&lt;/code&gt; は、通常のポイントのように貯め続けるものではなく、「スタンプがたまった次の決済で使える特典」です。&lt;/p&gt;
&lt;p&gt;返金時や、決済金額が値引きポイントより小さいときも、不正対策やユーザービリティの観点でクーポンに近い挙動になるようにしました。&lt;/p&gt;
&lt;p&gt;返金・失効では、主に次のように扱います。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返金時に、スタンプの数は戻さない&lt;/li&gt;
&lt;li&gt;返金時に、使用済みの値引きポイントは再付与しない&lt;/li&gt;
&lt;li&gt;決済金額より値引きポイントが大きい場合、使い切れなかった分は失効させる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例えば、1,000円の決済で100円分の値引きポイントを使ったあとに返金された場合でも、通常の返金処理の中でスタンプや値引きポイントの状態までは巻き戻しません。巻き戻す設計にすると、「再付与した値引きポイントをもう一度使えるのか」「スタンプも取り消すのか」「会計上は何を取り消すのか」を追加で扱う必要があり、スタンプカードとしてのルールも複雑になります。&lt;/p&gt;
&lt;p&gt;また、500円分の値引きポイントを持っているお客さまが300円の決済をした場合は、300円分を消費し、残り200円は失効します。&lt;code&gt;DISCOUNT_POINT&lt;/code&gt; は残高として管理しつつ、次の決済だけで使われる特典に近い性質を持たせたかったためです。&lt;/p&gt;
&lt;p&gt;このように整理することで、クーポンのような挙動のポイントである&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;を実現しています。&lt;/p&gt;
&lt;h3&gt;会計: お金の流れを先にデザイン&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DISCOUNT_POINT&lt;/code&gt; を新しい残高として扱う以上、アプリに表示して決済で使えるだけでは不十分です。付与された時点、使われた時点、失効した時点で会計上どう記録されるのかを説明できないまま進めると、後から設計を大きく見直すことになります。&lt;/p&gt;
&lt;p&gt;そこで、まだ設計が固まりきる前に、プロジェクト全体の MoneyFlow、つまりお金がどう動くかを図にして、経理チームに相談しに行きました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/4e9413f6--2026-06-06-17.08.53.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この図では、&lt;code&gt;DISCOUNT_POINT&lt;/code&gt; の付与・消費・失効が、会計上の記録につながることを描いています。全体ではなく一部だけを載せています。このプロジェクトでは早い段階からお金の流れを具体的な図にして議論していました。&lt;/p&gt;
&lt;p&gt;実装が本格化する前に、お金の流れと会計上の意味を一緒に確認できたことは、後から振り返っても大きかったと思います。MoneyFlow そのものについては、@komatsu さんの 「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260605-moneyflow-bridging-payment-and-accounting/&quot; title=&quot;決済プラットフォームと経理を繋ぐ MoneyFlow&quot;&gt;決済プラットフォームと経理を繋ぐ MoneyFlow&lt;/a&gt;」に詳しくまとまっています。&lt;/p&gt;
&lt;p&gt;Otoku Revolutionではこのように整理したうえで、&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;の付与・消費・失効をbalance-serviceで扱い、会計連携までつなげて、会計レポートを作成できるようにしました。&lt;/p&gt;
&lt;h2&gt;リリース後、事前付与施策で効いた設計&lt;/h2&gt;
&lt;p&gt;リリース後には、多くのお客さまにこの還元プログラムを知ってもらうための施策も必要になりました。&lt;/p&gt;
&lt;p&gt;そこで、対象のお客さまにあらかじめ&lt;code&gt;DISCOUNT_POINT&lt;/code&gt;を付与する施策を追加で開始することにしました。キャンペーン開始直後のタイトなスケジュールの中、スピーディーに付与実施までもっていくことができました。&lt;/p&gt;
&lt;p&gt;このような柔軟な追加施策を実施できたのは、単に実装が早かったからではありません。&lt;code&gt;DISCOUNT_POINT&lt;/code&gt; が残高管理サービスで管理される残高として扱われ、付与、消費、失効、会計上の記録の意味がすでに整理されていたことが効きました。&lt;/p&gt;
&lt;p&gt;追加施策でも、付与する残高の種類、決済時の使われ方、使い切れなかった分の扱い、履歴での見え方、会計上の記録を新しく考え直す必要はありませんでした。「誰に、いくら、どの条件で付与するか」に集中できたことが、速度を出せたポイントかなと思います。&lt;/p&gt;
&lt;h2&gt;振り返り&lt;/h2&gt;
&lt;p&gt;Otoku Revolutionでは、最初から「値引きポイントをどう見せるか」だけでなく、「決済では何が起きるか」「返金ではどう扱うか」「会計ではどう記録するか」をまとめて確認できたことが大きかったです。そのおかげで、&lt;code&gt;DISCOUNT_POINT&lt;/code&gt; を残高として付与し、決済で優先的に使うといアイディアで最後まで手戻りなくリリースまで実現できました。&lt;/p&gt;
&lt;p&gt;個人的には、プロジェクトの構想段階から体験面の議論に混ぜてもらいながら、エンジニアとして実現性や整理が必要になりそうな点を少し先回りして確認しにいけたことは、私にとっても学びの多い進め方でした。体験を決めたあとに会計や法務、返金のルールに合わせて作り直すのではなく、最初からそれらを含めて設計できたことは大きかったと思います。普段 Balance Team で扱っている残高・会計・帳簿の知識が、お客さまに届く体験につながったことも印象に残っています。&lt;/p&gt;
&lt;p&gt;決済基盤の仕事をしていると正しさや安全性を守る場面が多いですが、その知識の延長線上で新しい体験を実現できたことは、素直にうれしかったです。&lt;/p&gt;
&lt;p&gt;次の記事は @kobaryo さんの「任意の単位での債権の色付けと与信の分割を実現する Debt View」です。私がこのプロジェクトに多くの時間を割いていた間、Balance Team の日々の業務を @kobaryo さんに大きく支えてもらっていました。次の記事もぜひ読んでみてください。&lt;/p&gt;
</content:encoded></item><item><title>Product Engineer として働く — 小規模開発チームでClient-BEの越境開発を試した記録</title><link>https://engineering.mercari.com/blog/entry/20260622-6d55d015b2/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260622-6d55d015b2/</guid><description>&lt;p&gt;本記事は「Merpay &amp;amp; Mercoin Tech Openness Month 2026」の17日目の記事です。 この記事は新しいロールであるPdE (Product Engineer)というClient / [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 23 Jun 2026 10:00:32 GMT</pubDate><content:encoded>&lt;p&gt;本記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay &amp;amp; Mercoin Tech Openness Month 2026&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の17日目の記事です。&lt;/p&gt;
&lt;p&gt;この記事は新しいロールであるPdE (Product Engineer)というClient / Backend の境界をまたぐ「越境開発」ロールの取り組みについて、@anzai, @victoria Li, @ninnin, @panoramaの4名でお送りします。記事本文は、そのうちの1人である anzai が、自分の体験を一人称で振り返る形で書いています。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;「Client エンジニアが Backend を書き、Backend エンジニアが Client を書く」——そんな体制は実際に成立するのか。Q4 に試した小さな実験を紹介します。&lt;/p&gt;
&lt;p&gt;ひとことでまとめると、Android エンジニアが Postgres のデータベース（DB）設計とサーバー実装を書き、Backend エンジニアが iOS/Android の画面を作りました。やってみて成立はしたものの、楽な道ではありません。何が効いて、どこで詰まったのか。本記事では「越境開発」というテーマに絞って、その実際を共有します。&lt;/p&gt;
&lt;h2&gt;越境開発の背景&lt;/h2&gt;
&lt;p&gt;私は普段メルペイでClient（Android / iOS）のチームを見ている Engineering Manager です。今回はManagerとしてではなく、自分の手でコードを書く一員として KYC（本人確認）領域のAI pod (小規模な開発チーム)に参加しました。&lt;/p&gt;
&lt;p&gt;このチームでは、Product Manager（PdM）を置かず、エンジニアが1人で1プロジェクトを仕様策定からデリバリーまで一気通貫に持つ（1 Person 1 Release）、という体制を試しています。 そしてこの体制を回そうとすると、避けて通れない課題が1つ出てきます。それは「1人で End-to-End（E2E）に持つなら、Clientも Backend も自分で書くことになる」ということです。&lt;/p&gt;
&lt;p&gt;KYC のプロジェクトは、ほとんどの場合 iOS / Android のClientと、サーバー側の API・DB の両方に手を入れます。従来はここをクライアント担当とサーバー担当で分けていました。1人1プロジェクトにするなら、その境界をまたぐ必要があります。E2Eで開発する中で「Client エンジニアが Backend も、Backend エンジニアが Client も開発できるか」を、実プロジェクトで検証することにしました。&lt;/p&gt;
&lt;p&gt;いくつかのプロジェクトで Client-Backend を一気通貫で開発した結果、今期はどちらも成立しました。Client エンジニアがサーバーの DB と API を書いてリリースし、Backend エンジニアがモバイルの画面を出す。これ自体が、まず確かめたかったことです。&lt;/p&gt;
&lt;p&gt;以下では、私がどう開発を進めたかを時系列で振り返り、そのあとで、どこで詰まったかを共有します。&lt;/p&gt;
&lt;h2&gt;AI を活用した越境開発の進め方&lt;/h2&gt;
&lt;p&gt;私はClientエンジニアです。今回担当したプロジェクトで最終的に必要としていたアウトプットは、具体的には2つでした。1つはお客さまに見せる UI/UX、もう1つは、その後の業務報告に使うレポートです。この2つの具体的なアウトプットから逆算して、「では、その裏側でどんな仕組みを実装しなければならないか」を決めていく必要がありました。そしてその仕組みの大部分が、私にとって初めてのサーバーサイド（Go / Postgres）です。&lt;/p&gt;
&lt;p&gt;そこでまず、既存の実装を AI Agent に読み込ませ、「この2つのアウトプットを満たすには、どんな設計・実装が必要か」を AI に検討してもらうところから始めました。要件は手元にあるので、あとはそれを初めての言語と環境にどう落とすか。&lt;/p&gt;
&lt;p&gt;この記事のここから先は、その道のりを時系列でたどります。&lt;/p&gt;
&lt;h2&gt;AI の説明もコードも、最初はわからない&lt;/h2&gt;
&lt;p&gt;最初にぶつかったのは、コードを書く以前の問題でした。AI Agent が返してくる説明そのものが、何を言っているのかわからないのです。&lt;/p&gt;
&lt;p&gt;たとえば「Postgres を起動して、goose で既存の migration を流して、psql で確認して」と AI に言われても、最初は何を指しているのか、まったく理解できませんでした。goose とは何か、psql で何を確かめればいいのか ── そのひとつひとつが、Client 開発しかしてこなかった私には初めての言葉です。AI は正しいことを言っているのかもしれないけれど、こちらにそれを受け取る前提知識がない、という状態からのスタートでした。&lt;/p&gt;
&lt;p&gt;ここでやったのは、ひたすら AI に問い返すことです。「それはどういう意味か」「図にして説明してくれ」と、いろいろなパターンで噛み砕いてもらう。そして自分が納得できたら、その理解を「こういうことですよね」と、今度は別のBackendエンジニアに確認しに行く。これを繰り返すことで、環境がどう動いていて、何をすればテストが回るのか、開発の前提条件をまず頭に入れていきました。&lt;/p&gt;
&lt;p&gt;前提が見えてきたところで、いよいよ実装です。さきほどの要件をもとに「設計して実装してみて」と AI Agent にお願いし、コードを書いてもらいます。すると今度は、出てきたコードそのものが読めません。&lt;/p&gt;
&lt;p&gt;なぜここで &lt;code&gt;defer&lt;/code&gt; を呼んでいるのか。なぜ &lt;code&gt;cancel()&lt;/code&gt; しなければならないのか。ここで &lt;code&gt;context&lt;/code&gt; を受け取っているのは、つまりどういうことなのか。Go のイディオムが、ことごとくわからないのです。&lt;/p&gt;
&lt;p&gt;そこで最初は、作ってもらったコードを一行一行、すべて AI に解説してもらいました。&lt;code&gt;context&lt;/code&gt; のキャンセルでリソースを解放しないと goroutine が漏れる、だからこの位置で &lt;code&gt;defer cancel()&lt;/code&gt; する。そうした説明を1つずつ受けながら、文法書ではなく目の前の実コードを教材にして読み解いていきます。&lt;/p&gt;
&lt;p&gt;なお、DB についてはまったくのゼロからではありませんでした。モバイルでも端末上の SQLite でテーブルを設計し、クエリを書く機会はあります。正規化やインデックスといった基礎概念そのものは持っていたので、サーバーの Postgres は「知っている概念を別のコンテキストに翻訳する」作業に近く、そこは助けになりました。&lt;/p&gt;
&lt;h2&gt;チャンクごとの並走型にたどり着く&lt;/h2&gt;
&lt;p&gt;ただ、「全部書いてもらってから一行ずつ解説」では、どうしても効率が良くありません。これを変えたのが、チームのふりかえりでした。&lt;/p&gt;
&lt;p&gt;私たちは週に1回レトロスペクティブを開き、チームの進め方を少しずつ改善していました。その中で、メンバーの一人が「チャンク（意味のある塊）ごとの並走型」というやり方を持ち込みます。AI に一気に全部を生成させるのではなく、意味のあるコードブロック単位で実装させる。そのブロックが「なぜ必要なのか」を自分が理解できればそのまま進め、理解できなければきちんと問い返す。「ここは A と B のやり方がありそうだけれど、なぜ A を選んだのか」と都度たずねていくことで、コードの一行ずつの意図を理解しながら前に進みます。&lt;/p&gt;
&lt;p&gt;このやり方だと、巨大な生成物を上から順に読み下す必要がなくなり、開発の流れを「意味のある順序」で理解できます。機械的な行順ではなく、設計の意図に沿った順序で頭に入っていく感覚です。質問と回答はログとして残し、読み返せば「今日は何を学んだか」の復習にもなりました。&lt;/p&gt;
&lt;h2&gt;選んだ理由を残し、AI のコードを自分で理解する&lt;/h2&gt;
&lt;p&gt;この進め方には、思わぬ副産物もありました。「いくつかの選択肢のなかで、なぜこの A を選んだのか」を開発の過程ですでに言語化しているので、それをそのまま Pull Request（PR）の説明に書けるのです。結果として、レビューはそれほど詰まることなく得られました。初めての領域でも、選択の根拠を添えられればレビュアーは判断しやすいですし、「ここまで考えた上でのPRなんだな」という信頼も得られます。ビギナーのPRを読むことはコードオーナーにとって大変な負担ですので、このような越境開発において信頼を作るための調査は必要な工程だったと思います。&lt;/p&gt;
&lt;p&gt;ここで1つ、立ち止まって考えるべき論点があります。AI が書いたコードを、人間がわざわざ読んで理解する必要はあるのか。生成して、テストが通って、動けばよいのではないか。これはこれから重要になる議論だと思います。&lt;/p&gt;
&lt;p&gt;私の現時点の答えは「理解すべき」です。&lt;/p&gt;
&lt;p&gt;これからの生産性は、書いたコード量ではなく、意思決定の回数と質でほぼ決まっていくと考えています。そして意思決定の質と回数を上げるには、ドメインの理解が必要です。「このバックエンドの設計は、これで本当に正しいのか」を判断する場面で、いまのところ最終的な判断は人間がしています。その判断を下せるだけの理解を持っていないと、意思決定の回数も質も上がっていきません。だからこそ、AI が書いたコードでも中身を理解しておく価値がある、というのが今の立場です。&lt;/p&gt;
&lt;p&gt;またAIの仕組み的に、永遠にそのPRに対して改善ポイントを考え出すことができます。たとえば、あるPRについて1回目のセッションでAIに聞くと A・B・C の3点を直すよう言われ、その修正結果を別のセッションで聞くと、今度は E・F・G の3点を直すよう言われる、ということが起こります。それを全部反映すると、最終的にはオーバーエンジニアリングした膨大なPRになりがちなので、現状は「どこまでが適切か」を人が判断する必要があるでしょう。&lt;/p&gt;
&lt;p&gt;もしこの「理解する」のに2〜3年かかる長期投資なら、学ぶ理由はもっと厳しく問われるでしょう。ですが実際はそうではありませんでした。いまの AI Agent によるオンボーディングは本当にしやすく、いわば優秀な指導役が隣について、いつでもペアプロに付き合ってくれるような状況です。この環境であれば、3ヶ月もあれば、いま開発している領域の一通りの知識は得られるという手応えがありました。もちろん、もともとシニアエンジニアとしての経験がある、という条件付きではあります。投資回収が数年ではなく数ヶ月の単位に縮んでいるのなら、理解する側を選ぶのが合理的だと考えています。&lt;/p&gt;
&lt;h2&gt;設計の最終確認はシニアエンジニアと&lt;/h2&gt;
&lt;p&gt;とはいえ、AI に聞けばそのまま採用、とはしませんでした。象徴的だったのがインデックスの設計です。&lt;/p&gt;
&lt;p&gt;「このカラムにインデックスを張るべきか」を、AI の提案を鵜呑みにするのではなく、なぜ必要なのか／不要なのかを自分で根拠を持って調べました。どんなクエリパターンで引かれるのか、カーディナリティはどうか、読み取りが速くなるぶん書き込みコストとのトレードオフはどうなるのか。こうした観点を1つずつ確かめて、「この設計にはこういう理由がある」と説明できる状態にします。&lt;/p&gt;
&lt;p&gt;それでも、最後はシニアエンジニアと一緒にレビューしました。正直に言えば、これは自分の自信のなさを埋めるためでもありました。AI が生成したものが本当に正しいのか、その最終的な判断が、私一人ではどうしてもつかなかったからです。&lt;/p&gt;
&lt;p&gt;このレビューには、不安を消す以上の価値がありました。シニアエンジニアが、考え得るパターンをいくつも挙げてくれたうえで「では、この方法でいきましょう」と意思決定でき、いくつかの改善点も見つかりました。よりベターな やり方にたどり着くうえで、人と一緒に見てもらう工程はやはり必要だったように思います。こちらは Backend エンジニアが Client 開発をする場合も同様でした。&lt;/p&gt;
&lt;p&gt;AI で下書きと理解を加速し、自分で根拠を固め、最終的には人とレビューする。この3段構えが、初めての領域で品質を担保し、かつ自分が安心して前に進むうえで、現実的なやり方でした。AI は出発点と伴走者としては非常に強力でしたが、設計の最終的な妥当性を見極め、選択肢を広げてくれるのは、まだ人間のレビューだった、というのが実感です。&lt;/p&gt;
&lt;p&gt;なお、今回書いた DB 設計は2テーブルだけのシンプルな構成で、本格的な分散やシャーディングの考慮までは要りませんでした。テーブルが増え、複雑なインデックス設計が絡む規模で、AI とこの3段構えがどこまで通用するのかは、まだ検証できていません。「シンプルだったから回った」可能性は十分にあります。&lt;/p&gt;
&lt;p&gt;最後に補足すると、こうして進められた背景には、組織図上で「越境させた」のではなく、同じチームの中で互いの領域をペアプロで教え合える環境があったことが大きいです。AI に聞いてもわからないこと、そもそも何を聞けばいいかわからないことは、隣にいる詳しいメンバーにその場で聞く。AI と人、両方に頼れる相手がいたことが、越境のコストを最も下げました。&lt;/p&gt;
&lt;h2&gt;つまずいたポイントと対策&lt;/h2&gt;
&lt;p&gt;ここまでは「どう乗り越えたか」を中心に書いてきましたが、実際には数多くの詰まりどころがありました。そしてその多くは設計や実装の難しさではなく、環境・ツール・運用、つまりコードを書く以外の全部にありました。Client しか書いてこなかった人間が Backend に入ると、ここでことごとく足を取られます。代表的なものを課題ごとに紹介します。&lt;/p&gt;
&lt;h2&gt;現状の開発スタイルがわからない&lt;/h2&gt;
&lt;p&gt;最初の壁は、開発スタイルそのものの違いでした。&lt;/p&gt;
&lt;p&gt;Clientエンジニアの感覚として、まずローカルでビルドして動作を確認しようとしました。ところが、このコードをもともとメンテナンスしていたチームは、開発環境（dev）にまずデプロイしてから動かす、というスタイルを取っていました。つまり、ローカルの &lt;code&gt;docker compose&lt;/code&gt; は、実のところほとんど使われていなかったのです。&lt;/p&gt;
&lt;p&gt;私はそれを知らないまま、ローカルの &lt;code&gt;docker compose&lt;/code&gt; を立ち上げようとして、「Postgres のバージョンが合わない」「環境変数が無効だ」と、動かない環境を前に四苦八苦しました。結果ローカルでテストできるようになったので良かったのですが、リポジトリ固有のこうした暗黙知は、AI ではどうにもならない部分です。&lt;/p&gt;
&lt;h2&gt;enum を一つ増やしたら、芋づる式にバージョンが上がる&lt;/h2&gt;
&lt;p&gt;次につまずいたのは、依存関係の更新でした。やりたかったのは、protobuf の定義に enum を一つ増やし、新しく追加された定数を参照することだけです。ところが、その定義を取り込もうと &lt;code&gt;go mod tidy&lt;/code&gt; を実行したとたん、直接は触っていないはずのライブラリのバージョンが一斉に上がってしまいました。gRPC まわりから、最終的には Go 本体の要求バージョンまで動いてしまう。たった一つの enum のために、なぜここまで広がるのか、最初はまったく見当がつきませんでした。&lt;/p&gt;
&lt;p&gt;やっかいだったのは、その「なぜ」に対して、もっともらしい説明がいくつも出てきて、しかもそれが間違っていたことです。AI に理由を聞くと、それらしい仮説、たとえば「余計な更新を巻き込んでいるだけだから、最小限に戻せる」といった答えを返してきます。ですが、その通りに戻そうとしても差分は収まりませんでした。&lt;/p&gt;
&lt;p&gt;ひたすらAIと問答し、ようやく分かりました。原因は依存が構造的に噛み合っていたことでした。新しい生成ライブラリが新しい gRPC 系を要求し、それがさらに別の基盤ライブラリを引き、最終的に Go 本体の下限まで押し上げている構造だったのです。本当にそうなのかの依存関係のグラフを図示し、それでも自信が持てなかったのでPR Reviewでシニアエンジニアにレビューいただきました。&lt;/p&gt;
&lt;h2&gt;ローカルでのテストと Lint&lt;/h2&gt;
&lt;p&gt;DB アクセス層（DAO: Data Access Object）のテストをローカルで回そうとすると、ただ実行するだけでは通りませんでした。テスト用の Postgres を専用ポートで立て、&lt;code&gt;PGPORT&lt;/code&gt; を明示的に指定する、といった、チームでは当たり前すぎて誰もドキュメントに書かない作法を、1つずつ踏みながら知っていきます。&lt;/p&gt;
&lt;p&gt;たとえば DAO テストは、&lt;code&gt;5556&lt;/code&gt; のような専用ポートでテスト用の Postgres を立て、&lt;code&gt;PGPORT&lt;/code&gt; でそこを向けて初めて通ります。これを知らないと、テストはローカルの既定のデータベースに接続しようとして、原因の見えないまま失敗し続けます。どこにも書かれていないこの一手にたどり着くまで、エラーメッセージとにらめっこする時間が続きました。&lt;/p&gt;
&lt;p&gt;Lint も同様でした。ローカルで走らせると、設定のバージョン差（v1 系と v2 系の非互換）でうまく通らず、CI（Continuous Integration）に任せるという割り切りに落ち着きました。「ローカルで全部緑にしてから push する」という前提が、まず崩れます。&lt;/p&gt;
&lt;h2&gt;E2E 開発のマシン要件&lt;/h2&gt;
&lt;p&gt;1人で Client も Backend も1台で回すと、マシンへの負荷が一気に上がります。実際、Android ビルド＋エミュレータ、iOS ビルド＋シミュレータ、Docker コンテナ3つを同時に動かして、メモリを90GB使う場面がありました。スワップなしで E2E 開発をするなら、96GB は欲しいところです。&lt;/p&gt;
&lt;p&gt;特にBackendのEngineerは元々高いスペックのPCを持っておらず、Client開発でまず最初のローカルビルドを試す段階で躓いてしまいました。ここからローカルビルドに耐えるPCを支給してもらうまでClient開発に着手できないということが起きてしまっていました。&lt;/p&gt;
&lt;p&gt;越境して E2E を1人で持つということは、ツールチェーンも全部抱えるということでもあります。ハードウェアは目立ちませんが、無視できない条件でした。チーム内でも「越境する人ほどマシン要件が上がる」という前提を共有し、開発機の選定では多めのメモリを見込んでおく必要がある、という話になりました。&lt;/p&gt;
&lt;h2&gt;振り返って&lt;/h2&gt;
&lt;p&gt;これらの課題に共通していたのは、詰まったポイントのほとんどが、設計や実装の難しさではなく、環境・ツール・運用の暗黙知だったことです。コードを書く力そのものよりも、その手前のところで足を取られていた、というのが実感でした。&lt;/p&gt;
&lt;p&gt;ここで効いてくるのが、もともと人間のオンボーディングでも重要だった条件です。「DX（Developer Experience）が整っている」「オンボーディング資料や開発ドキュメントが整備されている」という、人にとっての整備状況が、そのまま AI にとっての整備状況でもありました。ドキュメントが薄く暗黙知に頼っている領域では、人も AI も同じように迷います。&lt;/p&gt;
&lt;p&gt;今のところは、やってみて初めてわかる問題を踏んでは1つずつ潰していく、いわば「悲鳴駆動」で進めるしかなく、チームを立ち上げてから数ヶ月は生産性を上げるのが難しいのが正直なところです。逆に言えば、ここをドキュメントとセットアップスクリプトであらかじめ潰しておけば、越境のコストは大きく下げられる、という改善の的もはっきり見えました。次に越境に挑むなら、最初に着手するのはこの整備だろうと考えています。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;KYC で試した範囲では、Client ⇄ Backend の越境開発は成立しました。Client エンジニアがサーバーを書き、Backend エンジニアが iOS/Android を出せています。&lt;/p&gt;
&lt;p&gt;それを支えたのは、いくつかの条件でした。まず、小規模な開発チームとして一体で動き、ペアプロやオンボーディングセッションをしながら互いの領域を学べたこと。次に、AI Agent が初見の言語やコードを一文ずつ理解する伴走者になってくれたこと。ただし、設計の最終的な妥当性を見極め、そして開発者の不安を払拭するのは、いまも人間のシニアレビューでした。環境構築やツールのバージョン、ローカルテストといった暗黙知を埋めるには、オンボーディング環境とドキュメントが要ります。一方で、選択の根拠を開発の過程で言語化し、それを残しておけば、専門外の領域でもレビューは回りました。そして最後に、これらすべてを1台で回すには、メモリ96GB級の開発機が現実的に必要でした。&lt;/p&gt;
&lt;p&gt;そして、これらが揃っていても、最初の数ヶ月は生産性が落ちます。越境は成長痛とセットでやってくる、というのが一番の学びでした。逆に言えば、その成長痛をチームとして引き受ける覚悟と、AI を含めたオンボーディングの仕組みがあれば、「クライアントエンジニア」「サーバーエンジニア」という肩書きの境界は、思っていたより動かせます。&lt;/p&gt;
&lt;p&gt;次は、もっと複雑な DB 設計を含むプロジェクトで、AI がどこまで越境を支えられるのかを確かめていきます。エンジニア一人ひとりが領域をまたいで動けるようになることは、巡り巡って、より早く・より確かな価値をお客さまに届けることにつながると考えています。これからもこういった挑戦を続けていきたいと思います。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;次の記事は yutaroさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>TiDB/AlloyDBの大規模テーブルを高速にBigQueryに同期するための工夫</title><link>https://engineering.mercari.com/blog/entry/20260619-8358897bd1/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260619-8358897bd1/</guid><description>&lt;p&gt;こんにちは。Data Ingestion チームでData Engineerをしている @orfeon です。この記事は「Merpay &amp;amp; Mercoin Tech Openness Month 2026」の14 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 19 Jun 2026 10:00:04 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。Data Ingestion チームでData Engineerをしている @orfeon です。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay &amp;amp;amp; Mercoin Tech Openness Month 2026&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の14日目の記事です。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;Data Ingestion(旧Data Platform)チームでは、多数のマイクロサービスが管理する&lt;strong&gt;データベース・テーブル&lt;/strong&gt;から、大量のデータを継続的にDWH（データウェアハウス）へ同期する必要があります。同期対象には数億〜&lt;strong&gt;数百億件&lt;/strong&gt;に達する大規模なテーブルも含まれ、これらをいかに速く・安全に・一貫性を保ったまま抽出するかが、DWHの鮮度や安定性にとって大事になります。&lt;/p&gt;
&lt;p&gt;これまで &lt;strong&gt;Cloud Spanner&lt;/strong&gt; からのデータ取得では、Spannerの分散DB特有の機能（後述）を活用することで、大規模テーブルでも高いスループットでの取得を実現できていました。 一方、社内にはTiDBやAlloyDBといったSpanner以外のデータベースも多く利用されており、その中には数百億件以上に達するテーブルもあります。 これらのテーブルは従来、主キーなどで&lt;strong&gt;シーク方式&lt;/strong&gt;で取得していましたが、単一コネクションでの&lt;strong&gt;シーケンシャルなデータ取得&lt;/strong&gt;になるため、大規模テーブルでは取得に非常に時間がかかっていました。&lt;/p&gt;
&lt;p&gt;そこで今回、Spannerと同じように、&lt;strong&gt;それぞれのDBに特有の機能を活用して並列取得などでスループットを上げる&lt;/strong&gt;よう工夫しました。 具体的には、&lt;strong&gt;TiDB&lt;/strong&gt; と &lt;strong&gt;AlloyDB&lt;/strong&gt; の大規模テーブルをDWHへ同期する仕組みを &lt;strong&gt;Cloud Dataflow（Apache Beam）&lt;/strong&gt; 上に構築しました。 本記事では、その中核となる2つのSourceモジュール  &lt;code&gt;TiDBSource&lt;/code&gt; と &lt;code&gt;PostgresSource&lt;/code&gt;  について、高いスループットを実現するための工夫を解説します。&lt;/p&gt;
&lt;h2&gt;なぜ汎用JDBCではなく専用モジュールなのか&lt;/h2&gt;
&lt;p&gt;Beam/Dataflowには汎用的な &lt;code&gt;JdbcIO&lt;/code&gt; が既に存在します。 しかし汎用JDBCは「&lt;code&gt;SELECT&lt;/code&gt;を実行して結果を1行ずつ読む」という標準的な経路をたどるため、大規模テーブルでは以下のボトルネックが発生します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1行ごとのSQL処理オーバーヘッド&lt;/strong&gt;: 通常のクエリ実行では、サーバ側でのタプルのテキスト/プロトコル変換などが行ごとに発生する。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;並列化の難しさ&lt;/strong&gt;: テーブルを並列に読むには「どこで分割するか」を決める必要があるが、&lt;code&gt;OFFSET&lt;/code&gt;ベースの分割はオフセットが大きくなるほど遅くなり、フルスキャンを誘発する。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一貫性の確保&lt;/strong&gt;: 並列に複数コネクションから読む場合、各コネクションが別々の時点を読むと整合性が崩れる。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;そこで今回のモジュールでは、&lt;strong&gt;それぞれのデータベースが持つネイティブなバルク転送機構と物理的なデータ配置情報を活用&lt;/strong&gt;し、汎用JDBCのボトルネックを回避する設計にしました。&lt;/p&gt;
&lt;p&gt;加えて運用上の大きなメリットとして &lt;strong&gt;分割キー（フィルタ条件）の自動抽出&lt;/strong&gt; があります。 マイクロサービスごとに膨大なテーブルを扱う環境では、テーブル1つひとつに対して「どのカラムで分割するか」を人手で指定するのは現実的ではありません。 両モジュールはテーブルのメタデータから&lt;strong&gt;主キー（PK）や暗黙の行ID、物理ブロック位置を自動で見つけ出し&lt;/strong&gt;、分割範囲の絞り込み条件を組み立てます。 利用者は接続先とテーブル名を指定するだけで、同じ設定が多数のテーブルに横展開することができます。&lt;/p&gt;
&lt;h2&gt;なぜCloud Spannerでは高いスループットでデータ取得が可能なのか&lt;/h2&gt;
&lt;p&gt;今回の設計の発想は、既にうまくいっていたSpannerからの取得方法を、TiDBやAlloyDBにも持ち込むことにありました。 そこでまずSpannerが大規模テーブルでも高いスループットを出せている理由を説明します。 Spannerは分散データベースとして、以下の機能を組み合わせています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PartitionQuery / PartitionRead（Splitベースの自動分割）&lt;/strong&gt;: Spannerはデータを内部的に &lt;strong&gt;Split&lt;/strong&gt;（キー範囲＋負荷ベース）へ分割して保持しています。&lt;code&gt;PartitionQuery&lt;/code&gt; はこのSplit境界に基づいてクエリを複数のパーティションに自動分割します。クライアントはキー範囲などSplitの内部構造を意識する必要がありません。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BatchReadOnlyTransaction（スナップショット一貫性）&lt;/strong&gt;: 全パーティションの読み取りが、&lt;code&gt;TimestampBound&lt;/code&gt; で指定した同一スナップショットを参照することを保証します。ロックを取らずに一貫した読み取りができます。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Partition Tokenの分散・並列実行&lt;/strong&gt;: 分割結果は&lt;strong&gt;シリアライズ可能なPartition Token&lt;/strong&gt;として返されるため、複数プロセス・複数マシン、そして&lt;strong&gt;Beam Worker&lt;/strong&gt;に配布してそのまま並列実行できます。Apache Beamの &lt;code&gt;SpannerIO&lt;/code&gt; も内部でこの仕組みを使っています。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Partition Tokenによる自動バージョン保持&lt;/strong&gt;: Tokenが有効な間は対象バージョンがGCされないことが保証されるため、クライアント側で明示的なバージョン保護（SafePoint管理）が不要です。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Boost（Spanner固有）&lt;/strong&gt;: Google管理の独立した計算リソースで読み取るオプションで、&lt;strong&gt;本番ワークロードへの影響をほぼゼロ&lt;/strong&gt;にしつつ弾力的にスケールできます。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらは「&lt;strong&gt;物理的なデータ配置に沿った自動分割&lt;/strong&gt;」「&lt;strong&gt;スナップショット一貫性&lt;/strong&gt;」「&lt;strong&gt;分割単位の分散ワーカーへの配布と並列実行&lt;/strong&gt;」という構図で成り立っています。Spannerではこれらが高度に抽象化されたAPIとして提供されていますが、&lt;strong&gt;TiDBやAlloyDB(PostgreSQL)にもそれに近いDB固有の機能が存在します&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;このSpannerの機能とTiDBやPostgreSQLの機能は以下のように対応します。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Spanner&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TiDB（dumpling相当）&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;AlloyDB（PostgreSQL）&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PartitionQuery（Split境界で自動分割）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TABLESAMPLE REGIONS()&lt;/code&gt;（TiKV Region境界）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctid&lt;/code&gt;物理ブロック範囲（&lt;code&gt;pg_relation_size&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BatchReadOnlyTransaction（スナップショット）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tidb_snapshot&lt;/code&gt; MVCC(Multi-Version Concurrency Control) + TSO(Timestamp Oracle）&lt;/td&gt;
&lt;td&gt;バッチ読み取り（&lt;code&gt;ctid&lt;/code&gt;スナップショットのズレは許容）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partition Tokenの分散実行&lt;/td&gt;
&lt;td&gt;Range条件の分散実行（本記事の設計）&lt;/td&gt;
&lt;td&gt;Range条件の分散実行（本記事の設計）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partition Tokenによる自動GC保護&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tidb_gc_life_time&lt;/code&gt; の引き上げで代替&lt;/td&gt;
&lt;td&gt;（該当なし）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;SpannerのSpannerIOで提供されている「分割 → 配布 → 並列スナップショット読み取り」を、TiDB/AlloyDBではDB固有の機能を組み合わせて自前で構築する、というのが本記事のモジュールの狙いです。 以降その共通の仕組みと各DB向けの実装を見ていきます。&lt;/p&gt;
&lt;h2&gt;共通アーキテクチャ&lt;/h2&gt;
&lt;p&gt;両モジュールに共通する基本戦略は次の3ステップです。 &lt;code&gt;TiDBSource&lt;/code&gt;/&lt;code&gt;PostgresSource&lt;/code&gt;はCloud Dataflow バッチジョブとして実行され、以下3つのステップで役割が分かれます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;テーブルの範囲分割&lt;/strong&gt;: 1本のコネクションでメタデータだけを取得し、テーブルを物理的な分割単位（Range）のリストに列挙する&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;再分配&lt;/strong&gt;: 分割単位をPCollectionの「種」として撒き、&lt;code&gt;Reshuffle&lt;/code&gt;でワーカーに再分配する&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;並列読み込み&lt;/strong&gt;: 各ワーカーが担当Rangeをネイティブのバルク転送機構で並列に読み取る&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/e7586f4b--2026-06-15-6.32.06-1024x585.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;以降、TiDBとPostgreSQLそれぞれについて、この3ステップの中身を掘り下げます。まずTiDBから、この3つのステップがどのように実装されるかを見ていきます。&lt;/p&gt;
&lt;h2&gt;TiDBテーブルからのデータ抽出&lt;/h2&gt;
&lt;h3&gt;TiDB公式ツール dumpling に学ぶ&lt;/h3&gt;
&lt;p&gt;TiDBには &lt;a href=&quot;https://docs.pingcap.com/tidb/stable/dumpling-overview/&quot; title=&quot;dumpling&quot;&gt;dumpling&lt;/a&gt; という高速なエクスポートツールが公式に提供されています。 TiDBSource の設計は、このdumplingが高スループットを実現している仕組みを参考にしています。 まずはdumpling側の要点を整理します。&lt;/p&gt;
&lt;h3&gt;テーブルのチャンク分割と並列読み取り&lt;/h3&gt;
&lt;p&gt;dumplingは、1テーブルを丸ごと1クエリで読むのではなく、テーブルをチャンク（範囲）に分割し、各チャンクを独立したSELECTクエリとして並列実行します。 チャンク分割は3段階のフォールバック構造になっています。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;戦略&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;方式&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;概要&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A（最優先）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TiKV Regionベース分割&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TABLESAMPLE REGIONS()&lt;/code&gt; でRegion境界をチャンク境界にする&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B（フォールバック）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;数値インデックスベース分割&lt;/td&gt;
&lt;td&gt;数値型PK/インデックスのMIN/MAXから均等分割&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C（最終）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;テーブル全体ダンプ&lt;/td&gt;
&lt;td&gt;分割可能なフィールドがない場合は1クエリ&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;特に重要なのが戦略Aです。 TiDBではデータがTiKV上で Region（デフォルト96MB単位） に分散配置されます。 dumplingはこのRegion境界をそのままチャンク境界として利用するため、各チャンクが異なるTiKVノードへの読み取りリクエストに分散され、クラスタ全体のI/O帯域を引き出せます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/8be6ae90--2026-06-15-6.34.38-1024x521.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;dumplingの並列実行の仕組み: Producer-Consumer&lt;/h3&gt;
&lt;p&gt;分割したチャンクを並列に読み出すために、dumplingは内部で &lt;strong&gt;Producer-Consumer&lt;/strong&gt; という構造をとります。登場人物は次の3つです（いずれもdumplingの実装に出てくる用語です）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Producer（プロデューサ）&lt;/strong&gt;: テーブルをチャンクに分割し、「このチャンクを読め」というタスクを作り続ける係。dumplingではメインのgoroutineが担当します。先ほどのRegion境界などをもとにタスクを生成します。 &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Writer（ライター）&lt;/strong&gt;: 生成されたタスクを受け取り、実際にSELECTを発行してデータを読み出す係。&lt;code&gt;--threads&lt;/code&gt; で指定した数だけ並列に動き、それぞれが独立したDB接続を持ちます。タスクを消費するConsumer側にあたります。 &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;infiniteChan（無制限チャネル）&lt;/strong&gt;: ProducerとWriterの間をつなぐ、容量に上限のないキュー（待ち行列）。Writerの処理が詰まってもProducerがブロックされず、生成済みのタスクをいくらでも貯めておけます。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/dd846eb6--2026-06-16-10.17.54.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;このように、タスクを作成する人（Producer）とタスクを実行する人（Writer）を分離し、その間を待ち行列（infiniteChan）でつなぐことで、分割と読み取りを互いに待たせずに並列で回す基本構造です。後述のTiDBSourceは、この役割分担をそのままDataflowの分散モデルに置き換えています。&lt;/p&gt;
&lt;h3&gt;Snapshot読み取り&lt;/h3&gt;
&lt;p&gt;dumplingはTiDBのMVCC (Multi-Version Concurrency Control)機構を利用し、特定のTSO（Timestamp Oracle）時点の &lt;strong&gt;スナップショット&lt;/strong&gt; から一貫したデータを読み取ります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ロック不要&lt;/strong&gt;: &lt;code&gt;FLUSH TABLES WITH READ LOCK&lt;/code&gt; のような排他ロックが不要で、書き込みをブロックしない。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一貫性保証&lt;/strong&gt;: 全Writerが同一時点のデータを読むため整合性が保たれる。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高スループット&lt;/strong&gt;: ロック競合がないため並列度を上げられる。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;加えてdumplingは、長時間のダンプ中にTiDBのGC（Garbage Collection）がスナップショット時点の古いバージョンを回収しないよう、PD（Placement Driver）に対してGC SafePointを登録します。&lt;/p&gt;
&lt;h3&gt;TiDBの機能を活用したDataflowでの実装&lt;/h3&gt;
&lt;p&gt;TiDBSource は、dumplingのこれらのアイデアを Apache Beam / Dataflowのモデルに移植 したものです。dumplingがgoroutineで実現していた並列性を、Dataflowの分散ワーカーによる並列性に置き換えています。対応関係は次の通りです。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;dumpling&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TiDBSource (Dataflow)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Producerがチャンクタスクを生成&lt;/td&gt;
&lt;td&gt;パイプライン構築時に&lt;code&gt;Range&lt;/code&gt;のリストを生成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;infiniteChan&lt;/code&gt; + 複数Writer goroutine&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Reshuffle&lt;/code&gt; + &lt;code&gt;ParDo&lt;/code&gt;による分散ワーカー並列処理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;各Writerが独立DB接続でSELECT&lt;/td&gt;
&lt;td&gt;各ワーカーが&lt;code&gt;@Setup&lt;/code&gt;で独自コネクションを確立&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TSOスナップショット読み取り&lt;/td&gt;
&lt;td&gt;TSOを一度取得し全ワーカーに配布&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;ステップ1: 分割キーの決定とRangeの列挙&lt;/h4&gt;
&lt;p&gt;パイプラインの初期起動時に1本のコネクションを張り、出力スキーマの確定・スナップショットTSOの取得・テーブルの分割を行います。 ここではメタデータと境界値だけを読み、実データのスキャンは行いません。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分割キーの自動解決&lt;/strong&gt; は次の優先順位で行われます。利用者がカラムを指定しなくても、テーブルのメタデータから自動的に決定されます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;利用者が &lt;code&gt;splitField&lt;/code&gt; を明示指定していればそれを使う&lt;/li&gt;
&lt;li&gt;なければ&lt;strong&gt;単一カラムの主キー（PK）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;それもなければ&lt;strong&gt;暗黙の行ID &lt;code&gt;_tidb_rowid&lt;/code&gt;&lt;/strong&gt;（クラスタードキーを持たないテーブル向け）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;_tidb_rowid&lt;/code&gt; は、明示的な主キーを持たないテーブルでTiDBが内部的に振る暗黙の行IDです。 これを分割キーに使えるため、主キー設計に依存せず、どんなテーブルでも分割の足がかりを得られます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rangeの列挙&lt;/strong&gt; は、先述のdumplingの戦略A→B→Cと同じ3段階フォールバックで行います。&lt;/p&gt;
&lt;p&gt;戦略Aは、次のSQLでTiKVのRegion境界を取り出します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT `pk` FROM table TABLESAMPLE REGIONS() ORDER BY `pk`&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TABLESAMPLE REGIONS() は各Regionの先頭行を返すため、結果の各値が「次のチャンクの下限」になります。 境界値の列 b[1], b[2], …, b[n] から、隣り合う境界で挟まれた半開区間を生成します。 取りこぼしを防ぐため、最初の区間は下側を、最後の区間は上側を開いておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chunk[ -∞,  b[1] ),  chunk[ b[1], b[2] ),  …,  chunk[ b[n], +∞ )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TABLESAMPLE REGIONS() はTiDB v5.0以降の構文です。 非TiDBのMySQLや古いTiDBではこのクエリが失敗するため、自動的に戦略B（数値MIN/MAX均等分割）へフォールバックします。 戦略Bは、SELECT MIN(pk), MAX(pk) で取得した範囲を、推定行数とチャンクあたりの目標行数 splitSize から決めた個数で等分します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chunks = ⌈ 推定行数 / splitSize ⌉
step   = (max − min) / chunks + 1
区間   = [min, min+step), [min+step, min+2·step), … , [ …, max]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(stepの計算では厳密な切り上げ ⌈(max−min)/chunks⌉ ではなく+1 としています。半開区間 [cutoff, cutoff+step) で走査するため、割り切れるケースでもmax が最終チャンクに確実に含まれるようstep を 1 大きく取っており、実際のチャンク数は chunks 以下になります)&lt;/p&gt;
&lt;h4&gt;ステップ2: Rangeの再分配（Reshuffle）&lt;/h4&gt;
&lt;p&gt;範囲が決まったら次にワーカーに範囲ごとの処理を並列にさせる必要があります。列挙した&lt;code&gt;Range&lt;/code&gt;のリストを並列実行するよう明示的に指定するためにPCollection化した&lt;code&gt;Range&lt;/code&gt; の後に、 &lt;code&gt;Reshuffle.viaRandomKey()&lt;/code&gt; を挟みます。 &lt;code&gt;Reshuffle&lt;/code&gt; には2つの重要な役割があります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;fusionの分断&lt;/strong&gt;: Dataflowは連続する処理を一つの処理Stageとして結合してしまうことがあります。これがないとDataflowは&lt;code&gt;Create&lt;/code&gt;と後続の読み取り&lt;code&gt;ParDo&lt;/code&gt;を融合し、Rangeが1ワーカーに偏って並列性が出ません。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ランダムキーによる再分配&lt;/strong&gt;: 全Rangeが利用可能なワーカーへ均等にばらまかれ、クラスタ全体に読み取り負荷が分散されます。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これがdumplingの「&lt;code&gt;infiniteChan&lt;/code&gt;から複数Writerがタスクを取り出す」構造に相当します。&lt;/p&gt;
&lt;h4&gt;ステップ3: 各ワーカーでのスナップショット並列読み取り&lt;/h4&gt;
&lt;p&gt;各ワーカーは処理開始時（&lt;code&gt;@Setup&lt;/code&gt;）に自前のコネクションを確立し、パイプライン構築時に取得・配布されたTSOで &lt;code&gt;tidb_snapshot&lt;/code&gt; をセットします。 &lt;strong&gt;全ワーカーが同一TSOを使うことで、分散読み取りでも単一時点の一貫したスナップショットになります&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;TSOの取得は、一貫スナップショットを開始してその時点の論理時刻を読むだけで完了します（トランザクション自体はすぐロールバックします）。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;START TRANSACTION WITH CONSISTENT SNAPSHOT;SELECT @@tidb_current_ts;   -- ← この値（TSO）を全ワーカーに配布&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;各ワーカー側では、読み取り前にセッション変数を設定します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SET @@tidb_snapshot = &amp;#039;&amp;lt;配布されたTSO&amp;gt;&amp;#039;;  -- ロックなしで同一MVCC版を読む
SET @@session.tidb_enable_paging = ON;  -- 大量スキャン時のメモリ使用量を抑制&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tidb_enable_paging&lt;/code&gt; はCoprocessorリクエストのメモリ使用量を抑える設定です（TiDB v6.2.0以降はデフォルト有効。変数を知らないDBではスキップ）。&lt;/p&gt;
&lt;p&gt;実際の読み取りでは、担当Rangeを分割キーへの&lt;strong&gt;値の範囲条件&lt;/strong&gt;に変換してSELECTに組み込みます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT * FROM table WHERE `pk` &amp;gt;= 1000 AND `pk` &amp;lt; 2000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここで重要なのは、この条件が&lt;strong&gt;主キー（インデックス）に対する値の比較&lt;/strong&gt;である点です。 データベースはこの行値比較を使ってインデックスを&lt;strong&gt;シーク&lt;/strong&gt;し、該当範囲の先頭へ直接ジャンプして必要な行だけを読みます。 &lt;code&gt;OFFSET&lt;/code&gt;のように先頭から数え直す必要がないため、どのチャンクも一定コストで読み出せます。&lt;/p&gt;
&lt;p&gt;また、読み取りはMySQL Connector/Jの&lt;strong&gt;行単位ストリーミングモード&lt;/strong&gt;（&lt;code&gt;fetchSize = Integer.MIN_VALUE&lt;/code&gt;）で行います。 これは結果セット全体をワーカー側メモリにバッファせず1行ずつ取り出す特別な設定で、巨大チャンクでもワーカーのメモリ消費が一定に保たれます。 dumplingがgo-sql-driver/mysqlのストリーミング動作で実現しているのと同じ効果を、JDBC側で引き出している形です。&lt;/p&gt;
&lt;h3&gt;制約: GC SafePoint&lt;/h3&gt;
&lt;p&gt;dumplingはPDクライアント経由でGC SafePointを登録しますが、これは JDBCコネクションからは到達できません。 そのためTiDBSourceではSafePoint登録を行わず、長時間の読み取りに対しては運用側でクラスタの tidb_gc_life_time を引き上げ、読み取り中にGCがスナップショットのバージョンを回収しないようにする運用方針としています。&lt;/p&gt;
&lt;h2&gt;AlloyDB（PostgreSQL）テーブルからのデータ抽出&lt;/h2&gt;
&lt;h3&gt;AlloyDBとPostgreSQL互換性&lt;/h3&gt;
&lt;p&gt;AlloyDBは、Google CloudがPostgreSQL互換で提供するフルマネージドのデータベースサービスです。 &lt;strong&gt;ワイヤプロトコルもSQLの方言もPostgreSQLと互換&lt;/strong&gt;であるため、クライアントから見ればPostgreSQLそのものとして扱え、標準のPostgreSQL JDBCドライバがそのまま使えます。 さらに &lt;code&gt;ctid&lt;/code&gt;（物理行位置）や &lt;code&gt;COPY&lt;/code&gt;、&lt;code&gt;pg_relation_size&lt;/code&gt; といったPostgreSQLの内部機能・システムカタログも利用できます。&lt;/p&gt;
&lt;p&gt;したがって、AlloyDBからのデータ抽出は &lt;strong&gt;PostgreSQLの機能を前提に設計できます&lt;/strong&gt;。 以下では&lt;code&gt;PostgresSource&lt;/code&gt;がPostgreSQLのどの機能を使っているかを説明しますが、その内容は基本的にAlloyDBにも当てはまります。&lt;/p&gt;
&lt;h3&gt;通常のJDBCクエリ取得との違い&lt;/h3&gt;
&lt;p&gt;PostgresSource も「物理的な分割 → 並列読み取り」という骨格はTiDBと同じですが、データ転送経路とテーブル分割の方式 がPostgreSQL固有の機能に最適化されています。 まず、通常のJDBC経由のクエリ取得との違いを整理します。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;観点&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;通常のJDBC (&lt;code&gt;SELECT&lt;/code&gt;)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;PostgresSource (&lt;code&gt;COPY ... TO STDOUT BINARY&lt;/code&gt;)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;転送経路&lt;/td&gt;
&lt;td&gt;拡張クエリプロトコル&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;COPYプロトコル（バルク転送専用）&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;行ごとの処理&lt;/td&gt;
&lt;td&gt;パース/プラン/結果整形が走りうる&lt;/td&gt;
&lt;td&gt;クエリは1回プラン。あとはタプルを連続送出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;データ形式&lt;/td&gt;
&lt;td&gt;テキスト or バイナリのフィールド単位&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;バイナリのタプルストリームを直接デコード&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;並列分割&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OFFSET&lt;/code&gt;/&lt;code&gt;LIMIT&lt;/code&gt;等（大きいほど低速）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ctid&lt;/code&gt;物理ブロック範囲（フルスキャン不要）&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;PostgreSQLの COPY は、もともと大量データのインポート/エクスポートのために用意された専用経路で、通常のクエリ実行に伴う1行ごとのオーバーヘッドを回避できます。 PostgresSource はJDBCドライバの CopyManager API（PGCopyInputStream）を使い、COPY (SELECT &amp;#8230;) TO STDOUT (FORMAT BINARY) のバイナリ出力ストリームを受け取って、Avroのレコードへ直接デコードします。 中間のテキスト変換を挟まないぶん、CPU負荷とアロケーションを抑えられます。&lt;/p&gt;
&lt;h3&gt;PostgreSQLの機能を活用したDataflowでの実装&lt;/h3&gt;
&lt;h4&gt;ステップ1: ctidブロック範囲の列挙&lt;/h4&gt;
&lt;p&gt;PostgreSQLの全タプルには ctid（物理的な行ロケーション = (ブロック番号, ブロック内オフセット)）が付与されています。 PostgresSource はテーブルを 物理ブロック範囲 で分割し、各範囲を TID range scan で読みます。 分割の計画には実データのスキャンが一切不要です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/9ed54715--2026-06-15-6.35.33-1024x529.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ブロック数は、テーブルの実ディスクサイズをブロックサイズで割って求めます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT pg_relation_size(&amp;#039;table&amp;#039;::regclass) / current_setting(&amp;#039;block_size&amp;#039;)::bigint&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pg_relation_size&lt;/code&gt; は実ディスクサイズを返すため、統計情報の &lt;code&gt;pg_class.relpages&lt;/code&gt; 推定値よりも正確で、しかもstat呼び出し1回で済みます。&lt;/p&gt;
&lt;p&gt;1ブロックあたりの行密度は推定行数（&lt;code&gt;pg_class.reltuples&lt;/code&gt;）から求め、目標行数 &lt;code&gt;splitSize&lt;/code&gt; に対応するブロック幅を機械的に算出します。 &lt;code&gt;[0, blockCount)&lt;/code&gt; をこの幅で割っていくだけなので、&lt;strong&gt;フルスキャンも&lt;code&gt;OFFSET&lt;/code&gt;も不要&lt;/strong&gt;です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;行密度 = 推定行数 / ブロック数1範囲のブロック幅 = max(1, round( splitSize / 行密度 ))
範囲   = [0, w), [w, 2w), … , [kw, blockCount)   ※最後の範囲は上限を開く&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最後の範囲を上限なし（オープンエンド）にしておくことで、分割を計画した後に追記された行も読み取れます。各範囲は ctid の半開区間条件に変換されます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;WHERE ctid &amp;gt;= &amp;#039;(0,0)&amp;#039;::tid AND ctid &amp;lt; &amp;#039;(3,0)&amp;#039;::tid&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この条件も、TiDBの主キー範囲と同様に物理位置の値による範囲比較です。 PostgreSQL 14以降ではこれが TID range scan として処理され、該当ブロックへ直接シークして必要な範囲だけを読みます。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意点&lt;/strong&gt;: &lt;code&gt;ctid&lt;/code&gt; は物理的な行位置なので、読み取り中にINSERT/UPDATE（別ページへの移動）/VACUUMが起きると、同じ行を二重に読んだり取りこぼしたりする可能性があります。同期中に更新されないテーブルに対して使うか、バッチ読み取りで一般的なスナップショットのズレを許容する前提で利用します。また、TID range scanはPostgreSQL 14以降のサポートで、それ以前のバージョンでは各範囲がシーケンシャルスキャンにフォールバックします。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;ステップ2: Rangeの再分配&lt;/h4&gt;
&lt;p&gt;TiDBと同様、RangeのリストをCreateで撒き、Reshuffle.viaRandomKey()でワーカーへ再分配します。fusion分断と負荷分散の狙いは同じです。&lt;/p&gt;
&lt;h4&gt;ステップ3: 各ワーカーでのバイナリCOPY並列読み取り&lt;/h4&gt;
&lt;p&gt;各ワーカーは、担当Rangeのctid条件を組み込んだSELECTを COPY (&amp;#8230;) TO STDOUT (FORMAT BINARY) でラップし、バイナリストリームを受け取ります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;COPY (SELECT * FROM table WHERE ctid &amp;gt;= &amp;#039;(0,0)&amp;#039;::tid AND ctid &amp;lt; &amp;#039;(3,0)&amp;#039;::tid)TO STDOUT (FORMAT BINARY)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返ってくるのはPostgreSQLのCOPYバイナリフォーマットです。&lt;code&gt;PostgresSource&lt;/code&gt;はこれを直接パースします。 先頭の固定シグネチャ（&lt;code&gt;PGCOPY\n\377\r\n\0&lt;/code&gt;）とヘッダを読み飛ばし、以降はタプルを1件ずつ読みます。 各タプルは「フィールド数」に続いて、フィールドごとに「長さ（&lt;code&gt;-1&lt;/code&gt;はNULL）＋値のバイト列」が並ぶ構造で、終端は番兵値（フィールド数 = &lt;code&gt;-1&lt;/code&gt;）で示されます。&lt;/p&gt;
&lt;p&gt;値のデコードは、PostgreSQLのバイナリ表現をAvroの値へ型ごとに変換します。たとえば次のような処理です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;整数・浮動小数: ビッグエンディアンでそのまま読む&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numeric&lt;/code&gt;: base-10000 のdigit配列から十進数を復元&lt;/li&gt;
&lt;li&gt;&lt;code&gt;date&lt;/code&gt; / &lt;code&gt;timestamp&lt;/code&gt;: PostgreSQLエポック（2000-01-01）基準の値をUnixエポック基準へ補正&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uuid&lt;/code&gt; / &lt;code&gt;json&lt;/code&gt; / &lt;code&gt;jsonb&lt;/code&gt; / &lt;code&gt;bytea&lt;/code&gt;: それぞれの専用処理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらをドライバのテキスト変換を介さず自前でデコードすることで、転送経路を最短化しています。&lt;/p&gt;
&lt;p&gt;なお、IAM認証（Cloud SQL / AlloyDB）にも対応しており、&lt;code&gt;user&lt;/code&gt;未指定時はワーカーのサービスアカウントをDBユーザとして使い、接続URLに &lt;code&gt;enableIamAuth=true&lt;/code&gt; を自動付与します。&lt;/p&gt;
&lt;p&gt;検証用の12つのカラムを持つ6億件のダミーテーブルデータをAvroファイルとしてGCSに出力するタスクで、6コア並列で8分で処理完了するようになりました。&lt;/p&gt;
&lt;h4&gt;制約: なぜTiDBSourceのようにワーカー間の一貫性を担保できないのか&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;TiDBSource&lt;/code&gt; では &lt;code&gt;tidb_snapshot&lt;/code&gt; によって全ワーカーが同一時点を読み、ワーカー間の一貫性を担保していました。一方の &lt;code&gt;PostgresSource&lt;/code&gt; では、各&lt;code&gt;ctid&lt;/code&gt;レンジが&lt;strong&gt;それぞれ独立したトランザクションで読まれる&lt;/strong&gt;ため、レンジ間（別接続・別時刻）での一貫性は保証されません。読み取り中にINSERT/UPDATE（&lt;code&gt;ctid&lt;/code&gt;が別ページへ移動）/VACUUMが起きると、レンジをまたいで行の重複や欠落が起こりえます。&lt;/p&gt;
&lt;p&gt;PostgreSQLにも一見すると同等の仕組みがあります。&lt;code&gt;pg_dump&lt;/code&gt; の並列モードは、&lt;code&gt;pg_export_snapshot()&lt;/code&gt; でスナップショットをエクスポートし、各ワーカーが &lt;code&gt;SET TRANSACTION SNAPSHOT&lt;/code&gt; でそれを取り込むことで、ワーカー間の一貫性を担保しています。&lt;br /&gt;
&lt;code&gt;PostgresSource&lt;/code&gt; で同じことを今回実現できなかった理由は &lt;strong&gt;スナップショットの「寿命」の違い&lt;/strong&gt; にあります。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;TiDB（&lt;code&gt;tidb_snapshot&lt;/code&gt;）&lt;/th&gt;
&lt;th&gt;PostgreSQL(&lt;code&gt;pg_export_snapshot()&lt;/code&gt; )&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;実体&lt;/td&gt;
&lt;td&gt;TSO（論理タイムスタンプ＝ただの数値）&lt;/td&gt;
&lt;td&gt;エクスポート元トランザクションに紐づくスナップショットID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;寿命&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;永続的&lt;/strong&gt;（GCされるまで。トランザクション非依存）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;一時的&lt;/strong&gt;（エクスポート元トランザクションが開いている間だけ有効）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;共有方法&lt;/td&gt;
&lt;td&gt;数値を渡し、各セッションが独立に &lt;code&gt;SET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元トランザクションが生存中に各セッションが &lt;code&gt;SET TRANSACTION SNAPSHOT&lt;/code&gt; で取り込む&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;PostgreSQLのエクスポートされたスナップショットは、&lt;strong&gt;それをエクスポートしたトランザクションが終了するまでしかインポートできません&lt;/strong&gt;。&lt;code&gt;pg_dump&lt;/code&gt; の並列モードが成立するのは、単一プロセスのリーダーがダンプ全体の間ずっとエクスポート元トランザクションを開いたまま保持し、自身でワーカーを起動するからです。 &lt;code&gt;PostgresSource&lt;/code&gt; が動くCloud Dataflowの実行モデルでは、元トランザクションを&lt;strong&gt;並列読み取りの全期間にわたって保持する場所を確保するのが難しかったのです&lt;/strong&gt;。TiDBSourceではランチャーでTSO（数値）がトランザクションと無関係に有効だったためワーカーに配るだけで済みました。&lt;/p&gt;
&lt;p&gt;そのため&lt;code&gt;PostgresSource&lt;/code&gt; では各レンジ独立読み取りを前提とし、「同期中に更新されないテーブルに対して使う／バッチ読み取りで一般的なスナップショットのズレを許容する」という運用方針にしています。&lt;/p&gt;
&lt;h2&gt;まとめ: 高スループットを支える設計要素&lt;/h2&gt;
&lt;p&gt;両モジュールでは、&lt;strong&gt;「テーブルの物理的なデータ配置に沿って分割し、その分割単位を分散ワーカーで並列に読み取る」&lt;/strong&gt; という共通する設計思想で実装しました。&lt;/p&gt;
&lt;p&gt;以下 &lt;strong&gt;SpannerSource&lt;/strong&gt;（SpannerのPartitionQuery等を活用したモジュール）も加えた各設計要素の比較表です。すでにいずれかのDBに親しんでいる人は別のDBの機能と比較することで関心・理解が深まるかもしれません。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;要素&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;SpannerSource&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TiDBSource&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;PostgresSource&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;物理分割の基準&lt;/td&gt;
&lt;td&gt;Split境界（&lt;code&gt;PartitionQuery&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;TiKV Region境界（&lt;code&gt;TABLESAMPLE REGIONS()&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctid&lt;/code&gt;物理ブロック範囲（&lt;code&gt;pg_relation_size&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分割キーの自動抽出&lt;/td&gt;
&lt;td&gt;Spannerが自動分割（指定不要）&lt;/td&gt;
&lt;td&gt;PK / &lt;code&gt;_tidb_rowid&lt;/code&gt; を自動解決&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctid&lt;/code&gt;（全テーブル共通）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分割計画のコスト&lt;/td&gt;
&lt;td&gt;API呼び出しのみ（フルスキャン不要）&lt;/td&gt;
&lt;td&gt;境界キーの列挙のみ（フルスキャン不要）&lt;/td&gt;
&lt;td&gt;stat呼び出しのみ（フルスキャン・&lt;code&gt;OFFSET&lt;/code&gt;不要）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;範囲の読み取り&lt;/td&gt;
&lt;td&gt;Partition Tokenを実行&lt;/td&gt;
&lt;td&gt;PK値の範囲比較でインデックスをシーク&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctid&lt;/code&gt;の範囲比較でTID range scan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;転送機構&lt;/td&gt;
&lt;td&gt;gRPCストリーミング（&lt;code&gt;executeStreamingSql&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;ストリーミングResultSet（&lt;code&gt;fetchSize=MIN_VALUE&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;バイナリCOPY（&lt;code&gt;COPY ... TO STDOUT BINARY&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;一貫性&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BatchReadOnlyTransaction&lt;/code&gt;（&lt;code&gt;TimestampBound&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tidb_snapshot&lt;/code&gt;によるMVCCスナップショット&lt;/td&gt;
&lt;td&gt;バッチ読み取り（&lt;code&gt;ctid&lt;/code&gt;スナップショットのズレは許容）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;並列化&lt;/td&gt;
&lt;td&gt;Partition Tokenを&lt;code&gt;Reshuffle&lt;/code&gt; + &lt;code&gt;ParDo&lt;/code&gt;で分散&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Reshuffle&lt;/code&gt; + &lt;code&gt;ParDo&lt;/code&gt;（dumplingのWriter並列に相当）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Reshuffle&lt;/code&gt; + &lt;code&gt;ParDo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;バージョン保護&lt;/td&gt;
&lt;td&gt;Partition Tokenで自動保持&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tidb_gc_life_time&lt;/code&gt; の引き上げで代替&lt;/td&gt;
&lt;td&gt;（該当なし）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;フォールバック&lt;/td&gt;
&lt;td&gt;Partition Query → 通常Query&lt;/td&gt;
&lt;td&gt;Region → 数値MIN/MAX → 全体&lt;/td&gt;
&lt;td&gt;TID range scan → シーケンシャルスキャン（PG14未満）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;汎用JDBCに対する優位性は、(1) 分割キーを自動抽出でき、分割計画も安価なので並列度を素直に上げられること、(2) 各ワーカーの転送がネイティブ機構でCPU/メモリ効率が高いこと、(3) DB固有のスナップショット機構で一貫性を担保できること(TiDB) の3点に整理できます。これらはもともとSpannerSourceがSpannerの機能で実現していた特性を、今回 TiDB / PostgreSQL 固有の機能を組み合わせて再現したものになります。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;多数のマイクロサービスが抱える大量のテーブルをDWHへ同期するという要件に対し、汎用的なJDBC経路ではなく、TiDBとPostgreSQL（AlloyDB）それぞれの &lt;strong&gt;分散ストレージアーキテクチャや物理データ配置を活かした専用Sourceモジュール&lt;/strong&gt; をCloud Dataflow上に実装しました。これは、すでにSpannerからの取得で高スループットを実現できていた「分割 → 配布 → 並列スナップショット読み取り」というパターンを、他のDBにも横展開した取り組みと言えます。&lt;/p&gt;
&lt;p&gt;今回は各DBが公式ツール（&lt;a href=&quot;https://github.com/pingcap/tidb/tree/master/dumpling&quot; title=&quot;dumpling&quot;&gt;dumpling&lt;/a&gt; / &lt;a href=&quot;https://github.com/postgres/postgres/tree/master/src/bin/pg_dump&quot; title=&quot;pg_dump&quot;&gt;pg_dump&lt;/a&gt;）で利用されている高速化のノウハウを参考にさせてもらい、Dataflowの分散実行モデルに取り込みました。「分割キーの自動抽出」「物理配置に沿った安価な分割」「ネイティブなバルク転送」「DB固有のスナップショット」という要素の組み合わせが、大規模テーブルでも高いスループットと一貫性を両立させる助けになりました。&lt;/p&gt;
&lt;p&gt;次の記事は cyan さんの「Scaling Myself: How I Run 22 Claude Code Sessions for DS4 Migration」です。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>メルペイのキャンペーン基盤をルールベース汎用システムに書き直し、Otoku Revolutionするまでの話</title><link>https://engineering.mercari.com/blog/entry/20260617-d943dec64a/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260617-d943dec64a/</guid><description>&lt;p&gt;この記事は Merpay &amp;amp; Mercoin Tech Openness Month 2026 の 13 日目の記事です。 こんにちは。Growth Platform Team でメルペイのポイント還元キャンペー [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 18 Jun 2026 10:00:57 GMT</pubDate><content:encoded>&lt;style&gt;
p.fontbold{
    font-weight: bold;
}
p.codeboxbefore{
    margin-bottom:-70px;
}
@media screen and (max-width: 575px) {
    p.codeboxbefore{
        margin-bottom:-13.4vw;
    }
}
&lt;/style&gt;
&lt;p&gt;この記事は &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay &amp;amp; Mercoin Tech Openness Month 2026&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt; の 13 日目の記事です。&lt;/p&gt;
&lt;p&gt;こんにちは。Growth Platform Team でメルペイのポイント還元キャンペーン基盤である Santa サービスの開発を担当している @hasegway です。&lt;/p&gt;
&lt;p&gt;なお、タイトルに登場する「Otoku Revolution」とは、コード決済を一定回数使うたびに必ず値引き体験が届く新企画のキャンペーン (本記事では「コード決済の回数連動キャンペーン」と呼びます) の社内呼称です。詳しくは&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260623-ff72f126be/&quot; title=&quot;本連載 17 日目の @yutaro の記事&quot;&gt;本連載 17 日目の @yutaro の記事&lt;/a&gt;をご確認ください。本記事では、長く運用してきた Santa サービスをルールベースの汎用基盤 (以降「Rulebase 基盤」と呼びます) として書き直したリファクタリングの話と、新基盤の最初のキャンペーンとして「コード決済の回数連動キャンペーン」を立ち上げた話を取り上げます。順を追ってお話しする前に、まず Santa という基幹サービスがどのような歴史を経て、どんな負債を抱えるに至ったかについてお話しさせてください。&lt;/p&gt;
&lt;h2&gt;Santa の歴史&lt;/h2&gt;
&lt;p&gt;Santa という名前は、初期に&lt;strong&gt;「使った翌日にバッチ処理でポイントを付与する」&lt;/strong&gt; サービスだったところから来ています。夜のうちに溜まったイベントを翌朝まとめて配って回る、シンプルな仕組みでした (初期の仕組みについては &lt;a href=&quot;https://engineering.mercari.com/blog/entry/2019-12-09-172422/&quot; title=&quot;メルペイのキャンペーンを支えるサンタの秘密&quot;&gt;メルペイのキャンペーンを支えるサンタの秘密&lt;/a&gt; が詳しいです)。&lt;/p&gt;
&lt;p&gt;それから何年も経て、今では 1 日数百万イベントを処理し、メルカリ / メルペイのさまざまな利用シーンでポイント付与を行う基幹サービスへと成長しました。この成長の過程で一貫していたのは、&lt;strong&gt;「キャンペーンの種別ごとに専用のイベントパイプラインを実装する」&lt;/strong&gt; スタイルで機能を積み重ねてきたことです。それぞれ独自のテーブル、独自のイベントハンドラ、独自の Cap (上限) ロジック、独自のポイント付与フローを持っていました。&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20211203-campaign-filtering-system/&quot; title=&quot;2021 年のフィルタリング機能&quot;&gt;2021 年のフィルタリング機能&lt;/a&gt; は「複数の条件を AND/OR で組み合わせる」 発想を Santa に持ち込んだ、汎用化の最初の一歩でした。&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20221207-mercard-behind-the-scenes-07/&quot; title=&quot;2022 年のメルカード常時還元&quot;&gt;2022 年のメルカード常時還元&lt;/a&gt; は、その上に「メルカードステージ別の還元率」「複数月にまたがる Provision (引当金) 管理」 といった精緻なロジックを乗せた大規模なパイプラインで、現在でも Santa 最大のパイプラインです。それでも、キャンペーン本体のコードは CampaignType ごとに別物のままでした。&lt;/p&gt;
&lt;p&gt;この構造は当初の要件においては合理的でした。しかしキャンペーン要件の複雑化とともに、コードの再利用性が低い設計による開発速度の低下、バグのキャンペーン別対応による運用負荷の増大といった課題が積み重なっていきました。2025年夏の時点では、Santa エンジニアチーム内でこれら課題への共通理解ができており、次世代キャンペーン構造のラフ設計まで進んでいました。ただし当時はスケジュール上の制約で本格着手を見送り、2025年10月にローンチしたメルカリモバイル向け特典キャンペーンは既存システムを拡張する形で実装しました。&lt;/p&gt;
&lt;p&gt;その後、PoCを進め、2025年12月にチーム向けに成果を発表。2026年春にかけて、汎用キャンペーンテーブルにルール評価とアクション実行モジュールを組み合わせた Rulebase 基盤を新規に構築しました。現時点では「コード決済の回数連動キャンペーン」を1件稼働させている段階で、既存のキャンペーン群は引き続き従来の専用パイプラインで動いています。これら既存キャンペーンの段階的な移行は、これから先のフェーズです。&lt;/p&gt;
&lt;h2&gt;専用パイプラインが抱えた負債&lt;/h2&gt;
&lt;p&gt;Rulebase 基盤を作る前の Santa は、キャンペーンの種別(CampaignType) ごとに「専用パイプライン」を実装する、というスタイルで成長してきました。代表的なキャンペーン種別と、各種別の代表的なキャンペーン企画は次のとおりです。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;キャンペーン種別&lt;/th&gt;
&lt;th&gt;主なキャンペーン企画&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;購入時還元&lt;/td&gt;
&lt;td&gt;「買ってお得!dポイント」など&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;メルペイの定額払いの還元&lt;/td&gt;
&lt;td&gt;「はじめての定額払いキャンペーン」など&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;メルペイスマートマネーの還元&lt;/td&gt;
&lt;td&gt;「初回利用」「カムバックキャンペーン」など&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;メルカード還元&lt;/td&gt;
&lt;td&gt;「メルカード常時還元」など&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;これらの種別はそれぞれが、専用のテーブル群、決済や返済を受け取る Pub/Sub の入り口、ビジネスロジックを担う Interactor、ポイント付与履歴テーブルへの書き込みパス、付与上限の計算ロジックを抱えています。また、企画ごとの細かい要件の実現のため、基本のパイプラインの中にさまざまな分岐処理が加えられています。新たにキャンペーン企画を 1 本立てるたびに、これらのさまざまなレイヤーを個別に調査・変更しなければならない、というのが Santa の標準作業でした。&lt;/p&gt;
&lt;p&gt;そして、長年運用するなかでこの構造がいくつかの負債を生んでいました。&lt;/p&gt;
&lt;h3&gt;累積した運用負債&lt;/h3&gt;
&lt;p&gt;まず、専用パイプラインを実装するスタイルでは、新たなキャンペーン要件のための追加開発が横展開して再利用しづらい問題がありました。また、当初に想定していたキャンペーン要件では考えられなかった新たな要件は、個別実装で対応せざるを得ないこともありました。これらは徐々に開発速度の低下やリグレッションテストの複雑化を招いていきました。特に MercardCampaignType ではこの問題が顕著で、リファクタリングに踏み切る直接的な引き金になりました。&lt;/p&gt;
&lt;h3&gt;MercardCampaignType が「なんでも置き場」化していった&lt;/h3&gt;
&lt;p&gt;2022 年にローンチした MercardCampaign パイプラインは、当初「利用ステージ別の還元率」と「清算起点のリアルタイムのポイント付与」を素直に扱うことを想定した、シンプルな常時還元キャンペーン向けの設計でした。&lt;/p&gt;
&lt;p&gt;その後、メルカードまわりのキャンペーン要件は急速に広がっていきます。累計購入額連動キャンペーン、メルカリ NFT 決済への対応、メルカード ゴールド、メルカリモバイル契約者向けの特典など、どれも「メルカード保有者・利用者」という共通の文脈はあるものの、設計当時の想定にはなかった要件ばかりです。それでも置き場としては MercardCampaignType が最も適切だったため、これらの新要件は順次 MercardCampaignType の上に実装されていきます。本来シンプルに設計されていた入れ物に想定外の機能が次々と追加され、還元率算出や会計コード指定などの仕組みが本来の用途を超えて流用されるようになっていきました。&lt;/p&gt;
&lt;p&gt;ここでは、特に歪みが目に見える形で表面化した3 つの事例 — 累計購入型 (2024 年 9 月)、メルカリ NFT 会計コード差し替え対応 (2025 年 12 月)、メルカリモバイル向け特典キャンペーン (2025 年 10 月) — を順に見ていきます。&lt;/p&gt;
&lt;h3&gt;累計購入型 と メルカリ NFT 会計コード差し替え&lt;/h3&gt;
&lt;p&gt;累計購入額連動キャンペーンでは、購入金額が複数のしきい値を順に超えるたびに段階的にポイントが付与されます (例: 累計購入額に応じて最大 P1,500 もらえる)。このキャンペーンが MercardCampaignType に投入され、本来の還元率スキーマに「購入累積トラッカー」型の挙動が乗りました。メルカリ NFT 会計コード差し替え対応では「キャンペーンの各種条件は他と共通にしつつNFT取引のみ会計コードを差し替える」という要件のため、コード内に分岐処理が追加され、キャンペーン設定値が複雑化しました。どちらもメルカード保有者向けの施策ではあるものの、当初想定の責務範囲を超えた要件です。&lt;/p&gt;
&lt;p&gt;そして3 例目の、もっとも歪みが大きく出たケースが、メルカリモバイル向け特典キャンペーンです。&lt;/p&gt;
&lt;h3&gt;メルカリモバイル向け特典キャンペーン (2025 年 10 月)&lt;/h3&gt;
&lt;p&gt;メルカリモバイル向け特典キャンペーンの要件は、&lt;strong&gt;3 種類のメルカードステータス (保有無し / 通常版 / ゴールド) × 4 種類のモバイルデータプラン (4GB / 10GB / 20GB / 40GB) = 12&lt;/strong&gt; の独立したキャンペーンパターンが毎月必要、というものでした。&lt;/p&gt;
&lt;p&gt;実装上は、本来メルカードステージ別の還元レートを保持するDBフィールドが「データプラン別のレート」の入れ物として流用されました。3 ステータス ×4 データプランの 12 組み合わせを、メルカード用テーブルの行として毎月生成する運用です。&lt;/p&gt;
&lt;p class=&quot;codeboxbefore&quot;&gt;また、お客さまのメルカードステータスやモバイルデータプランは日々変わりうるため変化に合わせて還元レートや月々の上限を計算し直す必要があります。そして、同じお客さまが同月内で別の組み合わせ向けキャンペーンにも二重にマッチして重複付与が起こりうる、というリスクも残ります。後者の重複付与を防ぐために、コード側にはこんな雰囲気のハードコードマップが入りました (簡略化したイメージ)。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// ポイント重複付与防止のため、キャンペーンIDをコードに埋め込み
TemporaryCampaignIDMapping = map[string]string{
    &amp;quot;202510&amp;quot;: &amp;quot;campaign-id-1&amp;quot;,
    &amp;quot;202511&amp;quot;: &amp;quot;campaign-id-2&amp;quot;,
    // 毎月追加が必要...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そして各所に、データプラン別ステージを判定する if 文が散らばりました。&lt;/p&gt;
&lt;p&gt;こうして、毎月 12 パターン分のデータ追加運用が必要な「Temporary」ハードコードマップが本番に居続け（Temporaryとは・・）、モバイル専用ステージを分岐させる if 文がポイント計算・フィルタ評価・API レスポンスに散在し、将来データプランが1つ増えるたびにコード変更とデプロイが必要になり、MercardCampaignType 全体のリグレッションテストも巻き込む、という構造が出来上がっていきます。当初は&lt;strong&gt;「モバイルキャンペーンをアーキテクチャ刷新のきっかけにして抜本対応する」&lt;/strong&gt;計画もありましたが、ローンチ期日との両立が難しく、最終的に既存パイプラインを拡張する判断を取りました。当時の制約下では合理的な選択ですが、根本的な構造課題は持ち越し、運用負荷は増えています。&lt;/p&gt;
&lt;p&gt;3 事例ともビジネス文脈では筋が通っている一方、「メルカード還元の入れ物」が「メルカード周辺キャンペーン全般の入れ物」として使われ、MercardCampaignType のスキーマと責務範囲が押し広げられてきたのが当時の状態で、そのひずみは無視できない大きさになっていました。&lt;/p&gt;
&lt;h2&gt;Rulebase 基盤の設計&lt;/h2&gt;
&lt;p&gt;このリファクタリングそのものは前から検討していたものの、ローンチ責任との両立が難しく、本格着手は半年寝かせています。その間も内部で PoC は進め、本実装で固めた方針は「キャンペーンの挙動を、専用コードから設定データへ」というシンプルなものです。&lt;/p&gt;
&lt;p&gt;より具体的には、&lt;strong&gt;「どのようなきっかけで動くか」&lt;/strong&gt;(TriggerType)、&lt;strong&gt;「どのような条件でマッチさせるか」&lt;/strong&gt;(RuleCondition と、その評価を担う RuleEvaluator)、&lt;strong&gt;「何をするか」&lt;/strong&gt;(ActionExecutor) という3 つの軸を、できるだけ atomic な (再利用可能なサイズの) 部品として定義し、その組み合わせで多様なキャンペーン要件に対応する、というのが基本的な発想です。従来のように「メルカードキャンペーン専用のロジックを書き下ろす」のではなく、Trigger / Condition / Action を小さな部品として用意し、キャンペーン定義はその組み合わせとして書く、という発想です。専用パイプライン時代との根本的な違いはここにあります。一度実装した Condition や Action を別の CampaignType から設定値だけで呼び出せたり、動的な条件分岐や繰り返し条件を1 つのキャンペーン定義の中で表現できたりする能力も、ここから生まれます。&lt;/p&gt;
&lt;h3&gt;全体像&lt;/h3&gt;
&lt;p&gt;チーム内発表でも、次の対比を使って説明しました。&lt;/p&gt;
&lt;p class=&quot;codeboxbefore fontbold&quot;&gt;As-Is (メルカード専用ハンドラの内部に if が積まれている)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (h *MercardCampaignHandler) Execute(event Event) {
    if user.Stage == StageA {
        if user.Status == &amp;quot;Active&amp;quot; {
            if !h.HasReward(user.ID, event.ID) {
                points = amount * 0.03
                h.RewriteRewardHistory(user.ID, event.ID)
            }
        }
    } else if user.Stage == StageB {
        if user.Status == &amp;quot;Gold&amp;quot; {
            points = amount * 0.10
            if h.HasReward(user.ID, event.ID) {
                h.RewriteReward(user.ID, event.ID, points)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;codeboxbefore fontbold&quot;&gt;To-Be (キャンペーン定義は JSON データ、評価エンジンは汎用)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;rule_id&amp;quot;: &amp;quot;mobile-std-4gb&amp;quot;,
  &amp;quot;conditions&amp;quot;: [
    {&amp;quot;type&amp;quot;: &amp;quot;user_attribute&amp;quot;,    &amp;quot;params&amp;quot;: {&amp;quot;stage&amp;quot;: &amp;quot;Standard&amp;quot;, &amp;quot;plan&amp;quot;: &amp;quot;4GB&amp;quot;}},
    {&amp;quot;type&amp;quot;: &amp;quot;period&amp;quot;,            &amp;quot;params&amp;quot;: {&amp;quot;start&amp;quot;: &amp;quot;2025-10-01&amp;quot;, &amp;quot;end&amp;quot;: &amp;quot;2025-10-31&amp;quot;}},
    {&amp;quot;type&amp;quot;: &amp;quot;payment_attribute&amp;quot;, &amp;quot;params&amp;quot;: {&amp;quot;transaction_type&amp;quot;: &amp;quot;code_payment&amp;quot;}}
  ],
  &amp;quot;action&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;ADD_POINTS&amp;quot;,
    &amp;quot;params&amp;quot;: {&amp;quot;rate&amp;quot;: 0.05, &amp;quot;currency_points&amp;quot;: 60, &amp;quot;monthly_cap&amp;quot;: 200}
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この JSON を Spanner に永続化したスキーマが次の3 テーブルです。Campaigns 配下に CampaignRules、その配下に RuleConditions を &lt;code&gt;INTERLEAVE IN PARENT&lt;/code&gt; で並べる構造になっています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- 大枠の宣言: 期間・CampaignType・キャンペーン固有設定 (JSON)
Campaigns(
  CampaignID, CampaignType, StartAt, EndAt, Metadata, ...
)

-- 1 キャンペーン内の複数ルール: 何をトリガに、どう集計し、どんなアクションを取るか
CampaignRules(
  CampaignID, RuleID, TriggerType, CalculationType,
  ActionType, ActionParams (JSON), Priority, Enabled
) INTERLEAVE IN PARENT Campaigns

-- 1 ルール内の複数条件: AND/OR グループで合成
RuleConditions(
  CampaignID, RuleID, ConditionID,
  ConditionType, ConditionParams (JSON), ConditionGroup
) INTERLEAVE IN PARENT CampaignRules&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これに対応する評価エンジン (Rule Evaluation Engine) を新設し、以下のような4 層構造の EvaluationData を入力として走らせます。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Event Data&lt;/td&gt;
&lt;td&gt;受信した Pub/Sub イベント&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rule &amp;amp; Campaign&lt;/td&gt;
&lt;td&gt;DB から読んだ &lt;code&gt;CampaignRule&lt;/code&gt; + &lt;code&gt;RuleConditions&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User Data&lt;/td&gt;
&lt;td&gt;お客さまの属性 (カード種類、利用履歴など)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Providers&lt;/td&gt;
&lt;td&gt;外部サービスへの DI ハンドル&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;イベントが届いてから付与までの流れは次のとおりです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/fe7fe0da-01-evaluation-event-sequence.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Condition/Rule&lt;/strong&gt; と &lt;strong&gt;Action&lt;/strong&gt; は、新しい種別が必要になったときに対応する実装を追加しておくことで、以降のあらゆるキャンペーン定義から再利用できる仕組みになっています。新規キャンペーンの立ち上げ自体は、すでにカタログに揃っているものの組み合わせで実現できるものであれば、コード変更を伴わずに SQL の INSERT で反映できます。&lt;/p&gt;
&lt;h3&gt;3 つの軸の中身&lt;/h3&gt;
&lt;p&gt;ここからは、前述した3 つの軸が Rulebase 基盤の中でそれぞれどう設計されているかを順に見ていきます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/07ac6847-02-three-axis-pipeline.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;TriggerType — どのようなきっかけで動くか&lt;/h4&gt;
&lt;p&gt;CampaignRules テーブルの &lt;strong&gt;TriggerType&lt;/strong&gt; 列が、各ルールが「どのイベントに反応するか」 を表します。&lt;strong&gt;TriggerType&lt;/strong&gt; は外部イベントと1対1になるよう定義しており、Pub/Sub から届いたメッセージを内部ドメインのモデルに正規化したうえで、合致する &lt;strong&gt;TriggerType&lt;/strong&gt; を持つルール群を CampaignRules から引き当て、条件マッチングを担う2 段目のハンドラ層に引き渡すところまでが、この軸の責任範囲です。条件評価や副作用はここでは扱わず、後段に切り出しています。&lt;/p&gt;
&lt;h4&gt;RuleCondition と RuleEvaluator — どのような条件でマッチさせるか&lt;/h4&gt;
&lt;p&gt;CampaignRules に紐づく RuleConditions テーブルには、評価したい条件が1行1 件 並んでいます。各レコードは次の3 つで構成されます。&lt;strong&gt;ConditionType&lt;/strong&gt; は、その条件がどんな種類の判定をするかを示す分類で、後段でどの ConditionEvaluator にディスパッチするかを決めます。&lt;strong&gt;ConditionParams&lt;/strong&gt; は、その ConditionType に渡す具体的なパラメータで、種別ごとに必要な引数が違うため、固定スキーマではなく JSON で柔軟に保持しています。&lt;strong&gt;ConditionGroup&lt;/strong&gt; は、複数の条件を AND と OR で組み合わせるためのグルーピングラベルで、これを使うことで &lt;code&gt;A AND (B OR C)&lt;/code&gt; のような複合的な論理式を、フラットな行データで表現できるようにしています。&lt;/p&gt;
&lt;p&gt;たとえば「ある期間内で、お客さま属性 B か C のいずれかにマッチしたら成立」 という &lt;code&gt;A AND (B OR C)&lt;/code&gt; の条件は、RuleConditions テーブルに次のように 3 行で並びます。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ConditionID&lt;/th&gt;
&lt;th&gt;ConditionType&lt;/th&gt;
&lt;th&gt;ConditionGroup&lt;/th&gt;
&lt;th&gt;グループの意味&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;&lt;code&gt;period&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;単独で AND&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;&lt;code&gt;user_attribute&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;g1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;グループ g1 内で &lt;code&gt;OR&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;&lt;code&gt;payment_attribute&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;g1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;グループ g1 内で &lt;code&gt;OR&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;評価エンジンの入力は、先に挙げた4 つのレイヤーの情報を1つに束ねた &lt;strong&gt;EvaluationData&lt;/strong&gt; です。ConditionEvaluator 側からは「これさえ読めば判定に必要な値はそろっている」 状態で参照できるようにしてあります。PoC ではここにキャンペーン固有の計算結果も持たせる案を検討していましたが、本実装では「条件評価のフェーズとアクション実行のフェーズで責務を分けるべき」と判断し、&lt;strong&gt;EvaluationData&lt;/strong&gt; は条件評価に必要な情報だけに絞っています。&lt;/p&gt;
&lt;p&gt;評価エンジン本体は、ConditionType ごとの個別判定を担う &lt;strong&gt;ConditionEvaluator&lt;/strong&gt; と、ルール 1 件分の真偽をまとめる &lt;strong&gt;RuleEvaluator&lt;/strong&gt; の 2 段構成です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/2d12acf6-05-rule-evaluator-dispatch.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;下段の &lt;strong&gt;ConditionEvaluator&lt;/strong&gt; は、ConditionType ごとに 1 つずつ実装が用意されており、&lt;strong&gt;EvaluationData&lt;/strong&gt; を読み取ってその条件 1 件分の真偽を返します。判定は副作用を持たず、外部 API 呼び出しや DB 書き込みは起こりません。&lt;/p&gt;
&lt;p&gt;上段の &lt;strong&gt;RuleEvaluator&lt;/strong&gt; は、その上に乗ってルール 1 件分の評価を組み立てます。具体的には、(1) RuleCondition の各行をその ConditionType に応じた ConditionEvaluator に振り分け、(2) 各行が返した真偽を ConditionGroup のセマンティクス (NULL は単独 AND、同じ値どうしは OR、別の値どうしは AND) で AND/OR 合成し、(3) ルール 1 件分の最終的な真偽と、どの条件がどう寄与したかの内訳を返します。返るのは真偽と内訳だけで、計算結果や副作用は含めません。&lt;/p&gt;
&lt;p&gt;新しい評価軸が必要になったときは、対応する &lt;strong&gt;ConditionEvaluator&lt;/strong&gt; を実装して &lt;strong&gt;ConditionType&lt;/strong&gt; の enum に登録します。一度カタログに加われば、以降のキャンペーン定義は RuleConditions の 1 行としてその軸を設定値ベースで呼び出せます。&lt;/p&gt;
&lt;h4&gt;ActionExecutor — 何をするか&lt;/h4&gt;
&lt;p&gt;マッチしたルールに対応する Action を、&lt;strong&gt;ActionType ごとの Executor&lt;/strong&gt; が実行します。上限計算、ポイント付与ステータスの遷移、ポイント台帳への書き込み、外部へのイベント発行といった副作用は、ここで起こります。Condition / Rule 側は副作用を持たない設計なので、外部に作用するロジックはこの層に集約されます。新しい Action 種別が必要になったときは、対応する Executor を実装して ActionType の enum に登録します。一度カタログに加われば、以降のキャンペーン定義は &lt;strong&gt;CampaignRules.ActionType&lt;/strong&gt; と &lt;strong&gt;ActionParams&lt;/strong&gt; を指定する形で、その Action を汎用的に呼び出せます。&lt;/p&gt;
&lt;h3&gt;3 軸を支える共通ヘルパー&lt;/h3&gt;
&lt;p&gt;Executor の責務は、Action ごとの副作用そのもの (DB 書き込み、外部 API 呼び出し、PointStatus の遷移など) です。一方で、ポイント計算と Cap (上限) 適用、PointStatus / ProvisionStatus のライフサイクル管理、外部マイクロサービスに渡す冪等キーの生成、設定値テンプレートの展開といった「Action 種別をまたいで毎回必要になる横断的な処理」は、3 軸とは別レイヤーの共通ヘルパーに切り出しています。各 Executor は、自分の Action に応じて必要なヘルパーだけを必要なときに呼び出す形にしてあります。実装したヘルパーはいくつかありますが、代表例を 2 つ挙げます。&lt;/p&gt;
&lt;h4&gt;PointLifecycleManager (PointStatus / ProvisionStatus のオーケストレーション)&lt;/h4&gt;
&lt;p&gt;ポイント付与にまつわるライフサイクルをまとめて扱うヘルパーです。&lt;strong&gt;お客さまへのポイント付与状態&lt;/strong&gt; (PointStatus) と、&lt;strong&gt;会計上の引当状態&lt;/strong&gt; (ProvisionStatus) は、それぞれ独立したステートマシンとして表現しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/44003550-03-point-status.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/498701ca-04-provision-status.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上が &lt;strong&gt;PointStatus&lt;/strong&gt; で、&lt;code&gt;planned&lt;/code&gt; で台帳に予定を立て、外部の付与 API が成功すると &lt;code&gt;confirmed&lt;/code&gt;、後処理まで終わると &lt;code&gt;completed&lt;/code&gt; に進みます。&lt;code&gt;planned&lt;/code&gt; 直後にキャンセルされた場合は &lt;code&gt;cancelled&lt;/code&gt;、外部呼び出しが失敗した場合は &lt;code&gt;failed&lt;/code&gt; に倒れる、というのが主な遷移です。下が &lt;strong&gt;ProvisionStatus&lt;/strong&gt; で、引当が立っていない &lt;code&gt;not_linked&lt;/code&gt; から、引当が紐づいた &lt;code&gt;linked&lt;/code&gt; を経て、最終的に &lt;code&gt;revoked&lt;/code&gt; (引当処理が確定した状態) に遷移するのが主軸です。&lt;/p&gt;
&lt;p&gt;旧メルカードパイプラインでは、PointStatus の遷移と ProvisionStatus の遷移がそれぞれポイント付与処理側と引当処理側に分散して実装されていて、両者がどう連動しているかの見通しが悪くなっていました。Rulebase 基盤では、両方のステートマシンを &lt;strong&gt;PointLifecycleManager&lt;/strong&gt; 配下に切り出し、&lt;code&gt;CompletePoint&lt;/code&gt; / &lt;code&gt;ReversePoint&lt;/code&gt; / &lt;code&gt;CreateProvision&lt;/code&gt; / &lt;code&gt;UpdateProvision&lt;/code&gt; / &lt;code&gt;RevokeProvision&lt;/code&gt; の各 Action から必要なときに呼び出す形にしてあります。これによって、複数月にまたがる引当が必要なメルカード系のキャンペーンも、引当が不要なシンプルなキャンペーンも、同じ部品の組み合わせでライフサイクルを表現できます。&lt;/p&gt;
&lt;h4&gt;TemplateExpander (設定値テンプレートの展開)&lt;/h4&gt;
&lt;p&gt;旧システムには汎用的な文字列テンプレートの仕組みがなく、たとえば会計コードを決済種別ごとに分けて積むような要件が出てくると、会計コードのパターン数だけキャンペーンレコードを別に切るしかありませんでした。本来 1 つのキャンペーンとして扱いたいものをパターン数だけ重複登録する必要があり、運用負荷の原因として積み上がっていた部分です。&lt;/p&gt;
&lt;p&gt;これを汎用化するために用意したのが &lt;strong&gt;TemplateExpander&lt;/strong&gt; です。キャンペーン定義のなかに &lt;code&gt;{user_id}&lt;/code&gt; / &lt;code&gt;{campaign_id}&lt;/code&gt; / &lt;code&gt;{payment_count}&lt;/code&gt; といったプレースホルダーを書いておくと、実行時に &lt;code&gt;EvaluationData&lt;/code&gt; から取り出した実値に置き換えます。会計コード (たとえば &lt;code&gt;merpay_xxx_campaign-{campaign_id}-{user_status}&lt;/code&gt;) や、ハッシュのシード文字列 (&lt;code&gt;{user_id}:{campaign_id}:{payment_count}&lt;/code&gt;) などがその利用例です。新しいパターンが必要になったときも、設定マスタ側のテンプレート文字列を差し替えれば、コード変更や追加レコードなしに同一キャンペーンの中で扱えます。&lt;/p&gt;
&lt;h3&gt;最初の実装事例: コード決済の回数連動キャンペーン&lt;/h3&gt;
&lt;p&gt;ここまでに作った汎用基盤の「入れ物」を使った最初の移行ですが、感覚的には「リスクが低く、運用負荷の高いところ」 から始めたくなります。新しめで蓄積データの少ないものほど移行リスクが下がり、運用負荷が高いほど移行の効果が出やすいからです。当初はメルカリモバイル向けキャンペーンを最初の移行対象に据える予定でした。元々これをアーキテクチャ刷新のきっかけにする案も挙がっていましたし、稼働開始から日が浅く蓄積データが少ない一方で 12 パターンの毎月運用で運用負荷が高い、というまさにスイートスポットの条件に当てはまっていたためです。&lt;/p&gt;
&lt;p&gt;ただ、ちょうどそのタイミングで、新規企画として「コード決済の回数連動キャンペーン」 の話が立ち上がります。新規企画であれば、常時稼働している既存パイプラインを止めずに載せ替える、というコストを払わずに、最小構成で「設定値ベースでキャンペーンを組み立てる」 ことの検証ができます。結果として、最初の事例は移行ではなくゼロからの立ち上げに振り直し、本企画のキャンペーンを Rulebase 上で直接立ち上げる形で進めました。既存のメルカリモバイルなどの移行は、先のフェーズに持ち越しています。&lt;/p&gt;
&lt;p&gt;この新規企画は、N 回利用するごとに付与が発火するシンプルな仕組みで、お客さまから見ると「使い続けるほど、確実に値引きが返ってくる」体験になります。&lt;/p&gt;
&lt;p&gt;これをRulebase上で組むにあたって必要だったのは &lt;strong&gt;累計カウントと周期報酬を扱う ActionType&lt;/strong&gt; （&lt;code&gt;COUNT_AND_REWARD&lt;/code&gt;）を 1 つだけ追加することだけでした。あとは&lt;strong&gt;既存の ConditionType&lt;/strong&gt; (期間 / お客さま属性 / 決済属性) の組み合わせで実装できた点です。Rulebase 上で「ルール評価とアクション実行を最大限再利用し、なるべく設定値だけで組み立てる」形を、最初に検証できたケースになりました。&lt;/p&gt;
&lt;p&gt;仕組み自体はシンプルです。お客さまのエントリ状況を「お客さま属性条件」で見て、対象決済を「決済属性条件」で絞り込んだうえで、&lt;code&gt;COUNT_AND_REWARD&lt;/code&gt; Executor がカウンタをインクリメントします。カウンタが規定回数に達するとポイントを付与してカウンタをリセットし、これをキャンペーン期間中ずっと繰り返す、という流れです。データとしては、Campaigns 1 行、CampaignRules 1 行、RuleConditions 数行、というシンプルな構造で表現できます (簡略化したイメージ)。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// Campaigns
{
  &amp;quot;campaign_id&amp;quot;: &amp;quot;...&amp;quot;,
  &amp;quot;campaign_type&amp;quot;: 9,                  // InfinitePayment
  &amp;quot;start_at&amp;quot;: &amp;quot;2026-04-01&amp;quot;,
  &amp;quot;end_at&amp;quot;:   &amp;quot;2026-09-30&amp;quot;,
  &amp;quot;metadata&amp;quot;: { ... }
}

// CampaignRules (1 件)
{
  &amp;quot;trigger_type&amp;quot;: &amp;quot;PaymentCharge&amp;quot;,
  &amp;quot;action_type&amp;quot;:  &amp;quot;COUNT_AND_REWARD&amp;quot;,
  &amp;quot;action_params&amp;quot;: {
    &amp;quot;trigger_count&amp;quot;:          3,
    &amp;quot;reward_currency_points&amp;quot;: 100
  }
}

// RuleConditions (3 件: 期間 / エントリ状況 / 決済種別)
{ &amp;quot;condition_type&amp;quot;: &amp;quot;period&amp;quot;,            &amp;quot;params&amp;quot;: { ... } }
{ &amp;quot;condition_type&amp;quot;: &amp;quot;user_attribute&amp;quot;,    &amp;quot;params&amp;quot;: { &amp;quot;attribute&amp;quot;: &amp;quot;entry_status&amp;quot;, &amp;quot;promotion_key&amp;quot;: &amp;quot;...&amp;quot; } }
{ &amp;quot;condition_type&amp;quot;: &amp;quot;payment_attribute&amp;quot;, &amp;quot;params&amp;quot;: { &amp;quot;attribute&amp;quot;: &amp;quot;transaction_type&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;code_payment&amp;quot; } }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実装上の新規追加は &lt;code&gt;COUNT_AND_REWARD&lt;/code&gt; Executor と、カウンタを保持する 2 つのテーブル (現在のカウントとログ) くらいです。他は基盤の汎用部品をそのまま組み合わせて完結しました。&lt;/p&gt;
&lt;p&gt;補足として、この回数連動キャンペーンの意義は「単純なキャンペーン派生がやりやすくなった」ことではありません。SQL INSERT で似たキャンペーンを増やすこと自体は、従来の専用パイプライン時代でもそれなりにできていました。Rulebase 基盤で新しく可能になったのは、CampaignType をまたいだ Rule・Action の設定ベース再利用です。一度実装した Condition や Action は、別の CampaignType のキャンペーンからも設定値の組み合わせで呼び出せます。回数連動キャンペーンで使った &lt;code&gt;COUNT_AND_REWARD&lt;/code&gt; も、他のキャンペーンが「累積回数で発火する」要件を持てば、コードを書き足さずに設定だけで使い回せます。&lt;/p&gt;
&lt;p&gt;同じ仕組みは、動的な条件分岐を 1 つのキャンペーン設定で表現することにも転用できます。たとえばメルカリモバイル向けキャンペーンでは「3 ステータス × 4 データプラン」を 12 個の独立キャンペーン定義として毎月準備していましたが、Rulebase なら属性条件・繰り返し条件・上限条件を組み合わせて 1 つのキャンペーン設定の中で表現できます。「N 回ごとに発火」のような周期的な振る舞いも、&lt;code&gt;COUNT_AND_REWARD&lt;/code&gt; を Executor 側に持つことで汎用パーツとして扱えます。今後の新企画では、既存のルール・アクションの組み合わせで表現できる範囲が広がっているかぎり、ゼロからの実装ではなく「設定値だけで作って試す」ところから入れる、というのが新基盤の最大のメリットです。&lt;/p&gt;
&lt;h3&gt;余談: ランダムなのに、何度引いても同じ金額&lt;/h3&gt;
&lt;p&gt;仕様としては&lt;strong&gt;「規定回数に到達したら、複数の候補金額から重み付きランダムで 1 つを引き当てる」&lt;/strong&gt;という要件があります。素直に &lt;code&gt;math/rand&lt;/code&gt; で抽選してしまうと、Pub/Sub の at-least-once 配信下では同じ PaymentCharge が再配信されたときに 1 回目と 2 回目で別の金額が選ばれてしまうことがあります。PointID ベースの冪等性チェックは通っているのに、2 回目の試行から見える金額が初回付与額とずれる、というケースが起きうる構造です。&lt;/p&gt;
&lt;p&gt;そこで抽選は乱数ではなく決定論的ハッシュで行いました。&lt;code&gt;user_id&lt;/code&gt;・キャンペーン識別子・&lt;code&gt;payment_count&lt;/code&gt; を組み合わせた文字列をシードに、SHA-256 でハッシュ化したものから重み付きで候補を 1 つ選びます。アルゴリズムの概略は次のとおりです (簡略化したイメージ)。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// seed の例: &amp;quot;12345:campaign-abc:3&amp;quot;  (user_id : campaign_id : payment_count)
seed, _ := expander.Expand(params.DeterministicReward.SeedFormat)

h := sha256.Sum256([]byte(seed))
v := binary.BigEndian.Uint64(h[0:8])     // 先頭 8 バイトを uint64 として取り出す

var totalWeight uint64
for _, rv := range variations {
    totalWeight += rv.Weight
}
target := v % totalWeight                // 重みの合計で割った余り (これが候補選択用の値)

var cumulative uint64
for i, rv := range variations {
    cumulative += rv.Weight
    if target &amp;lt; cumulative {
        return rv, i                     // 累積重みで当たった候補を選択
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;お客さまから見た「使うたびに違う金額が返ってくる」体験はそのままに、同じ &lt;code&gt;(user_id, campaign_id, payment_count)&lt;/code&gt; の組み合わせに対しては何度 retry が走っても、別プロセスから読まれても、同じ金額が一意に確定します。&lt;/p&gt;
&lt;p&gt;そして、シードさえ揃えば抽選結果は確定値なので、Pub/Sub のイベント処理完了を待たずに「今回付与される金額」を計算できます。決済成功直後の API レスポンスやフロントエンド側で「今回は ○ ポイントです」と確定表示を返すことができ、後段で UserPoints が書かれたあとに金額が変わって見える、といった不整合の心配もありません。at-least-once と weighted random を両立させるための仕組みが、お客さまへの早い表示にもそのまま使える形になっています。&lt;/p&gt;
&lt;h3&gt;QA 環境の改善に助けられた話&lt;/h3&gt;
&lt;p&gt;今回の QAではいくつかの環境パターンを切り替えて実施する必要があり、工数ボリュームに不安を感じていました。ちょうど良いタイミングで今まで Santa サービスでは実現できていなかった「イベント駆動部分専用の QA 複製環境」をチームメンバーが作りきってくれたことで、並行して QA を実施できるようになり、大変助かりました。その詳細は本連載 8 日目の &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260610-be556591c7/&quot; title=&quot;@mikupo の記事&quot;&gt;@mikupo の記事&lt;/a&gt; で詳しく解説されています。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;今回のリファクタリングでは、「キャンペーン定義をコードからデータへ」 という方針のもと、3 テーブル + 評価エンジンに再構成しました。ローンチ責任との両立で半年寝かせていたリファクタリングの機会を諦めずに掴んで「入れ物」を作り切って本番でキャンペーンをひとつ稼働させたことで、システムに今後の新要件についての受け皿を作ることができました。&lt;/p&gt;
&lt;p&gt;ただし、回数連動キャンペーン自体は本記事執筆時点ではローンチしたばかりで、まだ派生キャンペーンの実例はありません。設定だけで派生が立ち上がる世界の本格検証は、これからの新企画を通じて行っていくフェーズです。加えて、メルカード常時還元などの既存のパイプラインもまだ稼働しており、Temporary マップも本番に残ったままです。常時稼働するキャンペーンシステムでは、入れ物を作る難しさよりも、止めずに載せ替えるタイミングと手順の設計こそが、ここから先の本題になります。&lt;/p&gt;
&lt;p&gt;今回の記事は以上になります。なにか参考になることがあれば幸いです。&lt;/p&gt;
&lt;p&gt;次の記事は orfeon さんの「TiDB / AlloyDB の大規模テーブルを高速にBigQueryni同期するための工夫」です。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>クラウドネイティブ会議に出展しました</title><link>https://engineering.mercari.com/blog/entry/20260615-cloud-native-kaigi/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260615-cloud-native-kaigi/</guid><description>&lt;p&gt;DBRE(DataBase Reliability Engineering)チームの@coyamaとIDP(IDentity Platform)チームの@taskです。 メルカリでは2026年5月14日から2026年5月 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 17 Jun 2026 11:00:30 GMT</pubDate><content:encoded>&lt;p&gt;DBRE(DataBase Reliability Engineering)チームの@coyamaとIDP(IDentity Platform)チームの@taskです。&lt;/p&gt;
&lt;p&gt;メルカリでは2026年5月14日から2026年5月15日まで開催されたクラウドネイティブ会議にスポンサーとしてブース出展しました。2日間で125名にクイズへ参加いただき、想定以上に“認証”や“マイクロサービス規模”といった話題で多くの議論が生まれました。本記事では、ブースの展示内容やクイズやアンケートの結果に加えて、来場者とのやり取りや感想を紹介します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/3c3dea4d-cnk2026-panel-1024x576.png&quot; alt=&quot;Cloud Native Kaigi 2026 Panel&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;メルカリブースの展示&lt;/h2&gt;
&lt;p&gt;メルカリの技術面の取り組みを参加者に認識いただくために、有志のエンジニアが中心となりブースを設計しました。メルカリのブースでは主に以下を展示しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;メルカリの技術スタックのクイズ/アンケート&lt;/li&gt;
&lt;li&gt;メルカリのアーキテクチャ変遷やKubernetesクラスタでのパケットキャプチャに関するポスター&lt;/li&gt;
&lt;li&gt;AI SecurityのLLM Key Serverに関するスライド&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/9a164d1a-cnk2026-mercari-booth-1024x768.png&quot; alt=&quot;Mercari Booth&quot; /&gt;&lt;/p&gt;
&lt;p&gt;クイズ/アンケートに回答いただいた方には、メルカリのストラップやボールペン、マスキングテープをプレゼントしました。メルカリのストラップは、参加者から「メルカリらしいノベルティ」というコメントもあり好評でした。&lt;/p&gt;
&lt;p&gt;メルカリのアーキテクチャ変遷のポスターでは、以下の転換点について展示をしていました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;データセンターからパブリッククラウドへの移行&lt;/li&gt;
&lt;li&gt;アプリケーションのKubernetesへのデプロイ&lt;/li&gt;
&lt;li&gt;モノリシックアーキテクチャからマイクロサービスアーキテクチャへの移行&lt;/li&gt;
&lt;li&gt;マイクロサービスアーキテクチャの課題とモジュラーモノリスの取り組み&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「モノリシックアーキテクチャからマイクロサービスアーキテクチャに移行する際に、どうシステムを分割すべきか」や「マイクロサービスアーキテクチャの課題は何か」といった議論ができました。また、他社でもメルカリと類似した課題に直面していることを改めて認識しました。&lt;/p&gt;
&lt;p&gt;ブースの展示内容に関連した資料は以下に公開されています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://speakerdeck.com/foostan/sreshi-dian-tezhen-rifan-rumerukarinoakitekutiyabian-qian-topu-bian-de-nakao-e&quot;&gt;SRE視点で振り返るメルカリのアーキテクチャ変遷と普遍的な考え &amp;#8211; Speaker Deck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.usenix.org/conference/srecon26americas/presentation/shibuya&quot;&gt;It&amp;#8217;s Not Always the Network (But Here&amp;#8217;s How to Prove It): Kubernetes Packet Capture for SREs | USENIX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251202-llm-key-server/&quot;&gt;LLM Key ServerによるLLM APIへの安全で便利なアクセス提供 | メルカリエンジニアリング&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;メルカリのテックスタックに関するクイズ&lt;/h2&gt;
&lt;p&gt;今回のクイズは、メルカリが日々向き合っているサービス全体やテックスタックについて興味を持ってもらうべく、全4問で構成しました。特定のディープなインフラ技術に寄るのではなく、広めのプラットフォームエンジニアリングに関する話題について広くカバーしつつ、ユーザが実際に目にするプロダクト寄りの問題も含めてみました。　その結果、広めのテックスタックをカバーした中〜高程度の問題セットになりました。&lt;/p&gt;
&lt;p&gt;そして、クラウドネイティブ会議中にて2日間で125名の回答をいただき、分布は以下の通りでした。中央値は2点で、4点満点を取ったのはわずか9名のみだったため、なかなか手応えのあるクイズになっていたと思います。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/7326952e-cnk2026-panel-score-overview.png&quot; alt=&quot;Total Score Distribution&quot; /&gt;&lt;/p&gt;
&lt;p&gt;また、それぞれの回答の分布は以下の通りでした。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/6266c213-cnk2026-panel-quiz-combined-1024x732.png&quot; alt=&quot;Score Distribution&quot; /&gt;&lt;/p&gt;
&lt;p&gt;出題した問題の中で、特に注目を集めたのが以下のログイン認証方法に関する問題です。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;メルカリで利用されているログイン認証方法の上位3つを使用頻度の高い順にあげてください。&lt;br /&gt;
What are the top three login authentication methods used in Mercari, ranked from most to least used?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google+SMS &amp;gt; Password+SMS &amp;gt; Facebook&lt;/li&gt;
&lt;li&gt;Password+SMS &amp;gt; Passkeys &amp;gt; Google+SMS&lt;/li&gt;
&lt;li&gt;Password+SMS &amp;gt; Apple &amp;gt; Passkeys&lt;/li&gt;
&lt;li&gt;Passkeys &amp;gt; Password+SMS &amp;gt; Email Link&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;これはクイズ作成時の情報なのですが、この回答について「一般的な Web サービスをイメージすると、パスワードやソーシャルログインが上位を占めると思いがちなので意外だった」「パスキーの普及率が意外と高いのが面白い」といったコメントが多かったです。普通はこうだろうという先入観で答えると間違えてしまう問題だったので、メルカリが技術で攻めた良い事例なのかもしれません。&lt;/p&gt;
&lt;p&gt;また、こちらのログイン認証方法について、メルカリは 2023 年からパスキーログインの普及率拡大を推進しているため、最新のメトリクスによると Passkeys &amp;gt; Password+SMS &amp;gt; Google+SMS に順位が変わっています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://about.mercari.com/press/news/articles/20240129_passkeys/&quot;&gt;メルカリ、すべてのログインに 生体認証「パスキー」を導入 | 株式会社メルカリ&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;また、他にも管理されているマイクロサービス数に関する問題についてもコメントをいただくことが多かったです。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;メルカリの microservices-terraform リポジトリでは、多数のサービス群がInfrastructure-as-Codeとして管理されています。その管理下にあるサービスの総数はいくつでしょうか？&lt;/p&gt;
&lt;p&gt;The microservices-terraform repo manages Infrastructure-as-Code for a large portfolio of services. What is the total number of services managed in this repo?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;100 &amp;#8211; 250 services&lt;/li&gt;
&lt;li&gt;250 &amp;#8211; 500 services&lt;/li&gt;
&lt;li&gt;501 &amp;#8211; 1000 services&lt;/li&gt;
&lt;li&gt;more than 1000 services&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;特に、「想定よりも多くのマイクロサービスを抱えているのに驚いた」「多くのマイクロサービスを抱える上での新たな課題は何か」と言ったコメントが多かったです。こちらの変遷については&lt;a href=&quot;https://speakerdeck.com/foostan/sreshi-dian-tezhen-rifan-rumerukarinoakitekutiyabian-qian-topu-bian-de-nakao-e?slide=24&quot;&gt;SRE視点で振り返るメルカリのアーキテクチャ変遷と普遍的な考え &amp;#8211; Speaker Deck&lt;/a&gt;でも触れられている通りで、こちらの資料を肴にブースにおけるディスカッションが起きたのが面白い点でした。&lt;/p&gt;
&lt;p&gt;いずれにせよ、どのクイズにおいても新しいディスカッションや雑談のネタになったため、このようなメルカリ独自のトピックにおける広めのクイズ設定は狙い通りでした。&lt;/p&gt;
&lt;h2&gt;Cloud Native Kaigiでのブース出展を終えて&lt;/h2&gt;
&lt;p&gt;今回のクラウドネイティブ会議では、本当に多くの方にメルカリブースへお立ち寄りいただきました。セッションの合間の休憩時間にはブース前が熱気に包まれる場面もあり、運営メンバー一同、クラウドネイティブ・コミュニティの圧倒的な熱量を直接肌で感じることができました。&lt;/p&gt;
&lt;p&gt;(coyama) メルカリからもプラットフォームエンジニアリングに関わる複数のチームのメンバーが参加していました。そのため、参加者から寄せられた幅広い質問に対応できる体制で、2日間を終えることができました。例えば、DBREに関する質問では、「メルカリのテックブログでTiDB Auto Scalerの記事を読んで参考になった」という声をきっかけに、トラフィックパターンについて議論することができました。また、セキュリティに関する質問については、セキュリティエンジニアに回答してもらうことで、うまく役割分担ができていました。複数のチームからメンバーが集まることでプラットフォームエンジニアリングやSREに関する幅広いコンテンツを提供できたのは良かったと思います。&lt;/p&gt;
&lt;p&gt;(task) ブース運営を通して特に印象的だったのは、今回の「テックスタッククイズ」が単なるエンタメで終わらず、&lt;strong&gt;そこからディープな技術的ディスカッションへと発展したこと&lt;/strong&gt;です。&lt;/p&gt;
&lt;p&gt;例えば、正答率が低かったTerraformのMonorepoに関する問題をきっかけに、「実際、数百のサービスが相乗りしている状態で、CIの実行速度やデプロイの安全性はどう担保しているんですか？」「うちの会社でもプラットフォーム基盤をどう切り出すかで悩んでいて……」といった、現場のエンジニアならではのリアルな悩みや知見の交換が、ブースのあちこちで自然発生していました。&lt;/p&gt;
&lt;p&gt;問題の難易度に「うわっ、ガチなやつだ…（笑）」と頭を抱えつつも楽しんでくださる参加者の皆さんの姿や、「メルカリのプラットフォームって、ここまでプロダクトの体験（パスキー等）に踏み込んで作り込まれてるんですね！」という驚きの声は、日々大規模なシステム基盤と向き合っている私たちにとって、何よりのモチベーションになりました。&lt;br /&gt;
社外の優秀なエンジニアの方々と直接お話しし、フィードバックをいただけたことは、チームにとっても非常に大きな刺激となりました。&lt;/p&gt;
&lt;p&gt;改めて、ブースに足を運んでくださった皆様、そして難問クイズに挑戦してくださった125名の猛者の皆様、本当にありがとうございました！&lt;/p&gt;
&lt;p&gt;メルカリでは、今回出題したような「プラットフォームからプロダクトまで」を跨ぐような大規模で複雑な技術課題に、一緒に立ち向かってくれる仲間を絶賛募集しています。&lt;br /&gt;
「今回のクイズの内容、もっと詳しく聞いてみたい！」「自分ならもっと良いアーキテクチャにできる！」と興味を持ってくださった方、ぜひ以下のリンクからカジュアルにお話ししましょう！お待ちしています！&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://careers.mercari.com/&quot;&gt;株式会社メルカリ 採用情報&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mercari.connpass.com/event/395377/&quot;&gt;7/8(水)14:00- Mercari AI Career Fes 2026 &amp;#8211; connpass&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/dec1e6de-cnk2026-hiring3-1024x769.png&quot; alt=&quot;we are hiring&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>修正PRを食べてレビュースキルが賢くなる：Claude Codeによる自己改善サイクル</title><link>https://engineering.mercari.com/blog/entry/20260616-0f89ad4c7b/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260616-0f89ad4c7b/</guid><description>&lt;p&gt;はじめに こんにちは。メルペイQAチームでQA Engineerをしている @um(うめ) です。この記事は「Merpay &amp;amp; Mercoin Tech Openness Month 2026」の12日目の記事で [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 17 Jun 2026 10:00:17 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。メルペイQAチームでQA Engineerをしている &lt;a href=&quot;https://x.com/umetsuyu&quot; title=&quot;@um(うめ) &quot;&gt;@um(うめ) &lt;/a&gt;です。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay &amp;amp; Mercoin Tech Openness Month 2026&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の12日目の記事です。&lt;/p&gt;
&lt;p&gt;コードレビューは品質を守る重要なプロセスですが、人間が行うレビューには限界があります。観点の漏れ、見落とし、初めて触るコードベースへの不慣れ、これらはチームが大きくなるほど顕在化する課題です。&lt;/p&gt;
&lt;p&gt;私たちのチームでは、こうした課題に対処するため、Claude CodeのSkillsを活用したAIレビューの仕組みを導入しました。&lt;/p&gt;
&lt;p&gt;この記事では、 &lt;code&gt;/review-pr&lt;/code&gt; と &lt;code&gt;/improve-review-pr&lt;/code&gt; という2つのコマンドを組み合わせた自己改善サイクルについて紹介します。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;/review-pr：AIによるPRレビュー&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;概要&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude Code には標準で &lt;code&gt;/review&lt;/code&gt; というレビュースキルが組み込まれていますが、所属チームではこれとは別に、チーム固有の観点を盛り込んだレビュースキル &lt;code&gt;/review-pr&lt;/code&gt; を独自に作成・運用しています。汎用的な観点だけでは拾いきれないポイントを、チームのコードベース／アーキテクチャに合わせて追加・調整できることが、独自運用を選んだ最大の理由です。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/review-pr&lt;/code&gt; は、PRの変更内容をAIが自動分析し、不具合リスクを多角的にレビューするスキルです。 &lt;code&gt;/review-pr &amp;lt;PR番号&amp;gt;&lt;/code&gt; というコマンド一発で実行でき、以下の6つの汎用的観点を自動チェックします。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;汎用的観点&lt;/th&gt;
&lt;th&gt;チェック内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;コードの正確性&lt;/td&gt;
&lt;td&gt;ロジックの誤り、nil参照、型の不一致、境界値での挙動など&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;エッジケース・異常系&lt;/td&gt;
&lt;td&gt;エラーハンドリングの漏れなど&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;後方互換性&lt;/td&gt;
&lt;td&gt;API変更による既存の呼び出し元への影響&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;機能的影響&lt;/td&gt;
&lt;td&gt;機能の喪失、拡張性への制約&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;テストの十分性&lt;/td&gt;
&lt;td&gt;PRにおけるテストの追加・更新有無&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit Testの網羅性&lt;/td&gt;
&lt;td&gt;既存テストも含めた正常系・異常系・境界値のカバレッジ&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Extra観点：コードベース固有のチェック項目&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;上記6つは汎用的な観点ですが、 &lt;code&gt;/review-pr&lt;/code&gt; スキルにはこれに加えて「Extra観点」と呼ぶセクションも存在します。このセクションには、チームのコードベース固有のアーキテクチャや設計パターンに基づいたチェック項目が蓄積されています。&lt;/p&gt;
&lt;p&gt;具体的な内容はリポジトリの特性に依存するため詳細には触れませんが、汎用的なレビュー観点では検出しにくい「そのプロジェクトならではの見落としパターン」を防ぐための項目が並んでいます。このExtra観点こそが、後述する &lt;code&gt;/improve-review-pr&lt;/code&gt; によって継続的に育てられていく部分です。&lt;/p&gt;
&lt;p&gt;しかし、この仕組みを形骸化せず運用し続けるためには課題がありました。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;課題：スキルは使い続けても賢くならない&lt;/h2&gt;
&lt;p&gt;当初の仕組みのままだと、過去の学び（見落としパターン）が仕組み側に蓄積されません。どれだけ &lt;code&gt;/review-pr&lt;/code&gt; スキルを使い続けても、新しい不具合のパターンを自動で学習することはありません。&lt;/p&gt;
&lt;p&gt;ある日、AIレビューをかいくぐる形で不具合が混入し、チームが修正PRを作成する必要が生じたとします。同じようなパターンの不具合が再発しても、スキルのファイルを手動で更新しない限り、AIは同じ見落としを繰り返します。&lt;/p&gt;
&lt;p&gt;これは「人間が毎回ルールを追記しなければならない」という運用負荷を生みます。私たちがこの課題を解決するために作ったのが &lt;code&gt;/improve-review-pr&lt;/code&gt; コマンドです。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;/improve-review-pr：修正PRを糧にスキルを育てる&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;概要&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/improve-review-pr &amp;lt;修正PR番号&amp;gt;&lt;/code&gt; は、対応漏れが発覚した修正PRを分析し、 &lt;code&gt;/review-pr&lt;/code&gt; スキルにExtra観点として再発防止のチェック項目を追加するコマンドです。&lt;/p&gt;
&lt;p&gt;修正PRが発生するたびに「なぜ元のレビューで見落としたか」を分析し、同じパターンの見落としを二度と起こさないようスキルを更新します。ユーザーの承認を経てから &lt;code&gt;SKILL.md&lt;/code&gt; を更新する設計で、チームの知見を継続的に蓄積できます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;仕組み&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/improve-review-pr &amp;lt;修正PR番号&amp;gt;&lt;/code&gt; を実行すると、以下のステップが走ります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1:修正PRの情報取得&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gh&lt;/code&gt; コマンドで修正PRのタイトル・説明・変更ファイル・差分を取得します。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2:元PRの特定（任意）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;修正PRの説明文から「この修正が対応した元のPR」を自動抽出します。特定できない場合はユーザーに確認し、不明な場合もそのまま分析を続行します。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3:対応漏れの分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;「なぜこの変更が元PRのレビューで見落とされたか」を分析します。元PRが判明している場合は「元PRのどのファイル変更が、修正の必要性を示唆していたか」まで深掘りします。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4:パターンの抽象化と分類&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;個別の事例を汎用的なチェックパターンに変換します。以下の基準で追加先を判断します。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分類&lt;/th&gt;
&lt;th&gt;条件&lt;/th&gt;
&lt;th&gt;追加先&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;リポジトリ固有&lt;/td&gt;
&lt;td&gt;コードベース固有のアーキテクチャに起因&lt;/td&gt;
&lt;td&gt;リポジトリ固有チェックとして追加&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;汎用&lt;/td&gt;
&lt;td&gt;一般的なレビュー観点で防げる&lt;/td&gt;
&lt;td&gt;既存の観点セクションに追記&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;既存チェックの強化&lt;/td&gt;
&lt;td&gt;類似するチェックが既に存在する&lt;/td&gt;
&lt;td&gt;既存項目を拡張&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Step 5:重複チェックと提案&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/review-pr&lt;/code&gt; のSKILL.mdを読み込み、類似チェックの有無を確認した上で改善提案します。承認を得てから更新します。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 6:SKILL.mdの更新&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ユーザーの承認後、スキルファイルを更新し、変更箇所をサマリとして表示します。&lt;/p&gt;
&lt;p&gt;この2つのコマンドを組み合わせることで、以下の自己改善サイクルが回り始めます。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;自己改善サイクルの全体像&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/a7e9ad3d-.png&quot; alt=&quot;自己改善サイクルの全体像&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先週マージされたPRから候補を検出するため、週次でGitHub Actionsが実行され、候補が存在する場合、Slackに通知します。&lt;/p&gt;
&lt;p&gt;チームメンバーはその通知を見て &lt;code&gt;/improve-review-pr&lt;/code&gt; を実行することで、レビュースキルに改善が1つ積み上がります。&lt;/p&gt;
&lt;p&gt;「修正PRを都度見逃さずスキルに反映する」という運用負荷を自動化によって低減した、継続的なフィードバックループです。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;プロセス設計のポイント：Human-in-the-Loop&lt;/h2&gt;
&lt;p&gt;完全自動化も技術的には可能ですが、あえて「ユーザーの確認・承認を経てから更新する」設計にしています。理由は2つあります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.誤ったパターンの混入を防ぐ&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AIが導出したパターンが常に最適な状態とは限りません。&lt;/p&gt;
&lt;p&gt;チェック観点がもう一段階抽象化が必要だったり、チームの開発方針に対して妥当ではない場合もあります。人間が内容を確認することで、このような不適切なチェック項目が追加されてしまうリスクを低減しています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.チームの知識として定着させる&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;承認プロセスを通じることで、「なぜこのチェック項目が追加されたか」をチームメンバーが意識する機会が生まれます。&lt;/p&gt;
&lt;p&gt;単なるルールの羅列ではなく、背景のある知識として蓄積されていきます。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;Claude Codeのスキル機能を活用することで、AIレビューを「固定されたツール」ではなく、チームの知見を蓄積し続ける仕組みとして運用できるようになりました。今後さらに多くの修正PRを経てスキルが充実していくことを期待しています。&lt;/p&gt;
&lt;p&gt;同様の仕組みに興味がある方の参考になれば幸いです。&lt;/p&gt;
&lt;p&gt;次の記事は hasegway さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>Cloud Functions 世代移行で発生した1000万件のメッセージ滞留：Pub/Sub × Cloud Run × Spanner のチューニング</title><link>https://engineering.mercari.com/blog/entry/20260615-7283dc59d4/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260615-7283dc59d4/</guid><description>&lt;p&gt;はじめに こんにちは。Merpay の Payment &amp;amp; Customer Platform で会計システムを開発・運用する Accounting チームで Backend Engineer をしている @me [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 16 Jun 2026 10:00:55 GMT</pubDate><content:encoded>&lt;h1&gt;はじめに&lt;/h1&gt;
&lt;p&gt;こんにちは。Merpay の Payment &amp;amp; Customer Platform で会計システムを開発・運用する Accounting チームで Backend Engineer をしている &lt;a href=&quot;https://x.com/mewuto&quot;&gt;@mewuto&lt;/a&gt; です。本記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の11日目の記事です。&lt;/p&gt;
&lt;p&gt;マネージドサービスの世代移行では、コードを変えていなくても、デフォルト値の違いだけでシステムの振る舞いが変わることがあります。ある月末の早朝、Cloud Functions の世代移行が引き金となり、会計イベント基盤で Cloud Pub/Sub のメッセージが大量に滞留して、新規の会計データを Cloud Spanner に登録できないインシデントが発生しました。&lt;/p&gt;
&lt;p&gt;直接の引き金は、Cloud Functions を 1st gen から 2nd gen（Cloud Run ベース）へ移行した際に &lt;code&gt;--max-instances&lt;/code&gt; を明示しておらず、Cloud Functions のスケール上限が「無制限」（1st genのデフォルト）から「100」（2nd genのデフォルト）へ下がっていたことでした。しかし、障害がここまで大きくなったのは、この引き金だけが原因ではありません。本記事では、1000万件規模に達したこの滞留がどのように構成されていたのかを解き、Cloud Run と Spanner、そして監視の各面で私たちが講じた対策を紹介します。&lt;/p&gt;
&lt;h1&gt;1. 月末ピークを支える会計イベント基盤&lt;/h1&gt;
&lt;p&gt;まず、私たちが運用する会計システムの全体像を説明します。会計システムは、社内の各マイクロサービスが発行する「会計イベント」を集約し、Cloud Spanner に記録する基盤です。この会計データは送信元サービスとの突合（リコンサイル）を経て、加盟店への精算や月次の会計締めといった後続処理の前提となるため、取りこぼしや遅延がそのまま業務影響に直結します。&lt;/p&gt;
&lt;p&gt;会計システムは、次のような非同期パイプラインでイベントを受け取ります。送信元から Spanner までは、おおむね一方向の流れです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/3ff0a0ad-chatgpt-image-jun-9-2026-05_19_14-pm-2.png&quot; alt=&quot;会計イベント基盤の構成図&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この構成には、今回の障害を理解するうえで重要な特性が2つあります。&lt;/p&gt;
&lt;p&gt;1つ目は、すべてが非同期で動く点です。送信元マイクロサービスはイベントを発行したら応答を待たず、配信・リトライ・整合性の担保はすべて Pub/Sub 以降のパイプラインが引き受けます。特に Pub/Sub から Cloud Run へは push 型サブスクリプションで配信されるため、消費側である Cloud Run とその書き込み先である Spanner の処理能力が、そのままパイプライン全体のスループット上限になります。&lt;/p&gt;
&lt;p&gt;2つ目は、負荷が月末に集中する点です。業務特性上、月末・月初の締めに合わせてトランザクションが一気に押し寄せ、平常時の数十倍のスパイクが発生します。&lt;/p&gt;
&lt;p&gt;なお、Pub/Sub に届いたメッセージは、この push 経路とは別の pull 型サブスクリプションを通じて Dataflow でも読み取られ、並行して Cloud Storage（GCS）に保存されています。本処理の滞留に巻き込まれないこのGCS上のデータを使って後の復旧を行いました。&lt;/p&gt;
&lt;h1&gt;2. 1000万件の滞留はどのように発生したか&lt;/h1&gt;
&lt;p&gt;インシデントは、ある月末最終営業日の早朝に発生しました。6時ごろから Pub/Sub のメッセージが滞留しはじめ、Spanner への新規登録が滞り、新規の会計データを受け付けられない状態が断続的に続きました。復旧と再発を繰り返し、収束までには丸一日以上を要しました。&lt;/p&gt;
&lt;p&gt;データロスそのものは、先ほど触れた GCS上のデータから再投入することで回避できました。しかし会計システムでは、送信元サービスと DB の間でリコンサイルが完了したものだけが、加盟店精算や月次の会計締めといった後続処理の対象になります。Spanner に登録されなかったデータはリコンサイル未完了のまま残り、締め日と重なったことで、広範な業務影響につながりました。&lt;/p&gt;
&lt;p&gt;この滞留が深刻なのは、一度始まるとインフラリソースの上限でスケールが頭打ちになり、処理が遅れるほど次の処理も遅れる悪循環に陥るからです。会計イベントが高い密度で連続して到着すると、maxInstances=100 で頭打ちになった Cloud Run はこれを処理しきれません。上限まで張り付いた Cloud Run が一斉に書き込むことでSpanner CPUが逼迫し、1件あたりの書き込み時間が延びます。すると、処理スループットがさらに落ち、滞留はさらに進んでしまいます。やがて滞留時間が eventMaxAge（メッセージを破棄するまでの最大保持期間）を超えると、メッセージは Spanner に登録されないまま破棄されてしまいます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/e9d19bff-chatgpt-image-jun-9-2026-06_19_14-pm-1.png&quot; alt=&quot;滞留発生の流れ&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;3. きっかけは 2nd gen 移行時のデフォルト値&lt;/h1&gt;
&lt;p&gt;滞留の原因を調べていくと、今回の障害の1ヶ月以上前に行った Cloud Functions の世代移行に行き着きました。私たちは関数を 1st gen から 2nd gen（Cloud Run ベース）へ移行しましたが、このときデプロイコマンドで &lt;code&gt;--max-instances&lt;/code&gt; を明示しておらず、世代によってデフォルト値が変わることを、十分に意識できていませんでした。&lt;/p&gt;
&lt;p&gt;この「明示しなかった」ことが、スケール上限を静かに引き下げていました。下の表のとおり、1st gen ではスケール上限が実質無制限だったのに対し、2nd gen のデフォルトは 100 です。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;デフォルトの max instances&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1st gen&lt;/td&gt;
&lt;td&gt;無制限（プロジェクト Quota の範囲）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2nd gen&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;コードもデプロイスクリプトも変えていないのに、移行しただけで上限が実質無制限から 100 へ下がっていたことになります。平常時は 100 インスタンスで十分だったため、移行後しばらくは問題が表面化せず、私たちの体感としては、ある月末に突如として障害が発生したように見えました。しかし実際は、監視がなかったために気づけていなかっただけでした。インシデント後に振り返ると、上限に迫っていた月もあり、水面下ではすでに限界の兆候が出ていたのです。&lt;/p&gt;
&lt;h1&gt;4. 障害を大きくした複数の要因&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;--max-instances&lt;/code&gt; の指定漏れは引き金ではありましたが、それだけでこの障害を説明することはできません。調べてみると、2nd gen移行後に迎えた月末のピーク時でも条件はほとんど同じだったからです。maxInstances は 100、eventMaxAge も同じ 21分で、トラフィック総量も3時間で約17〜19GBとほぼ変わりませんでした。&lt;/p&gt;
&lt;p&gt;それでも、そのときは障害は起きていませんでした。決定的に違ったのは、トラフィックの「連続性」です。合間に負荷が落ち着く「谷」があり、その短い谷の間に Spanner CPU と未 ack の蓄積がリセットされていました。ところが障害当日は、この谷がないままピークが約40分間続き、システムは回復の契機をついに得られませんでした。&lt;/p&gt;
&lt;p&gt;整理すると、今回の障害は3つの要因が連鎖して成り立っていました。まず maxInstances=100 という処理上限が滞留を発生させ、次にトラフィックの連続性がその滞留を固定化し、最後に eventMaxAge によるメッセージのドロップ、という連鎖により実害となりました。いずれか1つでも条件が違えば被害の規模は抑えられたはずで、だからこそ私たちは対策を1箇所に絞らず、制御できる Cloud Run と Spanner の両方に講じることにしました。&lt;/p&gt;
&lt;h1&gt;5. Cloud Run の処理能力を引き上げる&lt;/h1&gt;
&lt;p&gt;最初に着手したのは、直接の引き金となった Cloud Run のスケール上限です。あわせて、1インスタンスあたりの処理効率も見直しました。本番に反映した設定値は次のとおりです。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;パラメータ&lt;/th&gt;
&lt;th&gt;変更前&lt;/th&gt;
&lt;th&gt;変更後&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max-instances&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100（暗黙のデフォルト）&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;min-instances&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0（暗黙のデフォルト）&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;concurrency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1（暗黙のデフォルト）&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;デフォルト&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;512MB&lt;/td&gt;
&lt;td&gt;1Gi&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ここでの本質は、値を大きくしたこと以上に、必要なパラメータをすべて明示したことにあります。今回の引き金は、暗黙のデフォルト値に依存していたことでした。そこで &lt;code&gt;max-instances&lt;/code&gt; をはじめとするスケール関連のパラメータをデプロイ定義に明示し、世代やデフォルトの変化に左右されない状態にしました。あわせて、 min-instances を 1 にしてコールドスタートを避け、concurrency を 10 に引き上げ、それに見合うよう CPU とメモリも増強しています。なお max-instances は、書き込み先である Spanner への負荷も考えて、一度に上げきらず段階的に引き上げました。&lt;/p&gt;
&lt;p&gt;concurrency を 1 から引き上げることができたのは、ハンドラ内の共有状態を見直し、複数リクエストを同時に処理しても安全だと確認できたためです。Spanner クライアントは内部にコネクションプールを持ち並行アクセスに耐えられます。また、その初期化は &lt;code&gt;sync.Once&lt;/code&gt; によって一度だけ行われます。こうした前提を確かめたうえで同時処理数を増やし、必要なインスタンス数そのものを抑えました。&lt;/p&gt;
&lt;h1&gt;6. Spanner autoscaler の監視軸を Total CPU へ広げる&lt;/h1&gt;
&lt;p&gt;Cloud Run の処理能力を上げると、今度は書き込み先である Spanner がボトルネックになります。Spanner には以前から autoscaler を導入していましたが、障害のさなか、CPU が高負荷であるにもかかわらず PU（Processing Unit。Spanner の計算容量の単位）はまったくスケールアップしていませんでした。autoscaler があるのにスケールしない、という一見不可解な状況でした。&lt;/p&gt;
&lt;p&gt;原因は、autoscaler が見ていた指標にありました。Spanner の CPU 使用率には High / Medium / Low の優先度があり、その合算が Total CPU 使用率です。当時の autoscaler は High priority CPU 使用率だけを見ており、障害時は Total CPU が 100% に張り付く一方で、High priority 単体では閾値（60%）の超過が続かず、起動条件を満たしませんでした。この死角は月末ピークに限りません。たとえばリアルタイム性を求めない大量データの削除ジョブは、意図的に Low priority で実行するため、Total は高いのに High は低いという同じ状態を容易に作り出します。優先度別の CPU だけを見る監視は、こうしたケースを構造的に取りこぼすのです。&lt;/p&gt;
&lt;p&gt;しかし、これは autoscaler の設定変更では解決できませんでした。当時、OSSである mercari/spanner-autoscaler には、Total CPU でスケールする機能そのものが存在しなかったからです。そこで、OSS をメンテナンスしている SREチームに相談し、High priority と Total を OR 条件で評価する dual CPU scaling mode を実装してもらい、&lt;a href=&quot;https://github.com/mercari/spanner-autoscaler/releases/tag/v0.8.0&quot;&gt;v0.8.0&lt;/a&gt; として取り込みました。設定した閾値は次のとおりです。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;パラメータ&lt;/th&gt;
&lt;th&gt;変更前&lt;/th&gt;
&lt;th&gt;変更後&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total CPU 閾値&lt;/td&gt;
&lt;td&gt;（監視なし）&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High priority CPU 閾値&lt;/td&gt;
&lt;td&gt;60%&lt;/td&gt;
&lt;td&gt;55%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Total CPU 閾値は新設で、これが今回の障害への直接の対策です。値は Google Cloud の throughput 最適化推奨（〜70%）に合わせました。High priority CPU 閾値は、より早くスケールアップを起動するために 60% から 55% へ下げています。下げすぎると平常時のコストが増えるため、より低い 50% 等は避け、Google Cloud の推奨（regional で 65% 以下）に収まる 55% を選びました。これにより autoscaler は High priority(55%) と Total(70%) のいずれかが閾値を超えればスケールアップするようになり、「Total は逼迫しているのに High が閾値未満だからスケールしない」という障害時の挙動は原理的に起こらなくなりました。&lt;/p&gt;
&lt;p&gt;また、スケール条件に加えて、運用面でも2つの調整を行いました。1つは、早朝5時から7時のスケールダウンを禁止することです。月末ピークの立ち上がりで、せっかく確保した PU が削られてしまうのを防ぎます。もう1つは、スケジュールによる事前の PU 確保です。月末早朝や大規模なリコンサイルが走る月など、負荷が読める期間にはあらかじめ PU を確保しておき、リアクティブなスケールアップだけに頼らない構えにしました。&lt;/p&gt;
&lt;h1&gt;7. 上限への接近を検知する監視を整える&lt;/h1&gt;
&lt;p&gt;今回の障害には、予兆を早期に捉える機会も、発生を即座に検知する機会も逃してしまったという課題があります。スケール上限に達してもそれを知らせるアラートがなく、水面下で限界に近づいていた兆候も見逃していました。そこでDatadog に2つの監視を追加しました。&lt;/p&gt;
&lt;p&gt;1つは、Cloud Run のインスタンス数が上限の 40% / 60% に達した時点で警告・重大として通知する監視です。これにより、メッセージのドロップが始まる前に、上限への接近そのものを検知できます。もう1つは、Pub/Sub の未配信メッセージ数が閾値を超えたら通知する監視で、滞留という現象を直接とらえます。後者は誤検知を避けるため、当初は保守的な閾値から始め、定常状態の理解が進むにつれて段階的に下げていく方針にしています。&lt;/p&gt;
&lt;p&gt;設定値を最適化するだけでは不十分です。これ自体は当たり前かもしれませんが、どれだけ良い値を入れても、その値への接近を検知できなければ、次の同じ障害は防げません。キャパシティの設計と、その接近を知る監視をセットで整える。この当たり前を当たり前に徹底することこそが大切だと、今回あらためて実感しました。特に、システムをゼロから作った当人ではなく、引き継いで運用していることのほうが多い現場では、こうした設定や前提を定期的に振り返り、整え直す文化を持つことが欠かせません。&lt;/p&gt;
&lt;h1&gt;8. まとめ&lt;/h1&gt;
&lt;p&gt;今回の障害は、1つのデプロイフラグを明示しなかったことから始まりました。しかし振り返れば、本当の教訓はもっと一般的なところにあります。&lt;/p&gt;
&lt;p&gt;第一に、マネージドサービスの世代移行では、自分が変更していないデフォルト値こそが、負荷時に問題として表面化します。スケール上限・並行数・タイムアウトのような、平常時には効かないパラメータは、移行のたびに明示しておくべきでした。第二に、障害の原因は単一とは限りません。「直近の月は同じ条件で起きなかった」という事実がトラフィックの連続性という決定的な要因を浮かび上がらせたように、トリガー・増幅・被害化のそれぞれに目を向けることが再発防止には欠かせません。そして第三に、設定の最適化と、その状態に気づくための監視は、つねに一体で考える必要があります。&lt;/p&gt;
&lt;p&gt;マネージドサービスの便利なデフォルト値は、平常時には何も語りません。だからこそ、ピーク時に初めて顔を出すその振る舞いを、移行のたびに確かめておく価値があります。この記事が、みなさんが運用するサービスの設定を今一度見直す、ひとつのきっかけになれば幸いです。&lt;/p&gt;
&lt;p&gt;次の記事は um（うめ）さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>Build First, Discuss Later｜初回ミーティングに動くプロトタイプを持ち込んだら、意思決定が爆速になった</title><link>https://engineering.mercari.com/blog/entry/20260615-build-first-discuss-later/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260615-build-first-discuss-later/</guid><description>&lt;p&gt;こんにちは、メルペイiOSエンジニアのkubomiです。 この記事は Merpay &amp;amp; Mercoin Tech Openness Month 2026 の 10日目の記事です。 生成AIによって、エンジニアが短 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 15 Jun 2026 10:00:35 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、メルペイiOSエンジニアのkubomiです。&lt;br /&gt;
この記事は &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay &amp;amp; Mercoin Tech Openness Month 2026&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt; の 10日目の記事です。&lt;/p&gt;
&lt;p&gt;生成AIによって、エンジニアが短時間でプロトタイプをつくれる場面はかなり増えました。最近、小規模なプロジェクトで「初回ミーティングの前に、動くものをつくり切ってしまう」という進め方を試したところ、意思決定のスピードが劇的に変わりました。私はこのやり方を &amp;quot;Build First, Discuss Later（まずつくる、議論は後）”と呼んでいます。この記事では、その具体的な進め方と、実践を通じて私自身に起きたマインドセットの変化を紹介します。&lt;/p&gt;
&lt;h2&gt;よくある開発フローと、その課題&lt;/h2&gt;
&lt;p&gt;私たちの現場では、開発に取りかかる前に、まず関係者の認識をそろえておくのが一般的です。具体的には、最初にプロダクトマネージャー（PdM）が大まかな仕様を用意し、それをもとにキックオフミーティングを開いて詳細を議論します。議論を経てPdMが仕様を固め、エンジニアはその仕様をもとに見積もりを出して実装に入ります。&lt;/p&gt;
&lt;p&gt;ただ、議論して仕様を固めたつもりでも、いざつくり始めると「あれ、ここの挙動どうするんだっけ？」という疑問が次々と出てくることがあります。そのたびにPdMへ確認したり、追加のミーティングを開いたりすると、少しずつコミュニケーションの往復が増えていきます。&lt;/p&gt;
&lt;h2&gt;&amp;quot;Build First, Discuss Later&amp;quot; という提案&lt;/h2&gt;
&lt;p&gt;そこで私が実践したのが、プロセスの順番をあえて逆転させる &amp;quot;Build First, Discuss Later&amp;quot; です。仕様が固まる前に、まず動くプロトタイプをつくってしまうという発想で、ミーティングはその動くものを土台に議論を進めます。&lt;/p&gt;
&lt;p&gt;従来は「議論して仕様を固めてからつくる」流れでしたが、これを「先につくり、その動くものを見ながら議論する」へと入れ替えます。実際に触れる画面があると、抽象的な仕様書をめぐる議論よりもはるかに早く、関係者の認識がそろっていきます。&lt;/p&gt;
&lt;p&gt;ただし、何にでもこの進め方を使うわけではありません。何日もかかるような大きな実装でこれをやると、方針が変わったときの手戻りが大きすぎます。私の場合は、数時間から1日以内でつくれるくらいの小規模な施策に限定しています。そのくらいの規模なら、悩んで待つより、とりあえずつくってしまったほうが圧倒的に速い、という実感があります。&lt;/p&gt;
&lt;h2&gt;ミーティングにプロトタイプを持ち込む3つのステップ&lt;/h2&gt;
&lt;p&gt;私は &amp;quot;Build First, Discuss Later&amp;quot; を、ミーティングの前・中・後という3つの場面に分けて実践しています。ここでは、アプリの画面にバナーを追加した事例を例に、それぞれの場面で意識していることを順に紹介します。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ミーティング前：自分のベスト案でつくり切る&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ミーティング前は、手に入る計画書や仕様書をAIと一緒に読み込み、「なぜつくるのか（Why）」「何をつくるのか（What）」を自分なりに解釈します。この段階で最も大事なのは、完璧な実装をつくることではなく、どこが曖昧なのかを目に見える形にすることだと考えています。実際、詳細が決まっていないことがほとんどですが、曖昧な点にぶつかっても立ち止まりません。PdMに質問する代わりに、いったん自分が考えるベストな案でつくり切り、迷ったポイントはミーティングのアジェンダに整理しておきます。&lt;/p&gt;
&lt;p&gt;たとえば、バナー追加の事例では、リリースに必要な最小限の機能に絞って早くリリースするか、将来使い回せる再利用性を優先するか迷いながらも、まず最小限の機能で動くプロトタイプをつくりました。UIデザインがまだない場合も、既存の画面部品を組み合わせた仮の見た目で形にしました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ミーティング中：動くものを見ながら論点を解消する&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ミーティング中は、その動くプロトタイプを見せながら議論し、可能な限りその場で論点を解消します。意見が分かれそうな箇所には、あらかじめA案とB案を用意し、「私はこういう理由でA案を推します」と推奨案まで添えておきます。バナーの例では、期日を踏まえて、リリースに必要な機能に絞った設計プランと、将来の再利用まで見据えた設計プランを提示し、PdMはその場で前者のプランに合意できました。細かな仕様も、プロトタイプを見ながらサクサクと決まっていきました。判断の材料がそろっているため、議論は驚くほど早く前に進みます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ミーティング後：決まった内容をすぐ反映する&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ミーティング後は、決まった内容を仕様書に反映し、実装を微修正したうえで品質保証（QA）のテストに回します。大きなつくり直しが起きにくく、初回ミーティングの直後にはリリースが見えている、という状態になりました。バナーの例では、私がつくった仮の見た目をデザイナーが本番デザインへブラッシュアップし、実装側はそれを反映する微修正で済みました。&lt;/p&gt;
&lt;h2&gt;&amp;quot;Build First, Discuss Later&amp;quot; で起きた3つの変化&lt;/h2&gt;
&lt;p&gt;この進め方を試してみると、ミーティングの進み方やPdMとのやり取りがかなり変わりました。特に大きかった変化は、次の3つです。&lt;/p&gt;
&lt;p&gt;1つ目は、ミーティングがほぼ1回で完結するようになったことです。うまくいけば、初回ミーティングが終わった時点で仕様も実装もほぼ固まっており、開発見積もりすら不要になることもあります。その後の往復も大きく減りました。&lt;/p&gt;
&lt;p&gt;2つ目は、議論が速く、かつ正確になったことです。実際に動くものを見せながら「この画面の挙動はこれでよいですか」と確認できるため、言葉だけのやり取りで生じがちな認識のズレが起きにくくなりました。&lt;/p&gt;
&lt;p&gt;3つ目は、PdMの負担が軽くなったことです。エンジニアが具体的な仕様の案まで持っていくので、PdMは方針を確認するだけで済みます。特にPdMが複数プロジェクトを兼務しているような状況では、その確認コストを減らせるだけでも大きな価値があります。&lt;/p&gt;
&lt;h2&gt;「待つエンジニア」から「提案するエンジニア」へ&lt;/h2&gt;
&lt;p&gt;こうした変化は、単に開発プロセスやコミュニケーションを効率化しただけでなく、私自身のエンジニアとしてのマインドセットにも影響を与えました。&lt;/p&gt;
&lt;p&gt;以前の私は、決まった要件を正しく実装することがエンジニアの主な役割だと思っていました。けれど、PdMが持つWhyと大まかなWhatを起点に、まず動くものをつくってみると、「これは要らないかもしれない」「こっちの方がお客さまに価値を届けられるのでは」といった議論を、自分から持ち込めるようになりました。&lt;/p&gt;
&lt;p&gt;いちばん大きかったのは、「私はこれがいいと思う」というアイデアを持ってミーティングに参加できるようになったことです。ただ仕様を待つのではなく、要件定義の段階から意見を出し、仕様を決めていく側に少しずつ入っていけるようになりました。&lt;/p&gt;
&lt;p&gt;そうやって関わっていると、「この機能は今いちばん自分が詳しい」というオーナーシップも自然と生まれてきます。自分の提案が仕様に反映され、動くものを通じてプロダクトの方向性が決まっていく。その過程に関われるようになって、プロダクトづくりが前より一層楽しくなりました。&lt;/p&gt;
&lt;p&gt;生成AIによって「まずつくってみる」ハードルが下がったことで、エンジニアが上流の議論に入りやすくなったと感じています。プロトタイプをつくってミーティングに持ち込むことは、単に開発を速くするだけではなく、エンジニアがより主体的にプロダクトづくりに関わるためのきっかけにもなるのだと思います。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;&amp;quot;Build First, Discuss Later&amp;quot; は、先に動くプロトタイプをつくり、それを見ながら議論することで、意思決定を速くする進め方です。みなさまも、「仕様待ちで開発が始められない」「仕様が曖昧で手戻りが多い」と感じたら、自分なりのプロトタイプを会議に持ち込んでみてください。会話が前に進むだけでなく、プロダクトづくりの楽しさも少し違って見えてくると思います。&lt;/p&gt;
&lt;p&gt;次の記事は mewutoさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>内製ワークフローエンジンの設計とメルカリでの活用事例</title><link>https://engineering.mercari.com/blog/entry/20260612-workflow-engine/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260612-workflow-engine/</guid><description>&lt;p&gt;こんにちは。メルペイでソフトウェアエンジニアをしている @sapuri です。この記事は Merpay &amp;amp; Mercoin Tech Openness Month 2026 の 9日目の記事です。 はじめに 本記 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 12 Jun 2026 10:00:39 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイでソフトウェアエンジニアをしている &lt;a href=&quot;https://x.com/sapuri_tappuri&quot; title=&quot;@sapuri&quot;&gt;@sapuri&lt;/a&gt; です。この記事は &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay &amp;amp; Mercoin Tech Openness Month 2026&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt; の 9日目の記事です。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;本記事は、2026年4月27日の &lt;a href=&quot;https://upsider.connpass.com/event/382902/&quot; title=&quot;Background Job Talk 〜 Temporal 活用と独自実装の舞台裏編〜&quot;&gt;Background Job Talk 〜 Temporal 活用と独自実装の舞台裏編〜&lt;/a&gt; で発表した「内製ワークフローエンジンの設計とメルカリでの活用事例」を記事化したものです。&lt;/p&gt;
&lt;p&gt;マイクロサービスアーキテクチャのような分散システムでは、複数のサービスにまたがる処理のデータ整合性をどう保つか、いわゆる分散トランザクションの扱いが大きな課題となります。&lt;/p&gt;
&lt;p&gt;メルカリでは、この課題を Saga パターンによる結果整合性で解決するために、自社でワークフローエンジンを開発して運用しています。&lt;/p&gt;
&lt;p&gt;このワークフローエンジンは、もともとメルコインの決済基盤における分散トランザクション管理のために開発したものです。メルペイの Payment Service で得た知見も取り入れながら設計し、現在はメルカリグループ内の複数のユースケースで利用が広がっています。&lt;/p&gt;
&lt;p&gt;この記事では、内製に至った背景とワークフローエンジンの具体的な設計、社内での活用事例について紹介します。&lt;/p&gt;
&lt;h2&gt;分散トランザクション管理の課題と Saga パターン&lt;/h2&gt;
&lt;p&gt;メルカリでは主にマイクロサービスアーキテクチャを採用しています。&lt;/p&gt;
&lt;p&gt;そのため、お客さまがアプリで1つの操作をすると、そのリクエストは基本的に複数のサービスをまたいで処理されます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/55c062e1-06.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;例えば、メルカリアプリからビットコインを購入するときの決済リクエストでは、取引データの作成、メルコインの日本円残高の減算、メルペイのポイントの減算、ビットコイン残高の加算、取引データの更新といった複数の処理が関わります。&lt;/p&gt;
&lt;p&gt;この決済リクエストは、1つのトランザクションとして扱う必要があります。つまり、一連の処理をすべて成功させるか、すべて失敗させるかのどちらかに寄せる必要があります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/1bbb4ae3-slide-07-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;しかし、各サービスがそれぞれデータベースを持っているため、単純にロールバックすることはできません。この点を考慮せずに実装すると、エラーのタイミングによってデータの不整合が発生します。&lt;/p&gt;
&lt;p&gt;例えば、このような不整合が起こりえます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;決済が失敗したのにメルコインの日本円残高が減っている&lt;/li&gt;
&lt;li&gt;残高は減ったがビットコイン残高が加算されない&lt;/li&gt;
&lt;li&gt;ビットコインと交換できているのに取引が完了扱いになっていない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;また、Two-Phase Commit のような分散トランザクションでは長期間リソースをロックするため、サービスの可用性が下がる可能性があります。&lt;/p&gt;
&lt;p&gt;そのため、メルカリでは結果整合性のアプローチで、このような分散トランザクションを解決しています。&lt;/p&gt;
&lt;h3&gt;Saga&lt;/h3&gt;
&lt;p&gt;この結果整合性を実現するためのアーキテクチャの1つとして、Saga というパターンがあります。&lt;/p&gt;
&lt;p&gt;Saga は、トランザクションを複数の小さなトランザクションに分割して順次実行することで長時間のロックを不要にします。途中でリトライ不可能なエラーが出た場合は、成功済みの処理に対する補償トランザクションを逆順で実行します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/9459dfc8-slide-09-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先ほどの暗号資産購入の例で、途中のビットコイン残高を増やす処理でリトライできないエラーが発生した場合を考えます。&lt;/p&gt;
&lt;p&gt;この場合、この時点までに成功した処理を取り消す補償トランザクションを逆順に実行します。すでにメルコインの残高とメルペイのポイントが減らされているので、まずポイントを戻し、その次にメルコイン残高を戻し、最後に取引データを失敗として更新します。&lt;/p&gt;
&lt;p&gt;このように実装することで、途中のどこで失敗しても結果整合性を保って処理を完了させることができます。&lt;/p&gt;
&lt;p&gt;このあたりの話は以前の記事でも紹介しているので、興味のある方はそちらもぜひご覧ください。&lt;br /&gt;
&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20230614-distributed-transaction/&quot; title=&quot;メルコイン決済基盤における分散トランザクション管理 | メルカリエンジニアリング&quot;&gt;メルコイン決済基盤における分散トランザクション管理 | メルカリエンジニアリング&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;ワークフローエンジンの検討&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/4d007387-slide-10-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;実装の方針が決まったので、実際に Saga パターンを実装するためにワークフローエンジンの導入を検討しました。&lt;/p&gt;
&lt;p&gt;主に検討したツールは、&lt;a href=&quot;https://cloud.google.com/workflows&quot; title=&quot;GCP Workflows&quot;&gt;GCP Workflows&lt;/a&gt;、&lt;a href=&quot;https://cadenceworkflow.io/&quot; title=&quot;Cadence&quot;&gt;Cadence&lt;/a&gt;、&lt;a href=&quot;https://temporal.io/&quot; title=&quot;Temporal&quot;&gt;Temporal&lt;/a&gt; です。メルカリでは主に GCP を使ってサービスを構築しているため、まず GCP Workflows を検討しました。ただ、各処理を HTTP のエンドポイントとして実装する必要があり、ユニットテストがやりにくいという懸念がありました。また、YAML ではなく Go のコードでワークフローを記述したいという要望もありました。&lt;/p&gt;
&lt;p&gt;Cadence と Temporal も検討しましたが、メルカリでは Cloud Spanner をメインに使っているため、Spanner に対応していなかったことから採用できませんでした。また、Temporal はシステムの規模が大きく、仕組みも比較的複雑なため、運用面にも不安がありました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/2e9c03fb-slide-11-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;このように、既存ツールでは要件を満たせなかったため、自社でワークフローエンジンを開発することにしました。&lt;/p&gt;
&lt;p&gt;開発時には、Cadence / Temporal のインターフェースの良さを取り入れつつ、メルペイの Payment Service ですでに実績があった「DB への実行状態の永続化 x インメモリキュー x Worker での実行管理」のアーキテクチャを再利用する方針にしました。また、Go 専用で必要な機能のみに絞ることで、数人の兼務メンテナーでも運用できる規模にしています。&lt;/p&gt;
&lt;h2&gt;ワークフローエンジンの設計&lt;/h2&gt;
&lt;h3&gt;アーキテクチャ&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/ebc5275a-magician-architecture-1024x564.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;このワークフローエンジンは、アプリケーションサーバーと同じ Pod でデプロイされることを想定しています。&lt;/p&gt;
&lt;p&gt;Go runtime で動作し、利用者は SDK として扱います。&lt;/p&gt;
&lt;p&gt;アプリケーションは &lt;code&gt;Manager&lt;/code&gt; というインターフェースを使ってワークフローエンジンを操作します。主に Register と Execute という2種類のインターフェースを使います。&lt;/p&gt;
&lt;p&gt;アプリケーションはワークフローを普通の Go の関数として実装するので、その関数の内容を事前にワークフローエンジンに登録する必要があります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/76138004-slide-14-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;manager.RegisterWorkflow()&lt;/code&gt; が呼び出されると、Manager は Registry というインメモリの領域に関数を格納します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/88758346-slide-15-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;manager.Workflow().Execute()&lt;/code&gt; は、実際にワークフローを実行するインターフェースです。&lt;/p&gt;
&lt;p&gt;呼び出されると、Manager は Engine Server という gRPC サーバーに対して Workflow や Activity を作成するリクエストを送ります。&lt;/p&gt;
&lt;p&gt;Engine Server は関数名や引数、実行状態を DB に保存し、インメモリキューである Channel に &lt;code&gt;WorkflowStarted&lt;/code&gt; イベントを publish します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/d1564f60-slide-16-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;その後、Worker という goroutine が &lt;code&gt;WorkflowStarted&lt;/code&gt; イベントを subscribe し、Registry から実行する関数を取得して、Go のリフレクションを使って実行します。&lt;/p&gt;
&lt;p&gt;実行が完了すると Worker は Engine Server に完了を報告し、Engine Server は結果を保存して &lt;code&gt;WorkflowCompleted&lt;/code&gt; イベントを publish します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/6f16541a-slide-17-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;その後、Worker が &lt;code&gt;WorkflowCompleted&lt;/code&gt; イベントを subscribe し、アプリケーションに関数の実行結果を返却します。&lt;/p&gt;
&lt;p&gt;もしその結果がエラーだった場合は、後述する &lt;code&gt;ErrorMarshaler&lt;/code&gt; というインターフェースで、その Workflow を完了させるかどうかを判定します。&lt;/p&gt;
&lt;p&gt;ここまで説明したコンポーネントの役割を整理します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Manager&lt;/strong&gt;: SDK のエントリーポイント。アプリケーションは &lt;code&gt;Workflow()&lt;/code&gt;、&lt;code&gt;Activity()&lt;/code&gt;、&lt;code&gt;RegisterWorkflows()&lt;/code&gt; などを呼び出します。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Engine Server&lt;/strong&gt;: Create、Complete、List などの gRPC API を提供するサーバー。DB に Workflow や Activity の I/O と状態を保存します。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Channel&lt;/strong&gt;: Workflow や Activity の状態遷移イベントのハブとなるインメモリキュー。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workers&lt;/strong&gt;: Workflow や Activity を実行する goroutine 群。Channel から状態遷移イベントを購読し、イベントの種別に応じた処理を実行します。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Registry&lt;/strong&gt;: Register された関数をインメモリで保持します。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recovery Worker&lt;/strong&gt;: Engine Server に対して定期的に未完了の Workflow と Activity を List してリトライします。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;コードサンプル&lt;/h3&gt;
&lt;p&gt;アプリケーション側の実装イメージは次のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (s *Service) createExchangeWorkflow(ctx context.Context, params *CreateExchangeParams) (*CreateExchangeResult, error) {
    saga := workflow.NewSaga(s.wm)

    if err := s.wm.Activity(s.authorizeBalance, params.Balance).ExecuteWait(ctx); err != nil {
        return nil, err
    }
    saga.AddCompensation(s.cancelBalance, params.Balance)

    if err := s.wm.Activity(s.authorizePoint, params.Point).ExecuteWait(ctx); err != nil {
        if !isCompletableError(err) {
            return nil, err
        }

        if cerr := saga.Execute(ctx, func(e execution.Execution) error {
            return e.Wait(ctx)
        }); cerr != nil {
            return nil, fmt.Errorf(&amp;quot;failed to execute compensation activities: %w, orig_err: %v&amp;quot;, cerr, err)
        }

        return nil, err
    }

    return &amp;amp;CreateExchangeResult{}, nil
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;まず、&lt;code&gt;createExchangeWorkflow&lt;/code&gt; という関数が定義されています。&lt;/p&gt;
&lt;p&gt;この関数は、残高を確保する &lt;code&gt;authorizeBalance&lt;/code&gt; という Activity と、ポイントを確保する &lt;code&gt;authorizePoint&lt;/code&gt; という Activity を順に実行して結果を返す処理です。&lt;/p&gt;
&lt;p&gt;特徴的なのは、それぞれの Activity を実行した直後に、この SDK が提供する Saga の &lt;code&gt;AddCompensation&lt;/code&gt; インターフェースで補償トランザクションを登録している点です。&lt;/p&gt;
&lt;p&gt;これにより、&lt;code&gt;authorizeBalance&lt;/code&gt; Activity が成功した後に &lt;code&gt;authorizePoint&lt;/code&gt; Activity が失敗した場合は、&lt;code&gt;authorizeBalance&lt;/code&gt; を取り消す処理である &lt;code&gt;cancelBalance&lt;/code&gt; という関数が補償トランザクションとして実行されます。&lt;/p&gt;
&lt;h3&gt;エラーハンドリング&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/20d88a41-slide-22-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;このワークフローエンジンでは、3種類のエラーを定義しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Completable Error&lt;/strong&gt;: Workflow や Activity を失敗として完了させてよい、想定されたエラーです。例として、残高不足や利用制限があります。&lt;/li&gt;
&lt;li&gt;Retryable Error: リトライ対象のエラーです。&lt;/li&gt;
&lt;li&gt;Incompletable Error: Workflow を完了させずに停止し、Recovery Worker が後でリトライするエラーです。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/dc333d4c-slide-23-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Completable Error は、明示的に完了できるエラーだけを完了扱いにするための仕組みです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;クライアント側で &lt;code&gt;ErrorMarshaler&lt;/code&gt; というインターフェースを実装したエラーとして定義される&lt;/li&gt;
&lt;li&gt;該当しないエラーはすべて未完了として実行を停止し、Recovery Worker によってリトライされる&lt;/li&gt;
&lt;li&gt;明示的に Completable Error を返さない限り Workflow は完了しない&lt;/li&gt;
&lt;li&gt;アプリケーションが意図していない異常な状態で Workflow が完了しない設計になる&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type ErrorMarshaler interface {
    MarshalCompletableError(error) ([]byte, error)
    UnmarshalCompletableError(marshaledErr []byte) error
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/345c18d9-slide-24-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;具体例として、ドメインのカスタムエラー型に &lt;code&gt;Completable()&lt;/code&gt; というメソッドを定義し、それを使って &lt;code&gt;ErrorMarshaler&lt;/code&gt; を実装します。&lt;/p&gt;
&lt;p&gt;このようなカスタムエラー型を作っておくことで、ビジネスロジックで特定のエラーコードを含むエラーを返すと、ワークフローエンジンで完了可能なエラーとして処理されます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Error struct {
    code ErrorCode
    msg  string
}

func (e *Error) Error() string {
    return e.msg
}

func (e *Error) Completable() bool {
    return e.code == ErrCodeCompletable
}

type workflowError struct {
    Code ErrorCode
    Msg  string
}

type workflowErrorMarshaler struct{}

func (workflowErrorMarshaler) MarshalCompletableError(err error) ([]byte, error) {
    var aerr *Error
    if !errors.As(err, &amp;amp;aerr) {
        return nil, err
    }

    if !aerr.Completable() {
        return nil, err
    }

    return json.Marshal(&amp;amp;workflowError{
        Code: aerr.code,
        Msg:  aerr.msg,
    })
}

func (workflowErrorMarshaler) UnmarshalCompletableError(data []byte) error {
    var werr workflowError
    if err := json.Unmarshal(data, &amp;amp;werr); err != nil {
        return err
    }

    return &amp;amp;Error{
        code: werr.Code,
        msg:  werr.Msg,
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;活用事例&lt;/h2&gt;
&lt;p&gt;ここまで、独自に開発したワークフローエンジンの設計について紹介しました。&lt;/p&gt;
&lt;p&gt;ここからは、決済以外のユースケースも含めて、社内での活用事例を3つ紹介します。&lt;/p&gt;
&lt;h3&gt;1. メルカリモバイル: 同期レスポンスと非同期処理の分離&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/c18ab958-slide-26-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;例えば、メルカリモバイルの回線開通フローでは、Workflow と Child Workflow というサブのワークフローを定義し、同期レスポンスと非同期処理を分離しています。&lt;/p&gt;
&lt;p&gt;具体的には、Workflow と Child Workflow で役割を分けています。Workflow は回線開通リクエストを DB に保存し、Child Workflow を fire-and-forget で起動してレスポンスを返します。&lt;/p&gt;
&lt;p&gt;Child Workflow は、非同期で Pub/Sub イベントを発行します。失敗した場合は、Recovery Worker が Child Workflow を復旧します。&lt;/p&gt;
&lt;p&gt;これにより、クライアントに即座にレスポンスを返しつつ、Pub/Sub 発行のような後続処理がバックグラウンドで実行されることを保証できます。&lt;/p&gt;
&lt;h3&gt;2. メルカリ グローバル EC 基盤: Saga によるチェックアウト管理&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/20d6630a-slide-27-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリ グローバル EC 基盤では、Spanner ではなく PostgreSQL を採用しています。&lt;/p&gt;
&lt;p&gt;ここでは、購買代行パートナーを経由して海外のお客さまが日本のメルカリの商品を購入する処理を例にします。&lt;/p&gt;
&lt;p&gt;例えばチェックアウト確定フローでは、クーポン消費、注文作成、購買代行パートナーへの注文連携、注文確定通知送信の順に処理が進みます。&lt;/p&gt;
&lt;p&gt;このチェックアウトの処理で、冒頭で紹介した暗号資産購入のユースケースと同様に Saga パターンを使ってトランザクションを管理しています。&lt;/p&gt;
&lt;p&gt;途中で失敗した場合には、Saga による補償トランザクションでキャンセルします。&lt;/p&gt;
&lt;p&gt;内製のワークフローエンジンを採用した理由には、社内にすでにある類似実装や運用基盤を活用したかったことがあります。また、メンテナーが社内にいるため直接サポートを受けられることや、必要な機能を柔軟に追加できて最適化しやすいことも大きな理由でした。&lt;/p&gt;
&lt;h3&gt;3. eKYC: Signal を使った long-running workflow&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/09625c27-slide-28-1024x576.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;eKYC によるお客さまの本人確認フローは、Workflow の中で数時間から数日の審査待ちが発生するという、いわゆる long-running workflow になっています。&lt;/p&gt;
&lt;p&gt;これを実現するために、Temporal でも提供されている Signal という機能を実装しました。&lt;/p&gt;
&lt;p&gt;Signal を使うことで、Workflow を中断し、外部から Workflow に情報を送って再開させるユースケースを実現できます。&lt;/p&gt;
&lt;p&gt;長期フローを細切れのジョブとして分割せず、書類検証、審査待ち、承認または拒否という一連の流れを1本の Workflow として表現できます。&lt;/p&gt;
&lt;p&gt;Signal を使ったアプリケーション側の実装イメージは次のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type ApprovalSignalParams struct {
    Approved     bool
    RejectReason string
}

func (s *Service) approvalWorkflow(ctx context.Context, req *ApprovalRequest) (*ApprovalResult, error) {
    var result *ApprovalResult
    if err := s.wm.Activity(s.verifyDocument, req).ExecuteGet(ctx, &amp;amp;result); err != nil {
        return nil, err
    }

    var params ApprovalSignalParams
    if err := s.wm.Signal(&amp;quot;approval&amp;quot;).Receive(ctx, &amp;amp;params); err != nil {
        return nil, err
    }

    if !params.Approved {
        return nil, newCompletableError(params.RejectReason)
    }

    return result, nil
}

func (s *Service) handleApproval(ctx context.Context, workflowIdempotencyKey string, params ApprovalSignalParams) error {
    return s.wm.Signal(&amp;quot;approval&amp;quot;).Send(ctx, workflowIdempotencyKey, params)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;approvalWorkflow&lt;/code&gt; という関数は、&lt;code&gt;verifyDocument&lt;/code&gt; という Activity を実行した後に、&lt;code&gt;approval&lt;/code&gt; という signal を待機します。&lt;/p&gt;
&lt;p&gt;審査が終わり、外部から &lt;code&gt;approval&lt;/code&gt; signal をこの Workflow に送信すると、&lt;code&gt;approvalWorkflow&lt;/code&gt; が途中から再開します。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;分散システムにおけるデータ整合性の課題に対して、メルカリでは Saga パターンによる結果整合性を採用し、それを支える仕組みとしてワークフローエンジンを内製しています。&lt;/p&gt;
&lt;p&gt;この記事では、その背景と設計、社内での活用事例について紹介しました。&lt;/p&gt;
&lt;p&gt;なお、この SDK を使った開発を支援するために、専用の静的解析ツールも開発して運用しています。このあたりの運用面についても発表で触れているので、興味のある方は Speaker Deck のスライドもぜひご覧ください。&lt;/p&gt;
&lt;p&gt;&lt;iframe class=&quot;speakerdeck-iframe&quot; frameborder=&quot;0&quot; src=&quot;https://speakerdeck.com/player/3a470cc0a7214c56b7d9153114dc0180&quot; title=&quot;内製ワークフローエンジンの設計とメルカリでの活用事例&quot; allowfullscreen=&quot;true&quot; allow=&quot;web-share&quot; style=&quot;border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;&quot; data-ratio=&quot;1.7777777777777777&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;次の記事は kubomiさんの「Build First, Discuss Later｜初回ミーティングに動くプロトタイプを持ち込んだら、意思決定が爆速になった」です。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>Pub/Sub drivenなmicroserviceにPR単位の検証環境を導入した話</title><link>https://engineering.mercari.com/blog/entry/20260610-be556591c7/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260610-be556591c7/</guid><description>&lt;p&gt;こんにちは。今年4月に入社したメルペイ Loyalty &amp;amp; Santa（Growth Platform）チームでBackend Engineerをしている@mikupoです。この記事は「Merpay＆Mercoi [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 11 Jun 2026 10:00:29 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。今年4月に入社したメルペイ Loyalty &amp;amp; Santa（Growth Platform）チームでBackend Engineerをしている@mikupoです。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot; title=&quot;Merpay＆Mercoin Tech Openness Month 2026&quot;&gt;Merpay＆Mercoin Tech Openness Month 2026&lt;/a&gt;」の8日目の記事です！&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;私が所属しているSantaチームは、メルカリ・メルペイにおけるポイント還元やキャンペーンの基盤となるシステムを開発・運用しています。Santaの処理はすべて非同期で、Pub/SubのPull型 subscriptionを中心に構成されています。&lt;/p&gt;
&lt;p&gt;Santaの開発で課題になっていたのが、QAプロセスです。検証に使える開発環境（Dev環境）はチームに1つしかなく、複数の開発（Pull Request 以降、 PR) が重なるとQAを並列に進められませんでした。実装は終わっているのに、検証環境が空くのを待つ、そんな状況が発生していました。&lt;/p&gt;
&lt;p&gt;PRごとに独立した環境を用意できれば、この待ち時間はなくせます。ただ、Santaでそれを実現するのは簡単ではありませんでした。Pull型のsubscriptionでは、複数のconsumerが同じsubscriptionに接続している場合、どのconsumerがどのmessageを受け取るかをpublisher側から指定できません。そのため、「このイベントはこのPR環境へ」と狙って届けることができません。&lt;/p&gt;
&lt;p&gt;社内にはすでに、こうした課題に使えそうな仕組みもいくつかありました。ただ、それらを Santa に取り入れるには、本番の非同期処理の作りに大きく手を入れる必要がありました。今回達成したいのは QA の改善であり、そのために本番の非同期処理のかたちを大きく作り変えるのは避けたいと考えました。&lt;/p&gt;
&lt;p&gt;本記事では、この制約のなかで「Pull型を保ったまま、PRごとに環境を分ける」 をどう実現したのかを紹介します。&lt;/p&gt;
&lt;p&gt;Santaについてくわしく知りたい方は、「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/2019-12-09-172422/&quot; title=&quot;メルペイのキャンペーンを支えるサンタの秘密&quot;&gt;メルペイのキャンペーンを支えるサンタの秘密&lt;/a&gt;」をご確認ください。&lt;/p&gt;
&lt;h2&gt;PR単位の検証環境が必要になった背景&lt;/h2&gt;
&lt;p&gt;従来、Santaチームの検証に使えるDev環境は1つしかありませんでした。複数のPRを同じDev環境へ同時に反映すると、不具合が起きたときに原因となった変更を切り分けづらくなります。変更同士がconflictする場合は、そもそも並行してQAを進められませんでした。&lt;/p&gt;
&lt;p&gt;このボトルネックを減らすための選択肢として、Santaでは&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20210928-mtf2021-day5-4/&quot; title=&quot;Pull Request Replication Controller&quot;&gt;Pull Request Replication Controller&lt;/a&gt;（PRRC）に注目しました。PRRCとは、GitHubのPRを起点としてDev環境とは別に、PRごとの検証環境（PRRC環境）を作成するためのKubernetes custom controllerを使った仕組みです。&lt;/p&gt;
&lt;p&gt;PRRCによってPRごとの検証環境を作る道筋は見えました。次に検討したのが、社内で使われている&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251009-from-local-to-global-building-seamless-b2c-product-integration-at-mercari/&quot; title=&quot;Pub/Sub gRPC Pusher&quot;&gt;Pub/Sub gRPC Pusher&lt;/a&gt;です。Pub/Sub messageをgRPC requestとして扱えれば、既存の&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20220218-dynamic-service-routing-using-istio/&quot; title=&quot;Dynamic Service Routing&quot;&gt;Dynamic Service Routing&lt;/a&gt;と組み合わせてPRRC環境へルーティングできるように見えました&lt;/p&gt;
&lt;p&gt;しかし、SantaのPub/Sub処理はPull型subscriptionを前提に作られています。今回解決したかったのはQAプロセスのボトルネックであり、本番の非同期処理の仕組みを大きく変えることではありませんでした。一方で、gRPC Pusherをそのまま使うには、既存のPub/Sub handlerをgRPC endpointとして受けられる形に変える必要があります。そのため、QA環境の改善として取り組むには、本番環境への影響や変更範囲が大きくなりすぎると判断しました。&lt;/p&gt;
&lt;p&gt;そのため、私たちは既存のPull型Pub/Subを保ったままPR単位の検証環境を実現するために、Santa側で満たすべき要件を整理しました。&lt;/p&gt;
&lt;h2&gt;Pull型Pub/Subを保ったままPR単位の検証環境を作るための要件&lt;/h2&gt;
&lt;p&gt;既存のgRPC Pusherをそのまま使うのではなく、SantaのPull型 subscriptionを保ったままPR単位の検証環境を実現するには、まず満たすべき要件を整理する必要がありました。PRRC環境を実際の検証に使える状態にするには、messageを意図した環境で受け取れることと、PRRC向けの文脈を後続処理へ引き継げることが重要でした。&lt;/p&gt;
&lt;p&gt;ここでは、この2つの要件を順に説明します。&lt;/p&gt;
&lt;h3&gt;要件1：Dev環境とPRRC環境でmessageを分けて受け取れること&lt;/h3&gt;
&lt;p&gt;1つ目の要件は、Dev環境で確認したいmessageと、特定のPRRC環境で確認したいmessageを分けて受け取れることです。PRごとの検証環境を作れたとしても、それぞれの環境で確認したいmessageが混ざってしまうと、どのPRの変更による挙動なのかを切り分けづらくなります。&lt;/p&gt;
&lt;p&gt;messageが混ざる原因は、Pull型subscriptionの受け取り先をpublisher側から指定できないことにあります。図1のように、PRRC環境のconsumerをDev環境と同じsubscriptionにつなぐと、複数のconsumerが同じsubscriptionからmessageを受け取る構成になります。そのため、Dev環境で確認したいmessageをPRRC環境が受け取ったり、PRRC環境で確認したいmessageをDev環境が受け取ったりする可能性があります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/8ce29a21-screenshot-2026-06-10-at-15.38.04.png&quot; width=&quot;800&quot;&gt;&lt;/p&gt;
&lt;p&gt;このため、PR単位の検証環境として使うには、messageを環境ごとに分けて受け取れる仕組みが必要になります。&lt;/p&gt;
&lt;h3&gt;要件2：PRRC環境向けのルーティング情報を後続処理へ引き継げること&lt;/h3&gt;
&lt;p&gt;2つ目の要件は、messageがどのPRRC環境向けなのかを、後続の処理にも引き継げることです。Santaの処理はPub/Sub messageを受け取って終わりではなく、処理の途中で他のmicroserviceをgRPCで呼び出したり、別のPub/Sub messageをpublishしたりします。&lt;/p&gt;
&lt;p&gt;そのため、SantaのDev環境とPRRC環境でmessageを分けて受け取れるだけでは不十分です。結合テストでは、Santaから呼び出すmicroservice側にもPRRC環境が用意されている場合があります。その場合、Santaからの呼び出しも通常のDev環境ではなく、対応するmicroserviceのPRRC環境へルーティングできる必要があります。&lt;/p&gt;
&lt;p&gt;図2のように、このルーティングを実現するには、Santaが受け取ったmessageに付与されたルーティング情報を、後続のgRPC呼び出しやPub/Sub publishにも引き継ぐ必要があります。つまり、message自体にルーティング情報を持たせ、Santa内の処理でもその情報を落とさない仕組みが必要になります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/be0f5bb2-screenshot-2026-06-10-at-15.41.11.png&quot; width=&quot;800&quot;&gt;&lt;/p&gt;
&lt;h2&gt;実現方法の検討&lt;/h2&gt;
&lt;p&gt;ここまで整理した2つの要件を満たすには、Pub/Sub messageにルーティング情報を持たせたうえで、その情報をどの段階で使ってmessageを振り分けるかを決める必要がありました。大きく分けると、consumerがmessageを受け取ったあとに判断する方法、Topic自体を分ける方法、subscriptionのfilterで受け取るmessageを分ける方法があります。&lt;/p&gt;
&lt;p&gt;私たちは、それぞれの方法について、Pull型subscriptionを維持できるか、対象外のmessageを余計にpullしないか、PRRCごとの運用負荷が大きくなりすぎないか、という観点で比較しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/5315bd8b-screenshot-2026-06-10-at-15.43.24.png&quot; width=&quot;800&quot; &gt;&lt;/p&gt;
&lt;p&gt;最初に検討したのは、consumer側でmessage attributeを見て、対象外のmessageを処理しない方法でした。この方法は実装範囲が小さく見えますが、対象外のmessageも一度pullしてしまいます。対象外のmessageをackすると本来処理すべき環境に届かず、nackするとredeliveryが繰り返される可能性があります。&lt;/p&gt;
&lt;p&gt;TopicをDev用とPRRC用に分ける方法も検討しました。この方法では、あらかじめPRRC用のTopicとsubscriptionのペアを用意しておき、PRごとにどのペアを使うかを割り当てます。Topic単位で分離できるため構成は直感的ですが、PRごとの割り当てを手動で管理する必要があります。そのため、割り当て忘れや重複割り当てが起きやすく、PRRC環境が増えるほど運用負荷が高くなります。&lt;/p&gt;
&lt;p&gt;これらに対して、subscription filterを使う方法では、consumerがmessageをpullする前に、subscription側で受け取るmessageを分けられます。さらに、振り分けに使うmessage attributeをそのままルーティング情報として扱えるため、後続のPub/Sub publishやgRPC呼び出しにも同じ情報を引き継げます。つまり、要件1の「messageを環境ごとに分けて受け取ること」と、要件2の「ルーティング情報を後続処理へ引き継ぐこと」を、同じmessage attributeを軸に実現できます。&lt;/p&gt;
&lt;h2&gt;最終的な方針&lt;/h2&gt;
&lt;p&gt;最終的な方針は、Pull型subscriptionを維持したまま、pubsub messageのattributeで配送先を分けることです。各messageのattributeにルーティング情報を付け、subscription側はその値をfilterの条件にします。こうすると、同じtopicに届いたmessageでも、条件に一致したsubscriptionだけがmessageを受け取ります。&lt;/p&gt;
&lt;p&gt;図のように、たとえば PR番号1234 を検証する場合、その message の attribute には、PR番号に対応するルーティング情報(PR1234)を付与します。Dev環境のsubscriptionはこのmessageを受け取らず、PR1234用のsubscriptionだけが受け取ります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/400d03ee-screenshot-2026-06-10-at-15.45.12.png&quot; width=&quot;800&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;さらにこのルーティング情報は、Santaがpublishするmessageにも引き継げます。受け取ったPub/Sub messageのattributeからルーティング情報を読み取り、contextに格納し、後続のpublishや外部microserviceの呼び出しへ渡します。これにより、PRRC の文脈が処理の途中で途切れません。&lt;/p&gt;
&lt;p&gt;一方で、外部サービスの subscription にそのまま filterを足すことはできませんでした。Santa は、ポイント還元などのトリガーとなるイベントを、決済をはじめとする他のmicroservice から Pub/Sub で受け取っています。これらの外部 subscriptionは発行側の microservice（＝別チーム）の管理範囲にあり、Santa が直接 filterを足すと、別チームが管理するリソースに手を入れることになってしまいます。そこで外部 subscription については proxy sidecar を挟み、Santa 側の proxy topicへ再 publish する構成にしました。Santa の Pod は外部 subscription を直接 pullせず、proxy topic に対して作った Dev / PRRC 用の filtered subscriptionを読みます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/a1af9376-screenshot-2026-06-10-at-15.46.23.png&quot; width=&quot;800&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;この方針により、Pull型subscriptionを維持したまま、Pub/Sub messageを意図した環境だけに届けられ、しかも変更をSantaの管轄内だけで完結できます。一方で、PRRCごとにsubscriptionを作る必要があるため、その自動作成・削除の設計が新たに必要になりました。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;本記事では、Pull型Pub/Subを維持したまま、Pub/Sub message attributeとfiltered subscriptionを使ってSantaにPRRC環境を導入した取り組みを紹介しました。&lt;/p&gt;
&lt;p&gt;従来は、QA期間が重複する場合には統合環境を用意するか、複雑なプロジェクト同士の場合は直列で進めてQA待ちが発生するしかありませんでした。しかし、実際にこの仕組みを用いることで、直近の大きな2つのプロジェクトではQAを並列化して進めることができました。また、共有設定を変更するテストを直列でしか実施できなかったのが、QAメンバーの人数に合わせて3つ4つとスケールできるようになり、QA期間の短縮にも貢献できました。&lt;/p&gt;
&lt;p&gt;自分たちのチームの状況も踏まえながら、よりチームに合った解決策を模索し、それを実現できたのは、チームの皆さんの協力があってこそです。Loyalty &amp;amp; Incentiveチームの皆様、ありがとうございました！&lt;/p&gt;
&lt;p&gt;次の記事は sapuriさんの「内製ワークフローエンジンの設計とメルカリでの活用事例」です。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>AI と作る LP エディタ EGP Code を支える 4 つの仕組み</title><link>https://engineering.mercari.com/blog/entry/20260610-how-egp-code-works/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260610-how-egp-code-works/</guid><description>&lt;p&gt;こんにちは。メルペイのフロントエンドエンジニアの @mattsuu です。この記事は「Merpay &amp;amp; Mercoin Tech Openness Month 2026」の7日目の記事です。 EGP Code は [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 10 Jun 2026 11:00:52 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイのフロントエンドエンジニアの &lt;a href=&quot;https://github.com/ryo-manba&quot;&gt;@mattsuu&lt;/a&gt; です。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の7日目の記事です。&lt;/p&gt;
&lt;p&gt;EGP Code は、ランディングページ（LP）を AI と作る社内向けのエディタです。作成背景については &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260603-why-we-built-egp-code/&quot; title=&quot;AI と作る HTML ベースの LP エディタ EGP Code を内製した理由&quot;&gt;AI と作る HTML ベースの LP エディタ EGP Code を内製した理由&lt;/a&gt; という記事で紹介しました。本記事では、その内部で動いている 4 つの仕組みを紹介します。&lt;/p&gt;
&lt;p&gt;EGP Code が扱うのは、HTML と状態や動きを担う少数の &lt;code&gt;&amp;lt;egp-*&amp;gt;&lt;/code&gt;（独自の Web Components）を混ぜた 1 枚のページです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;section class=&amp;quot;p-6 text-center&amp;quot;&amp;gt;
  &amp;lt;h1&amp;gt;春のキャンペーン&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;応募受付中&amp;lt;/p&amp;gt;
  &amp;lt;egp-button&amp;gt;応募する&amp;lt;/egp-button&amp;gt;
&amp;lt;/section&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この HTML を AI エージェントとの対話やエディタで編集し、プレビューで確認して公開します。紹介する 4 つの仕組みは、(1) エージェントの再帰ループ、(2) Firestore を介したリアルタイム反映、(3) ブラウザだけで完結するテストランナー、(4) プレビューと HTML を結ぶ対応表です。&lt;/p&gt;
&lt;p&gt;1 つの指示がユーザーから届いてプレビューに反映されるまでの流れと、それぞれの仕組みが効く場所は次のとおりです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/4762550b-mermaid-diagram-2026-06-04-174519.png&quot; alt=&quot;1 つの指示がブラウザからサーバの AI エージェントへ渡り、その編集が Firestore を介してブラウザへリアルタイムに反映され、プレビューに届くまでの全体フロー図。ブラウザ側にプレビュー・HTML ソースマップ・ブラウザ内テスト、サーバ側にエージェントが置かれている。&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;仕組み 1: 文脈とツールを束ねるエージェントの再帰ループ&lt;/h2&gt;
&lt;p&gt;「フォントサイズを 24px にして」「文字を太くして」のように特定の要素への簡単な指示なら、その要素を特定して CSS を更新したり文言を調整したりするだけなので、ほぼ 1 回の操作で終わります。一方で複数の要素にまとめて指示したり、API を使った画面やテストを作ったり、Lint・テストのエラーを直したりする場合は、推論とツール実行を何度か往復することになります。&lt;/p&gt;
&lt;p&gt;エージェントは「推論 → ツール実行 → 結果を会話履歴に追加 → 再び推論」という再帰ループで動きます。ここでいうツールとは、AI が必要と判断したときに呼べる関数の定義と説明のことです。HTML を書き換える、ファイルを読む、Lint で検証する、テストを走らせる、といった操作をツールとして用意しておき、モデルがその中から必要なものを選んで呼び、戻り値を見て次の手を考えます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/237d7757-mermaid-diagram-2026-06-04-184332.png&quot; height=&quot;800&quot; alt=&quot;サーバがシステムプロンプト・ユーザーの指示と現在の HTML・会話履歴・ツール一覧の 4 種類の入力を束ねて LLM へ送り、LLM がツールを選んで実行し、結果を会話履歴に積んで次のラウンドへ再帰する 1 ラウンドの流れ図。ツールが不要になれば回答して完了する。&quot; style=&quot;display: block; margin: 0 auto;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;1 回の入力に対して 4 つの情報をまとめて渡します。システムプロンプトで AI エージェントに役割やコード生成のルールといった前提を与え、それにユーザーの指示と選択要素と現在の HTML、これまでの会話履歴、そして使えるツールの一覧を教えます。このうち現在の HTML・仕様・テストといったページの状態は、種類ごとに XML タグで区切ってまとめます。&lt;/p&gt;
&lt;p&gt;会話履歴の持ち方は、開発当初 OpenAI API 側に任せていました。しかし全社的に &lt;a href=&quot;https://developers.openai.com/api/docs/guides/your-data#zero-data-retention&quot; title=&quot;ZDR（Zero Data Retention）&quot;&gt;ZDR（Zero Data Retention）&lt;/a&gt; を適用することになり、プロバイダ側に会話を残せなくなったため、いまは履歴をすべて自前で記録し、毎回のリクエストに載せて送るステートレス方式にしています。履歴の肥大化を防ぐため、一定量を超えたら要約させてコンテキストを圧縮しています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// 1 ラウンドの推論
const stream = await client.responses.stream({
  ...args,
  input: sessionBuffer.getItems(), // 自前で管理している履歴全体を送る
  previous_response_id: undefined,
  store: false, // プロバイダ側に会話を保存させない
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ループの工夫をいくつか紹介します。&lt;/p&gt;
&lt;p&gt;1 つ目は、ツールの失敗の扱いです。ここでいう失敗とは、&lt;a href=&quot;https://developers.openai.com/api/docs/guides/tools-apply-patch&quot; title=&quot;&amp;lt;code&amp;gt;apply_patch&amp;lt;/code&amp;gt;&quot;&gt;&lt;code&gt;apply_patch&lt;/code&gt;&lt;/a&gt; の差分が当たらない、Lint がエラーを返す、テストが落ちるなど、ツールが期待どおりに完了しなかった状態を指します。こうした失敗ではループを止めず、エラーの内容をそのまま結果としてエージェントに返すことで自己修正させています。&lt;/p&gt;
&lt;p&gt;2 つ目は、&lt;code&gt;find_skill&lt;/code&gt; ツールによる情報の出し分けです。社内 API の使い方などのドキュメントは、最初は ID と一行説明の一覧だけを見せておき、本文は必要になったタイミングで読み込みます。たとえば「商品一覧を表示したい」という指示が来ると、エージェントはまず一覧から関連しそうなものを探し、&lt;code&gt;find_skill&lt;/code&gt; でそのドキュメント本文を取得します。エージェントは取得したドキュメントを読み、正しい引数で API を呼びます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/0366cbce-mermaid-diagram-2026-06-04-184756.png&quot; alt=&quot;「商品一覧を表示したい」という指示に対し、エージェントが API の一覧から候補を探し、find_skill でドキュメント本文を取得し、正しい引数で API を呼ぶまでの流れ図。&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この推論とツール実行の往復を繰り返し、最後にエージェントが &lt;code&gt;apply_patch&lt;/code&gt; で HTML を差分更新すると 1 つの指示が編集として完成します。&lt;/p&gt;
&lt;h3&gt;ループの途中で方向を変える Real-time steering&lt;/h3&gt;
&lt;p&gt;ここまでは、1 つの指示を最後まで処理してから次を受け取る前提でした。ですが実際には、処理の途中で「やっぱり色は青にして」と方針を変えたり、「ついでにフッターも直して」と指示を足したくなることがあります。完了を待たずに割り込みで指示を足し、走っているループに後から反映する仕組みを用意しています。こうした仕組みは &lt;a href=&quot;https://github.com/anthropics/claude-code/issues/30492&quot; title=&quot;Real-time steering&quot;&gt;Real-time steering&lt;/a&gt; とも呼ばれます。&lt;/p&gt;
&lt;p&gt;ユーザーが処理中に指示を足したときの流れは、次のようになります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/eb98b74a-mermaid-diagram-2026-06-09-112524.png&quot; alt=&quot;ユーザー・Firestore・エージェントを結ぶフロー図。ユーザーの指示でエージェントが推論とツール実行を繰り返し、追加の指示は Firestore に保存され、エージェントがループの途中でそれを読み取って、ツール実行を止めて割り込みを処理する。&quot; /&gt;&lt;/p&gt;
&lt;p&gt;エージェントが処理中（画面がローディング中）にユーザーがメッセージを送ると、クライアントはそれを通常の指示ではなく、割り込みメッセージとして送信します。サーバは受け取った割り込みメッセージを、 Firestore の通常の会話履歴とは別のサブコレクションに、いま走っているリクエストの ID を添えて書き込みます。エージェントは、自分が処理しているリクエストの ID に一致する割り込みだけを読み取ります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// 自分のリクエスト宛の割り込みメッセージだけを読み、読んだら消す
const consumeSteeringMessages = async (conversationId, requestId) =&amp;gt; {
  const snapshot = await steeringRef
    .where(&amp;#039;requestId&amp;#039;, &amp;#039;==&amp;#039;, requestId) // このリクエスト宛だけを対象にする
    .orderBy(&amp;#039;timestamp&amp;#039;, &amp;#039;asc&amp;#039;)
    .get();
  const messages = snapshot.docs.map((doc) =&amp;gt; doc.data());
  await deleteDocs(snapshot.docs); // 読んだら消す
  return messages;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ID で絞るので別のリクエスト宛の割り込みを拾うことはなく、読んだら消すので同じ指示が二重に効いたり取りこぼしたりすることもありません。処理のループに差し込むため、割り込みが来たときにエージェントが何をしようとしていたかで、対応が 2 つに分かれます。&lt;/p&gt;
&lt;p&gt;1 つ目は、ツールを呼ぼうとしていた場合です。そのツールを実行せず、戻り値の代わりに「ツールは実行していません。処理中に新しい指示が届きました」という内容を返します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// ツール実行の直前に割り込みを確認する
const steering = await consumeSteeringMessages(conversationId, requestId);
if (steering.length &amp;gt; 0 &amp;amp;&amp;amp; toolCalls.length &amp;gt; 0) {
  for (const toolCall of toolCalls) {
    // ツールは実行せず、戻り値の代わりに割り込みを差し込む
    buffer.pushMessage({
      role: &amp;#039;tool&amp;#039;,
      tool_call_id: toolCall.id,
      content: &amp;#039;[CONVERSATION_STEERING] 処理中に新しい指示が届きました ...&amp;#039;,
    });
  }
  continue; // 計画を立て直すため、もう一度推論へ
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2 つ目は、ツールを呼ばずに、ユーザーへの返信メッセージを作り終えていた場合です。本来ならこれを見せてループが終わるところですが、割り込みが届いたので止めるべきツールがありません。この返信はまだ画面に出していないので破棄し、直前のユーザーの指示に割り込みメッセージを足して送り直すことで、続きの作業を依頼します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// ツールがない場合は、画面に出していない返信を捨てて指示を足す
if (steering.length &amp;gt; 0 &amp;amp;&amp;amp; toolCalls.length === 0) {
  buffer.pop(); // まだ画面に出していない返信を捨てる
  const priorUserTurn = buffer.pop(); // 直前のユーザーの指示を取り出す
  buffer.pushMessage({
    role: &amp;#039;user&amp;#039;,
    content: `${priorUserTurn.content}\\n[CONVERSATION_STEERING] 処理中に新しい指示が届きました ...`,
  });
  continue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;いずれの場合も、すでに実行した副作用を巻き戻すわけではなく、これから実行するはずだったツールを止めたり、まだ画面に出していない返信を捨てたりして、計画を組み直しています。&lt;/p&gt;
&lt;h2&gt;仕組み 2: Firestore を指示の受け渡し場所にしたリアルタイム反映&lt;/h2&gt;
&lt;p&gt;仕組み 1 で見たように、エージェントは複数のツールを往復させて指示に応えるため、編集には時間がかかることがあります。処理が終わるまで画面が何も更新されないと、利用者は反映されたかどうか分からないまま待つことになります。&lt;/p&gt;
&lt;p&gt;そこで Firestore SDK を利用して、&lt;a href=&quot;https://firebase.google.com/docs/firestore/query-data/listen&quot;&gt;変更をリアルタイムに受け取れる&lt;/a&gt;ようにしています。サーバ側は 1 つの操作が終わるたびにその内容を Firestore へ書き込み、ブラウザ側はそれをサブスクライブして即座に検知・反映します。これで自前で WebSocket を張らずに、編集の途中経過をそのままプレビューへ反映できます。&lt;/p&gt;
&lt;p&gt;エージェントが何かを書き換えると、サーバは会話ごとのコレクションに、次のようなドキュメントを 1 件追加します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;status&amp;quot;: &amp;quot;PENDING&amp;quot;,
  &amp;quot;requests&amp;quot;: [
    {
      &amp;quot;action&amp;quot;: &amp;quot;setHtmlSchema&amp;quot;,
      &amp;quot;payload&amp;quot;: &amp;quot;&amp;lt;body&amp;gt; ...更新後の HTML... &amp;lt;/body&amp;gt;&amp;quot;,
      &amp;quot;reason&amp;quot;: &amp;quot;ユーザーの依頼を反映&amp;quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ブラウザ側の JS は、Firestore の SDK（&lt;code&gt;onSnapshot&lt;/code&gt;）でこのコレクションをサブスクライブしており、&lt;code&gt;PENDING&lt;/code&gt; のドキュメントが届くと &lt;code&gt;requests&lt;/code&gt; の各操作をエディタの状態へ反映します。たとえば &lt;code&gt;setHtmlSchema&lt;/code&gt; なら、エディタが表示している HTML を新しいものに置き換えて、プレビューを再描画します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/37a25ec9-mermaid-diagram-2026-06-04-184454.png&quot; height=&quot;600&quot; alt=&quot;サーバとブラウザが直接つながらず Firestore 経由でやり取りする図。サーバが PENDING のアクションを書き込み、ブラウザが onSnapshot で受け取って反映し、COMPLETED と結果を書き戻し、結果が必要なアクションだけサーバがポーリングして読む。&quot; style=&quot;display: block; margin: 0 auto;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ブラウザが &lt;code&gt;requests&lt;/code&gt; の操作を反映し終えると、その JS が &lt;code&gt;status&lt;/code&gt; を &lt;code&gt;COMPLETED&lt;/code&gt; に書き戻します。HTML 差し替えなどの「反映だけ」のアクションは、投げたら終わりで結果を待ちません。一方でテスト実行のように結果が必要なアクションでは、ドキュメントが &lt;code&gt;COMPLETED&lt;/code&gt; になるまで一定間隔で読み直して、書き込まれた結果を取り出します。取り出した結果はツールの戻り値としてエージェントに返り、それを見て次のツール呼び出しを決めます。&lt;/p&gt;
&lt;h2&gt;仕組み 3: ブラウザだけで完結するテストランナー&lt;/h2&gt;
&lt;p&gt;応募ボタンを押したときの API 呼び出しやその結果に応じた表示の切り替え、リンクによる画面遷移といった LP の動作を手動で確認するのは手間がかかります。そこで EGP Code では、こうした動作をテストで確かめられるようにしています。&lt;/p&gt;
&lt;p&gt;テストはブラウザ上のエディタで直接書いたり、AI に書かせたりできます。これらのテストは、サーバや CI ではなく、プレビューと同じブラウザの中で実行します。ただし Jest や Vitest は Node.js 上で動くツールなので、そのままブラウザには読み込めません。そこで &lt;code&gt;test&lt;/code&gt; / &lt;code&gt;it&lt;/code&gt; / &lt;code&gt;describe&lt;/code&gt; / &lt;code&gt;expect&lt;/code&gt; を提供する小さなランナーを自作しました。アサーションは単体で使える &lt;code&gt;@vitest/expect&lt;/code&gt;、DOM 操作は Testing Library をそのまま利用しています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// 自作の test 関数でテストを定義する
test(&amp;#039;エントリーボタンで API が呼ばれる&amp;#039;, async () =&amp;gt; {
  // Testing Library でユーザーのクリックを再現する
  await userEvent.click(screen.getByText(&amp;#039;エントリー&amp;#039;));
  // @vitest/expect で結果を検証する
  expect(mockEntry).toHaveBeenCalledWith({ campaign: &amp;#039;X&amp;#039; });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;テストが社内 API を呼ぶこともありますが、本番に飛ばすわけにはいきません。そこで iframe 内で &lt;code&gt;window.fetch&lt;/code&gt; を差し替え、リクエストはすべてモック関数に通します。モックしていない呼び出しはエラーになるので、本番へ漏れることはありません。&lt;/p&gt;
&lt;p&gt;テストの実行は、この iframe を作るエディタのページ（ホスト）と iframe の &lt;code&gt;postMessage&lt;/code&gt; のやり取りで進みます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/c7a3585d-mermaid-diagram-2026-06-04-184509.png&quot; alt=&quot;ホストとテスト専用 iframe の postMessage によるやり取りのシーケンス図。ホストが iframe を生成して srcdoc にテスト用 HTML を流し込み、iframe が自己初期化して準備完了を返し、ホストが実行を指示し、iframe が各テストの開始・終了と全体結果を返す。&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ホストは iframe を作ってテスト用 HTML を流し込み、iframe 側の初期化（&lt;code&gt;window.fetch&lt;/code&gt; の差し替えなど）が済むのを待ってから実行を指示します。先に指示が届くと取りこぼすため、必ず「準備完了」を待つようにします。結果は仕組み 2 のアクションでエージェントへ戻ります。失敗していれば、仕組み 1 で触れた自己修正がここで働き、内容を読んで実装を直してもう一度走らせます。&lt;/p&gt;
&lt;h2&gt;仕組み 4: プレビューの要素と HTML の位置を結ぶ対応表&lt;/h2&gt;
&lt;p&gt;ここまではエージェント主導の編集を見てきましたが、人が直接手を入れる場面もあります。Code タブを開くと Monaco エディタで HTML を直接編集でき、プレビューでリアルタイムに確認できます。プレビューの要素をクリックしてエージェントへ指示を出したり、Monaco の対応行へジャンプするには、「プレビューの要素と HTML 上の位置」を対応づける仕組みが必要です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/1aa073c8-preview-jump-ja.png&quot; alt=&quot;EGP Code のエディタ。左に Monaco エディタの HTML、右にライブプレビューが並び、プレビューで選んだ見出しに対応する HTML の行（h1）へジャンプしている画面&quot; /&gt;&lt;/p&gt;
&lt;p&gt;HTML をパースして各要素へ &lt;code&gt;data-egp-src-id&lt;/code&gt;（公開ページには残らない内部 id）を注入してプレビューを描画することで、これを実現しています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;section data-egp-src-id=&amp;quot;src-10&amp;quot;&amp;gt;
  &amp;lt;h1 data-egp-src-id=&amp;quot;src-11&amp;quot;&amp;gt;春のキャンペーン&amp;lt;/h1&amp;gt;
  &amp;lt;egp-button data-egp-src-id=&amp;quot;src-42&amp;quot;&amp;gt;応募する&amp;lt;/egp-button&amp;gt;
&amp;lt;/section&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;プレビュー上で「応募する」ボタンをクリックすると &lt;code&gt;src-42&lt;/code&gt; が取れます。クリック先が Web Components の内部要素などで id を持たない場合もあるため、&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Element/closest&quot; title=&quot;&amp;lt;code&amp;gt;closest&amp;lt;/code&amp;gt;&quot;&gt;&lt;code&gt;closest&lt;/code&gt;&lt;/a&gt; で祖先方向に遡って最寄りの &lt;code&gt;data-egp-src-id&lt;/code&gt; を探します。&lt;/p&gt;
&lt;p&gt;取得した id はエディタが各要素に振った内部 id で、対応表を引くキーになります。対応表はパース時に作る「id → その要素の HTML 上の位置」の Map で、id を引くと文字列オフセットやタグ名が取れます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// 対応表（id → HTML 上の位置とタグ）
Map {
  &amp;#039;src-10&amp;#039; =&amp;gt; { range: { startOffset: 0,  endOffset: 130 }, tagName: &amp;#039;section&amp;#039; },
  &amp;#039;src-42&amp;#039; =&amp;gt; { range: { startOffset: 64, endOffset: 110 }, tagName: &amp;#039;egp-button&amp;#039; },
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;クリックされるのは描画後の DOM 要素で位置を持つのはこの対応表です。要素に振った &lt;code&gt;data-egp-src-id&lt;/code&gt; をキーにすることで、両者を突き合わせています。この対応表には 2 つの用途があります。1 つ目が Monaco エディタへのジャンプです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// 対応表から位置を取得
const range = await mappingApi.findRangeById(&amp;#039;src-42&amp;#039;);
// オフセットを Monaco の行・列に変換
const pos = model.getPositionAt(range.startOffset);
// その行をエディタの中央に表示してカーソルを移動
editor.revealPositionInCenter(pos);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;取得した id を元に対応表から文字列オフセットを取り出して Monaco の行・列に変換します。その行をエディタ中央にスクロールしてカーソルを移動することでジャンプさせています。&lt;/p&gt;
&lt;p&gt;もう 1 つが AI への指示です。&lt;code&gt;data-egp-src-id&lt;/code&gt; が付いているのはプレビュー用に描画した HTML だけで、エージェントが読み書きする公開用の HTML には付いていないため、id をそのまま渡しても対応する要素は見つかりません。そこで id は対応表を引くキーとしてのみ使い、エージェントには、そこから取り出した要素の HTML・タグ名・行番号をユーザーの指示文と一緒に XML へまとめて渡します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;annotated_elements&amp;gt;
  &amp;lt;annotation&amp;gt;
    &amp;lt;tagName&amp;gt;egp-button&amp;lt;/tagName&amp;gt;
    &amp;lt;snippet&amp;gt;&amp;lt;egp-button class=&amp;quot;...&amp;quot;&amp;gt;&amp;lt;/snippet&amp;gt;
    &amp;lt;lineNumber&amp;gt;10&amp;lt;/lineNumber&amp;gt;
    &amp;lt;instruction&amp;gt;色を赤にして&amp;lt;/instruction&amp;gt;
  &amp;lt;/annotation&amp;gt;
&amp;lt;/annotated_elements&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;エージェントはこれらを手がかりに HTML の中から対象要素を見つけて指示に対応します。このように指示を XML タグで構造化する書き方は、&lt;a href=&quot;https://platform.claude.com/docs/ja/build-with-claude/prompt-engineering/claude-prompting-best-practices#xml&quot;&gt;Anthropic のプロンプトのベストプラクティスの一つ&lt;/a&gt;としても紹介されています。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;一見シンプルに見える AI エディタですが、実際に体験を作ろうとすると、待ち時間が長い、途中経過が見えない、どこを編集したか分からないといった小さなつまずきが積み重なります。EGP Code では、そのつまずきを 1 つずつ潰すために、ここまで紹介した仕組みを裏側で積み上げてきました。AI を組み込むときほど、モデルの手前にある体験と安全性を設計することが重要だと感じています。本記事が、AI を組み込んだプロダクトづくりの参考になれば幸いです。&lt;/p&gt;
&lt;p&gt;次の記事は＠mikupoさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>カーソルベースAPIのデータをマージするページネーション設計</title><link>https://engineering.mercari.com/blog/entry/20260610-merge-cursor-based-pagination/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260610-merge-cursor-based-pagination/</guid><description>&lt;p&gt;こんにちは。メルコインのフロントエンド（FE）エンジニアとしてインターンをしている@nanacomです。この記事は「Merpay &amp;amp; Mercoin Tech Openness Month 2026」の7日目の記 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 10 Jun 2026 10:00:48 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルコインのフロントエンド（FE）エンジニアとしてインターンをしている@nanacomです。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の7日目の記事です。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;インターンではFEに限らず、要件定義からバックエンド（BE）開発まで、1つのプロジェクトに幅広く取り組みました。その中で、メルコインの社内ツールを開発する際に、2つのAPIの結果を日時降順にマージして返すエンドポイントを実装するケースに直面しました。&lt;/p&gt;
&lt;p&gt;結果を結合して並べ替えるだけならシンプルですが、&lt;strong&gt;マージした一覧にもページネーションを提供しようとすると、各ソースのカーソルをどこまで進めるべきか&lt;/strong&gt;が複雑になります。本記事では、「マージ結果として採用された件数」と「各データソース側で進めるべきカーソル」のズレにどう対処したかを紹介します。具体的には、データ取得とカーソル確定を分離する「2フェーズ取得パターン」と、各ソースのカーソルを1つのトークンに束ねる「複合ページネーショントークン」の2つの設計を取り上げます。&lt;/p&gt;
&lt;h2&gt;前提：対象とするユースケース&lt;/h2&gt;
&lt;p&gt;マイクロサービスアーキテクチャでは、BFF（Backend For Frontend）で複数のサービスからデータを集約して一覧表示することがよくあります。今回対象としたのは、2つの独立したデータソース（A, B）のデータをマージするケースです。いずれも日時降順にソートされたデータを返し、それぞれがカーソルベースのページネーションAPIを提供しています。カーソルベースのページネーションとは、前回の取得結果の末尾を示すトークン（カーソル）を次のリクエストに渡すことで、続きのデータを取得する方式です。&lt;/p&gt;
&lt;p&gt;この2つのソースの結果を日時降順にマージした一覧をクライアントに返しつつ、その一覧自体にもページネーションを提供する必要がありました。つまり、各ソースが独立して管理するカーソルを、BFF側でどう扱うかが設計上の焦点でした。&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;attachment_36872&quot; aria-describedby=&quot;caption-attachment-36872&quot; style=&quot;width: 580px&quot; class=&quot;wp-caption alignnone&quot;&gt;&lt;img loading=&quot;lazy&quot; src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/04ece399--1024x537.png&quot; alt=&quot;&quot; width=&quot;580&quot; height=&quot;304&quot; class=&quot;size-large wp-image-36872&quot; srcset=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/04ece399--1024x537.png 1024w, https://storage.googleapis.com/prd-engineering-asset/2026/06/04ece399--300x157.png 300w, https://storage.googleapis.com/prd-engineering-asset/2026/06/04ece399--768x403.png 768w, https://storage.googleapis.com/prd-engineering-asset/2026/06/04ece399--1200x629.png 1200w, https://storage.googleapis.com/prd-engineering-asset/2026/06/04ece399-.png 1358w&quot; sizes=&quot;(max-width: 580px) 100vw, 580px&quot; /&gt;&lt;figcaption id=&quot;caption-attachment-36872&quot; class=&quot;wp-caption-text&quot;&gt;売買と入出金をマージした一覧表示（※表示データはすべてダミーです）&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;素朴なアプローチとその限界&lt;/h2&gt;
&lt;p&gt;この設計上の焦点に対して、私たちはまず2つの素朴なアプローチを検討しました。いずれも限界があり、最終的な設計への動機となりました。&lt;/p&gt;
&lt;h3&gt;アプローチ1：全件取得してソート&lt;/h3&gt;
&lt;p&gt;最も単純な方法は、両ソースから全件を取得し、アプリケーション側でソートしてからページごとに切り出す方法です。しかし、データ数が増えるとメモリ使用量とレイテンシーが線形に増加するため、スケールしません。&lt;/p&gt;
&lt;h3&gt;アプローチ2：各ソースからpageSize件取得してマージ&lt;/h3&gt;
&lt;p&gt;各ソースからそれぞれ &lt;code&gt;pageSize&lt;/code&gt; 件を取得し、マージして上位 &lt;code&gt;pageSize&lt;/code&gt; 件を選択する方法です。データ取得量を抑えられるため現実的ですが、ここで1つの問題が発生します。&lt;/p&gt;
&lt;p&gt;例として &lt;code&gt;pageSize=5&lt;/code&gt; のとき、Aから &lt;code&gt;[A1..A5]&lt;/code&gt;、Bから &lt;code&gt;[B1..B5]&lt;/code&gt; が返ってきたとします（いずれも日時降順）。これらをマージして上位5件を作ると、マージ結果に含まれるのがAから3件（A1,A2,A3）、Bから2件（B1,B2）になるとします。&lt;/p&gt;
&lt;p&gt;次のページでは本来、AはA4から、BはB3から取得を再開する必要があります。しかし各ソースAPIが返すカーソルは「返却リスト末尾の次」を指すため、手元のカーソルはA6（= Aを5件進めた次）やB6（= Bを5件進めた次）を指してしまいます。&lt;strong&gt;マージ結果に必要な再開位置（A4/B3）と、手元のカーソル（A6/B6）が一致しません。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（図1）各ソースから取得 (pageSize=5)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source A: [A1][A2][A3][A4][A5] -&amp;gt; cursorA = A6
Source B: [B1][B2][B3][B4][B5] -&amp;gt; cursorB = B6&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;マージして上位5件を採用すると、実際に消費したのは &lt;strong&gt;Aが3件 / Bが2件&lt;/strong&gt; になります（採用: A1 A2 A3 / B1 B2）。&lt;/p&gt;
&lt;p&gt;このとき次ページで「本当に再開したい位置」と「手元のカーソル」がズレます。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ソース&lt;/th&gt;
&lt;th&gt;次ページで本当は&lt;/th&gt;
&lt;th&gt;手元のカーソル&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;A4 から再開&lt;/td&gt;
&lt;td&gt;A6 を指す&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;B3 から再開&lt;/td&gt;
&lt;td&gt;B6 を指す&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;これが、本記事で解決する核心的な課題です。次のセクションでは、この課題に対して理想的にはどう解決すべきかを考え、そのうえで私たちが採った設計方針を説明します。&lt;/p&gt;
&lt;h2&gt;理想の解決策と現実の制約&lt;/h2&gt;
&lt;p&gt;カーソルベースAPIでは、返却件数とカーソルの進行量が常に一致します。&lt;code&gt;pageSize=5&lt;/code&gt; でリクエストすれば5件返り、カーソルも5件分進みます。しかし今回のように複数ソースのデータをマージするケースでは、5件取得しても実際に採用するのは一部だけです。この「取得件数」と「消費件数」のズレが根本原因です。&lt;/p&gt;
&lt;p&gt;仮に各ソースのAPIがカーソルではなくタイムスタンプによる範囲指定をサポートしており、かつソース内のタイムスタンプが一意であれば、この問題は発生しません。例えば、以下のように、マージ結果で最後に消費したアイテムの日時を基準に次ページを取得できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /orders?before=2025-01-01T10:00:00Z&amp;amp;limit=5
GET /transfers?before=2025-01-01T10:00:00Z&amp;amp;limit=5&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この方式であれば、各ソースの消費済み最終タイムスタンプを1つのトークンに含めるだけで、BFF側に状態を持たずに1回のリクエストでページネーションを実現できます。また &lt;code&gt;before&lt;/code&gt; で過去方向に切るため、新しいデータが追加されてもページ跨ぎの重複が起きません。&lt;/p&gt;
&lt;p&gt;しかし、各マイクロサービスのAPI仕様を変更するのは現実的ではないため、既存仕様のままBFF層で解決する方法を検討しました。&lt;/p&gt;
&lt;h2&gt;設計方針の決定&lt;/h2&gt;
&lt;p&gt;BFF層での解決策として、トークンへの情報埋め込み、サーバー側キャッシュ、データ取得とカーソル確定の分離という3つの方法を検討しました。設計のシンプルさとステートレス性を重視した結果、3つ目の「2フェーズ取得」方式を採用しました。&lt;/p&gt;
&lt;h3&gt;方法1：トークンに情報を詰め込む（拡張複合トークン）&lt;/h3&gt;
&lt;p&gt;各ソースのカーソルを1つのトークンに束ねて返す際に、&lt;strong&gt;カーソルだけでなく、次ページを再開するために必要な情報をまるごとトークン内に埋め込む&lt;/strong&gt;設計です。例えば「Aから何件/Bから何件消費したか」のようなメタ情報も含め、JSONにまとめてBase64エンコードして返します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;cursorA&amp;quot;: &amp;quot;abc123&amp;quot;,
  &amp;quot;cursorB&amp;quot;: &amp;quot;def456&amp;quot;,
  &amp;quot;consumedA&amp;quot;: 3,
  &amp;quot;consumedB&amp;quot;: 2
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この方式だと、クライアントが次のリクエストでトークンをそのまま返すことで、サーバーはトークンをデコードするだけで「次ページの再開位置（A4/B3など）」を復元できます。&lt;/p&gt;
&lt;p&gt;しかし、既存のソースAPIがカーソルベースの仕組みを提供している中で独自にオフセット等も管理すると、「カーソルの意味」が二重になり設計が複雑化するため、採用しませんでした。&lt;/p&gt;
&lt;h3&gt;方法2：Redisなどで「使わなかったデータ」を保持する（サーバー側キャッシュ）&lt;/h3&gt;
&lt;p&gt;各ソースから &lt;code&gt;pageSize&lt;/code&gt; 件ずつ取得してマージした結果、&lt;strong&gt;採用されなかった&amp;quot;余り&amp;quot;のデータ（例：A4, A5 / B3, B4, B5）をサーバー側で保持しておく&lt;/strong&gt;設計です。例えばユーザー（またはリクエスト）単位のセッションキーでRedisに格納します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;session:user123 → {
  unusedA: [A4, A5],
  unusedB: [B3, B4, B5]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;次のページのリクエストが来たら、&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;まずRedisに残っているデータを先に使ってマージし&lt;/li&gt;
&lt;li&gt;足りない分だけ各ソースAPIから追加取得する&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;という流れにすれば、カーソルのズレ問題を回避できます。&lt;/p&gt;
&lt;p&gt;しかし、サーバー側に状態を持つことになり、社内ツールの規模に対してインフラの運用コストが見合わないため、採用しませんでした。&lt;/p&gt;
&lt;h3&gt;方法3（採用）：データ取得とカーソル確定を分離する（2フェーズ取得）&lt;/h3&gt;
&lt;p&gt;上記2つの方法では、1回のAPI呼び出しでデータ取得とカーソル確定を同時に済ませようとしています。発想を変え、&lt;strong&gt;データを取得してマージするフェーズと、消費件数に基づいてカーソルを確定するフェーズを分ける&lt;/strong&gt;ことで、この問題を解決します。サーバーはステートレスのまま、既存APIの仕組みをそのまま活かせます。API呼び出し回数は増えますが、最もシンプルな設計です。&lt;/p&gt;
&lt;h3&gt;許容するトレードオフ&lt;/h3&gt;
&lt;p&gt;ただし、この方式では2回のAPI呼び出しの間に多少の時間差が生じます。そのわずかな間に対象データが追加された場合、次ページに重複したデータが現れる可能性があります。&lt;/p&gt;
&lt;p&gt;私たちはこの問題を、以下の理由から許容可能なトレードオフと判断しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;影響は「ページを跨ぐ際の重複表示」に限定される&lt;/li&gt;
&lt;li&gt;対象がリアルタイムに頻繁に更新されるデータではないため、発生頻度は低い&lt;/li&gt;
&lt;li&gt;完全な整合性を保証するには、各ソースのAPI仕様変更が必要になり、コストに見合わない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この判断のもと、以降のセクションで方法3の具体的な実装を説明します。&lt;/p&gt;
&lt;h2&gt;2フェーズ取得パターン&lt;/h2&gt;
&lt;p&gt;前のセクションで述べた方法3を、具体的にどう実装したかを説明します。データを取得してマージするフェーズと、消費件数に対応するカーソルを確定するフェーズに分けて設計しました。&lt;/p&gt;
&lt;h3&gt;フェーズ1：取得とマージ&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;ソースA、ソースBからそれぞれ &lt;code&gt;pageSize&lt;/code&gt; 件を並行して取得する&lt;/li&gt;
&lt;li&gt;日時降順でマージし、合計 &lt;code&gt;pageSize&lt;/code&gt; 件を取り出す&lt;/li&gt;
&lt;li&gt;ソースAとソースBそれぞれで、実際に消費した件数を記録する&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;この処理は、Go の &lt;code&gt;container/heap&lt;/code&gt; を使ったストリーミングマージとして実装できます。各ソースの先頭要素をヒープに入れ、日時が最も新しいものを1つずつ取り出しながら &lt;code&gt;pageSize&lt;/code&gt; 件を集めます。以下のコードのとおり、各ソースのインデックス（&lt;code&gt;indexA&lt;/code&gt;, &lt;code&gt;indexB&lt;/code&gt;）がそのまま消費件数を表します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func Merge(pageSize int32, itemsA, itemsB []*Item) ([]*Item, int32, int32) {
    indexA, indexB := 0, 0
    result := []*Item{}

    h := &amp;amp;timeHeap{}
    heap.Init(h)
    if len(itemsA) &amp;gt; 0 {
        heap.Push(h, &amp;amp;record{source: SourceA, time: itemsA[0].Timestamp})
    }
    if len(itemsB) &amp;gt; 0 {
        heap.Push(h, &amp;amp;record{source: SourceB, time: itemsB[0].Timestamp})
    }

    for h.Len() &amp;gt; 0 &amp;amp;&amp;amp; len(result) &amp;lt; int(pageSize) {
        r := heap.Pop(h).(*record)
        switch r.source {
        case SourceA:
            result = append(result, itemsA[indexA])
            indexA++
            if indexA &amp;lt; len(itemsA) {
                heap.Push(h, &amp;amp;record{source: SourceA, time: itemsA[indexA].Timestamp})
            }
        case SourceB:
            result = append(result, itemsB[indexB])
            indexB++
            if indexB &amp;lt; len(itemsB) {
                heap.Push(h, &amp;amp;record{source: SourceB, time: itemsB[indexB].Timestamp})
            }
        }
    }

    return result, int32(indexA), int32(indexB)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;戻り値の &lt;code&gt;indexA&lt;/code&gt; と &lt;code&gt;indexB&lt;/code&gt; が、フェーズ2でカーソルを正確に進めるための入力になります。&lt;/p&gt;
&lt;h3&gt;フェーズ2：カーソルの確定&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;ソースA、ソースBそれぞれにおいて、フェーズ1と同じ開始位置から消費件数分だけ再取得し、進んだ位置のページネーショントークンを取得する（&lt;code&gt;cursorA&lt;/code&gt;, &lt;code&gt;cursorB&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pageToken&lt;/code&gt; を &lt;code&gt;cursorA:cursorB&lt;/code&gt;（参照：次のセクション）とすることで、次ページの取得時に正しい位置からデータを取得できる&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;なお、一方のソースのデータがもう一方より古い場合など、フェーズ1でデータが返ってきたにもかかわらずマージで1件も採用されないケースがあります。この場合は、そのソースのカーソルを前回の位置のまま保持し、次ページのリクエストで再び同じデータを取得してマージの対象にします。また、フェーズ1でデータが0件だった場合は、そのソースを枯渇と判定し、ターミナルトークン &lt;code&gt;_&lt;/code&gt; を設定します。&lt;/p&gt;
&lt;p&gt;（図2）フェーズ1：取得とマージ（消費件数を記録）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source A ──(pageSize件)──┐
                         ├→ Merge → Top N
Source B ──(pageSize件)──┘
                    │
              消費件数を記録
              (A=3件, B=2件)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（図3）フェーズ2：カーソルの確定（消費件数分だけ進める）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source A ──(消費3件)──→ cursorA
Source B ──(消費2件)──→ cursorB
→ 複合トークン: &amp;quot;cursorA:cursorB&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;複合ページネーショントークン設計&lt;/h2&gt;
&lt;p&gt;2フェーズ取得パターンにより、各ソースで消費件数分だけ進んだカーソルを取得できるようになりました。次に、これらのカーソルをクライアントにどのように渡すかを設計します。今回の一覧取得APIでは、&lt;code&gt;pageToken&lt;/code&gt; を各ソースのカーソルを結合した複合トークンとして設計します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;&amp;quot;cursorA:cursorB&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;片方のソースが完全に尽きた場合は、ターミナルトークン &lt;code&gt;_&lt;/code&gt; で表現します。トークンがターミナルトークン &lt;code&gt;_&lt;/code&gt; だった場合、API呼び出しをスキップできます。これにより、初回リクエストから片方のソースが枯渇した状態まで、以下のようにページネーショントークンで表現することができます。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;トークン&lt;/th&gt;
&lt;th&gt;意味&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; (空文字)&lt;/td&gt;
&lt;td&gt;初回リクエスト&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;quot;cursorA:cursorB&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;両ソースとも継続あり&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;quot;_:cursorB&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ソースAは枯渇、Bのみ継続&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;quot;cursorA:_&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ソースBは枯渇、Aのみ継続&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;quot;_:_&amp;quot;&lt;/code&gt; → &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; に変換&lt;/td&gt;
&lt;td&gt;全データ取得済み（次ページなし）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;この複合トークンと2フェーズ取得パターンを組み合わせることで、サーバー側に状態を持たずに、マージした一覧のページネーションを実現できます。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;本記事では、カーソルベースAPIを持つ複数データソースから一覧を構築する際に直面した「マージで実際に消費した件数」と「APIが返すカーソル位置」のズレという課題と、その解決策を紹介しました。&lt;/p&gt;
&lt;p&gt;最初は1回のAPI呼び出しで全てを済ませようとしていましたが行き詰まり、「データを取得するフェーズ」と「カーソルを確定するフェーズ」に分離することで解決できました。1つの処理が複数の責務を担って複雑になったとき、フェーズを分けて各ステップの役割を単純化するアプローチは、ページネーションに限らず設計全般で有効な考え方だと感じています。&lt;/p&gt;
&lt;p&gt;このような設計上のトレードオフを実際に手を動かしながら考えられたのは、インターン期間中の貴重な経験でした。FEに限らず幅広く関わらせていただいたことに感謝しています。本当にありがとうございました！&lt;/p&gt;
&lt;p&gt;次の記事は＠mikupoさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>事業者請求払いのための与信管理マイクロサービスの設計</title><link>https://engineering.mercari.com/blog/entry/20260608-partner-credit-service/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260608-partner-credit-service/</guid><description>&lt;p&gt;こんにちは。メルペイ Payment &amp;amp; Customer Platform (PCP) チームでBackend Engineerをしている@imamuです。この記事は「Merpay＆Mercoin Tech O [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 09 Jun 2026 10:00:10 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイ Payment &amp;amp; Customer Platform (PCP) チームでBackend Engineerをしている@imamuです。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot;&gt;Merpay＆Mercoin Tech Openness Month 2026&lt;/a&gt;」の6日目の記事になります。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;「事業者請求払い」とは、事業者（加盟店やビジネスパートナー）向けの後払い決済の仕組みです。取引が発生した時点ではプラットフォーム側が代金を立替え、後からまとめて請求・回収します。事業者にとっては都度の支払いが不要になり、取引のたびにキャッシュフローを気にせず利用できるという利点があります。&lt;/p&gt;
&lt;p&gt;立替えて後から請求する以上、プラットフォーム側には一つの重要な責務が生まれます。&lt;strong&gt;事業者ごとに「いくらまで立替えてよいか」= 与信上限を安全に管理すること&lt;/strong&gt;です。これが破綻すると、回収できない債権が積み上がり事業全体のリスクになります。&lt;/p&gt;
&lt;p&gt;本記事では、事業者請求払いを実現するために与信ドメインをCredit Serviceとして切り出した際の4つの設計ポイントを紹介します。&lt;/p&gt;
&lt;h2&gt;背景：従来の請求払いの課題&lt;/h2&gt;
&lt;p&gt;事業者請求払いは、取引時に代金を立替え、後からまとめて請求・回収する仕組みです。概念的には、次のようなライフサイクルになります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/73fe9ff8--2026-06-08-17.35.22-1024x280.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;従来、この請求払いを実現する手段は主に3つありました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 外部のあと払いサービス&lt;/strong&gt;&lt;br /&gt;
いくつかのプロダクトは&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241221-invoice-payment/&quot;&gt;外部のあと払いサービスを使って与信管理・請求を実現していました&lt;/a&gt;が、プロダクト特性に応じたリスク管理やパートナーに応じた柔軟な請求、入金イベントをトリガーにした精算や会計連携のシステム化が難しい側面があり、システム外での運用が避けられませんでした。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 外部の請求代行サービス&lt;/strong&gt;&lt;br /&gt;
請求書の発行・送付といった請求業務のみを外部サービスに委ねる方法も取られていました。しかし、与信枠を自社で管理していないため上限を超過しても取引自体は通ってしまうリスクがありました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. クレジットカードによる代理決済&lt;/strong&gt;&lt;br /&gt;
一般的な法人カードで利用上限の範囲内で建替え、請求する運用も使われていましたが、取引前に「いまいくらまで使えるか」を自社システムで把握できず、取引が成立した後の決済段階で初めて残高不足エラーが顕在化し、お客さまの体験にも悪影響が出ていました。&lt;/p&gt;
&lt;h2&gt;目的：なぜ内製の与信管理が必要だったか&lt;/h2&gt;
&lt;p&gt;前節の3つの手段はいずれも、請求払いそのものは実現できていました。しかし、プロダクトごとに運用や要件が異なる状態では、&lt;strong&gt;事業者ごとの与信をプロダクト横断で一元管理できない&lt;/strong&gt;という共通の課題が残ります。事業者請求払いは複数のプロダクトでの利用が見込まれるため、プラットフォームとしては次の不変条件を常に満たす必要があります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;プロダクト横断で保有する債権の合計 &amp;lt; 事業者の与信上限&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例えば事業者の与信上限が &lt;code&gt;100&lt;/code&gt; のとき、プロダクトAの利用 &lt;code&gt;60&lt;/code&gt; とプロダクトBの利用 &lt;code&gt;60&lt;/code&gt; が同時に成立して合計 &lt;code&gt;120&lt;/code&gt; になる、といった状態を許してはなりません。プロダクトごとに与信判断が分散していると、この不変条件は容易に破られてしまいます。&lt;/p&gt;
&lt;p&gt;そこで私たちは与信管理をプラットフォーム側で提供し、次の3点を満たすことを目的にしました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;プロダクト横断で与信・債権を一元管理すること&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;不変条件「債権合計 &amp;lt; 与信上限」を常に満たせる&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;取引前に安全に通すこと&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;いま使える額を判断し、必要に応じて与信枠を確保したうえで取引を開始できる&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;請求・入金・精算プロセスまでシームレスにつなぐこと&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;取引の状態変化に追従し、後続の請求・入金・精算へ自然につなげる&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;設計：4つの設計ポイント&lt;/h2&gt;
&lt;p&gt;設計は次のようなサービス連携のユースケースを想定して組み立てました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/86be7d72-architecture4points-1024x309.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;まずPayment Serviceが各プロダクトから決済リクエストを受け付けます。これはPayment Serviceが決済手段の提供と決済リソースの状態管理を責務としているためです。そして、Credit Serviceに与信枠の利用を依頼し、Credit Serviceが債権管理を担うDebt Serviceに債権を登録します。この債権を集約してInvoice Serviceが請求書を発行します。この連携を以下の4つの観点で設計しました。&lt;/p&gt;
&lt;h3&gt;1. 与信管理の責務を1サービスに凝集する&lt;/h3&gt;
&lt;p&gt;まず決めたのは「与信ドメインをどのサービスが持つか」です。&lt;/p&gt;
&lt;p&gt;すでに存在するPayment Serviceは決済手段の提供と決済リソースの状態管理を責務としています。ここに与信枠の管理や利用可能額の計算といった与信ドメインの知識を持ち込むと、Payment Serviceが本来のスコープを超えて肥大化してしまいます。そこで、与信に関わる以下の責務を全てCredit Serviceに凝集しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;与信上限の決定&lt;/strong&gt;（事業者審査の結果として上限を確定する）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;与信枠の作成・更新&lt;/strong&gt;（プロダクトごとに任意に切れる枠を管理する）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;利用可能額の計算&lt;/strong&gt;（いまいくらまで使えるかを算出する）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;与信枠の利用&lt;/strong&gt;（与信を消費し、債権の登録を依頼する）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;審査（枠を決める）と利用（枠を使う）をあえて分離せず一体で持つことにしました。こうすることで、Payment Serviceは「与信枠を利用する」という一つの操作を呼ぶだけでよくなり、利用可能額のチェック・与信の消費・債権登録という一連の処理がサービス内に閉じます。また、横断的な与信上限（目的の不変条件）をアトミックに守るには、与信の利用を1サービスに集約する以外に方法がありません。このように、責務の凝集は明確なドメイン境界と不変条件から導いた設計判断です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/03b77715-cohesion.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2. 与信と請求を分離する&lt;/h3&gt;
&lt;p&gt;次に&lt;strong&gt;与信の単位と請求の単位は同じではない&lt;/strong&gt;という前提を設計に組み込みました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;与信は「どの枠でいくら使えるか」を、事業者が任意に切った枠（CreditLine）の単位で管理したい&lt;/li&gt;
&lt;li&gt;請求は「どの宛先にまとめて請求書を出すか」を、請求先（InvoiceAccount）の単位で管理したい&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この2つを密結合にしてしまうと、「請求の単位を変えたいだけ」で与信側まで作り直すことになります。両者は本来別々の関心事なので、疎結合に保つ必要があります。&lt;/p&gt;
&lt;p&gt;実現方法は次のとおりです。Credit Serviceは与信を消費して債権を登録するとき、その債権に&lt;strong&gt;「どの請求先に属するか」「どの与信枠に属するか」という集計のための情報&lt;/strong&gt;を持たせてDebt Serviceに渡します。Debt Serviceは、債権を&lt;strong&gt;複数の軸で集計・一覧できる仕組み&lt;/strong&gt;を備えており、以下のようにそれぞれのサービスが債権の情報を参照することができます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Credit Serviceは「与信枠の軸」で未返済の債権から利用額を計算する&lt;/li&gt;
&lt;li&gt;Invoice Serviceは「請求先の軸」で指定期間の債権から請求書を発行する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/aa6ef81b-creditinvoiceseparation-1024x391.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これにより与信は事業者全体で管理しつつ、請求は支店ごとに行うようなユースケースに対応できます。ポイントは&lt;strong&gt;債権の集計軸をDebt Serviceが中心的な概念として扱う&lt;/strong&gt;ことにあります。Credit Serviceは与信枠の軸だけを、Invoice Serviceは請求先の軸だけを関心に持ち、軸ごとの集計はDebt Serviceに委ねます。結果として、&lt;strong&gt;与信の粒度（CreditLine）と請求の粒度（InvoiceAccount）を独立して設計&lt;sup&gt;&lt;a href=&quot;#fn1&quot; id=&quot;ref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;できます&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;3. 事業者ごとに複数の与信枠を持てる&lt;/h3&gt;
&lt;p&gt;与信枠は事業者単位で1つではなく、&lt;strong&gt;1事業者が複数の与信枠（CreditLine）を持てる&lt;/strong&gt;構造にしました。店舗ごと・部門ごとといった、事業者で運用したい任意の単位で枠を切れます。これによってプロダクト毎に柔軟に利用制限を行うことができます。&lt;/p&gt;
&lt;p&gt;ただし、複数の枠を切っても事業者全体の与信上限（CreditLimit）は超えられません。そこで最終的な利用可能額は、与信枠の利用可能額と事業者全体の利用可能額の小さい方で決まります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最終的な利用可能額 = min(CreditLine の利用可能額, 事業者 CreditLimit の利用可能額)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ここで、似て非なる2つの「上限」を区別しておきます。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;CreditLimit&lt;/th&gt;
&lt;th&gt;CreditLine&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;意味&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;絶対に超えて立て替えない上限&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;クライアントが切れる&lt;strong&gt;実用枠&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;決め方&lt;/td&gt;
&lt;td&gt;与信審査の結果&lt;/td&gt;
&lt;td&gt;クライアントが任意設定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;クライアントの変更&lt;/td&gt;
&lt;td&gt;不可&lt;/td&gt;
&lt;td&gt;更新可能&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;「審査で決まる硬い上限」と「運用で柔軟に切れる枠」を分けることで、安全性と柔軟性を両立しています。&lt;/p&gt;
&lt;h3&gt;4. 与信の利用はライフサイクルに沿って状態を持つ&lt;/h3&gt;
&lt;p&gt;与信枠の利用は、一度の操作で完結するものではありません。実際の取引は、与信を押さえてから確定するまでに時間差があり、途中で取り消されることもあります。そこで与信の利用を、複数のフェーズを持つ&lt;strong&gt;与信トランザクション（CreditTransaction）&lt;/strong&gt;として管理します。&lt;/p&gt;
&lt;p&gt;取引開始時点で与信枠を押さえる&lt;strong&gt;確保（Authorize）&lt;/strong&gt;、取引確定時点で債権として確定する&lt;strong&gt;確定（Capture）&lt;/strong&gt;、確定前に押さえた与信を解放する&lt;strong&gt;取消（Cancel）&lt;/strong&gt;という3つのフェーズを扱います。これにより、取引の途中で状態が変化しても、与信の消費と解放を一貫したモデルで管理できます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/d91225dc-statustransition.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;確保のタイミングを選択可能に（ReservationPolicy）&lt;/strong&gt;&lt;br /&gt;
与信をいつ・どう押さえたいかはプロダクトによって異なります。そこで確保の仕方を ReservationPolicy として選べるようにし、同じ Credit Service を要件に合わせて使い分けられるようにしました。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;確保の仕方&lt;/th&gt;
&lt;th&gt;動作&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;仮確保&lt;/strong&gt;（PROVISIONAL）&lt;/td&gt;
&lt;td&gt;依頼時に仮の債権で与信を押さえ、確定で正式な債権にする／取消で解放する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;確定時に確保&lt;/strong&gt;（CHECK_THEN_CAPTURE）&lt;/td&gt;
&lt;td&gt;依頼時は利用可能額の確認だけ行い、確定時にまとめて確保する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;即時確保&lt;/strong&gt;（IMMEDIATE）&lt;/td&gt;
&lt;td&gt;依頼した時点で確保と確定を同時に行う&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;確定後の取消にも対応する（Reversal）&lt;/strong&gt;&lt;br /&gt;
運用上は、与信を確保して取引が確定した&lt;strong&gt;後&lt;/strong&gt;に取消が発生することがあります。確定後は与信を解放するだけでは済まず、すでに登録された債権をどう扱うかが問題になるため、確定後の取消を Reversal としてサポートしました。&lt;/p&gt;
&lt;p&gt;Reversal では、取消したい金額をその時点の債権残額に応じて未返済分と返済済み分に切り分けます。未返済分は債権そのものを取り消し、返済済み分は債権を取り消せないため、返金すべき金額としてレスポンスで返して呼び出し元（Payment Service）が返金できるようにします。これにより、確保→確定→取消・返金という取引のライフサイクル全体に対して一貫した扱いができます。&lt;/p&gt;
&lt;h2&gt;全体像：プラットフォーム全体で一気通貫に&lt;/h2&gt;
&lt;p&gt;ここまで見てきたCredit Serviceは、単独で成り立つものではありません。プラットフォームには、Payment（決済）・Debt（債権）・Invoice（請求）・Bank（入金）・Balance（残高）・Settlement（精算）といった、それぞれ明確な責務を持つサービスがすでに存在していました。そこにCredit Serviceが新たに加わることで、各サービスが自分の責務に集中したまま連携し、&lt;strong&gt;決済・与信管理・請求・精算がプラットフォーム全体で一気通貫に実現できる&lt;/strong&gt;ようになりました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/8967b118-platformoverview-1024x812.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この構成によって、新しいプロダクトがシームレスに事業者請求払いに対応することができます。あるプロダクトで事業者請求払いを利用したくなったら、必要なのは &lt;strong&gt;与信枠（CreditLine）と請求先（InvoiceAccount）を新たに作るだけ&lt;/strong&gt;です。プラットフォームの各サービスに手を入れることなく、そのプロダクトの取引が、与信照会から決済・請求・返済までのライフサイクルに自然に乗ります。&lt;/p&gt;
&lt;h2&gt;終わりに&lt;/h2&gt;
&lt;p&gt;与信管理マイクロサービスができたことによってPayment Platformとして事業者請求払いを実現できるようになりました。&lt;/p&gt;
&lt;p&gt;Payment Platformは今後も進化していきますが、「各サービスが明確な責務を持ち、疎結合に連携する」という設計方針そのものは変わりません。この方針のもとに安全かつ拡張可能な決済基盤を今後も実現していきたいと考えています。&lt;/p&gt;
&lt;p&gt;次の記事は nanacomさんとmattsuuさんです。引き続きお楽しみください。&lt;/p&gt;
&lt;p id=&quot;fn1&quot;&gt;&lt;a href=&quot;#ref1&quot;&gt;[1]&lt;/a&gt;この「債権を任意の軸で集計する仕組み」自体も独立した設計テーマであり、別記事で詳しく紹介される予定です。&lt;/p&gt;
</content:encoded></item><item><title>会計システムにおける訂正機能の設計と実装</title><link>https://engineering.mercari.com/blog/entry/20260605-188b7dae06/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260605-188b7dae06/</guid><description>&lt;p&gt;はじめに こんにちは。メルペイのAccountingチームでBackend Engineerをしている@hokaoです。 この記事は、Merpay &amp;amp; Mercoin Tech Openness Month 20 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 08 Jun 2026 10:00:33 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。メルペイのAccountingチームでBackend Engineerをしている&lt;a href=&quot;https://x.com/khokawo&quot;&gt;@hokao&lt;/a&gt;です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt; の 5 日目の記事です。&lt;/p&gt;
&lt;p&gt;会計データに誤りがあった場合、元のデータを残したまま打ち消すための記録を別途追加するのが会計上の一般的な手法です。本稿では、これをシステムとしてどう扱ったかを設計と実装の観点から紹介します。&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;Accountingチームでは、会計データを扱うシステムを開発しています。メルカリグループ全体で発生するお金の移動を伴う取引を記録・集計するシステムで、会計イベントの保存と経理向けのレポーティングを責務としています。&lt;/p&gt;
&lt;p&gt;会計データは、取引の事実を証明する証拠としての役割を持ちます。そのため、一度記録したデータを後から改変・削除することは原則として許されません。誤りがあったとしても元データを残したまま、打ち消すための記録を別途追加することで修正します。&lt;/p&gt;
&lt;p&gt;しかし、私たちのシステムにはこの打ち消すための機能が存在していませんでした。誤って登録された会計データが見つかるたびに、その件数・金額などの情報を手作業で特定し、経理に連携して対応してもらう必要がありました。この運用には、対応コストの大きさや作業ミスのリスクといった構造的な課題がありました。&lt;/p&gt;
&lt;p&gt;以降では、会計ドメインの前提を整理した上で、この課題を解決するために導入した打ち消し機能の設計と実装を順に説明します。&lt;/p&gt;
&lt;h2&gt;会計ドメインの前提&lt;/h2&gt;
&lt;p&gt;会計では、すべての取引を借方と貸方の 2 つに分けて記録する複式簿記という方式が使われています。借方と貸方それぞれに勘定科目・日付・金額を記載したものが「仕訳」で、これが会計データの最小単位になります。&lt;/p&gt;
&lt;p&gt;仕訳に誤りがあった場合、元の仕訳の借方と貸方を入れ替えた「逆仕訳」を計上して打ち消します。&lt;/p&gt;
&lt;p&gt;簡単な例として、ある仕入取引を次のように記録していたとします。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;借方: 仕入 100 円&lt;/li&gt;
&lt;li&gt;貸方: 現金 100 円&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この記録が誤りだった場合、逆仕訳は次のようになります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;借方: 現金 100 円&lt;/li&gt;
&lt;li&gt;貸方: 仕入 100 円&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;元の仕訳と逆仕訳を合算すると、勘定科目ごとに借方と貸方が打ち消し合い、金額がゼロになります。元データは残したまま、後から追加した記録によって取引を実質的に打ち消す形になります。&lt;/p&gt;
&lt;p&gt;ここで説明したのは、逆仕訳がなぜ打ち消しとして成立するのかという会計上の考え方です。実際の会計レポートでは、必ずしも借方と貸方を足し合わせて相殺しているわけではなく、レポートによっては逆仕訳の金額を符号反転させて打ち消しを表現しています。詳しくは後述します。&lt;/p&gt;
&lt;h2&gt;逆仕訳の設計と実装&lt;/h2&gt;
&lt;h3&gt;スキーマでの逆仕訳の表現&lt;/h3&gt;
&lt;p&gt;私たちの会計システムでは、上流のマイクロサービスから受け取った取引はまず会計イベントとして記録され、そこから仕訳が作成される構成になっています。それぞれ &lt;code&gt;AccountingEvents&lt;/code&gt; と &lt;code&gt;JournalEntries&lt;/code&gt; というテーブルで管理されています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/d096e5cc-chatgpt-image-jun-4-2026-11_18_38-am.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;会計イベント (&lt;code&gt;AccountingEvents&lt;/code&gt;) には、上流のマイクロサービスから会計の入力として受け取った取引が記録され、会計処理の種類や取引の中身を保持します。これに対して仕訳 (&lt;code&gt;JournalEntries&lt;/code&gt;) は、会計イベントに仕訳ルールを適用して必要な属性が確定した、正式な会計記録です。1 つの会計イベントから、仕訳ルールの適用によって複数の仕訳が作成されることもあります。&lt;/p&gt;
&lt;p&gt;逆仕訳の会計イベントには、必ず打ち消し対象となる元の会計イベントが存在します。そのため、&lt;code&gt;AccountingEvents&lt;/code&gt; に元の会計イベントへの参照 (&lt;code&gt;OriginalTransactionId&lt;/code&gt;) を持たせて関係性を表現します。この参照を持つイベントから作成される仕訳はすべて逆仕訳になります。&lt;/p&gt;
&lt;p&gt;また、登録時にはこの参照を辿って、次のようなバリデーションを行います。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;元の会計イベントが存在するか&lt;/li&gt;
&lt;li&gt;元の会計イベントと整合する勘定科目か&lt;/li&gt;
&lt;li&gt;元の会計イベントが既に他の逆仕訳によって打ち消されていないか&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;会計レポートには &lt;code&gt;JournalEntries&lt;/code&gt; から集計するものと、分析用に &lt;code&gt;AccountingEvents&lt;/code&gt; から集計するものがあります。どちらの場合でも JOIN なしで逆仕訳の判定ができるよう、&lt;code&gt;JournalEntries&lt;/code&gt; には、その仕訳が逆仕訳かどうかを表すフラグ (&lt;code&gt;IsReversal&lt;/code&gt;) を持たせています。このフラグは本質的には &lt;code&gt;AccountingEvents&lt;/code&gt; の &lt;code&gt;OriginalTransactionId&lt;/code&gt; から決まる情報ですが、&lt;code&gt;JournalEntries&lt;/code&gt; でも独立に判定できるよう別途持たせています。&lt;/p&gt;
&lt;p&gt;同じ会計イベントに対する逆仕訳の会計イベントが複数存在してはいけないため、上述のアプリケーション側のバリデーションに加えて、DB 側にも一意性制約を設けています。具体的には、&lt;code&gt;OriginalTransactionId&lt;/code&gt; カラムに &lt;code&gt;NULL_FILTERED&lt;/code&gt; オプションを指定した UNIQUE INDEX を張っています。これにより、&lt;code&gt;OriginalTransactionId&lt;/code&gt; が NULL のイベント (通常の会計イベント) は重複扱いされず、NULL でない値だけに一意性制約がかかります。&lt;/p&gt;
&lt;p&gt;これらをまとめると、スキーマの該当箇所は次のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE AccountingEvents (
  EventId STRING(100) NOT NULL,
  AccountingCode STRING(MAX) NOT NULL,
  OriginalTransactionId STRING(100),
  -- ...
) PRIMARY KEY (EventId);

CREATE NULL_FILTERED INDEX AccountingEventsByOriginalTransactionId
  ON AccountingEvents(OriginalTransactionId);

CREATE TABLE JournalEntries (
  Id STRING(100) NOT NULL,
  EventId STRING(100) NOT NULL,
  IsReversal BOOL NOT NULL,
  -- ...
) PRIMARY KEY (Id);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;仕訳作成での借方/貸方の入れ替え&lt;/h3&gt;
&lt;p&gt;逆仕訳の仕訳作成は、元の取引と同じ仕訳ルールを再利用しつつ、ルールから取り出した借方と貸方の属性をコード側で入れ替えて行います。&lt;/p&gt;
&lt;p&gt;会計イベントには取引の方向を表す ItemKey という識別子が含まれ、&lt;code&gt;X.to.Y&lt;/code&gt; の形式を取ります。仕訳ルールもこの ItemKey をキーに登録されています。逆仕訳イベントでは ItemKey が反転して &lt;code&gt;Y.to.X&lt;/code&gt; の形で届くため、ルールを検索する際は ItemKey を一度元の向きに戻します。&lt;/p&gt;
&lt;p&gt;ルールを取得した後、逆仕訳イベントの場合に限り、ルールから取り出した借方と貸方の各属性をコード側で入れ替えます。簡略化した擬似コードで示すと次のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;originalRule := getOriginalEventRule(ev)

debitAccountingTitleCode  := originalRule.DebitAccountingTitleCode
creditAccountingTitleCode := originalRule.CreditAccountingTitleCode
debitXxx                  := originalRule.DebitXxx
creditXxx                 := originalRule.CreditXxx
// ...

if isReversal {
    debitAccountingTitleCode, creditAccountingTitleCode = creditAccountingTitleCode, debitAccountingTitleCode
    debitXxx, creditXxx                                 = creditXxx, debitXxx
    // ...
}

debit := JournalEntry{
    AccountingTitleCode: debitAccountingTitleCode,
    Xxx:                 debitXxx,
    // ...
    IsReversal:          isReversal,
}
credit := JournalEntry{
    AccountingTitleCode: creditAccountingTitleCode,
    Xxx:                 creditXxx,
    // ...
    IsReversal:          isReversal,
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仕訳ルールが借方と貸方の対称な構造を持っているため、ItemKey の反転とそれに続く借方と貸方の入れ替えという 2 つの操作だけで逆仕訳を作成でき、実装はシンプルな修正で済みました。&lt;/p&gt;
&lt;h3&gt;会計レポートでの逆仕訳の打ち消し&lt;/h3&gt;
&lt;p&gt;会計レポートに打ち消しを反映するには、集計クエリで逆仕訳または逆仕訳の会計イベントを判定し、正しく金額を計算する必要があります。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;JournalEntries&lt;/code&gt; から集計するクエリでは &lt;code&gt;IsReversal&lt;/code&gt; フラグで判定し、分析用に &lt;code&gt;AccountingEvents&lt;/code&gt; から集計するクエリでは &lt;code&gt;OriginalTransactionId IS NOT NULL&lt;/code&gt; で判定します。&lt;/p&gt;
&lt;p&gt;会計ドメインの前提で述べたとおり、本来の逆仕訳は、勘定科目ごとに借方と貸方を足し合わせて相殺することで打ち消しを実現します。ただし、会計レポートには借方または貸方のどちらか一方だけを集計するものもあり、そうしたレポートでは足し合わせによる相殺は成立しません。そのため、集計クエリで逆仕訳または逆仕訳の会計イベントの金額を符号反転して合算するアプローチをとっています。なお、逆仕訳の金額自体は元の仕訳と同じ正の値で保存しています。&lt;/p&gt;
&lt;p&gt;会計レポートは、対象とするテーブルや集計の意味合いがそれぞれ異なるため、共通化が難しく、もともと個別に実装されています。逆仕訳の打ち消しを反映するためには、その個別実装それぞれに修正を入れる必要がありました。例えば &lt;code&gt;WHERE&lt;/code&gt; 句の絞り込み条件を、逆仕訳の会計イベントも拾えるように拡張するなどです。すべてのレポートに対してこのような修正を加えていったため、実装と検証には時間がかかりました。&lt;/p&gt;
&lt;h3&gt;逆仕訳バッチによる運用の自動化&lt;/h3&gt;
&lt;p&gt;加えて、誤って登録された会計データを訂正するために、逆仕訳を作成するためのバッチを新たに実装しました。このバッチは、対象となる取引の ID リストを受け取り、それぞれについて、上流のマイクロサービスの API を介して打ち消し用の取引を作成します。それが会計イベントとして本システムに登録され、逆仕訳が自動的に作成されます。&lt;/p&gt;
&lt;p&gt;個別の取引で失敗が発生した場合はログに記録しつつ、残りの処理は継続します。また、冪等性は上流のマイクロサービス側で保証されているため、同じ ID リストで再実行することも可能です。&lt;/p&gt;
&lt;p&gt;これにより、誤ったデータの打ち消しをシステム上で自動的に逆仕訳として反映できるようになり、これまで手作業で行っていた特定・連携の負荷と作業ミスのリスクが大きく軽減されました。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;本稿では、会計データの訂正を支える逆仕訳機能の設計と実装を紹介しました。&lt;/p&gt;
&lt;p&gt;会計のように不変性の制約が強いドメインで開発していると、ドメインの原則が設計判断をそのまま導いてくれる場面が多く、その面白さを今回の機能開発でも改めて感じました。&lt;/p&gt;
&lt;p&gt;今回の機能開発は、Accountingチームが抱える運用負荷削減という大きな取り組みの一環でもあります。会計システムは、会社が財務状況を正しく把握するための基盤であり、事業の成長に合わせてスケールできる状態に保ち続ける必要があります。これからも、持続的な開発ができるよう取り組んでいきたいと考えています。&lt;/p&gt;
&lt;p&gt;次の記事は imamu さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>決済プラットフォームと経理を繋ぐ MoneyFlow</title><link>https://engineering.mercari.com/blog/entry/20260605-moneyflow-bridging-payment-and-accounting/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260605-moneyflow-bridging-payment-and-accounting/</guid><description>&lt;p&gt;こんにちは、Merpay の Payment Core チームと Payment Solution チームで Engineering Manager (EM) をやっている komatsu です。普段は決済基盤や決済体験 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 05 Jun 2026 10:00:04 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、Merpay の Payment Core チームと Payment Solution チームで Engineering Manager (EM) をやっている komatsu です。普段は決済基盤や決済体験の開発をするチームを見ていたり、最近は PCP Foundation というチームを発足して Individual Contributor (IC) として基盤の整備や AI 周りのツールの導入も行っています。&lt;br /&gt;
この記事は &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt; の 4 日目の記事です。&lt;/p&gt;
&lt;p&gt;この記事では、私たちの Payment Platform が「決済」と「会計」のあいだに置いている共通言語 &lt;strong&gt;MoneyFlow&lt;/strong&gt; と、それを支えるために開発したツール群 &lt;strong&gt;mfgen (MoneyFlow Generator; /ˌemefˈdʒen/)&lt;/strong&gt; について紹介します。一見すると別物に見える「システムとしての決済基盤」と「上場企業として厳密さが求められる会計」ですが、実際には深く結びついています。私たちは、その複雑な関係を整理し、経理・PdM・エンジニアといった異なる立場の人々が共通の理解を持てるよう、MoneyFlow という共通言語を整備してきました。この一年ほどで MoneyFlow は徐々に組織へ浸透し、現在では同じフォーマットで記述し、同じフォーマットでレビューする開発プロセスが少しずつ定着しつつあります。本記事では、その取り組みの背景と、それを支える mfgen の仕組みについて深掘りしていきます。&lt;/p&gt;
&lt;h2&gt;決済プラットフォームと会計のつながり&lt;/h2&gt;
&lt;p&gt;メルカリグループの Payment Platform は、メルカリのマーケットプレイス (Consumer to Consumer; CtoC) だけでなく、メルカリShops やメルペイ、メルコイン、メルカリモバイル、メルカリAds、グローバルアプリなど、「お金が動くすべてのプロダクト」の土台となり、決済にまつわる汎用的な機能を提供しています。決済そのものについての記事は多く上がっているので、興味があれば &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-84e2b2cb78/&quot;&gt;メルカリのグローバル展開を支えるPayment Platformの進化&lt;/a&gt; や &lt;a href=&quot;https://engineering.mercari.com/blog/tag/payment/&quot;&gt;Payment に関するその他の記事&lt;/a&gt; もあわせてご一読ください。&lt;/p&gt;
&lt;p&gt;また、Payment Platform チームが掲げる理念の一つに「会計も含めた決済ソリューション」というものがあります。決済の一回一回は、お客さまや Payment Platform の利用者 (つまりプロダクトのマイクロサービス) から見れば「支払いが完了した」という体験です。しかし会社や経理から見れば、それは会計上の仕訳として正しく記録しなければならない事象でもあります。そこで Payment Platform は、外向きの決済 API を提供するだけでなく、その裏側で「お金がどう動き、どの勘定にどう記録されるか」までを引き受け、記帳や会計レポートの発行を通した社内向けの統合的な決済ソリューションとして存在しています。これによってプロダクトはビジネスロジックの開発に集中し、会計にまつわる大半の連携を Payment Platform に委ねることができます [^1]。&lt;/p&gt;
&lt;p&gt;たとえば、メルカリのシンプルな購入ひとつを取っても、段階ごとにお金が動き、それぞれに対応する会計上の記録が発生します。お客さまが支払う場面では、残高を使う場合は購入者の資金移動口座からエスクロー決済の預かり金へと振り替え、メルペイのクレジットを使う場合はあと払い債権を計上し、他社クレジットカードを使う場合は PSP (Payment Service Provider) に対する未収入金として処理します。そして取引が完了して売上が立つ段階では、預かり金を取り崩し、出品者の資金移動口座と手数料収入へと振り分けます。&lt;/p&gt;
&lt;p&gt;これはまだ単純な例ですが、実際には、コンビニ決済のような非同期な決済手段、キャンセルや返金、資金移動口座の開設有無、クーポン等による値引き、為替が絡む決済——こうした条件の組み合わせやタイミングの違いが絡み合い、仕訳はもっと複雑になります。特に、ひとつの API 呼び出しが複数の勘定に影響することもあれば、逆に、会計上はひとつの動きが複数の API にまたがることもある、という点も複雑性を増す要因となっています。この「ズレ」があるために、エンジニアと経理が直接話しても認識が噛み合わず、議論が長引くということが頻繁に起きていました。エンジニアが「決済 API のフロー」として見ているものを、経理は「仕訳」として見ており、同じ事象を、まったく異なる言語で眺めているからです。&lt;/p&gt;
&lt;h2&gt;MoneyFlow という共通言語&lt;/h2&gt;
&lt;p&gt;このすれ違いの根本にあるのは、Payment Platform のエンジニアと経理のあいだに横たわるドメイン的な距離です。両者は使う言葉からして異なり、エンジニアが API やエンドポイントで語るのに対し、経理は勘定科目や仕訳で語ります。前提とする知識も、システムの内部構造と会計基準というように噛み合いません。そのうえ、API と勘定科目が必ずしも 1:1 で対応しないため、片方の言葉をそのまま翻訳しても意味が通じないことがままあります。&lt;/p&gt;
&lt;p&gt;この距離を埋めるために、Payment Platform で直近約一年にわたって運用しているのが MoneyFlow です。これはシステム上のお金の動きを表現するためのフロー図であり、同時に、エンジニア・経理・Product Manager (PdM) の理解を揃えるための共通言語でもあります。MoneyFlow は主に三つの要素で構成します。一つ目の &lt;strong&gt;Actor&lt;/strong&gt; は取引の主体（誰が価値を出し、誰が受け取るのか）で、User (購入者や出品者)や Partner (加盟店さま等取引の相手方。社外事業者に限らず、会社として Mercari / Merpay / Mercoin もシステム上は Partner) が該当します。二つ目の &lt;strong&gt;Account&lt;/strong&gt; は各 Actor が持つ勘定 (ledger) で、&lt;code&gt;USER_FUNDS&lt;/code&gt; や &lt;code&gt;PARTNER_SALES&lt;/code&gt;、&lt;code&gt;USER_DEBT&lt;/code&gt;、&lt;code&gt;PARTNER_CLEARING_SALES&lt;/code&gt; などがあります。三つ目の &lt;strong&gt;Flow&lt;/strong&gt; はおおむね 1 回の API 呼び出しに相当するお金の移動を表し、Source (from) と Target (to) を持つことで、その決済でお金がどこからどこへ動くのかを示します。さらに各 Flow は、勘定科目に対応する accounting code を持ちます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/9b1ac78f-moneyflow-example.png&quot; alt=&quot;MoneyFlow example&quot; /&gt;&lt;/p&gt;
&lt;p&gt;こうした図として表現することで、エンジニアは価値交換を行う API を記述でき、経理は Flow を中心とした会計観点でのお金の動きを把握できます。図の Actors section は Actor の一覧とそれぞれが持つ Account (ledger) を表現し、MoneyFlow section は商流ごと・API ごとのお金の動きを表現します。&lt;/p&gt;
&lt;p&gt;この一年ほどをかけて、MoneyFlow は徐々に組織に浸透してきました。現在では、経理・PdM・エンジニアが同じフォーマットで書き、同じフォーマットでレビューする、といった開発の工程が少しずつスタンダードになりつつあります。著者自身もプロジェクトや開発の最初のフェーズでこれを行うことで、ステークホルダーと認識を合わせやすくなったと実感しており、Design Doc に並ぶ重要なドキュメントだと感じています。さらに、エンジニアリングと会計の両面を表現する概念を持ったことで、エンジニアは会計のドメインを、経理はエンジニアのドメインを理解しやすくなりました。エンジニアが勘定科目について経理に相談したり、経理が API 名を使って商流を表現したりと、お互いの歩み寄りがかなり加速したと感じています。&lt;/p&gt;
&lt;h2&gt;mfgen CLI — DSL による MoneyFlow の標準化&lt;/h2&gt;
&lt;p&gt;組織への浸透が進む一方で、MoneyFlow は最初からフォーマットが決まっていたわけではありません。初期は Draw.io (Diagrams.net) で手描きしていました。これはこれで描けるのですが、いくつかの課題がありました。まず、書き方が人によって異なり、解像度や粒度が書き手に依存するため、図にばらつきが出ます。次に、そもそもどう書けばよいか分からない人がほとんどで、書ける人が限られていました。書ける人がいないプロジェクトでは、会計観点の考慮漏れを見落とすこともありました。さらに、Draw.io は XML で表現できるものの human-friendly な宣言的記法が存在せず、同じフォーマットで安定して描画することや AI agent との親和性に欠けていました。&lt;/p&gt;
&lt;p&gt;そこでまず取り組んだのが、MoneyFlow を YAML の DSL として定義することでした。そして、その YAML から Draw.io (および PNG 画像) を生成する CLI ツール &lt;strong&gt;mfgen&lt;/strong&gt; を開発しました。YAML は次のようなイメージです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Example Payment Flow

actors:
  - id: user
    name: User
    type: USER
  - id: partner
    name: Partner
    type: PARTNER

accounts:
  - id: user_funds
    owner: user
    account_type: USER_FUNDS
    currency: JPY
  - id: partner_sales
    owner: partner
    account_type: PARTNER_SALES
    currency: JPY

flows:
  - id: MF1
    name: Payment
    api: Payment.CreateCharge
    currency: JPY
    accounting_code: xyz
    from:
      - id: user_funds
        amount: 1000
    to:
      - id: partner_sales
        amount: 1000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;描画のクライアントとしては依然として Draw.io を使っているため、CLI の実装の中身はかなり地味なものです。YAML をパースし、描画に必要な座標を計算し、Draw.io の XML を組み立てる——レイアウトのための座標計算や XML 生成を、ひとつずつ実装した形になります。この段階では、validation を強めに設けて一貫性を高めることと、AI agent による生成を簡単にすることを目的としました。具体的には、次のチェックを行います。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;必須フィールドを検証する&lt;/li&gt;
&lt;li&gt;ID の重複を検出する&lt;/li&gt;
&lt;li&gt;参照整合性を確認する (actors の owner、flow の from / to などが実在するか)&lt;/li&gt;
&lt;li&gt;セマンティクスを検証する (&lt;code&gt;from&lt;/code&gt; の合計と &lt;code&gt;to&lt;/code&gt; の合計が一致するか、&lt;code&gt;PARTNER_DEBT&lt;/code&gt; / &lt;code&gt;USER_DEBT&lt;/code&gt; に &lt;code&gt;debt_type&lt;/code&gt; があるか、など)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;さらに、GitHub Actions で YAML を自動変換して Draw.io 形式および画像でアップロードする CI を組みました。これによってレビューや共有、過去の MoneyFlow の管理が簡単になり、MoneyFlow が蓄積されることで次の MoneyFlow 作成時の AI による精度も向上していきました。&lt;/p&gt;
&lt;h2&gt;mfgen Web — リアルタイム共同編集の実現&lt;/h2&gt;
&lt;p&gt;DSL 化によって標準化は大きく進みましたが、mfgen CLI にも残された課題がありました。変換が YAML → Draw.io の片方向であるため、図を見ながら直接編集して保存する、という使い方ができません。ミーティング中に Draw.io を直接編集したりコメントを付けたりすると、それを YAML に取り込むコストが発生します。そして、Draw.io の表現力そのものが上限になる場面もありました。&lt;/p&gt;
&lt;p&gt;そこで開発したのが mfgen Web です。これは社内にホストしている Web ツールで、「Draw.io のような同時編集」と「YAML による宣言的な定義」の両方を実現します。具体的には、三つの編集方法を備えています。Canvas 上で直接描く方法では、Draw.io のようにノードを配置してつなぎます。MoneyFlow に特化した編集機能では、Actor / Account / Flow といったドメイン概念をそのまま編集できます。そして YAML を直接記述する方法では、エディタで宣言的に書けます。&lt;/p&gt;
&lt;p&gt;これらはすべて双方向に同期します。YAML を更新すれば図に即座に反映され、図をドラッグすれば YAML にも反映されます。さらに、複数人が同じ MoneyFlow をリアルタイムで共同編集でき、メンバーが作成した MoneyFlow はカタログとして蓄積されていくため、チームのドキュメンテーションとしても活用できる形になりました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/9e1bb5d2-mfgen-web.png&quot; alt=&quot;mfgen Web&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この手の内製ツールは Vibe Coding でおおよそ作れてしまう時代ですが、技術的なポイントを簡単に紹介します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/8ccc3027-mfgen-web-flow.png&quot; alt=&quot;mfgen Web flow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;サーバは役割の異なる 2 つに分かれています。&lt;strong&gt;api サーバ&lt;/strong&gt; (Hono) が SPA の配信と REST API を担い、&lt;strong&gt;collab サーバ&lt;/strong&gt; (Hocuspocus) が WebSocket での同時編集を担います。設計上のポイントは次のとおりです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Single Source of Truth として Y.Doc (Conflict-free Replicated Data Type; CRDT) を採用する。サーバが権威ある Y.Doc を保持して各クライアントと同期し、YAML はサーバ側で Y.Doc から投影して生成する派生物とすることで、クライアントが直接書き込むことはありません。これにより、YAML を入力とする CLI 版 mfgen とのデータ互換性を保っています。&lt;/li&gt;
&lt;li&gt;「追記ログ + スナップショット」方式で永続化する。編集は append-only な update ログに追記し、2-10 秒程度の debounce で Y.Doc 全体のスナップショットと生成した YAML を Postgres に書き込み、古い update を圧縮 (compaction) します。&lt;/li&gt;
&lt;li&gt;Canvas 上の座標を YAML から除外する。位置情報を YAML から外すことで、既存 CLI と等価なデータに保っています。&lt;/li&gt;
&lt;li&gt;Origin タグ (5 種) で編集の出どころを区別する。「UI 操作」「YAML 編集」「ドラッグ操作」「他クライアントからの更新」「初期同期」をタグで見分け、双方向同期で起こりがちな無限ループを防ぎつつ、Undo の対象も制御しています。&lt;/li&gt;
&lt;li&gt;Redis (Memorystore) の pub/sub で collab サーバ間のメッセージを fan-out する (&lt;code&gt;@hocuspocus/extension-redis&lt;/code&gt;)。複数レプリカ運用に備えた仕組みで、単一レプリカであれば Redis なしでも動作します。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;技術スタックは TypeScript / Bun に統一しています。フロントエンドは React + Vite + @xyflow/react + CodeMirror + Yjs、サーバは Hono + Hocuspocus + Drizzle ORM、データストアは Postgres と Memorystore Redis という構成です。これらを専用の Google Cloud プロジェクト上で、api / collab それぞれ独立したサービスとして運用しています。&lt;/p&gt;
&lt;p&gt;mfgen Web はまだ開発したばかりで活用事例はありませんが、Web 版になったことで、MoneyFlow はより実践的なツールになると感じています。今後さらに開発を続け、エディタ内に Claude Code SDK などで AI を搭載したり、経理によるレビュー後にシステムへ登録するところまで E2E で自動化できるようになれば、会計がプロダクト開発のブロッカーにならず、基盤として素早いサポートができるようになると信じています。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;この記事では、Payment Platform が「会計も含めた決済ソリューション」であること、そしてエンジニアと経理のあいだを繋ぐ共通言語 MoneyFlow と、それを実践的に運用するための mfgen について紹介しました。MoneyFlow を共通の出発点に置いたことで、これまで噛み合わなかったエンジニアと経理の議論は、同じ図を指しながら進められるようになりました。考慮漏れが減り、お互いがお互いのドメインに歩み寄る——そうした定性的な変化が、この直近多く見られるようになったと感じています。&lt;/p&gt;
&lt;p&gt;著者自身、会計はまだ勉強中の身ですが、システムと結びつけて考えるとかなり理解しやすくなる、というのが正直な実感です。そして、こうした内製ツールは AI の力を借りて本当に手軽に作れる時代になりました。私が所属する PCP Foundation チームでは、今後もこうした組織横断のツール開発とメンテナンスを通じて、チームの垣根を越えた生産性の向上につなげていきたいと思っています。&lt;/p&gt;
&lt;p&gt;[^1]: 決済 API を利用するタイミングと会計連携のタイミングが異なる場合はプロダクトが直接行っているが、この深掘りと進化はまた別の機会に。&lt;/p&gt;
&lt;p&gt;次の記事は hokao さんの「会計システムにおける訂正機能の設計と実装」です。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>MySQLで実装されたクエリをSpannerに移行した時に行ったパフォーマンスチューニング</title><link>https://engineering.mercari.com/blog/entry/20260602-61b0c122f5/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260602-61b0c122f5/</guid><description>&lt;p&gt;こんにちは、いつも心に冪等性 sinmetalです。「Merpay &amp;amp; Mercoin Tech Openness Month 2026」の3日目の記事です。 本記事では、MySQLで動いていた「お客さまが所持す [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 04 Jun 2026 10:00:38 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、いつも心に冪等性 sinmetalです。「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の3日目の記事です。&lt;/p&gt;
&lt;p&gt;本記事では、MySQLで動いていた「お客さまが所持するクーポン一覧取得」クエリをSpannerへ移行した際に直面したパフォーマンス問題と、その解決までの過程を紹介します。&lt;/p&gt;
&lt;h2&gt;DBごとのアーキテクチャの差&lt;/h2&gt;
&lt;p&gt;MySQLとSpannerはどちらもSQLを利用できますが、アーキテクチャが大きく異なるので、テーブルやクエリの設計は異なります。移行する時は差異があるSQLを修正するだけでなく、各DBのアーキテクチャまで意識して、実装を見直す必要があります。&lt;/p&gt;
&lt;h3&gt;MySQLのアーキテクチャ&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/aada1d1b-mysql-アーキテクチャ.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;MySQLはPrimary InstanceがWriteを担当するのでWriteをスケールさせたいなら、Primary Instanceのマシンを強化します。&lt;/p&gt;
&lt;p&gt;ReadはRead Replicaを増やすことでもスケールさせることができます。&lt;/p&gt;
&lt;p&gt;Read Replicaは独立しているので、OLAPのような特定のクエリを特定のInstanceで実行することもできます。&lt;/p&gt;
&lt;h3&gt;Spannerのアーキテクチャ&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/5dabf24e-spanner-アーキテクチャ図-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Spannerはデータを分割してSplitに保存します。Splitはデータサイズや負荷で自動的に増減し、データの分散範囲も調整されます。Writeに対してもスケールできますが、Read Replicaが無いので特定のクエリを特定のInstanceで処理するようなことはできません。Read Replicaだけを増やすということもできないので、Read性能だけを増やすこともできません。WriteもReadもどちらもAuto Scaleして分散して処理するのが強みです。&lt;/p&gt;
&lt;h2&gt;メルカリのクーポン機能の移行&lt;/h2&gt;
&lt;p&gt;クーポン機能の中に自分が所持しているクーポンの一覧を取得するAPIがあります。その中で実行されていたクエリのチューニングを行いました。&lt;/p&gt;
&lt;p&gt;まずはMySQLの実装をそのまま移行したので、以下のようなクエリになっていました。&lt;/p&gt;
&lt;p&gt;MySQLなら、それほど問題になるようなクエリではないかもしれませんが、Spannerの実行計画を見るとResidual Condition(Residual Conditionはインメモリで処理する必要がある状態です。後述※1)が必要、Sortが必要など非常に厳しい状態です。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  co.CouponOwnerID,
  co.CouponID,
  co.Expire,
  co.CreatedAt
FROM
  CouponOwners co
JOIN
  Coupons c
ON
  co.CouponID = c.CouponID
WHERE
  co.UserID = @userID
  AND c.IsActive = @isActive
  AND co.Expire &amp;gt; @now
  AND c.StartDate &amp;lt;= @now
  AND co.Used = @isUsed
  AND c.Type IN UNNEST(@couponType)
  AND c.MarketPlace = @marketPlace
ORDER BY
  co.CreatedAt DESC&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;+-----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ID  | Query_Execution_Plan                                                                                                                                                   |
+-----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|  *0 | Distributed Union (distribution_table: CouponOwnersByUserIDUsedExpireCreatedAtDESC, execution_method: Row, preserve_subquery_order: true, split_ranges_aligned: false) |
|   1 | +- Serialize Result (execution_method: Row)                                                                                                                            |
|   2 |    +- Sort (execution_method: Row)                                                                                                                                     |
|  *3 |       +- Distributed Cross Apply (execution_method: Row)                                                                                                               |
|   4 |          +- [Input] Create Batch (execution_method: Batch)                                                                                                             |
|   5 |          |  +- RowToDataBlock                                                                                                                                          |
|   6 |          |     +- Local Distributed Union (execution_method: Row)                                                                                                      |
|  *7 |          |        +- Filter Scan (execution_method: Row, seekable_key_size: 3)                                                                                         |
|  *8 |          |           +- Index Scan (Index: CouponOwnersByUserIDUsedExpireCreatedAtDESC, execution_method: Row, scan_method: Row)                                       |
|  34 |          +- [Map] Cross Apply (execution_method: Row)                                                                                                                  |
|  35 |             +- [Input] KeyRangeAccumulator (execution_method: Row)                                                                                                     |
|  36 |             |  +- DataBlockToRow                                                                                                                                       |
|  37 |             |     +- Batch Scan (Batch: $v2, execution_method: Batch, scan_method: Batch)                                                                              |
|  46 |             +- [Map] Local Distributed Union (execution_method: Row)                                                                                                   |
| *47 |                +- Filter Scan (execution_method: Row, seekable_key_size: 0)                                                                                            |
| *48 |                   +- Table Scan (Table: Coupons, execution_method: Row, scan_method: Row)                                                                              |
+-----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
  0: Split Range: (($UserID = @userid) AND ($Used = @isused) AND ($Expire &amp;gt; @now))
  3: Split Range: ($CouponID_1 = $CouponID)
  7: Residual Condition: ($UserID = @userid)
  8: Seek Condition: (IS_NOT_DISTINCT_FROM($UserID, @userid) AND ($Used = @isused)) AND ($Expire &amp;gt; @now)
 47: Residual Condition: (($IsActive = @isactive) AND ($MarketPlace = @marketplace) AND ($StartDate &amp;lt;= @now) AND ($Type IN @coupontype(array)))
 48: Seek Condition: ($CouponID_1 = $batched_CouponID&amp;#039;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DB Schema&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE Coupons (
  CouponID STRING(36) NOT NULL,
  Type STRING(36) NOT NULL,
  IsActive BOOL NOT NULL,
  StartDate TIMESTAMP NOT NULL,
  MarketPlace STRING(255) NOT NULL,
) PRIMARY KEY(CouponID);

CREATE INDEX CouponsByIsActiveStartDateTypeMarketPlaceID
  ON Coupons(IsActive, StartDate, Type, MarketPlace);

CREATE TABLE CouponOwners (
  CouponOwnerID STRING(36) NOT NULL,
  Used STRING(10) NOT NULL,
  UserID INT64,
  CouponID STRING(36) NOT NULL,
  Expire TIMESTAMP NOT NULL,
  CreatedAt TIMESTAMP NOT NULL OPTIONS (
    allow_commit_timestamp = true
  ),
  UpdatedAt TIMESTAMP NOT NULL OPTIONS (
    allow_commit_timestamp = true
  ),
) PRIMARY KEY(CouponOwnerID);

CREATE INDEX CouponOwnersByUserIDUsedExpireCreatedAtDESC
  ON CouponOwners(UserID, Used, Expire, CreatedAt DESC)
  STORING (CouponID);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実行計画には現れない問題もありました。&lt;/p&gt;
&lt;p&gt;HotSpotです。&lt;/p&gt;
&lt;p&gt;Coupons Tableはクーポンの情報が書かれているマスタテーブルなのですが、件数はそれほど多くありません。&lt;/p&gt;
&lt;p&gt;SpannerはRead Replicaが無いため、小さなTableや特定のRowへの高頻度の読み取りがMySQLと比べると不得意です。対象のRowが存在するSplitに負荷が集中してしまうからです。&lt;/p&gt;
&lt;p&gt;特に負荷が集中するものとしてメルカリのすべてのお客さまに配信する &lt;code&gt;超メルカリ市土日限定クーポン&lt;/code&gt; がありました。&lt;/p&gt;
&lt;p&gt;自分が所持しているクーポンの一覧を取得するとCoupons Tableの該当のRowがJOINのために参照されます。&lt;/p&gt;
&lt;p&gt;すべてのお客さまが持っていて、しかも、超メルカリ市という大きなキャンペーンの中で、特定の土日にだけ使えて、クーポン付与のタイミングでプッシュ通知も行われることもあり、高い確率でHotRowになります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/06/eb1e5ec4--hot-row-of-spanner-アーキテクチャ図-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Note: HotRowとは？&lt;/p&gt;
&lt;p&gt;アクセスが集中していてそれ以上分割不能なRowのことを指します。&lt;/p&gt;
&lt;p&gt;HotRowが発生しているかはHot Spot Statisticsを見ると分かります。&lt;/p&gt;
&lt;p&gt;開発環境でチェックする場合は、負荷テストを行い、Hot Split Statisticsをチェックします。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.cloud.google.com/spanner/docs/introspection/hot-split-statistics?hl=en#hot_row&quot;&gt;https://docs.cloud.google.com/spanner/docs/introspection/hot-split-statistics?hl=en#hot_row&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;パフォーマンスチューニング&lt;/h2&gt;
&lt;p&gt;これらの問題を解決するためにアーキテクチャを見直しました。&lt;/p&gt;
&lt;p&gt;やったことは大きく分けて3つです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Coupons TableのJOINをやめて、アプリ側に処理を寄せた&lt;/li&gt;
&lt;li&gt;ORDER BYを削除した&lt;/li&gt;
&lt;li&gt;NULLを許可しているColumnのFilter条件を調整してResidual Conditionが発生しないようにした&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Coupons TableのJOINをやめて、Coupons Tableはアプリケーション側で参照するようにし、更に一定期間メモリ上にキャッシュするようにしました。&lt;/p&gt;
&lt;p&gt;ValkeyやRedisに保存することも検討しましたが、アクティブな状態のクーポンの数は数十程度であること、変更はほぼないことで、メモリ上に持ってしまって良いだろうと判断しました。&lt;/p&gt;
&lt;p&gt;今後、他にもキャッシュしたいものが増えれば、専用のインフラを用意して、実装し直すかもしれません。&lt;/p&gt;
&lt;p&gt;キャッシュを入れたことで、HotRowが発生しづらくなり、クエリもCouponOwners Table単体で処理できるようになり、JOINも不要になりました。&lt;/p&gt;
&lt;p&gt;次に &lt;code&gt;ORDER BY co.CreatedAt DESC&lt;/code&gt; を無くしました。&lt;/p&gt;
&lt;p&gt;Filter条件として &lt;code&gt;Expire &amp;gt; @now&lt;/code&gt; があるので、CreatedAtのSortがあると単一のIndexをSeek Conditionで読むだけという処理にはできません。&lt;/p&gt;
&lt;p&gt;ExpireをResidual ConditionでFilter Scanするか、CreatedAtをSortするかを選択することになります。&lt;/p&gt;
&lt;p&gt;1人のお客さまが持っているクーポンの数は多くても10程度なので、Sortしてしまっても良いかなとは思いましたが、呼び出し回数が非常に多いAPIなので、アプリケーション側に処理を寄せています。代わりにアプリケーション側の負担が増えているので、どちらがよいかは悩ましいところです。今後の状況によってはSortをSpanner側に戻すこともあるかもしれません。&lt;/p&gt;
&lt;p&gt;もう一工夫している点として、UserIDのFilter条件 &lt;code&gt;(co.UserID IS NULL AND @userID IS NULL)&lt;/code&gt; を追加しています。&lt;/p&gt;
&lt;p&gt;これはCouponOwners.UserIDはNOT NULL制約がないため、@userIDにNULLが入る可能性をSpannerが考慮して、Filter ScanとしてResidual Condition: ($UserID = @userid)を追加してしまうのを抑制するためです。(元の実行計画の*7)(後述※2)&lt;/p&gt;
&lt;p&gt;結果としてクエリの実行計画は非常にシンプルなものにできました。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
  co.CouponOwnerID,
  co.CouponID,
  co.Expire,
  co.CreatedAt
FROM
  CouponOwners co
WHERE
  (co.UserID = @userID) OR (co.UserID IS NULL AND @userID IS NULL)
  AND co.Expire &amp;gt; @now
  AND co.Used = @isUsed&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;+----+-----------------------------------------------------------------------------------------------------------------------------------------+
| ID | Query_Execution_Plan                                                                                                                    |
+----+-----------------------------------------------------------------------------------------------------------------------------------------+
| *0 | Distributed Union (distribution_table: CouponOwnersByUserIDUsedExpireCreatedAtDESC, execution_method: Row, split_ranges_aligned: false) |
|  1 | +- Local Distributed Union (execution_method: Row)                                                                                      |
|  2 |    +- Serialize Result (execution_method: Row)                                                                                          |
|  3 |       +- Filter Scan (execution_method: Row, seekable_key_size: 3)                                                                      |
| *4 |          +- Index Scan (Index: CouponOwnersByUserIDUsedExpireCreatedAtDESC, execution_method: Row, scan_method: Row)                    |
+----+-----------------------------------------------------------------------------------------------------------------------------------------+
Predicates(identified by ID):
 0: Split Range: (($UserID = @userid) OR (ISNULL($UserID) AND ($Used = @isused) AND ($Expire &amp;gt; @now) AND ISNULL(@userid)))
 4: Seek Condition: (($UserID = @userid) OR (ISNULL($UserID) AND ($Used = @isused) AND ($Expire &amp;gt; @now) AND ISNULL(@userid)))&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;元のクエリと比べると非常にシンプルになり、高速で負荷の小さなものになりました。SQLが利用できるDBは増えていますが、インターフェースとしてSQLが使えても中のアーキテクチャがそれぞれ異なります。&lt;/p&gt;
&lt;p&gt;Spannerは予算さえあれば気軽にNodeを増やせるため、LatencyやCPU使用率に不満があれば、Nodeを増やすことで解決もできます。&lt;/p&gt;
&lt;p&gt;しかし、コスト効率はよくないですし、HotSpotのようにNodeを増やしても解決できない問題もあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.cloud.google.com/spanner/docs/introspection?hl=en&quot;&gt;Statistics tables&lt;/a&gt;を定期的に確認し、パフォーマンスの状態をモニタリングすること。&lt;/li&gt;
&lt;li&gt;新しいクエリの作成や既存のクエリの修正を行う場合は実行計画を確認すること。&lt;/li&gt;
&lt;li&gt;アクセスパターンに対してSpannerがどんな挙動をする必要があるかSpannerの気持ちになって考えること。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらを日常的に行うことで、よりよいSpannerライフが送れます。&lt;/p&gt;
&lt;p&gt;次の記事は komatuさんの「決済プラットフォームと経理を繋ぐ MoneyFlow」です。引き続きお楽しみください。&lt;/p&gt;
&lt;p&gt;※1 FilterScan operator の Seek ConditionとResidual Condition&lt;/p&gt;
&lt;p&gt;Seek ConditionはTableやIndexのスキャン範囲の開始と終了地点が特定できている状態の時に使われます。開始地点から終了地点まで読み込むだけなので、高速に動作します。&lt;/p&gt;
&lt;p&gt;Residual ConditionはTableやIndexを読み込む時に開始地点と終了地点が特定できず、データを実際に読んでFilterする必要がある時に利用されます。Seek Conditionと比べるとCPUを多く消費しますし、スキャンするデータ量に応じてLatencyも増加します。&lt;/p&gt;
&lt;p&gt;Seek ConditionとResidual Conditionについては&lt;a href=&quot;https://spanner-hacks.apstn.dev/seek-residual-conditions/&quot;&gt;Cloud Spanner Unofficial Hacks&lt;/a&gt;を読むとよいでしょう。&lt;/p&gt;
&lt;p&gt;※2 IS NOT DISTINCT FROM&lt;/p&gt;
&lt;p&gt;SpannerにはIS NOT DISTINCT FROMがないので、co.UserID IS NULL AND @userID IS NULLを入れているわけですが、2026年5月25日に再度試したところIS NOT DISTINCT FROMで動作していました。まだ、ドキュメントには反映されてないので、記事の中では使っていませんが、近々リリースノートが出るのでしょう。&lt;/p&gt;
</content:encoded></item><item><title>AI と作る HTML ベースの LP エディタ EGP Code を内製した理由</title><link>https://engineering.mercari.com/blog/entry/20260603-why-we-built-egp-code/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260603-why-we-built-egp-code/</guid><description>&lt;p&gt;こんにちは。メルペイのフロントエンドエンジニアの @mattsuu です。この記事は「Merpay &amp;amp; Mercoin Tech Openness Month 2026」の 2 日目です。 私たちのチームは、マー [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 03 Jun 2026 10:00:50 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイのフロントエンドエンジニアの &lt;a href=&quot;https://github.com/ryo-manba&quot;&gt;@mattsuu&lt;/a&gt; です。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の 2 日目です。&lt;/p&gt;
&lt;p&gt;私たちのチームは、マーケターや PM 向けの社内ツール群 Engagement Platform (EGP) を開発しています。ランディングページ (LP) の作成・公開もその一機能で、過去に &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241210-f7c478382a/&quot;&gt;WYSIWYG コンポーネントエディタ EGP Pages&lt;/a&gt; について同じチームから紹介記事を出しています。&lt;/p&gt;
&lt;p&gt;今回はその後継としてゼロから作り直した EGP Code を紹介します。AI エージェントと対話しながら LP を作るための、HTML ベースの社内向け LP エディタです。見た目を生成するだけでなく、本番運用に必要なところまで踏み込んで同じ編集体験の中に組み込んでいるのが特徴で、すでに 10 件以上の本番 LP がこの仕組みで作られています。&lt;/p&gt;
&lt;p&gt;v0、Gemini Canvas、Claude Design、Figma Make など、AI で UI を作れるツールはすでに数多くありますが、見た目は作れても本番 LP として運用するには、API 連携・品質保証・Native 連携といった社内固有の課題が残ります。EGP Code は、このギャップを埋めるために内製しました。&lt;/p&gt;
&lt;h2&gt;EGP Pages と AI 編集における課題&lt;/h2&gt;
&lt;p&gt;EGP Pages は、ブロックを選んで組み合わせるノーコードの WYSIWYG コンポーネントエディタです。ドラッグ&amp;amp;ドロップで &lt;code&gt;Layout&lt;/code&gt; や &lt;code&gt;Text&lt;/code&gt; といった 40 種類以上のコンポーネントから、ページを組み立てます。マーケターがエンジニアの手を借りずに LP を作れるという目的に対して非常によく機能しており、いまも多くの LP が EGP Pages で作られています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/05/31188f64-image.png&quot; alt=&quot;前身ツール EGP Pages の編集画面。コンポーネントを組み合わせてページを作るノーコードの WYSIWYG エディタ&quot; /&gt;&lt;/p&gt;
&lt;p&gt;転機になったのは、AI でページを編集したいというニーズが出てきたことです。EGP Pages は人がドラッグ&amp;amp;ドロップで組み立てる前提で設計されており、AI が扱う際にデータ構造が問題になります。例えば、ボタンを押すと数字が増えるページの JSON ツリーは次のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;components&amp;quot;: [{
    &amp;quot;id&amp;quot;: &amp;quot;root&amp;quot;,
    &amp;quot;elements&amp;quot;: [
      { &amp;quot;id&amp;quot;: &amp;quot;1&amp;quot;, &amp;quot;tagName&amp;quot;: &amp;quot;Context&amp;quot;,
        &amp;quot;props&amp;quot;: { &amp;quot;value&amp;quot;: [
          { &amp;quot;name&amp;quot;: &amp;quot;count&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;code&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;0&amp;quot; },
          { &amp;quot;name&amp;quot;: &amp;quot;increment&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;code&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;(count) =&amp;gt; count + 1&amp;quot; }
        ]}},
      { &amp;quot;id&amp;quot;: &amp;quot;2&amp;quot;, &amp;quot;tagName&amp;quot;: &amp;quot;Layout&amp;quot;,
        &amp;quot;props&amp;quot;: { &amp;quot;children&amp;quot;: [&amp;quot;:=element.3&amp;quot;, &amp;quot;:=element.4&amp;quot;] }},
      { &amp;quot;id&amp;quot;: &amp;quot;3&amp;quot;, &amp;quot;tagName&amp;quot;: &amp;quot;Text&amp;quot;,
        &amp;quot;props&amp;quot;: { &amp;quot;value&amp;quot;: &amp;quot;Count: ${context.count}&amp;quot; }},
      { &amp;quot;id&amp;quot;: &amp;quot;4&amp;quot;, &amp;quot;tagName&amp;quot;: &amp;quot;Action&amp;quot;,
        &amp;quot;props&amp;quot;: { &amp;quot;label&amp;quot;: &amp;quot;+1&amp;quot;,
          &amp;quot;onTriggerAction&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;SET_CONTEXT&amp;quot;,
            &amp;quot;payload&amp;quot;: { &amp;quot;count&amp;quot;: &amp;quot;${context.increment(context.count)}&amp;quot; }}]}}
    ]
  }]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;人がエディタ越しに触る分には、この構造でも問題ありませんが、LLM に直接編集を任せようとすると課題が見えてきます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ツリー構造が独自&lt;/strong&gt;: &lt;code&gt;&amp;quot;:=element.3&amp;quot;&lt;/code&gt; のような独自記法を AI に都度教える必要があり、プロンプトが長くなります。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ロジックが分散&lt;/strong&gt;: 状態・条件・動作・表示が &lt;code&gt;Context&lt;/code&gt; / &lt;code&gt;When&lt;/code&gt; / &lt;code&gt;Action&lt;/code&gt; / &lt;code&gt;Text&lt;/code&gt; に散らばり、挙動の把握にツリー全体を辿る必要があります。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON 文字列の中に JavaScript が埋まっている&lt;/strong&gt;: テンプレートリテラルか &lt;code&gt;eval&lt;/code&gt; される式かが描画コンポーネント次第で、正しい解釈が難しくなります。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;さらにこの JSON ツリーは等価な HTML のおよそ 2 倍のトークンを消費し、編集のたびに API コストとコンテキスト消費が膨らみます。加えてテスト基盤がなく、AI の編集結果を公開前に機械的に検証する手段がありませんでした。これは EGP Pages の設計が悪かったわけではなく、ノーコード時代に最適化された正しい設計でした。ただ AI に編集させるという前提が加わったことで設計を問い直す必要が出てきたのです。&lt;/p&gt;
&lt;h2&gt;HTML ベースで作り直す&lt;/h2&gt;
&lt;p&gt;選択肢は2つありました。既存の JSON 表現を AI 向けに改善するか、AI 前提でゼロから作り直すかです。私たちは後者を選び、ページの表現を HTML ベースにしました。HTML は人にも LLM にも馴染みがあり、独自の JSON ツリーや参照記法を毎回プロンプトで教える必要がないからです。&lt;/p&gt;
&lt;p&gt;先ほどのカウンターページは、EGP Code では次にようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
  &amp;lt;egp-script timing=&amp;quot;page-loaded&amp;quot;&amp;gt;
    rx.count = 0;
  &amp;lt;/egp-script&amp;gt;
  &amp;lt;egp-script&amp;gt;
    rx.increment = () =&amp;gt; { rx.count = (rx.count ?? 0) + 1; };
  &amp;lt;/egp-script&amp;gt;

  &amp;lt;p&amp;gt;&amp;lt;egp-text&amp;gt;Count: {{rx.count}}&amp;lt;/egp-text&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;egp-button :onclick=&amp;quot;rx.increment&amp;quot;&amp;gt;+1&amp;lt;/egp-button&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;機能は同じですが、コード量もトークン消費もおおよそ半分です。ただし、素の HTML だけでは、状態管理や条件分岐といった動的な振る舞いは表現できません。かといって &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; で自由に JavaScript を書かせると、ページの挙動を追いづらくなります。&lt;/p&gt;
&lt;p&gt;そこで、状態管理・条件分岐・繰り返しといった動的な部分だけを、少数の Web Components (&lt;code&gt;&amp;lt;egp-*&amp;gt;&lt;/code&gt;) に閉じ込めました。「静的な部分は普通の HTML、動的な部分だけ Web Components」という切り分けによって、EGP Pages のように状態・条件・動作が散らばらない構造になっています。&lt;/p&gt;
&lt;p&gt;見た目のスタイリングには Tailwind CSS を採用しています。Web 開発者に馴染みのある書き方に寄せることで、人も AI も独自の作法を覚えずに済みます。また、副次的な効果として、外部ライブラリへの依存が最小限になり、LP ごとに独自パッケージが混ざりません。ランタイムは中央で管理する少数のものだけで動くため、npm パッケージ起因のサプライチェーンリスクが問題になる中でも安全面で利点があります。&lt;/p&gt;
&lt;h2&gt;使い方&lt;/h2&gt;
&lt;p&gt;EGP Code では、ほとんどの操作を AI エージェントとのチャットで進めます。「LP を作りたい」のように要件が固まっていない依頼では、エージェントはいきなり作り始めるのではなく、文脈に応じた質問を返してくれます。対象デバイス、カラーテーマ、入れたいセクションといった項目を選択肢から答えていきます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/05/ee3e3795-screenshot-2026-05-25-at-17.42.31.png&quot; alt=&quot;LP を作るときにエージェントが返す質問画面。対象デバイス・カラーテーマ・入れるセクションなどを選択肢で答える&quot; /&gt;&lt;/p&gt;
&lt;p&gt;回答を送ると、エージェントが HTML やテストをまとめて生成し、たたき台となる LP ができあがります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/05/2341f84a-screenshot-2026-05-25-at-17.42.28.png&quot; alt=&quot;質問への回答をもとにエージェントが生成した LP。チャットに生成内容とテスト結果が並び、右にプレビューが表示されている&quot; /&gt;&lt;/p&gt;
&lt;p&gt;大まかな見た目ができたら、ボタンやテキストなどの要素を直接選んで仕上げます。対象をクリックして「文字サイズを大きくして」「文言を〜に変えて」のように頼めば、位置を説明しなくてもエージェントが直す箇所を正確に把握します。複数箇所にまとめてコメントを付けたり、参考にしたい画像を貼って渡すことも可能です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/05/0a61e43e-screenshot-2026-05-25-at-17.47.58.png&quot; alt=&quot;プレビュー上の要素を選び、「ボタンの背景色を赤にして」のように指示（コメント）を付けている様子&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;実運用に必要な 3 つの仕組み&lt;/h2&gt;
&lt;p&gt;LP と聞くと、文章と画像が並んだ静的なページを思い浮かべるかもしれませんが、実際には、エントリー状況で CTA ボタンを切り替えたり、お客さまの属性で見せ方が変わったりと動きを伴う LP も少なくありません。&lt;/p&gt;
&lt;p&gt;そのため、見た目だけが整っていれば十分というわけではなく、社内 API との連携、タップ時の分析ログ送出、公開前に表示や挙動を検証する仕組み、アプリと Web の遷移差を吸収する仕組みなど、周辺の仕組みもあわせて必要になります。&lt;/p&gt;
&lt;p&gt;EGP Code では、こうした仕組みを編集体験の中に組み込んでいます。以降では、この 3 つの仕組みを順に紹介します。&lt;/p&gt;
&lt;h2&gt;社内 API 連携と Logging&lt;/h2&gt;
&lt;p&gt;LP からは、商品一覧の取得やエントリー状況の確認といった社内 API への呼び出しが頻繁に発生します。しかし、社内 API は AI ツールの学習データに含まれていません。EGP Code では、このギャップを「使い方をその場で AI に教える」仕組みで埋めています。&lt;/p&gt;
&lt;p&gt;例えば「商品一覧を出す API は？」と聞くと、エージェントは候補の社内 API を用途つきで挙げてくれます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/05/1afd8b54-api_mosaic_simple.png&quot; alt=&quot;商品一覧を出す API は？」という質問に対し、エージェントが候補の社内 API を用途つきで挙げている様子（API 名はモザイク処理）&quot; /&gt;&lt;/p&gt;
&lt;p&gt;このやり取りの裏側では、エージェントが「関連する API を探す → 使い方を理解する → 型付きで実装する」という流れで動いています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/05/f752bc58-image-1.png&quot; alt=&quot;エージェントが社内 API を扱う流れの図。依頼を受け取り、関連 API を探し、使い方を理解し、型付きで実装するまでの 3 ステップ&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この流れを実現するために、いくつかの工夫をしています。まず、社内 API の使い方を、API ごとに Markdown ファイルに記載しています。実際には、次のようなファイルが並んでいます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;api-searchExampleItems.md
api-postExampleEntry.md
api-getExampleSegment.md
api-getExampleRecommendations.md
runtime-event-log.md
...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;すべての API の説明をプロンプトに乗せてしまうと不要にトークンを消費してしまいます。そこで各ドキュメントのタイトルと用途だけを渡しておき、AI が必要と判断したときにだけその本文を読み込ませる形にしています。&lt;/p&gt;
&lt;p&gt;次に、社内 API は型付きの薄いラッパー関数越しに呼び出します。LP から見えるのは関数呼び出しだけで、認証ヘッダ・サービス分岐・パスの違いはラッパーが吸収します。誤った使い方があれば Lint が検知します。&lt;/p&gt;
&lt;p&gt;分析ログの送出も同じドキュメント参照のしくみで扱っています。 ログが必要になったタイミングで、エージェントが対応するドキュメントを読み込み、API 呼び出しと同じ流れでログ用のコードを生成します。このしくみによって、AI に「商品一覧を出して」と頼むだけで、社内 API を正しく呼んだ動的な LP が組み上がります。&lt;/p&gt;
&lt;h2&gt;エディタ内で完結するテストと品質保証&lt;/h2&gt;
&lt;p&gt;AI が生成した API 呼び出しが正しく動いているか、ボタンが期待どおりに反応するかを、変更のたびに人が目視で確認するのは現実的ではありません。そこで EGP Code は、エディタ内にテストの仕組みを内蔵しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/05/4ef63e82-screenshot-2026-05-22-at-19.31.52.png&quot; alt=&quot;エディタ内でテストを実行した結果。左に全件パスしたテスト結果、右に対象 LP のプレビュー&quot; /&gt;&lt;/p&gt;
&lt;p&gt;エンジニア以外にとってテストは馴染みがなく、自分で作成するのはハードルの高い作業です。そこで、テストを直接書く代わりに、実現したい振る舞いを自然な言葉で書ける Spec タブを用意しています。ここに、LP の仕様を書き残していきます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/05/becc5bbd-screenshot-2026-05-22-at-19.47.15.png&quot; alt=&quot;Spec タブに LP の仕様を自然な言葉で書いた様子。SPEC.md に表示・操作・データ取得の受け入れ条件が並ぶ&quot; /&gt;&lt;/p&gt;
&lt;p&gt;あとは AI とのチャットで「@SPEC.md を元にテストを書いて」と頼むと、文脈に応じてテストが自動で生成されます。テストはエディタ向けに自作した Jest 風の API で書いており、内蔵のモックサーバで fetch を差し替えられるので、本番 API がなくても動的ページの挙動をエディタ内で再現できます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// ブラウザ上でそのまま実行される
test(&amp;#039;エントリーボタンで API が呼ばれる&amp;#039;, async () =&amp;gt; {
  render(html);
  await userEvent.click(screen.getByText(&amp;#039;エントリー&amp;#039;));
  expect(mockEntry).toHaveBeenCalledWith({ campaign: &amp;#039;X&amp;#039; });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;テストが失敗すると、その結果は AI にフィードバックされ、AI が自己修正します。&lt;strong&gt;仕様を書く → AI が実装とテストを生成する → ブラウザで挙動を確かめる&lt;/strong&gt;という流れがエディタ内で完結するため、静的な LP から API を使った動的な LP まで、同じ仕組みで品質を担保しながら作ることが可能です。&lt;/p&gt;
&lt;h2&gt;アプリと Web の差を吸収する Native 連携&lt;/h2&gt;
&lt;p&gt;キャンペーンページなどの LP は、Web ブラウザだけでなく、メルカリアプリ内の WebView でも開かれます。このとき通常のリンクのままだと、アプリ内では外部ブラウザが開いてしまい、アプリのネイティブ画面へ遷移できません。これを LP ごとに &lt;code&gt;userAgent&lt;/code&gt; 判定や Native bridge の呼び出しで書くのは現実的ではありません。そこで、その差をプラットフォーム側で吸収し、LP を作る側は専用の Web Components を使うだけで済むようにしました。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;egp-link href=&amp;quot;https://jp.mercari.com/search?keyword=camera&amp;quot;&amp;gt;
  カメラを探す
&amp;lt;/egp-link&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このリンクをクリックすると環境を自動で判定して、アプリ内なら Native bridge 経由でネイティブ画面へ、Web なら通常のリンクとして開きます。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;さまざまな AI ツールによって UI を作りやすくなっていますが、実運用の LP に乗せるには、API 連携・品質保証・Native 連携といった作り込みが必要になります。EGP Code は、そこをプラットフォーム側に組み込むことで、UI づくりから運用までを同じ編集体験の中でつなげようとしています。&lt;/p&gt;
&lt;p&gt;実際に次のような新しい進め方が出てきています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PM とフロントエンドエンジニアだけで、仕様策定から実装・リリースまでを完遂&lt;/li&gt;
&lt;li&gt;バックエンドエンジニアが API から LP まで 1 人で構築&lt;/li&gt;
&lt;li&gt;マーケターが静的な LP を 1 人で制作&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;テストや API 連携を含む動的な LP は、まだ非エンジニアだけで完結させるには難しい部分が残っています。それでも、誰がどこまで担えるかの境界は少しずつ動いていて、いずれはこうした動的な LP も非エンジニアだけで作れるようになると考えています。&lt;/p&gt;
&lt;p&gt;次の記事は @sinmetalさんの「MySQLからSpannerに移行した時のQueryチューニング」です。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>メルカリのグローバル展開を支えるPayment Platformの進化</title><link>https://engineering.mercari.com/blog/entry/20260528-84e2b2cb78/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260528-84e2b2cb78/</guid><description>&lt;p&gt;こんにちは。メルペイ Payment &amp;amp; Customer Platform（PCP）チームでBackend Engineerをしている @ryuyama です。この記事は「Merpay &amp;amp; Mercoi [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 01 Jun 2026 09:00:24 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイ Payment &amp;amp; Customer Platform（PCP）チームでBackend Engineerをしている &lt;a href=&quot;https://me.tychy.jp/&quot;&gt;@ryuyama&lt;/a&gt; です。この記事は「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026&lt;/a&gt;」の1日目の記事です。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;はじめに&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;メルカリでは、2025年9月30日にグローバル版のメルカリアプリをリリースしました。このアプリは台湾・USをはじめとして、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251007-a09afcd49b/&quot;&gt;3年以内に50カ国へ展開することを目指しており&lt;/a&gt;、メルカリのグローバル展開を加速させるための重要なマイルストーンとなりました。&lt;/p&gt;
&lt;p&gt;この記事では、メルカリ グローバルアプリ（以下、グローバルアプリ）のリリースに向けて、Payment Platformがどのような課題に向き合い、どのようにアーキテクチャを進化させてきたのかを紹介します。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Payment Platformに課せられたミッション&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;グローバルアプリ対応プロジェクトが立ち上がった際に、Payment Platformに課せられたミッションは、&lt;strong&gt;「3年以内に50カ国展開できる決済・会計システムを作ること」&lt;/strong&gt;でした。&lt;/p&gt;
&lt;p&gt;そこで、Payment Platformのシステムを大きく「決済」と「会計」のドメインに分け、達成すべきゴールを整理しました。&lt;/p&gt;
&lt;p&gt;決済基盤としては、主に以下のような対応が求められました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;50カ国の通貨・決済手段への対応&lt;/li&gt;
&lt;li&gt;決済画面の多言語対応&lt;/li&gt;
&lt;li&gt;複数のPSP（決済事業者）との接続&lt;/li&gt;
&lt;li&gt;為替レートを考慮した金額計算&lt;/li&gt;
&lt;li&gt;不正利用やチャージバックのリスクへの対応&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;会計基盤としては、以下のような状態をゴールと置きました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通貨ごと、国・地域ごとの売上、返金、決済手数料を正しく集計できること&lt;/li&gt;
&lt;li&gt;PSP（決済事業者）からの入金と、メルカリ側の取引データを照合できること&lt;/li&gt;
&lt;li&gt;在庫変動や税務関連の会計イベントを、外部システムから会計システムへ正しい粒度・タイミングで連携できること&lt;/li&gt;
&lt;li&gt;既存の国内向け会計システムとの整合性を保ちながら、新しい会計基盤へ移行できること&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;また、これらを一度きりのゴールとして実装するのではなく、グローバル展開のコストとスピードを強く意識する必要がありました。例えば、通貨や決済手段の追加をできるだけ迅速に行い、展開国を早く増やせること。不正対策、税務、会計などの各国・地域ごとの最適化を、それぞれ独立して進められることが求められました。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Payment Platformの進化&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;既存のPayment Platform&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;先ほど掲げたミッションを達成するために、現在のアーキテクチャにある課題洗い出しを行いました。Payment Platformは、日本のメルカリアプリの決済基盤をベースにして誕生し、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/2019-12-21-104948/&quot;&gt;メルペイの成長とともに進化してきました&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;決済のレイヤーでは、Payment Serviceというマイクロサービスが存在します。Payment Serviceでは、メルカリで商品を購入し、取引が完了するとお金が移動するという一連の取引を表現した Escrow というリソースや、お金の動きに注目した Charge、さらに3Dセキュアの普及や高度化する決済手段への対応のために &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20230613-source-payment/&quot;&gt;Source&lt;/a&gt; というリソースが開発されてきました。&lt;/p&gt;
&lt;p&gt;会計のレイヤーでは、Balance Service（残高サービス）やAccounting Service（会計サービス）といったサービスが存在します。Balance Serviceはお客さまの残高や加盟店の売上残高の管理を、Accounting Serviceは会計イベントの保存と経理向けのレポーティングを責務としています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/05/701084ad-blog_0601_1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これらはいずれも、これまでのメルカリグループ全体の成長を支えてきた重要なサービスです。&lt;/p&gt;
&lt;p&gt;一方で、グローバル対応に向けて、特に「展開コスト」と「展開スピード」という観点において、いくつかの課題がありました。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;グローバル展開に向けた課題&lt;/strong&gt;&lt;/h3&gt;
&lt;h3&gt;&lt;strong&gt;PSPや決済手段の追加にPayment Serviceが必要&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;1つ目の課題は、Payment Serviceにおいて、PSP（決済事業者）との接続ロジックと Source リソースが密結合していたことです。&lt;/p&gt;
&lt;p&gt;グローバル展開では、国や地域によって利用される決済手段が異なります。また、接続すべきPSPも国や地域ごとに変わる可能性があります。&lt;/p&gt;
&lt;p&gt;そのため、新しいPSPや決済手段を追加するたびにPayment Serviceのロジックへ変更が入る状態では、展開国が増えるほど開発・運用コストが増大してしまいます。&lt;/p&gt;
&lt;p&gt;また、Source が特定の決済手段と密結合していたため、新しい決済手段を追加する際には、新しいリソース定義や既存リソースへの影響を慎重に検討する必要がありました。&lt;/p&gt;
&lt;p&gt;国内向けの決済基盤としては十分に機能していた設計でも、50カ国展開を前提にすると、決済手段やPSPの追加をより小さな変更範囲で実現できる構成が必要でした。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;残高と会計イベントの整合性がアーキテクチャで保証できていない&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;2つ目の課題は、残高管理と会計イベントの整合性です。&lt;/p&gt;
&lt;p&gt;既存の構成では、Balance Serviceが残高を管理し、Accounting Serviceが会計イベントを保存していました。しかし、両者の整合性は会計レイヤー自体で保証されているわけではなく、Payment Service側のサービス間トランザクション管理に依存していました。&lt;/p&gt;
&lt;p&gt;グローバル展開では、通貨ごと、国・地域ごと、PSPごとの売上、返金、決済手数料を正しく集計できることが求められます。また、PSPからの入金とメルカリ側の取引データを照合できることや、在庫変動・税務関連の会計イベントを外部システムから会計システムへ正しい粒度・タイミングで連携できることも必要になります。&lt;/p&gt;
&lt;p&gt;そのため、単に決済処理の結果を保存するだけではなく、後続の照合やレポーティング、既存の国内向け会計システムとの整合性を見据えた会計基盤が必要でした。&lt;/p&gt;
&lt;p&gt;この課題は、グローバル対応が始まる以前から、メルカリのグローバルなマーケットプレイスの実現というビジョンに向けての課題だと認識されていました。これらの課題は次世代の会計システムであるBalance V2やBookkeeperとして、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20230614-distributed-transaction/&quot;&gt;メルコイン&lt;/a&gt;やメルカリ ハロの立ち上げに合わせて導入され、実運用の中で磨き上げられていました。一方で、メルカリのマーケットプレイスに関連する領域では既存システムからの移行コストが高く、導入の機会を待っている状況でした。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;現在のアーキテクチャ&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Payment Platformでは、グローバル対応に合わせて2つの大きな意思決定を行いました。&lt;/p&gt;
&lt;p&gt;1つ目は、PSP（決済事業者）と接続するための処理をPayment ServiceからPSPとの接続を責務とするPayment Provider Serviceに切り出し、PSPとの接続ロジックや決済手段に関するロジックを決済リソース管理から分離することです。&lt;/p&gt;
&lt;p&gt;2つ目は、会計システムにBalance V2とBookkeeperを利用し、Payment Serviceのトランザクション管理による整合性の管理から、残高と会計イベントが整合したモデルで扱われるアーキテクチャへ移行することです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/05/e4ea5470-blog_0601_2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Payment Serviceが決済リソースのライフサイクル管理に集中する&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;新しいアーキテクチャでは、Payment Serviceの責務をより明確にしました。&lt;/p&gt;
&lt;p&gt;Payment Serviceは注文や取引に紐づく決済リソースのライフサイクル管理に集中し、決済の作成、オーソリ、キャプチャ、キャンセルなどといった状態遷移を管理します。&lt;/p&gt;
&lt;p&gt;一方で、どのPSPのAPIをどのように呼び出すか、各決済手段にどのようなパラメータが必要でPSPから返ってくるレスポンスやWebhookをどのように処理するかといった詳細はPayment Provider Serviceに切り出しました。&lt;/p&gt;
&lt;p&gt;これにより、Payment Serviceは特定のPSPや決済手段に強く依存せず、より安定した決済リソース管理の責務に集中できるようになりました。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Balance V2 / Bookkeeperで残高と会計イベントの一貫性を担保する&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;会計基盤には、&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20241113-designing-a-zero-downtime-migration-solution-with-strong-data-consistency-part-i/&quot;&gt;Balance V2&lt;/a&gt;とBookkeeperを利用しました。&lt;/p&gt;
&lt;p&gt;Balance V2とBookkeeperでは、残高の変動と会計イベントを一貫したモデルとして扱います。これにより、残高更新と会計イベントの記録が別々のデータとして管理されるのではなく、複式簿記のように、「現在の残高（ストック）」と「残高が変化した理由（フロー）」を対応づけて保存されるようになりました。&lt;/p&gt;
&lt;p&gt;このアーキテクチャにより、Payment Service側で残高と会計イベント間の整合性を保つ複雑なサービス間トランザクション管理を担う必要が減り、Payment Serviceは決済リソースの管理により集中できるようになりました。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;終わりに&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;今回のプロジェクトでは、グローバル展開に必要な決済・会計基盤の土台を作ることができました。&lt;/p&gt;
&lt;p&gt;記事執筆時点では台湾・USへのローンチが完了し、クレジットカード決済・Apple Pay・Google Payなどの決済手段にも対応しています。&lt;/p&gt;
&lt;p&gt;また、Payment Platformで実現した抽象化を利用して、カートを使ったまとめ買い機能などの新しい購入体験も日本国内に先立ってグローバルアプリで提供することができました。&lt;/p&gt;
&lt;p&gt;一方で、今後取り組むべき課題もまだ残っています。&lt;/p&gt;
&lt;p&gt;例えば、日本のメルカリで日本円で売られている商品を海外通貨で販売する時の為替変換の仕組みは、現時点ではグローバルアプリ専用の実装に近く、社内のPayment Platformを利用しているチームにとって十分になめらかな体験になっているとは言えません。また、グローバルアプリ内でのポイントや残高の利用サポートも、今後進めていく必要があります。&lt;/p&gt;
&lt;p&gt;そして、より長期的には、今回整備した基盤を日本国内のメルカリにも適用するプロジェクトも進行しています。今回整備したグローバル向けの基盤や設計を国内向けの基盤にも還元することで、Payment Platform全体をより柔軟で拡張しやすい基盤へ進化させていきたいと考えています。&lt;/p&gt;
&lt;p&gt;次の記事は mattsuuさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>「Merpay＆Mercoin Tech Openness Month 2026」開催のお知らせ</title><link>https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260528-merpay-mercoin-tech-openness-month-2026/</guid><description>&lt;p&gt;こんにちは。メルペイ Engineering Engagement チームの @mikichin です。 メルカリグループは「あらゆる価値を循環させ、あらゆる人の可能性を広げる」をミッションに、さまざまなサービスを展開し [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 28 May 2026 10:00:42 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイ Engineering Engagement チームの &lt;a href=&quot;https://x.com/chida_miki&quot;&gt;@mikichin&lt;/a&gt; です。&lt;/p&gt;
&lt;p&gt;メルカリグループは「あらゆる価値を循環させ、あらゆる人の可能性を広げる」をミッションに、さまざまなサービスを展開しています。&lt;/p&gt;
&lt;p&gt;メルペイは単なる決済サービスではなく、新しい「信用」を基盤として、それに基づく循環型社会、なめらかな社会を創ることを、メルコインはテクノロジーによって、さまざまな価値観の境界線を打ち破り、誰もが暗号資産・デジタル資産などあらゆる価値を簡単に交換できる世界の実現を目指しています。&lt;/p&gt;
&lt;p&gt;そのためには、お客さま・企業・金融機関など、さまざまなステークホルダーに対して「OPENNESS」な姿勢で向き合うことで、もっと身近なものに変えていきたいと考えています。&lt;/p&gt;
&lt;p&gt;本企画は、技術も「OPENNESS」にしていこうという考えのもと、2019年にスタートしました。&lt;/p&gt;
&lt;p&gt;「Merpay &amp;amp; Mercoin Tech Openness Month 2026」では、メルペイ・メルコイン・メルカリモバイルの開発をしているエンジニアたちの取り組みをご紹介します。&lt;/p&gt;
&lt;p&gt;各エンジニア組織がテクノロジーでお客さまの課題解決を実現することを大切にし、その挑戦の中で得た知見を6月1日から約1ヶ月間に渡り公開していきます！技術、開発設計や思想、組織ストラクチャー、Tips、その他最近の取り組みなど、幅広くお伝えします。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;2019年は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/2019-05-17-100000/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;&lt;br /&gt;
2020年は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/notice-merpay-tech-openness-month-2020/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;&lt;br /&gt;
2021年は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20210825-notice-merpay-tech-openness-month-2021/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;&lt;br /&gt;
2022年は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20220401-notice-merpay-tech-openness-month-2022/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;&lt;br /&gt;
2023年は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20230531-notice-merpay-tech-openness-month-2023/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;&lt;br /&gt;
2025年は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250528-merpay-mercoin-tech-openness-month-2025/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;▼公開予定表 （こちらは、後日、各記事へのリンク集になります）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Title&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Author&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260528-84e2b2cb78/&quot; title=&quot;メルカリのグローバル展開を支えるPayment Platformの進化&quot;&gt;メルカリのグローバル展開を支えるPayment Platformの進化&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@ryuyama&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260603-why-we-built-egp-code/&quot; title=&quot;AI と作る HTML ベースの LP エディタ EGP Code を内製した理由&quot;&gt;AI と作る HTML ベースの LP エディタ EGP Code を内製した理由&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20260603-why-we-built-egp-code/&quot; title=&quot;Why we built EGP Code, an HTML-based AI editor for landing pages&quot;&gt;Why we built EGP Code, an HTML-based AI editor for landing pages&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@mattsuu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260602-61b0c122f5/&quot; title=&quot;MySQLで実装されたクエリをSpannerに移行した時に行ったパフォーマンスチューニング&quot;&gt;MySQLで実装されたクエリをSpannerに移行した時に行ったパフォーマンスチューニング&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@sinmetal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260605-moneyflow-bridging-payment-and-accounting/&quot; title=&quot;決済プラットフォームと経理を繋ぐ MoneyFlow&quot;&gt;決済プラットフォームと経理を繋ぐ MoneyFlow&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@komatsu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260605-188b7dae06/&quot; title=&quot;会計システムにおける訂正機能の設計と実装&quot;&gt;会計システムにおける訂正機能の設計と実装&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@hokao&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260608-partner-credit-service/&quot; title=&quot;事業者請求払いのための与信管理マイクロサービスの設計&quot;&gt;事業者請求払いのための与信管理マイクロサービスの設計&lt;/a&gt; &lt;br /&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20260608-partner-credit-service/&quot; title=&quot;Designing a Credit-Management Microservice for Partner Invoice Payment&quot;&gt;Designing a Credit-Management Microservice for Partner Invoice Payment&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@imamu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260610-merge-cursor-based-pagination/&quot; title=&quot;カーソルベースAPIのデータをマージするページネーション設計&quot;&gt;カーソルベースAPIのデータをマージするページネーション設計&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@nanacom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260610-how-egp-code-works/&quot; title=&quot;AI と作る LP エディタ EGP Code を支える 4 つの仕組み&quot;&gt;AI と作る LP エディタ EGP Code を支える 4 つの仕組み&lt;/a&gt; &lt;br /&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20260610-how-egp-code-works/&quot; title=&quot;Four Mechanisms Behind EGP Code, an AI-Powered Landing Page Editor&quot;&gt;Four Mechanisms Behind EGP Code, an AI-Powered Landing Page Editor&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@mattsuu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260610-be556591c7/&quot; title=&quot;Pub/Sub drivenなmicroserviceにPR単位の検証環境を導入した話&quot;&gt;Pub/Sub drivenなmicroserviceにPR単位の検証環境を導入した話&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@mikupo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260612-workflow-engine/&quot; title=&quot;内製ワークフローエンジンの設計とメルカリでの活用事例&quot;&gt;内製ワークフローエンジンの設計とメルカリでの活用事例&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@sapuri&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260615-build-first-discuss-later/&quot; title=&quot;Build First, Discuss Later｜初回ミーティングに動くプロトタイプを持ち込んだら、意思決定が爆速になった&quot;&gt;Build First, Discuss Later｜初回ミーティングに動くプロトタイプを持ち込んだら、意思決定が爆速になった&lt;/a&gt; &lt;br /&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20260615-build-first-discuss-later/&quot; title=&quot;Build First, Discuss Later: How Prototypes Speed Up Product Decisions&quot;&gt;Build First, Discuss Later: How Prototypes Speed Up Product Decisions&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@kubomi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260615-7283dc59d4/&quot; title=&quot;Cloud Functions 世代移行で発生した1000万件のメッセージ滞留：Pub/Sub × Cloud Run × Spanner のチューニング&quot;&gt;Cloud Functions 世代移行で発生した1000万件のメッセージ滞留：Pub/Sub × Cloud Run × Spanner のチューニング&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@mewuto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260616-0f89ad4c7b/&quot; title=&quot;修正PRを食べてレビュースキルが賢くなる：Claude Codeによる自己改善サイクル &quot;&gt;修正PRを食べてレビュースキルが賢くなる：Claude Codeによる自己改善サイクル &lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@um（うめ）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260617-d943dec64a/&quot; title=&quot;メルペイのキャンペーン基盤をルールベース汎用システムに書き直し、Otoku Revolutionするまでの話&quot;&gt;メルペイのキャンペーン基盤をルールベース汎用システムに書き直し、Otoku Revolutionするまでの話&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@hasegway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260619-8358897bd1/&quot; title=&quot;TiDB/AlloyDBの大規模テーブルを高速にBigQueryに同期するための工夫&quot;&gt;TiDB/AlloyDBの大規模テーブルを高速にBigQueryに同期するための工夫&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@orfeon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20260619-scaling-myself-how-i-run-22-claude-code-sessions-for-ds4-migration/&quot; title=&quot;Scaling Myself: How I Run 22 Claude Code Sessions for DS4 Migration&quot;&gt;Scaling Myself: How I Run 22 Claude Code Sessions for DS4 Migration&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@cyan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260622-6d55d015b2/&quot; title=&quot;Product Engineer として働く — 小規模開発チームでClient-BEの越境開発を試した記録&quot;&gt;Product Engineer として働く — 小規模開発チームでClient-BEの越境開発を試した記録&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@anzai &lt;br /&gt; @Li &lt;br /&gt; @ninnin &lt;br /&gt; @panorama&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260623-ff72f126be/&quot; title=&quot; Otoku Revolution ~ 楽しい自動値引き体験の裏側&quot;&gt; Otoku Revolution ~ 楽しい自動値引き体験の裏側&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@yutaro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260624-e6965edab9/&quot; title=&quot;DebtAccountView: 柔軟な与信の分割と請求を実現する債権管理の仕組み&quot;&gt;DebtAccountView: 柔軟な与信の分割と請求を実現する債権管理の仕組み&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@kobaryo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260626-6e9484f480/&quot; title=&quot;Growth Platform ― メルカリグループのグロースを支えるチームの5年間の歩み&quot;&gt;Growth Platform ― メルカリグループのグロースを支えるチームの5年間の歩み&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@yo-gawa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20260626-rethinking-payment-apis-at-merpay-the-exchange-abstraction/&quot; title=&quot;Rethinking Payment APIs at Merpay: The Exchange Abstraction&quot;&gt;Rethinking Payment APIs at Merpay: The Exchange Abstraction&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@haoyu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260630-b22667b4d6/&quot; title=&quot;AI-Native な開発の実践に向けて&quot;&gt;AI-Native な開発の実践に向けて&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@becosuke&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260630-28a5eee688/&quot; title=&quot;決済プラットフォームに常駐する自律AIエージェントの設計と運用&quot;&gt;決済プラットフォームに常駐する自律AIエージェントの設計と運用&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@abcdefuji&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;どんな知見が得られるのか、毎日が楽しみです。&lt;/p&gt;
&lt;p&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2026 の1日目は、メルペイ Payment Platform @ryuyama が執筆予定です。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;ひとつでも気になる記事がある方は、この記事をブックマークしておくか、 &lt;a href=&quot;https://x.com/mercaridevjp&quot;&gt;エンジニア向け公式X&lt;/a&gt;をフォロー＆チェックしてくださいね！&lt;/p&gt;
</content:encoded></item><item><title>mercari-pm-agentの設計——Claude Code SkillsとMCPでPMワークフローを自動化する</title><link>https://engineering.mercari.com/blog/entry/20260427-mercari-pm-agent-design-automating-the-pm-workflow-with-claude-code-skills-and-mcp/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260427-mercari-pm-agent-design-automating-the-pm-workflow-with-claude-code-skills-and-mcp/</guid><description>&lt;p&gt;はじめに こんにちは。MercariでPMインターンをしている菊池翔吾です。 インターン期間中に mercari-pm-agent というClaude CodeのSkillを開発しました。PMが行う「問題の発見→データ収 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 28 Apr 2026 10:00:47 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。MercariでPMインターンをしている&lt;a href=&quot;https://jp.linkedin.com/in/shogo-kikuchi-19b792321&quot; title=&quot;菊池翔吾&quot;&gt;菊池翔吾&lt;/a&gt;です。&lt;/p&gt;
&lt;p&gt;インターン期間中に &lt;strong&gt;&lt;code&gt;mercari-pm-agent&lt;/code&gt;&lt;/strong&gt; というClaude CodeのSkillを開発しました。PMが行う「問題の発見→データ収集→PRD作成→UIモック」の一連のワークフローを、1つのセッション内で処理するエージェントです。&lt;/p&gt;
&lt;p&gt;この記事では、PMのワークフローをClaude Code上でどのように実装したか——Skillの設計と、MCP（Model Context Protocol）を使ったNotion・Slack・Looker・Figmaとの接続方法——を中心に紹介します。&lt;/p&gt;
&lt;h2&gt;背景：メルカリPMの情報収集ワークフローと課題&lt;/h2&gt;
&lt;p&gt;メルカリのPMが意思決定を行うには、複数のツールを横断して状況を把握する必要があります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Notionで中期戦略・KPI目標の方向性を確認する&lt;/li&gt;
&lt;li&gt;Slackで社内の改善要望やフィードバックを検索する&lt;/li&gt;
&lt;li&gt;Lookerでユーザー行動の定量指標を確認する&lt;/li&gt;
&lt;li&gt;Figmaで対象画面の現状デザインを確認する&lt;/li&gt;
&lt;li&gt;これらを統合してPRD（製品要求仕様書）に落とし込む&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;各ツールへのアクセス自体は難しくありませんが、ツールを横断しながら「どのデータが今の判断に関係するか」を整理する作業には一定の時間がかかります。PMが本来時間を使うべきは、集めた情報をもとに深く考え、意思決定し、関係者と対話することのはずです。情報収集にかかる時間を、思考と意思決定に充てられるようにしたい——それがこのツールを作った動機です。&lt;/p&gt;
&lt;h2&gt;mercari-pm-agentの概要&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mercari-pm-agent&lt;/code&gt;&lt;/strong&gt; は、Claude CodeのSkillとして実装したPM支援エージェントです。&lt;/p&gt;
&lt;p&gt;PMがプロダクト上のビジネス課題を自然言語で入力すると、以下のステップが自動的に進みます。&lt;/p&gt;
&lt;h3&gt;処理の流れ&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/ecb7ba7a-mermaid-diagram-4-918x1024.png&quot; alt=&quot;Product development workflow from problem definition to final mockup, including data collection, opportunity scoring, PRD creation, UI specification, and prompt generation.&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;実装：Claude Code SkillsでPMワークフローを定義する&lt;/h2&gt;
&lt;h3&gt;Claude Code Skillsとは&lt;/h3&gt;
&lt;p&gt;Claude Code Skillsは、Claude Codeの振る舞いをMarkdownファイルで定義する仕組みです。&lt;strong&gt;&lt;code&gt;SKILL.md&lt;/code&gt;&lt;/strong&gt; にエージェントの動作手順・制約・ツールへのアクセス方法を記述することで、特定の業務フロー専用のエージェントを構築できます（&lt;a href=&quot;https://claude.com/blog/complete-guide-to-building-skills-for-claude&quot;&gt;公式ガイド&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;コードを書かずにエージェントの振る舞いを定義できる点が特徴です。PM向けSkillの実装例としては &lt;a href=&quot;https://github.com/phuryn/pm-skills&quot;&gt;phuryn/pm-skills&lt;/a&gt; も参考にしました。ただし、後述するように「Markdownを書くだけ」では精度は出ません。振る舞いの制約設計と評価サイクルが重要です。&lt;/p&gt;
&lt;h3&gt;ファイル構成：関心の分離をプロンプト設計に適用する&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;mercari-pm-agent/
├── [SKILL.md](http://skill.md/)  # エージェントの振る舞い定義（英語）
└── references/
    ├── [prd-template.md]  # PRDテンプレート
    ├── [prd-checklist.md] # PRD品質チェックリスト（9項目）
    ├── [ui-and-figma.md] # UI Spec・Figma Makeプロンプトテンプレート
    ├── [laplace-guide.md]  # データ解釈ガイド
    ├── [data-sources.md] # データソース一覧・使い方
    └── [quick-reference.md] # 出力チェックリスト&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初期は全ての定義を &lt;strong&gt;&lt;code&gt;SKILL.md&lt;/code&gt;&lt;/strong&gt; 1ファイルに集約していましたが、後述する評価スキルによるスコアリングを通じて、&lt;strong&gt;ファイルが長くなるほど出力精度が低下する&lt;/strong&gt;という問題を確認しました。&lt;/p&gt;
&lt;p&gt;これはLLMの特性と関係しています。コンテキストが長くなると、モデルが文脈の中で関連情報に適切に注目できなくなる現象（いわゆる「Lost in the Middle」問題）が知られており、Anthropicのプロンプトエンジニアリングガイドでもプロンプトを簡潔に保つことが推奨されています。&lt;/p&gt;
&lt;p&gt;対応として、&lt;strong&gt;振る舞いの定義&lt;/strong&gt;（&lt;strong&gt;&lt;code&gt;SKILL.md&lt;/code&gt;&lt;/strong&gt;本体）と&lt;strong&gt;参照データ・テンプレート&lt;/strong&gt;（references/）を分離しました。ソフトウェア開発における「関心の分離（Separation of Concerns）」をプロンプト設計に適用したアプローチです。&lt;strong&gt;&lt;code&gt;SKILL.md&lt;/code&gt;&lt;/strong&gt;はエージェントが「何をどの順序でするか」のみを保持し、具体的なデータやテンプレートは必要なタイミングでreferencesから参照する設計です。この構造変更だけでスコアが明確に改善しました。&lt;/p&gt;
&lt;p&gt;なお、&lt;strong&gt;&lt;code&gt;SKILL.md&lt;/code&gt;&lt;/strong&gt;は英語で記述しています。Claudeへの指示として英語の方が精度が高いためです。&lt;/p&gt;
&lt;h2&gt;MCP接続：複数ツールをエージェントに繋ぐ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mercari-pm-agent&lt;/code&gt;&lt;/strong&gt; の中核的な価値は、Step 2のデータ収集を自動化する点にあります。ここではMCP（Model Context Protocol）を使ったツール接続の設計について説明します。&lt;/p&gt;
&lt;h3&gt;MCPとは&lt;/h3&gt;
&lt;p&gt;MCPはAnthropicが策定したオープンプロトコルで、LLMアプリケーションが外部ツールやデータソースに接続するための標準仕様です。MCPサーバーを通じて、Claude CodeからNotion・Slack・Lookerなどの外部サービスをツールとして呼び出せるようになります。&lt;/p&gt;
&lt;h3&gt;接続しているMCPサーバー&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MCPサーバー&lt;/th&gt;
&lt;th&gt;種別&lt;/th&gt;
&lt;th&gt;取得できる情報&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Notion MCP&lt;/td&gt;
&lt;td&gt;公式（Notion提供）&lt;/td&gt;
&lt;td&gt;戦略ドキュメント・KPIダッシュボード&lt;/td&gt;
&lt;td&gt;中期戦略との整合性確認&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack MCP&lt;/td&gt;
&lt;td&gt;社内独自実装&lt;/td&gt;
&lt;td&gt;社内フィードバックチャンネルの投稿&lt;/td&gt;
&lt;td&gt;改善要望・現場の声の収集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Socrates&lt;/td&gt;
&lt;td&gt;社内独自実装（BigQuery・Lookerベース）&lt;/td&gt;
&lt;td&gt;CVR等の指標データ&lt;/td&gt;
&lt;td&gt;定量的な課題の裏付け&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Figma MCP&lt;/td&gt;
&lt;td&gt;社内独自実装&lt;/td&gt;
&lt;td&gt;デザインファイルのコンポーネント情報&lt;/td&gt;
&lt;td&gt;既存デザインの取得・UI Specへの反映&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;並列クエリと堅牢性の設計&lt;/h3&gt;
&lt;p&gt;Step 2（データ収集）では、これら複数のMCPを&lt;strong&gt;並列で&lt;/strong&gt;クエリします。&lt;strong&gt;&lt;code&gt;data-sources.md&lt;/code&gt;&lt;/strong&gt; に以下のルールを記述しています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- Pull in parallel during Data Enrichment — do not wait for one source
  before querying another.
  （データ収集フェーズでは並列で参照する。1つのソースの完了を待たないこと）

- If a source is unavailable, skip silently and mark it in the output.
  （ソースが利用不可の場合は、出力にその旨を明記してスキップする）&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直列での順次参照に比べてユーザーの待ち時間を削減するためです。また、いずれかのMCPが利用不可の状態でも処理が止まらないようフォールバック設計を入れています。&lt;/p&gt;
&lt;h3&gt;セキュリティ上の考慮&lt;/h3&gt;
&lt;p&gt;Slack MCPのセットアップには社内VPN接続とUser Tokenによる認証が必要です。トークンはClaude Codeの設定ファイルに環境変数として渡す形にしており、チャット上でトークン文字列が露出しない設計にしています。また、SlackのUser Tokenは7日で失効するため、更新用のスクリプトを別途用意しています。&lt;/p&gt;
&lt;h2&gt;開発で大事にしたこと&lt;/h2&gt;
&lt;h3&gt;評価基準を先に決める——プロンプトのTDD&lt;/h3&gt;
&lt;p&gt;実装を始める前に、まず「エージェントの出力をどう評価するか」の基準を定義しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;課題の理解精度（問題の本質を正しく捉えているか）&lt;/li&gt;
&lt;li&gt;仕様の具体性（実装可能なレベルで記述されているか）&lt;/li&gt;
&lt;li&gt;実現可能性（技術的・リソース的に妥当か）&lt;/li&gt;
&lt;li&gt;UXの妥当性（お客さまにとって使いやすいか）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これはソフトウェア開発におけるテスト駆動開発（TDD）に近い発想です。LLMベースのエージェントは「動くかどうか」より「正しく動くかどうか」の判定が難しい。評価軸を先に定義することで、プロトタイプの改善サイクルを感覚ではなく基準で回せるようになりました。実際のWeb改善課題を収集して評価データセットを作り、反復的に精度を上げていきました。&lt;/p&gt;
&lt;h3&gt;LLMの「それらしい嘘」を制約として防ぐ&lt;/h3&gt;
&lt;p&gt;LLMを業務フローに組み込む上で最も危険なのは、「根拠のないそれらしい情報」の生成です。データが存在しない状況でも、モデルは自然に「それっぽい数値」を出力します。PMがその数値を信じてPRDに記載してしまうと、意思決定の根拠がフィクションになります。&lt;/p&gt;
&lt;p&gt;これは「嘘をつくな」とプロンプトで命令するだけでは解決しません。モデルがデータ不足を認識したとき、どう振る舞うかを&lt;strong&gt;制約として設計&lt;/strong&gt;する必要があります。&lt;/p&gt;
&lt;p&gt;Data integrity rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unconfirmed data must be labeled &amp;quot;Not provided&amp;quot; or &amp;quot;To be validated&amp;quot;&lt;br /&gt;
（未確認のデータは &amp;quot;Not provided&amp;quot; または &amp;quot;To be validated&amp;quot; とラベルすること）&lt;/li&gt;
&lt;li&gt;Never fabricate numbers or sources&lt;br /&gt;
（数値や出典を捏造しないこと）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;さらに、PMの確認なしに次のステップへ自動的に進むことを禁じました。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;You are NOT allowed to infer completeness. Only explicit confirmation from the PM allows progression. （完了を推測して次へ進むことを禁じる。PMの明示的な確認があった場合のみ次へ進める）&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;これにより、エージェントが「それらしい流れ」で自動進行するのではなく、常にPMが意思決定のドライバーである状態を維持します。&lt;/p&gt;
&lt;h3&gt;スキルをスキルで評価する——自動評価パイプライン&lt;/h3&gt;
&lt;p&gt;設計したルールが実際に機能しているかを検証するため、評価専用のスキル（&lt;strong&gt;&lt;code&gt;skill-creator-max&lt;/code&gt;&lt;/strong&gt;）を別途作成しました。&lt;strong&gt;&lt;code&gt;mercari-pm-agent&lt;/code&gt;&lt;/strong&gt; に対してテストケースを投げ、出力の品質をスコアリングして返すエージェントです。このスコアを使った反復改善の中から、前述の「&lt;a href=&quot;http://SKILL.md&quot;&gt;SKILL.md&lt;/a&gt;は短いほど精度が上がる」という知見が得られ、ファイル分割の設計変更につながりました。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mercari-pm-agent&lt;/code&gt;&lt;/strong&gt; の開発を通じて得た、Claude Code Skillsを使ったエージェント設計の主な知見をまとめます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skillの設計は「振る舞いの仕様書」を書くことに近い。&lt;/strong&gt; 命令ではなく制約の設計が重要で、LLMが「どう振る舞うべきでないか」を明示することが精度に直結する。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCPによる外部ツール接続は並列設計で。&lt;/strong&gt; 直列参照はユーザー体験を悪化させる。フォールバック設計とあわせて、接続の堅牢性を考慮する必要がある。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;プロンプト設計にも関心の分離が有効。&lt;/strong&gt; コンテキストが長くなるほど精度が下がる。振る舞い定義と参照データの分離は、ソフトウェア設計の原則をLLM設計に適用した結果として機能した。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;評価基準は実装より先に作る。&lt;/strong&gt; LLMエージェントの品質評価は主観に陥りやすい。評価軸を先に定義し、評価専用のエージェントを作ることで客観的な改善サイクルが回せる。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mercari-pm-agent&lt;/code&gt;&lt;/strong&gt; はClaude CodeのSkillとして実装しているため、MCP設定が済んでいれば &lt;strong&gt;&lt;code&gt;/mercari-pm-agent&lt;/code&gt;&lt;/strong&gt; のコマンド1つで起動できます。&lt;br /&gt;
PMの業務効率化やClaude Code Skillsを使ったエージェント設計に興味のある方の参考になれば幸いです。&lt;/p&gt;
</content:encoded></item><item><title>TiDB Cloudにおけるオートスケールの実現</title><link>https://engineering.mercari.com/blog/entry/20260421-7b2dff6bce/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260421-7b2dff6bce/</guid><description>&lt;p&gt;DBRE (DataBase Reliability Engineering)チームの taka-h です。 2025年10月のTiDB User Dayにおいて、オートスケールについて取組み中(P. 81)であることを [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 22 Apr 2026 07:00:18 GMT</pubDate><content:encoded>&lt;p&gt;DBRE (DataBase Reliability Engineering)チームの taka-h です。&lt;/p&gt;
&lt;p&gt;2025年10月のTiDB User Dayにおいて、&lt;a href=&quot;https://static.pingcap.co.jp/files/2025/10/20165547/TiUD2025-Mercari.pdf&quot;&gt;オートスケールについて取組み中(P. 81)であることをご紹介&lt;/a&gt;しました。この記事では、その後のオートスケールの取り組み状況についてお伝えします。&lt;/p&gt;
&lt;p&gt;結論としては、2025年11月時点で、DBREが管理するTiDB移行済みの全クラスタでTiDBの水平方向オートスケール導入が完了し、その後も安定稼働しています。&lt;/p&gt;
&lt;p&gt;次の画像は、メルカリ内のとあるCluster(l1-2)で、オートスケールがCPU利用率の平均値を60パーセント前後に保つように動作している様子を示したものです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/3ebdbf5d-cleanshot-2026-04-10-at-16.43.50@2x.png&quot; alt=&quot;l1-2 Clusterでのオートスケール稼働の様子&quot; /&gt;&lt;/p&gt;
&lt;p&gt;なお、TiDB Cloudにはいくつかの製品ラインアップがありますが、本記事での以後のTiDB Cloudという言葉は、いわゆるサーバーレスではない「TiDB Cloud Dedicated」を指すものとします。&lt;/p&gt;
&lt;h2&gt;背景：なぜTiDB Cloudでオートスケールが必要だったか&lt;/h2&gt;
&lt;p&gt;メルカリでは、最初にTiDBのオートスケールの実装をすることを決め、初期の実装ターゲットをTiDBに絞ってオートスケールの導入を進めました。&lt;/p&gt;
&lt;p&gt;まずはこの経緯を簡単に説明します。&lt;/p&gt;
&lt;h3&gt;実装決定に至った経緯&lt;/h3&gt;
&lt;p&gt;最初に既存のライブラリの実装状況を調査しました。&lt;/p&gt;
&lt;p&gt;TiDBをKubernetes上でホスティングするためのライブラリとしては、&lt;a href=&quot;https://github.com/pingcap/tidb-operator&quot;&gt;tidb-operator&lt;/a&gt;があります。このライブラリでオートスケールが実装されていれば、我々が利用しているTiDBのマネージドサービスであるTiDB Cloudでもオートスケールが近々実現されることが想定されます。&lt;/p&gt;
&lt;p&gt;オートスケール導入を検討した当時は、次の状況でした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当初実装されていたオートスケールの機能が削除されていた&lt;/li&gt;
&lt;li&gt;TiDB Cloudの開発ロードマップにオートスケールの機能が入っていない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（最新の &lt;a href=&quot;https://docs.pingcap.com/tidb-in-kubernetes/dev/v2-vs-v1/#support-status-and-scale-sub-resources&quot;&gt;tidb-operator v2ではCRDのsub resourcesに対応&lt;/a&gt;しています）&lt;/p&gt;
&lt;p&gt;また、TiDB Cloudは自動での水平/垂直のオートスケールの機能が提供されていない一方で、スケール変更についてAPIが提供されています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingcap.com/tidbcloud/api/v1beta/#tag/Cluster/operation/UpdateCluster&quot;&gt;https://docs.pingcap.com/tidbcloud/api/v1beta/#tag/Cluster/operation/UpdateCluster&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;APIがあれば、オートスケールは実現のための必要条件を満たすので、まずはオートスケールの実現のため実装を進めることとしました。&lt;/p&gt;
&lt;h3&gt;実装対象の選定&lt;/h3&gt;
&lt;p&gt;オートスケールの実装を決めた後、最初にオートスケール化の対象を決める必要があります。&lt;/p&gt;
&lt;p&gt;メルカリにおけるTiDB Cloudのコストの構成としては、オートスケール導入前の時点で、大雑把にTiKVが6割程度、TiDBが2割程度でありました。TiKVが実データを持つのに対し、TiDBはステートレスでオートスケールに対する考慮事項が少なく、比較的容易に実現できますので、まずは実装対象をTiDBとし、要件を定義の上実装することにしました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/tidb-architecture/&quot;&gt;https://docs.pingcap.com/tidb/stable/tidb-architecture/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/cbb01885-cleanshot-2026-04-10-at-15.56.19@2x.png&quot; alt=&quot;TiDBのアーキテクチャ&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;方針1：Kubernetes HPA活用, しかしマネージドサービス特有の壁があった (オートスケール v1)&lt;/h2&gt;
&lt;p&gt;実装範囲を決めた後、最初はKubernetes HPA(Horizontal Pod Autoscaler)を活用して実装する方針としました。ここではその最初の方針決定の経緯、そして方針変更に至った経緯を順に説明します。&lt;/p&gt;
&lt;p&gt;要件を定義の上LLMで実装する予定でしたので、フルカスタム実装でもよかったのですが、主に、社内で&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20230620-f0782fd75f/&quot;&gt;Elasticsearchのオートスケールが実現されていた&lt;/a&gt;ため、これと同様にKubernetesのNativeのオートスケールの機能を活用して実現することに決定しました。&lt;/p&gt;
&lt;p&gt;具体的には、実績があり安心できるという理由の他には、Kubernetes Nativeのオートスケールを利用すると、次の利点がありました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;考慮すべき様々な一般的な考慮事項が既に満たされており、これを活用することでその実装が不要になり、開発すべき機能のスコープが絞られる
&lt;ul&gt;
&lt;li&gt;例) スラッシング回避、一度に増減できるノード数上限、など&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;datadog経由のメトリクス連携を既存のオートスケールの機能でできる
&lt;ul&gt;
&lt;li&gt;クレデンシャルのセットアップなども追加で不要&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;運用が他のKubenetesのものとそろう&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;既存のパターンとの違い: Podが管理するClusterに存在しない&lt;/h3&gt;
&lt;p&gt;既存のElasticsearchのオートスケールと今回のTiDB Cloudでは、前提条件が大きく異なります。既存のパターンは「自分たちが運用するKubernetesクラスタ上でPodが動いている」ことを前提に、HPAがPodのスケールを制御していました。&lt;/p&gt;
&lt;p&gt;一方で、TiDB Cloudを利用する場合、実際のPodはTiDB Cloud上で動作しており、メルカリが管理するKubernetesクラスタ上には存在しません。このため、既存パターンと同様にHPAを適用しようとすると「スケール対象として参照できるPodがない」という問題に直面します。&lt;/p&gt;
&lt;p&gt;そこで、TiDB Clusterを表すCustom Resourceを定義し、これをHPAのスケール対象として扱う方針で検討を進めました（次節で説明します）。&lt;/p&gt;
&lt;h3&gt;実際のPodがない対象へのHPA&lt;/h3&gt;
&lt;p&gt;ここでやりたかったのは、TiDB CloudのTiDBノード数変更を、KubernetesのHPAの仕組みに乗せて自動化することです。そのために、TiDB Clusterを表すCustom Resourceを定義し、HPAがその&lt;code&gt;replicas&lt;/code&gt;を増減できる構成を検討しました。&lt;br /&gt;
しかし、HPAがCustom Resourceをスケール対象として扱うには、&lt;code&gt;scale subresource&lt;/code&gt;（&lt;code&gt;/scale&lt;/code&gt;）を提供する必要があり、あわせて &lt;code&gt;labelSelectorPath&lt;/code&gt; の定義が求められます。&lt;code&gt;labelSelectorPath&lt;/code&gt; は、本来スケール対象に紐づくPod群を特定するための情報です。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#scale-subresource&quot;&gt;https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#scale-subresource&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;TiDB Cloudでは該当Podが自クラスタに存在しないため、この前提を満たせず、通常の形ではHPAが成立しませんでした。一方で、&lt;a href=&quot;https://github.com/kubernetes/kubernetes/blob/ee1ff4866e30ac3685da3e007979b0e9ab7651a6/pkg/controller/podautoscaler/horizontal.go#L699-L718&quot;&gt;External Metricsで AverageValue を使う場合に限り、結果的にselectorが評価に使われない挙動&lt;/a&gt;があり、&lt;code&gt;labelSelectorPath&lt;/code&gt; をダミーにしても動作しました（ただし仕様保証がなく、運用上のリスクが残ります）。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.cloud.google.com/kubernetes-engine/docs/tutorials/autoscaling-metrics&quot;&gt;https://docs.cloud.google.com/kubernetes-engine/docs/tutorials/autoscaling-metrics&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;: autoscaling/v1beta1（旧形式）
metrics:
- type: External
  external:
    metricName: pubsub.googleapis.com|subscription|num_undelivered_messages
    metricSelector:
      matchLabels:
        resource.labels.subscription_id: my-subscription
    targetAverageValue: &amp;quot;2&amp;quot;

: autoscaling/v2 (現行推奨)
metrics:
- type: External
  external:
    metric:
      name: pubsub.googleapis.com|subscription|num_undelivered_messages
      selector:
        matchLabels:
          resource.labels.subscription_id: my-subscription
    target:
      type: AverageValue
      averageValue: &amp;quot;2&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;方針2: フルカスタムへ切り替え、運用可能な形へ収束 (オートスケール v2)&lt;/h2&gt;
&lt;p&gt;ステージング環境までは、何とかこの実装で動作はしたのですが、External Metrics連携が想定通りに動作せず、次の理由からCustom Resourceを定義し、reconcileのループを回すところだけ残し、その他をフルカスタム実装に切り替えることにしました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native HPAの機能を活用している既存の実装は、現状利用している特定のパターンが将来的に利用できなくなる可能性が払拭できない&lt;/li&gt;
&lt;li&gt;デバッグが困難を極めた&lt;/li&gt;
&lt;li&gt;LLMでオートスケールのような「よくある」実装が非常に容易になっている&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;フルカスタム実装に切替えてから、オートスケールで考慮すべき一般的な考慮事項についての学びを1つ1つ獲得しながら、下記の方針で、迅速にそして大きな問題なくオートスケールを導入完了できました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最低限担保すべき範囲が明確になっており、そこが正確に実装できていること&lt;/li&gt;
&lt;li&gt;想定外であった場合に、それを検知する実装/設計になっていること&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;つまり、当初は実装精度が十分ではなく、オートスケールの制御精度もやや低い状態でした。そこで初期は目標値を保守的に設定し、まずは一定のコスト削減を達成しました。あわせて、目標値との差分（猶予）を確保しつつ、想定外を検知できる仕組みを用意しました。そのうえで、運用しながら段階的に精度を高めていきました。精度が低い状態を許容するには、想定外の事態や異常の検知が十分に検討され、適切に実装されていることが前提になります。&lt;/p&gt;
&lt;h3&gt;最低限担保すべき範囲&lt;/h3&gt;
&lt;p&gt;オートスケールでは、ある指定されたメトリックが目標値になるようリソースを増減させます。&lt;br /&gt;
運用開始時に、この目標値に余裕を持たせる前提であれば、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最小ノード数を一定以下にしないこと&lt;/li&gt;
&lt;li&gt;一度に可能な増減ノード数を制御できていること&lt;/li&gt;
&lt;li&gt;ノード増減操作の連続操作に制約を与えること&lt;br /&gt;
が期待通りに動作していれば、提供するサービス品質に影響が発生するということはないと考えました。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;想定外を検知する実装/設計&lt;/h3&gt;
&lt;p&gt;最低限担保する範囲を厳密に守りながら、実装速度を優先し、運用中に改善を重ねていく前提で開発を進める方針でしたので、運用中の改善を許容するためには「異常な状態」をうまく検知できる必要があり、そのために必要なメトリクス/アラートについて、事前に多くの検討を行いました。&lt;/p&gt;
&lt;p&gt;ここでの検討事項には、次の3つのポイントがありました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 制御するパラメータに対する適切なアラート設計/設定&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;現時点ではCPU利用率を一定範囲にコントロールすることを目標にオートスケールを運用しています。当たり前ですが、利用率が低すぎれば、それはリソースが有効に活用できておらず、利用率が高すぎれば、サービスの提供品質に問題がでる、こういったことが起きない範囲にコントロールすることを目指します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;オートスケールでの制御目標値&lt;/li&gt;
&lt;li&gt;実際のアラートの閾値(Cluster/Node)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上記のような値を適切な上下関係に設定し、運用を行うこととしました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/b3659087-generated-image-1776263205379.jpg&quot; alt=&quot;CPU利用率の閾値の設定例&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 依存関係のモニタリング&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;オートスケールが依存しているAPIの品質/問題に対して異常検知を行うことが重要であると考えました。今回は現状のメトリクスを把握するためのdatadogのAPI、そしてTiDB CloudのAPIが該当します。&lt;/p&gt;
&lt;p&gt;例えば、APIレスポンスが遅くなったり、特定のエラーコードが一定以上発生したり、データ点が欠損したり、あるいは、変更操作を起こった際の所要時間が通常より長くなっていないか、などを観測する、といったことです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/ed97ef0b-cleanshot-2026-04-16-at-11.48.15@2x.png&quot; alt=&quot;メトリクス欠損の監視例&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/e39903c7-cleanshot-2026-04-16-at-11.46.41@2x.png&quot; alt=&quot;変更中状態の継続期間の監視&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. オートスケールの想定内・想定外の規定&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;オートスケールでは、ノードを追加したり、削除したり、変更操作を随時行っています。&lt;/p&gt;
&lt;p&gt;一方で、あるノードの予期せぬ再起動、といったことも発生します。&lt;/p&gt;
&lt;p&gt;外部から観測できるメトリクス(Clusterの状態や、NodeのUptimeなど)から、どのような状態/変化が想定通りか、あるいは想定外かを考え、規定しています。&lt;/p&gt;
&lt;h2&gt;これらを可能にした周辺改善&lt;/h2&gt;
&lt;p&gt;オートスケールは、当たり前のように実施しなければならない項目ではありますが、それを実現するために、メルカリ社そしてTiDB Cloudの提供元であるPingCAP社の様々な改善がありました。&lt;/p&gt;
&lt;p&gt;一番大きなものは、TiDB CloudのTiDBのスケール操作(スケールアップ/スケールダウン/スケールイン)がgracefulではなかった点を、メルカリ/PingCAP社で改善したことです(&lt;a href=&quot;https://static.pingcap.co.jp/files/2025/10/20165547/TiUD2025-Mercari.pdf&quot;&gt;TiDB User Day 2025 P.36~&lt;/a&gt;)。&lt;/p&gt;
&lt;p&gt;そして、TiDB CloudのdatadogのCPUメトリクスの値の信頼性が十分でなく、オートスケールなどに利用するのが難しい、といった問題への対処、また、オートスケールをAPIで実施する際の&lt;a href=&quot;https://docs.pingcap.com/tidbcloud/release-notes-2025/#august-12-2025&quot;&gt;権限制御の改善&lt;/a&gt;、などがありました。&lt;/p&gt;
&lt;p&gt;度重なる要望に対して、改善を積み重ねていただいたPingCAP社には改めて感謝します。&lt;/p&gt;
&lt;h2&gt;今後: TiKVへの展開の難しさと次の一手&lt;/h2&gt;
&lt;p&gt;現在、より大きなコスト割合を占めるTiKVのスケーリングに取り組んでいます。&lt;/p&gt;
&lt;p&gt;TiKVはデータを保持するため、これを水平スケールする際には、データの偏りをなくすためのリバランスが必要です。水平スケールの戦略を取るためには、スケールインの際には削除対象のノードのすべてのデータを他のノードに移動してからではないと、ノードを削除できません。&lt;/p&gt;
&lt;p&gt;現在、「1日の中で負荷が上下し、TiKVで必要なリソースが変化するので、これに最適なリソースにし、コストを抑制しながら必要なリソースを必要なだけ確保したい」という要望があります。データのリバランスは、既存のノードの性能にも影響を与える可能性があり、そのため一般的にはその速度がある程度抑制されています。水平スケールに求めるスケール速度が、このような1日の中でノードを増減する必要がある、という場合においてスケールインは不向きであるといえるでしょう。また、逆にもう少し長いスパンでのスケールの自動追従であれば、コストに対するインパクトがそこまでないため実現の重要性は下がるでしょう。&lt;/p&gt;
&lt;p&gt;これに対して、リバランスの速度を調整する、という選択肢も考えられます。しかし、現状ではTiDBではこのリバランスの設定を含む、TiKVの設定変更に対してAPIが提供されておらず、現状取りうる手段は、サポートチケット経由での設定の変更依頼となります。&lt;/p&gt;
&lt;p&gt;そこで、まずは垂直スケールの機能の導入を目指しています。&lt;/p&gt;
&lt;h3&gt;TiKVの垂直スケールにおけるメモリ有効活用&lt;/h3&gt;
&lt;p&gt;今後も追加で様々な課題が見つかるかとは思いますが、現状、大きなインスタンスクラスでTiKVを運用した場合、メモリを有効活用できない課題がありましたので、それを解決しています。&lt;/p&gt;
&lt;p&gt;MySQLやその他のデータストアや、システムでもよくある問題ですが、データベースをホストしているノードの安定稼働のために、データベース以外、あるいは主に利用するリソース以外で利用するリソースを一定割合確保するため、主に利用するメモリの利用率を保守的に設定することがあります。&lt;/p&gt;
&lt;p&gt;TiDBにおいては、TiKVの&lt;code&gt;block_cache.capacity&lt;/code&gt;, &lt;code&gt;memory_usage_limit&lt;/code&gt;といった設定が主に積極的に活用したいリソースでありますが、現状のTiKVの実装状況で発生している拘束条件として、これらのパラメータについては、初期値は固定の割合で指定されるものの、初期値ではない値に変更する場合に、利用するメモリ利用量を数値で指定する必要がありました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/tikv-configuration-file/#memory-usage-limit&quot;&gt;https://docs.pingcap.com/tidb/stable/tikv-configuration-file/#memory-usage-limit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/tikv-configuration-file/#capacity&quot;&gt;https://docs.pingcap.com/tidb/stable/tikv-configuration-file/#capacity&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/af30892e-cleanshot-2026-04-21-at-10.31.27@2x.png&quot; alt=&quot;block-cache.capacityのドキュメント&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先述の通り、TiDB Cloudではこれらの設定を変更するAPIもありませんので、メモリを有効活用するためにメモリ利用量を、値を指定して変更する場合、現状サポートチケット経由で変更依頼が必要です。この状況では、負荷に合わせて自動で垂直スケールしたい、といった場合に、サポートチケット経由での変更が必要になり、人の手の介入が発生するため、結果として適切にメモリ利用量を自動で追従させられません。&lt;/p&gt;
&lt;p&gt;すなわち、TiKVを大きなインスタンスクラスで運用した場合、メモリが余剰しリソースを有効活用できない、という問題がありました。具体的にはblock-cache.capacityのデフォルト値の45%に近い、50%前後までしか、メモリを活用できませんでした。&lt;/p&gt;
&lt;p&gt;そこで、TiKVのメモリを割合で指定し、大きなインスタンスタンスクラスで運用する場合に、これを大きな値に設定することができるように修正しました。この修正はmasterブランチにマージ済みで、次のマイナーバージョンである8.5.7で利用可能になる見込みです。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/tikv/tikv/pull/19419&quot;&gt;https://github.com/tikv/tikv/pull/19419&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;メルカリではTiDB Cloudを運用しており、負荷に合わせTiDBの水平方向のオートスケールを安定的に運用しています。これにより、TiDBの50%前後のコストが削減されました（トータルでは、全体の2割の約半分、の削減効果）。&lt;/p&gt;
&lt;p&gt;既存のKubernetesのNativeの機能を活用する方針で初期の検討を始め、ステージング環境までも動作させたものの、LLMにて再実装を行ったこと、また、LLMでの実装については、&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最低限担保すべき範囲が明確になっており、そこが正確に実装できていること&lt;/li&gt;
&lt;li&gt;想定外であった場合に、それを検知する実装/設計になっていること&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;を基本方針として迅速に実装を完了させたこと、&lt;/p&gt;
&lt;p&gt;一見当たり前のようにみえる、オートスケールの実現自体には、実はとても大変な基礎的な改善の数々があること、また、最後にTiKVの垂直スケールでメモリを有効活用するため、修正をコントリビュートしていること、をお伝えしました。&lt;/p&gt;
&lt;p&gt;最後に、現在メルカリでは、この記事の発行者の所属する DBREチーム の EM(Engineering Manager) を募集しています。詳しくは&lt;a href=&quot;https://apply.workable.com/mercari/j/7AD4EF9218&quot;&gt;こちら&lt;/a&gt;をご覧ください。&lt;/p&gt;
</content:encoded></item><item><title>メルカリのAI活用を支えるセキュアなDevin管理</title><link>https://engineering.mercari.com/blog/entry/20260403-secure-devin-management/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260403-secure-devin-management/</guid><description>&lt;p&gt;はじめに こんにちは。メルカリのAI Securityエンジニアの@hi120kiです。 メルカリでは、AI AgentサービスDevinを社内の複数チームに展開しています。Devinは自律的にコードの調査・作成・PR提 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 03 Apr 2026 10:20:18 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。メルカリのAI Securityエンジニアの&lt;a href=&quot;https://twitter.com/hi120ki&quot;&gt;@hi120ki&lt;/a&gt;です。&lt;/p&gt;
&lt;p&gt;メルカリでは、AI Agentサービス&lt;a href=&quot;https://devin.ai/&quot;&gt;Devin&lt;/a&gt;を社内の複数チームに展開しています。Devinは自律的にコードの調査・作成・PR提出までをこなせるサービスですが、組織として運用するうえでは管理上の課題がいくつかあります。&lt;/p&gt;
&lt;p&gt;本記事では&lt;a href=&quot;https://careers.mercari.com/mercan/articles/55843/&quot;&gt;AI Securityチーム&lt;/a&gt;がAI Agent Platformチームと協力し、Devin Enterprise APIを活用したカスタムTerraformプロバイダーと自動管理ツール群を自作しました。これにより、メンバーと権限の管理・シークレットローテーション・APIキーのライフサイクル管理・監査の仕組みを構築した取り組みについて紹介します。&lt;/p&gt;
&lt;h2&gt;Enterprise運用の課題&lt;/h2&gt;
&lt;p&gt;メルカリではDevinのEnterpriseプランを採用しています。Remote環境で動作するAI Agentを組織的に運用するためにOktaによるSSO、監査ログ、権限の管理、チームごとの環境分離が必須要件であり、これらを満たすために選定しました。&lt;/p&gt;
&lt;p&gt;Devin EnterpriseではCoreプランやTeamプランのように1つのOrganizationを共有するのではなく、Enterpriseという管理基盤から複数のOrganizationを一元管理します。メルカリには複数のビジネス領域にまたがる多数のチームがあり、各チームが扱う情報を分離して保護する必要があります。そのためチームや目的に応じてOrganizationを割り当てています。&lt;/p&gt;
&lt;p&gt;ただし、10以上のOrganizationと多数の利用者を抱える環境では、次の課題が生じます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;権限管理の課題&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;メンバーのOrganizationへのアサインが手動操作に依存&lt;/li&gt;
&lt;li&gt;「誰がどのOrganizationに所属しているか」の状態管理が困難&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;シークレット管理の課題&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;各Organizationにサードパーティサービスごとの認証情報を個別に設定する必要&lt;/li&gt;
&lt;li&gt;シークレットを手動で一斉ローテーションする手間&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;アクセス権の課題&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Devin APIキーの有効期限管理が標準機能として提供されておらず、各Organization内に長期間未ローテーションのAPIキーが残存するリスク&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Devinの活用が広がるほど管理するOrganizationも増え、これらの課題の負担は拡大します。以前はWeb UIでの手作業に頼っていましたが、2025年末以降DevinがEnterprise向けAPIを&lt;a href=&quot;https://docs.devin.ai/api-reference/v2/overview&quot;&gt;v2&lt;/a&gt;から&lt;a href=&quot;https://docs.devin.ai/api-reference/v3/overview&quot;&gt;v3&lt;/a&gt;へ拡充したことで、ほとんどの管理操作をAPI経由で自動化できるようになりました。これを受け、Go言語とGitHub Actionsを用いた管理基盤を内製しています。&lt;/p&gt;
&lt;h2&gt;Devin APIの概要&lt;/h2&gt;
&lt;p&gt;Devinは&lt;code&gt;v3&lt;/code&gt; として&lt;a href=&quot;https://docs.devin.ai/api-reference/v3/overview&quot;&gt;最新のEnterprise管理向けAPI&lt;/a&gt;を提供しています。Enterprise・Organization単位のMember・Role管理や、各OrganizationのSecret・Knowledgeを操作できます。v3 APIで以下の自動管理機能を実現しました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;カスタムTerraformプロバイダー&lt;/li&gt;
&lt;li&gt;シークレットの一斉ローテーション&lt;/li&gt;
&lt;li&gt;Google Cloudサービスアカウントキーのローテーション&lt;/li&gt;
&lt;li&gt;セキュリティ管理基盤との連携&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;APIキー管理のみ&lt;a href=&quot;https://docs.devin.ai/api-reference/v2/overview&quot;&gt;v2 API&lt;/a&gt;を使用しています。v2 APIでは複数OrganizationにまたがるAPIキーの作成・取得・削除が可能で、以下を実施しています。&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;利用者が発行したAPIキーの定期無効化&lt;/li&gt;
&lt;li&gt;社内AgentのDevin Wiki利用向けAPIキー管理&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これらのAPI仕様はREST形式のAPIとしてDevinの公式ドキュメントにリクエストおよびレスポンスの詳細な仕様とともにドキュメント化されており、一般的なREST APIクライアントを実装することでそれぞれの機能を呼び出すことができます。今回これらのREST APIクライアントは、メルカリ社内で広く用いられているGo言語を用いてそれぞれのAPIが関数に対応するように実装し再利用しやすいように整備しました。&lt;/p&gt;
&lt;p&gt;以下の章からそれぞれの管理機能の詳細を紹介します。&lt;/p&gt;
&lt;h2&gt;1. カスタムTerraformプロバイダー&lt;/h2&gt;
&lt;p&gt;管理基盤の中核は、&lt;a href=&quot;https://developer.hashicorp.com/terraform/plugin/framework&quot;&gt;Terraform Plugin Framework&lt;/a&gt;で構築したカスタムTerraformプロバイダーによるOrganizationおよびメンバー管理です。&lt;/p&gt;
&lt;p&gt;メルカリではGoogle Cloudをはじめリソース管理にTerraformを広く利用しており、エンジニアが日常的に扱っている点から採用しました。DevinをInfrastructure as Codeで管理すると、メンバー追加や権限変更にPRレビューを挟める・Organizationやメンバーの状態をコードで把握できるようになります。公式のTerraformプロバイダーは現時点で提供されていないため自作しました。&lt;/p&gt;
&lt;p&gt;利用者や管理者は各チーム用のOrganizationをTerraformで定義します。ACU(Agent Compute Unit)上限もここで設定し、チームごとの利用量を制御します。&lt;code&gt;max_cycle_acu_limit&lt;/code&gt; はOrganization全体のACU上限、&lt;code&gt;max_session_acu_limit&lt;/code&gt; は1セッションあたりの上限で、想定外のコスト超過を防ぎます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource &amp;quot;devin_organization&amp;quot; &amp;quot;mercari_example_team&amp;quot; {
  name                  = &amp;quot;mercari-example-team&amp;quot;
  max_cycle_acu_limit   = 500
  max_session_acu_limit = 250
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;またメンバーのOrganizationへのアサインもTerraformで宣言的に管理します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# メンバー定義（メールアドレスで参照）
data &amp;quot;devin_member&amp;quot; &amp;quot;mercari_example_team&amp;quot; {
  for_each = toset([
    &amp;quot;user-1@example.com&amp;quot;,
    &amp;quot;user-2@example.com&amp;quot;,
    &amp;quot;user-3@example.com&amp;quot;,
  ])
  email = each.value
}

# Organizationへのアサイン
resource &amp;quot;devin_organization_member&amp;quot; &amp;quot;mercari_example_team&amp;quot; {
  for_each = data.devin_member.mercari_example_team

  user_id     = each.value.user_id
  org_id      = devin_organization.mercari_example_team.org_id
  org_role_id = &amp;quot;mercari_org_member&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Organizationの追加やACU上限の変更、メンバーの追加・削除は、Terraformコードの変更→PRレビュー→マージという通常の開発フローで行います。&lt;code&gt;terraform plan&lt;/code&gt; の出力で「誰がどのOrganizationに追加/削除されるか」が明確にわかり、意図しない権限変更を防げます。&lt;/p&gt;
&lt;p&gt;このTerraformプロバイダーではDevin Knowledgeも管理できます。KnowledgeはDevinにおけるAgent Skillのような存在です。メルカリのDevin環境では各チームが別々のOrganizationに分かれており、互いの利用状況を閲覧できません。セキュリティ面では望ましい分離ですが、活用ノウハウの共有が難しくなります。Knowledgeをプロバイダーで管理できるようにし、チーム間での活用ノウハウの配布を可能にしました。&lt;/p&gt;
&lt;h2&gt;2. シークレットの一斉ローテーション&lt;/h2&gt;
&lt;p&gt;DevinはSessionごとに独立した仮想マシンを起動するため、初期状態ではGitHub等ソースコード管理サービスへの権限しか持ちません。クラウド環境やチケット管理サービスなどへ接続するには、APIキー等の認証情報を個別に設定する必要があります。&lt;/p&gt;
&lt;p&gt;一方、DevinはAI Agentとして与えられたAPIキーを自由に扱えるうえ、Organization内のメンバーはSession内部のファイルシステムやシェルにアクセスできるため認証情報の取り扱いには注意が必要です。そこでメルカリでは、Devinに設定するAPIキー群を管理者が一元管理し、短い間隔で定期ローテーションすることで、長期間有効な認証情報がDevin上に残らないようにしています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/583513cb-secure-devin-management-rotate.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ただし手動でのローテーションは負担が大きく、以前は多数のOrganizationの複数Secretをローテーションするだけでかなりの時間を要していました。しかしDevinが&lt;a href=&quot;https://docs.devin.ai/api-reference/release-notes#january-2026&quot;&gt;2026年1月にSecret管理機能をv3 APIへ追加&lt;/a&gt;したことで、これらの操作を自動化できるようになりました。現在のローテーション手順は以下のとおりです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Devin管理者がそれぞれのサービスで認証情報をローテーションする&lt;/li&gt;
&lt;li&gt;新しい認証情報を事前に作成済みのGoogle Cloud Secret Managerに追加する&lt;/li&gt;
&lt;li&gt;自動化をGitHub Actions経由で起動する&lt;/li&gt;
&lt;li&gt;ローテーションが実行され、Secret Managerから各Organizationに配布される&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これにより、最小限の作業で10以上のOrganizationのシークレットを一斉ローテーションできるようになりました。&lt;/p&gt;
&lt;h2&gt;3. Google Cloudサービスアカウントキーのローテーション&lt;/h2&gt;
&lt;p&gt;メルカリでは主にGoogle Cloudを利用しておりライブラリの取得やテスト環境との接続にはGoogle Cloudの権限をDevinに付与する必要があります。しかしDevinは現在&lt;a href=&quot;https://docs.cloud.google.com/iam/docs/workload-identity-federation&quot;&gt;Workload Identity Federation&lt;/a&gt;に対応できるようなOIDCトークン発行機能がないため、サービスアカウントキーを用いる必要があります。&lt;/p&gt;
&lt;p&gt;しかし前提として、メルカリでは&lt;a href=&quot;https://docs.cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys&quot;&gt;Google Cloud公式のベストプラクティス&lt;/a&gt;に従い、Organization Policyでサービスアカウントキーの発行を一律禁止しています。このためDevin専用のGoogle Cloud Projectを設け、さらに&lt;a href=&quot;https://docs.cloud.google.com/resource-manager/docs/organization-policy/restricting-service-accounts#limit_key_expiry&quot;&gt;iam.serviceAccountKeyExpiryHours&lt;/a&gt;を追加のOrganization Policyとして設定しました。これにより、自動化が停止した場合でもサービスアカウントキーは一定期間で無効化されます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/32a4f918-secure-devin-management-sakey.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この仕組みのうえで、Organizationごとに個別のサービスアカウントキーを定期ローテーションしながら付与しています。&lt;/p&gt;
&lt;h2&gt;4. セキュリティ管理基盤との連携&lt;/h2&gt;
&lt;p&gt;Devin Enterprise採用の要件の一つに監査ログがあります。メルカリではAI Security および&lt;a href=&quot;https://careers.mercari.com/en/mercan/articles/35948/&quot;&gt;Threat Detection and Response&lt;/a&gt;チームのAnnaがDevin v3 APIを通じて&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20220513-detection-engineering-and-soar-at-mercari/&quot;&gt;内製セキュリティ監視プラットフォーム&lt;/a&gt;との連携を構築しました。&lt;/p&gt;
&lt;p&gt;この連携では、Admin権限を持つEnterprise Service Userと&lt;a href=&quot;https://docs.devin.ai/api-reference/v3/audit-logs/enterprise-audit-logs&quot;&gt;Enterprise Audit Logs&lt;/a&gt;エンドポイントを利用しています。これはv2 APIにおけるエンドポイントとは異なりページネーションがあるため、すべての監査ログを正確に取得することができます。これによりGoogle CloudのCloud Run Job を使って5分おきにAPIを取得し、前回取り込んだ最後の監査ログのタイムスタンプ以降の新規監査ログをすべて取得したうえでGoogle CloudのPubSubトピックへと転送しています。そして転送された監査ログはセキュリティ調査のためのBigQueryに保存されます。&lt;/p&gt;
&lt;h2&gt;5. 利用者が発行したAPIキーの定期無効化&lt;/h2&gt;
&lt;p&gt;Enterprise全体のAPIキーを全件取得し、作成から一定期間が経過したキーを自動で無効化します。Devinの標準機能にはないセキュリティポリシーを、APIで独自に実装しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/0f0ee7d5-secure-devin-management-api-key.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これらのAPIキーは主に&lt;a href=&quot;https://docs.devin.ai/work-with-devin/devin-mcp&quot;&gt;Devin MCP&lt;/a&gt;の接続に用いられます。APIキー経由で間接的にソースコードを取得できるため、厳格な管理が求められます。AI Agentを複数利用する開発環境では、使わなくなったAgentの設定ファイルに認証情報が残る・個人のAPIキーを複数人が利用する自作Agentに設定して社内公開してしまう、といった事態が起こりえます。&lt;/p&gt;
&lt;p&gt;一定期間経過したAPIキーを自動無効化することで、利用中のAgentだけがAPIキーを保持する状態を維持し、複数人で共有するAgentには、次章で紹介するGoogle Cloud Secret Manager経由のAPIキーを利用させることで、Agentが持つ権限の可視化も実現しました。&lt;/p&gt;
&lt;h2&gt;6. 社内AgentのDevin Wiki利用向けAPIキー管理&lt;/h2&gt;
&lt;p&gt;メルカリでは各チームの開発用Organizationとは別に、Devin Wiki用のOrganizationを運用しています。Devin Wikiは&lt;a href=&quot;https://docs.devin.ai/work-with-devin/devin-mcp&quot;&gt;Devin MCP&lt;/a&gt;経由でリポジトリの内容を取得したり、自然言語で検索したりできます。&lt;/p&gt;
&lt;p&gt;ソースコードの探索をAI Agentが直接行うとコンテキストを大量に消費します。ソースコード調査が必要な場面ではDevinに処理を委託することで、コンテキスト消費を抑えられます。&lt;/p&gt;
&lt;p&gt;ただし&lt;a href=&quot;https://docs.devin.ai/work-with-devin/devin-mcp&quot;&gt;Devin MCP&lt;/a&gt;の利用にはAPIキーが必要で、前章のとおり一定期間で自動無効化されます。例外となるAPIキーを設けることもできますが、目的外利用を完全には防げません。そこでAPIキーを短い間隔で定期的に再作成し、Google Cloud Secret Managerに保存する自動化を構築しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/51df8579-secure-devin-management-gsm-key.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これにより、Devin MCPを利用するAI AgentのサービスアカウントをTerraform上で一元管理し利用状況を可視化するとともに、APIキーの定期再作成による目的外利用の防止も実現しました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource &amp;quot;google_secret_manager_secret&amp;quot; &amp;quot;shared_wiki_api_key&amp;quot; {
  secret_id = &amp;quot;shared-wiki-api-key&amp;quot;
}

resource &amp;quot;google_secret_manager_secret_iam_member&amp;quot; &amp;quot;shared_wiki_api_key&amp;quot; {
  for_each  = toset(local.accessor_service_accounts_shared_wiki_api_key)
  secret_id = google_secret_manager_secret.shared_wiki_api_key.secret_id
  role      = &amp;quot;roles/secretmanager.secretAccessor&amp;quot;
  member    = &amp;quot;serviceAccount:${each.value}&amp;quot;
}

locals {
  accessor_service_accounts_shared_wiki_api_key = [
    &amp;quot;agent-1@---.iam.gserviceaccount.com&amp;quot;,
    &amp;quot;agent-2@---.iam.gserviceaccount.com&amp;quot;,
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;管理操作を動かすCIパイプライン&lt;/h2&gt;
&lt;p&gt;これらの管理操作はすべてGitHub Actionsで自動化しています。SaaS管理向けに独自管理ツールを作る場合、長期的なメンテナンスが避けられません。組織変更時の引き継ぎも考慮すると、依存関係を小さく保ち、メンテナンスしやすい技術・プラットフォームを選ぶ必要があります。&lt;/p&gt;
&lt;p&gt;Secret ManagerやサービスアカウントはGoogle Cloud上に置きつつも、処理の実行にはGitHub Actionsを選びました。リポジトリ内の自動化がデプロイなしで直接動作するためメンテナンスの手間が減り、不要なクラウドリソースを持たないことでコストと管理・引き継ぎ時の認知負荷も抑えられます。また定期実行に加え手動トリガー（&lt;code&gt;workflow_dispatch&lt;/code&gt;）にも対応しており、緊急時のシークレットローテーションを即座に実行できます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/04/5665aa6d-secure-devin-management-architecture.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;一方、GitHub Actionsは自由に実行できてしまうため、権限管理や&lt;a href=&quot;https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets&quot;&gt;ブランチ保護の設定&lt;/a&gt;を厳格に行っています。認証情報の取得には&lt;a href=&quot;https://docs.cloud.google.com/iam/docs/workload-identity-federation&quot;&gt;Google Cloud Workload Identity Federation&lt;/a&gt;を使用し、GitHub ActionsからサービスアカウントやSecret Managerへ&lt;a href=&quot;https://github.com/google-github-actions/auth&quot;&gt;安全にアクセス&lt;/a&gt;しています。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;Devin Enterpriseを大規模に運用するにあたり、標準機能だけではカバーできない管理要件を、v2およびv3 APIを活用した自作ツールで補いました。これにより、これまで手作業に依存していた管理上の課題を克服し、多数のOrganizationを同時に提供しつつ適切に管理できるようになりました。&lt;/p&gt;
&lt;p&gt;現在提供されているDevin v3 APIには、Enterprise管理に必要なエンドポイントが揃っています。今後は、Devinの機能拡張に合わせて、さまざまなリソースの管理を安全性を維持したまま自動化していく計画です。&lt;/p&gt;
&lt;p&gt;また、この記事が、同様の課題を抱えている方の参考になれば幸いです。&lt;/p&gt;
&lt;p&gt;メルカリでのAI/LLM活用やセキュリティに関する取り組みに興味がある方は、ぜひ&lt;a href=&quot;https://careers.mercari.com/&quot;&gt;メルカリの採用ページ&lt;/a&gt;をご覧ください。&lt;/p&gt;
</content:encoded></item><item><title>メルカリの Confluence → Notion 移行プロジェクト(2025年〜2026年3月の軌跡)</title><link>https://engineering.mercari.com/blog/entry/20260331-f448507968/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260331-f448507968/</guid><description>&lt;p&gt;目次 はじめに プロジェクトの発足 直面した「2つの壁」 完璧なインポートは幻想（技術の壁） 金融事業ゆえの厳しい制約（コンプライアンスの壁） 現状と今後 もし過去に戻れるとしたら？ Confluence導入時まで戻れた [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 31 Mar 2026 13:28:19 GMT</pubDate><content:encoded>&lt;h2&gt;目次&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;はじめに&lt;/li&gt;
&lt;li&gt;プロジェクトの発足&lt;/li&gt;
&lt;li&gt;直面した「2つの壁」
&lt;ul&gt;
&lt;li&gt;完璧なインポートは幻想（技術の壁）&lt;/li&gt;
&lt;li&gt;金融事業ゆえの厳しい制約（コンプライアンスの壁）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;現状と今後&lt;/li&gt;
&lt;li&gt;もし過去に戻れるとしたら？
&lt;ul&gt;
&lt;li&gt;Confluence導入時まで戻れたら…&lt;/li&gt;
&lt;li&gt;Confluence→Notion移行プロジェクト開始時に戻れたら…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;おわりに&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。Engineering Officeの&lt;a href=&quot;https://x.com/kiko_tw&quot; title=&quot;kiko&quot;&gt;kiko&lt;/a&gt;です。&lt;br /&gt;
今、私はCentral Knowledge Management Committeeのメンバーとして、社内のドキュメント基盤をConfluenceからNotionへ移行するプロジェクトを推進しています。&lt;/p&gt;
&lt;p&gt;当プロジェクトはまだ完遂していませんが、3月末に一つの節目を迎えたので、同Committeeメンバーのt-yamaさんと共に、これまでの振り返りをまとめました。&lt;/p&gt;
&lt;p&gt;これから社内ドキュメントツールを導入しようとしている方や、他ツールからNotionへの大規模移行を検討している方にとって、少しでも役立てば幸いです。&lt;/p&gt;
&lt;h2&gt;プロジェクトの発足&lt;/h2&gt;
&lt;p&gt;このプロジェクトは、単なるツールの乗り換えではなく、メルカリ全体のKnowledge Management（ナレッジマネジメント）の再構築として発足しました。(&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251224-mercariadventcalendar/&quot; title=&quot;メルカリのNotion導入についてのブログ&quot;&gt;メルカリのNotion導入についてのブログ&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;これまでConfluence含め複数のツールに分散していた社内ドキュメントを、AI/LLMとの親和性が高いNotionに集約することで、以下のメリットがあると考えています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ツールとして構造が一元化されるため、AIがコンテキストにしやすい&lt;/li&gt;
&lt;li&gt;AIや他ツールとの連携コストを下げられる&lt;/li&gt;
&lt;li&gt;ドキュメンテーションの重複やメンテナンスコストを減らせる&lt;/li&gt;
&lt;li&gt;ツールの利用自体のナレッジを社内で共有、蓄積しやすくなる&lt;/li&gt;
&lt;li&gt;ツールの違いによる、チーム間のナレッジのシェアや、働き方の違いといった分断を減らせる&lt;/li&gt;
&lt;li&gt;将来別のツールに移行する場合にコストを低くできる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;直面した「2つの壁」&lt;/h2&gt;
&lt;p&gt;Confluenceからの移行の検討が始まったのは、2025年7月のこと。&lt;br /&gt;
まずは、VP、PM Office、リスク・コンプライアンスチーム等へヒアリングしました。ヒアリングの結果、「一部移行を見送るドキュメントは在るものの、全体としては大きなブロッカーはない」と判断し、本格始動しました。しかし、プロジェクトを進めるうちに、2つの大きな壁にぶつかることになります。参考までに、それぞれに対する判断と対策についても合わせて付記します。&lt;/p&gt;
&lt;h3&gt;1. 完璧なインポートは幻想（技術の壁）&lt;/h3&gt;
&lt;figure id=&quot;attachment_36637&quot; aria-describedby=&quot;caption-attachment-36637&quot; style=&quot;width: 963px&quot; class=&quot;wp-caption alignnone&quot;&gt;&lt;img loading=&quot;lazy&quot; src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/d95487ae--2026-03-31-13.33.13.png&quot; alt=&quot;&quot; width=&quot;963&quot; height=&quot;1151&quot; class=&quot;size-full wp-image-36637&quot; srcset=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/d95487ae--2026-03-31-13.33.13.png 963w, https://storage.googleapis.com/prd-engineering-asset/2026/03/d95487ae--2026-03-31-13.33.13-251x300.png 251w, https://storage.googleapis.com/prd-engineering-asset/2026/03/d95487ae--2026-03-31-13.33.13-857x1024.png 857w, https://storage.googleapis.com/prd-engineering-asset/2026/03/d95487ae--2026-03-31-13.33.13-768x918.png 768w&quot; sizes=&quot;(max-width: 963px) 100vw, 963px&quot; /&gt;&lt;figcaption id=&quot;caption-attachment-36637&quot; class=&quot;wp-caption-text&quot;&gt;↑ガイドラインに、移行に関する情報・Tipsを記載&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;エクスポート・インポートの過程で、情報の欠損やレイアウト崩れが発生しました(我々が調べた当時、50項目以上)。これは、ConfluenceとNotionが別ツールである限り仕様として当然に起きるものです。ただ、その量と範囲は結構なもの…ITチームは泥臭い検証を繰り返しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;判断：手間をかけずに100%完璧な移行は不可能。移行後に必ず手作業が発生すること前提で進める。&lt;/li&gt;
&lt;li&gt;対策：欠損箇所（マクロや特殊な表など）を特定。移行前に修正すべき点や、インポート後に対応すべき点、修正のベストプラクティスなどをまとめたガイドラインを整備。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 金融事業ゆえの厳しい制約（コンプライアンスの壁）&lt;/h3&gt;
&lt;p&gt;プロジェクト中盤、メルペイ・メルコインといった金融事業において、特定のドキュメントに求められるコンプライアンス要件が、現時点のNotion標準機能ではクリアしきれないことが判明しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;判断：対象ドキュメントの移行は全社の移行とは分ける。&lt;/li&gt;
&lt;li&gt;対策：メルペイ・メルコイン側で担当者を立ててもらい、全社の移行とは別で対象ドキュメントの要件定義・ソリューション考案を進めてもらう。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;現状と今後&lt;/h2&gt;
&lt;p&gt;2つの大きな壁はありましたが、当初から対象外としていたものや別ルート対応になったものを除き、今後も利用する社内ドキュメントはほぼNotionへのインポートが完了。3月末に、Confluenceへの常時アクセスが不要なライセンスの棚卸しが完了しました。&lt;br /&gt;
ただ、Confluenceには、情報のオーナーがいない等の理由でNotionへ移行しなかったものの、情報資産として有用なドキュメントが多数残っています。これらの情報を、必要な時にいつでも閲覧できるよう、一時・恒久アクセス付与の仕組みを提供しています。&lt;br /&gt;
最終的には、Confluence内のドキュメントは閉架図書のように取り扱う形を目指しています。&lt;/p&gt;
&lt;h2&gt;もし過去に戻れるとしたら？&lt;/h2&gt;
&lt;p&gt;さて、「もし最初から今の知見があったら何をするか？」を考えると、やり直したいポイントはいくつかあります。戻るポイントを、Confluence導入時とプロジェクト開始時のそれぞれに設定して考えてみました。&lt;/p&gt;
&lt;h3&gt;Confluence導入時まで戻れたら…&lt;/h3&gt;
&lt;p&gt;どんなツールでも、導入時から完璧な運用ができるわけではありません。何年も運用していれば前提が変わってくるもので、それは導入時に何かしていれば防げたという類のものでもないと思います。&lt;/p&gt;
&lt;p&gt;ただ、もし今からConfluence導入時期に戻れるとしたら、Confluenceはもっと自由度を抑えた運用設定にしておくべきだったと思います。弊社のConfluence利用はユーザの自由度が高く、スペース作成やAPIの発行など、ルールはあってもシステム的に制御できている状態ではありませんでした。良く言えば「自由」、悪く言えば「制御できていなかった」状態だったと思います。&lt;/p&gt;
&lt;p&gt;自由度が高いと、ユーザが意図せずセキュリティ上リスクのある設定をしてしまったり、例外的な使い方やエッジケースが大量に生まれてしまうというデメリットもあります。例えばAPIトークンはユーザ自身で発行できたため、個人やチーム単位でConfluenceとのAPI連携を前提とした仕組み・運用が多数存在していました。その結果、Notionへの移行が決まると、「同じようにAPIを使わせてほしい」「システム連携も対応してほしい」という声が自然と出てきました。&lt;/p&gt;
&lt;p&gt;Notion導入時にこうしたルールを再度整備し運用に落とし込んだところ、「前より不便になった」「自由ではなくなった」という声も一部からありました。ただ、これはNotionが厳しいのではなく、前が自由すぎたのが問題だったと考えています。&lt;/p&gt;
&lt;p&gt;とはいえ、最初は自由であとから制限をかけるというのは、反発を生みやすいものです。これはツール移行に限らず、プラットフォーム運用全般に言えることだと思います。組織が成熟していく過程で、後からルールを導入するのは仕方がない面もあります。ただ、自由な期間が長いほど反発は大きくなるので、できるだけ早いタイミングでルールを整備しておくのが望ましいと思います。&lt;/p&gt;
&lt;h3&gt;Confluence→Notion移行プロジェクト開始時に戻れたら…&lt;/h3&gt;
&lt;p&gt;今回のような大規模な社内ドキュメントツールの移行は何度もやるようなものではありません。全員が初めての経験で、ノウハウも正解もない中、手探りで進めていくことになります。&lt;/p&gt;
&lt;p&gt;その中で一番感じたのは、Confluenceの現状に対する解像度をもっと早く高めておくべきだったということです。ConfluenceとNotionは別のサービスである以上、完璧な移行はできず、人的リソースや期限もあるため必然的にベストエフォートでの対応になります。何に力を入れて、何を割り切るか——その判断には、ある問題がどれだけのユーザに影響するのか、メジャーな話なのかエッジケースなのかを見極める必要があります。&lt;/p&gt;
&lt;p&gt;しかし実際には、その見極めに必要な情報が足りないことが多くありました。本当はエッジケースなので割り切った方がよいのに、ニーズの総数がわからないために決断できない、ということがよく発生しました。例えばマクロが移行できないとわかったとき、そもそも何のマクロが会社全体でどのくらい使われているのかがすぐには把握できませんでした。このあたりは後からわかるようになりましたが、技術面だけでなく、ユーザが実際にどう使っているか、どのスペースの更新頻度が高いか、特殊な要件があるかといったユースケースの理解を早期に深めておけば、判断はもっと速くできたと思います。&lt;/p&gt;
&lt;p&gt;解像度の問題に加えて、「やった方がよいのはわかっているが、リソースが足りなくてできない」という場面もかなり多くありました。例えば、自前で移行ツールを作れば、移行できない問題やエッジケースに部分的に対応できるかもしれません。しかし、ツールの冪等性の担保や、逆に新たな移行不能要素が生まれないかの検証など、別のリスクが発生します。それが起きたときにプロジェクトが破綻しないかというリスク許容度も含めると、安易には踏み切れません。やれたら良いけれど余裕がない——でも周りから見ると「なぜやらないのか」と思われてしまう——というジレンマは常にありました。&lt;/p&gt;
&lt;p&gt;このように移行プロジェクトでは、技術的な対応に加えて、現場での使われ方の把握、リソース管理、ベストエフォートの判断、ユーザへのコミュニケーションなど、さまざまな観点での対応力が求められます。そのため、移行専任のプロジェクトとして適切なメンバーをアサインすることが非常に重要だと感じています。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;今回のプロジェクトは、社内ドキュメントツールの管理・運用の仕組みを考え直す良い機会になりました。&lt;br /&gt;
また、移行のやり方に唯一の正解はありませんが、少なくとも言えるのは次の1点です。&lt;br /&gt;
「欠損や崩れが起きることを前提に、準備と運用設計をしておくほど、移行は楽になる。」&lt;br /&gt;
今回の経験をもとに、今後もNotionの環境構築・設計に力を入れていきたいと思います。&lt;/p&gt;
</content:encoded></item><item><title>大規模データ更新・削除を“安全に分割実行”するための汎用ツール</title><link>https://engineering.mercari.com/blog/entry/20260327-876b78716e/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260327-876b78716e/</guid><description>&lt;p&gt;DBRE (DataBase Reliability Engineering)チームの taka-h です。 大規模なデータ更新や削除は、やりたいこと自体はSQLで表現できても、そのまま一度に実行すると運用上のリスクが高 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 30 Mar 2026 07:00:59 GMT</pubDate><content:encoded>&lt;p&gt;DBRE (DataBase Reliability Engineering)チームの taka-h です。&lt;/p&gt;
&lt;p&gt;大規模なデータ更新や削除は、やりたいこと自体はSQLで表現できても、そのまま一度に実行すると運用上のリスクが高くなります。例えば大きなトランザクションが発生すると、レプリケーション遅延やDB負荷の増大、UNDOログの肥大化などにつながり、結果としてサービス影響を招く可能性があります。&lt;/p&gt;
&lt;p&gt;そこで私たちは、UPDATE/DELETEのような「最終的にやりたい操作」をSQLに近い形で記述しつつ、実行時には安全な単位に分割して処理できる汎用ツールを実装しました。さらに、実行中に処理速度などの設定を変更できることや、監視結果に応じて自動で一時停止できることなど、実運用で必要になる制御も組み込んでいます。&lt;/p&gt;
&lt;p&gt;本記事では、なぜこの問題が起きるのか、従来どのように回避してきたのか、そして今回のツールがどのように安全性と運用性を両立するのかを紹介します。最後に、ツールのREADMEも公開するので、同様の課題を持つ方が自分たちの環境に合わせて実装する際の叩き台として使えるはずです。&lt;/p&gt;
&lt;p&gt;なおこのツールは、社内の次のようなデータベース運用の支援を前提とします。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;データをアーカイブ／削除する&lt;/li&gt;
&lt;li&gt;データをバックフィルする&lt;/li&gt;
&lt;li&gt;データを一括で更新する&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;大規模データの更新/削除操作における課題&lt;/h2&gt;
&lt;p&gt;小規模なデータベースであれば、目的のSQLをそのまま実行しても問題にならないことがあります。一方で、一定以上の規模のデータを扱う場合は、同じSQLでも“そのまま一括実行する”こと自体がリスクになります。&lt;/p&gt;
&lt;p&gt;主な理由は、処理対象が多いと大きなトランザクションが発生しやすく、その副作用がDB全体に波及するためです。具体的には、変更の伝播（レプリケーションなど）に遅延が発生したり、DBが高負荷になったり、UNDOログが肥大化して回復や性能に影響が出たりします。&lt;/p&gt;
&lt;p&gt;このような場合の従来の方針は、「対象を小分けにして処理する」でした。たとえば、対象の主キーをある程度の件数に分割し、短いトランザクションを繰り返すようなSQLを作成してもらったり、専用の使い切りのスクリプトを都度用意して対応していました。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;BEGIN;
-- 対象の主キーを少量ずつ指定して処理する
DELETE FROM items WHERE id IN (...);
COMMIT;
SLEEP ...;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ただし、毎回使い切りのスクリプトを作ったり、対象主キーを取り出して分割したりするのは手間です。依頼者側に“安全な形のSQL”を組み立ててもらう必要が出るなど、運用コストが積み上がっていきます。&lt;/p&gt;
&lt;p&gt;そこで、この問題に対して汎用的な解決策を提供するツールを実装しました。&lt;/p&gt;
&lt;h2&gt;解決策: 汎用化ツール&lt;/h2&gt;
&lt;p&gt;このツールでは、利用者は「最終的に達成したい条件」をSQLに近い形で記述します。一方で実行時には、その条件に合致する対象を主キー単位で取得し、バッチに分割して短いトランザクションを繰り返すことで、安全にUPDATE/DELETEを進められるようにしています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/15f2e4cf-cleanshot-2026-03-25-at-16.25.12@2x.png&quot; alt=&quot;ツールのコンフィグのイメージ&quot; /&gt;&lt;/p&gt;
&lt;p&gt;また、実運用では「削除や更新の進捗」とは独立に、DB全体が高負荷になったり、想定外の問題が発生したりします。そのため、状況に応じて処理速度や挙動を調整できること、そして必要なら自動的に一時停止できることが重要です。&lt;/p&gt;
&lt;p&gt;この要件に対して本ツールでは、処理間隔やバッチサイズなどの設定を実行中に変更できる機能を持たせています。これは、MySQLのオンラインスキーマ変更ツールである &lt;a href=&quot;https://github.com/github/gh-ost&quot;&gt;gh-ost&lt;/a&gt; が「実行中に操作を制御できる」点で運用上便利なのと同じ発想です。さらに、監視結果に応じて自動で処理を一時停止する仕組みも組み込んでいます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/f3eacbcb-cleanshot-2026-03-25-at-16.04.45@2x.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最終的なコンフィグ例は上図の通りです。実行したい条件（SQLに近い記述）と、どう安全に実行するか（運用上の関心事項）を分離して設定できます。また、processingに属する項目の多くは実行中に変更可能です。&lt;/p&gt;
&lt;p&gt;このツールは主に生成AIを利用して実装し、動作確認のうえ社内で既に利用しています。コード自体のOSSとしての公開にはふみきれなかったのですが、次の章でこのツールのREADME.mdを公開します。これをご利用の環境に合わせた要件の追加、修正をしていただいた上で、生成AIを利用し同様のツールが利用できるようになることを期待しています。&lt;/p&gt;
&lt;p&gt;もし試してみて有用だった点や改善アイデアがあれば、SNSなどで議論いただけると嬉しいです。また、「メルカリのDBREチームの公開したREADME.mdで作ってみた」ということで宣伝いただけるとありがたいです。&lt;/p&gt;
&lt;p&gt;最後に、現在メルカリでは、この記事の発行者の所属する DBREチーム の EM(Engineering Manager) を募集しています。詳しくは&lt;a href=&quot;https://apply.workable.com/mercari/j/7AD4EF9218&quot;&gt;こちら&lt;/a&gt;をご覧ください。&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;http://汎用データ更新ツールのREADME.md&quot;&gt;汎用データ更新ツールのREADME.md&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;
# data-updater

A tool for batch data operations (UPDATE, DELETE, or NULL) on database records using primary keys with configurable conditions.

## Features

- Cursor-based batch processing with configurable batch size
- **Three operation types**: UPDATE, DELETE, and NULL (before_sql only)
- **Parallel execution**: SELECT and UPDATE operations run concurrently for better performance
- **Replica support**: Route SELECT queries to replica database to reduce primary load
- **JOIN support**: Complex queries with multiple tables to identify target records
- **Before SQL hooks**: Execute SQL before each batch (archiving, audit logging)
- **Custom ORDER BY**: Process records in custom order
- Interactive commands for runtime control (similar to gh-ost)
- **YAML-based configuration**: All settings in a single configuration file
- Real-time status monitoring with ETA
- Pause/resume functionality
- Dynamic configuration updates
- Socket-based remote control interface
- **Failed ID tracking**: Records failed updates and displays summary on exit
  - For batch-level failures: Records only first and last ID of the failed batch
  - For partial updates: Logs the discrepancy but doesn&apos;t track individual IDs
  - Writes detailed report to file if &gt;100 failures
- **Automatic resume**: Saves progress to status file after each batch
  - Automatically resumes from last successful position on restart
  - No need to manually track progress or specify resume points
  - Status files are adapter/table specific for multiple concurrent jobs

## Install

```bash
go install github.com/xxx/cmd/data-updater
```

## Quick Start

1. Create a configuration file:

```yaml
# config.yaml
database:
  host: localhost
  port: 3306
  user: myuser
  password: mypassword
  database: mydatabase
  options:
    charset: utf8mb4
    parseTime: &amp;quot;true&amp;quot;

processing:
  batch_size: 1000
  interval: 1s

adapter:
  table_name: users
  pk_columns:
    - user_id
  update_sql: &amp;quot;status = &amp;#039;processed&amp;#039;, updated_at = NOW()&amp;quot;
  where_clause: &amp;quot;status = &amp;#039;pending&amp;#039;&amp;quot;
```

2. Run the tool:

```bash
# Normal mode - executes updates
data-updater --config config.yaml

# Debug mode - SELECT only, no updates
data-updater --config config.yaml --debug

# Resume from specific ID
data-updater --config config.yaml --resume-from &amp;quot;12345&amp;quot;

# Show version
data-updater -v
```

## Operation Types

The tool supports three operation types:

### UPDATE (default)
Updates records matching the specified conditions.

```yaml
adapter:
  table_name: users
  pk_columns: [&amp;quot;user_id&amp;quot;]
  operation: update  # or omit (default)
  update_sql: &amp;quot;status = &amp;#039;processed&amp;#039;, updated_at = NOW()&amp;quot;
  where_clause: &amp;quot;status = &amp;#039;pending&amp;#039;&amp;quot;
```

### DELETE
Deletes records matching the specified conditions.

**Important**: The DELETE operation permanently removes data. Always test with &lt;code&gt;--debug&lt;/code&gt; mode first.

```yaml
adapter:
  table_name: old_logs
  pk_columns: [&amp;quot;id&amp;quot;]
  operation: delete
  where_clause: &amp;quot;created_at &amp;lt; &amp;#039;2023-01-01&amp;#039;&amp;quot;
```

### NULL
Executes only &lt;code&gt;before_sql&lt;/code&gt; without UPDATE or DELETE. Useful for archiving, copying, or transforming data.

```yaml
adapter:
  table_name: items
  pk_columns: [&amp;quot;id&amp;quot;]
  operation: &amp;quot;null&amp;quot;
  before_sql: |
    INSERT INTO archived_items (id, name, created_at, archived_at)
    SELECT id, name, created_at, NOW() FROM items WHERE id IN (?)
  where_clause: &amp;quot;status = &amp;#039;inactive&amp;#039;&amp;quot;
```

## Configuration

All settings are managed through a YAML configuration file:

### Database Configuration
```yaml
database:
  host: localhost         # Database host (default: localhost)
  port: 3306             # Database port (default: 3306)
  user: myuser           # Database user (required)
  password: mypassword   # Database password (required)
  database: mydatabase   # Database name (required)
  options:               # MySQL connection options (optional)
    charset: utf8mb4
    parseTime: &amp;quot;true&amp;quot;
    loc: UTC
    timeout: 30s
  # Replica configuration (optional)
  replica_host: replica-db.example.com  # SELECT queries go here
  replica_port: 3306                     # Defaults to primary port
  replica_user: replica_user             # Defaults to primary user
  replica_password: replica_password     # Defaults to primary password
```

When &lt;code&gt;replica_host&lt;/code&gt; is configured:
- SELECT queries (fetching PKs, COUNT) are routed to replica
- UPDATE/DELETE operations always use primary
- SELECT FOR UPDATE (pessimistic locking) uses primary

### Processing Configuration
```yaml
processing:
  batch_size: 1000          # Number of rows per batch
  interval: 1s              # Time between batches (e.g., 1s, 500ms, 2m)
  debug_mode: false         # Log queries without executing updates
  pipeline_buffer: 1        # Buffer size for parallel SELECT/UPDATE
  pessimistic_locking: true  # Use SELECT FOR UPDATE (default: true)
  lock_retry_count: 3       # Number of lock acquisition retries
```

### Adapter Configuration
```yaml
adapter:
  table_name: users         # Target table (required)
  table_alias: u            # Alias for main table (required when using joins)
  pk_columns:               # Primary key column(s) (required)
    - user_id
  operation: update         # &amp;quot;update&amp;quot; (default), &amp;quot;delete&amp;quot;, or &amp;quot;null&amp;quot;
  update_sql: &amp;quot;status = &amp;#039;processed&amp;#039;&amp;quot;  # SET clause (required for update)
  before_sql: &amp;quot;...&amp;quot;         # SQL to execute before operation (required for null)
  where_clause: &amp;quot;status = &amp;#039;pending&amp;#039;&amp;quot;  # Additional WHERE (optional)
  join_clause: &amp;quot;...&amp;quot;        # JOIN statements (optional)
  order_by: &amp;quot;created_at&amp;quot;    # Custom ORDER BY (optional, defaults to PK)
```

### Interactive Control
```yaml
interactive:
  enabled: true             # Enable socket-based control
  socket_path: &amp;quot;/tmp/data-updater.sock&amp;quot;  # Unix socket path
```

### Status File (Automatic Resume)
```yaml
status_file:
  enabled: true             # Enable automatic resume
  path: &amp;quot;/var/lib/status&amp;quot;   # Custom path (optional)
```

## Advanced Features

### JOIN Support

Use JOINs for complex queries that need to reference multiple tables:

```yaml
adapter:
  table_name: items
  table_alias: i
  pk_columns: [&amp;quot;id&amp;quot;]
  operation: delete
  join_clause: |
    LEFT JOIN transaction_evidences te ON te.item_id = i.id
  where_clause: |
    i.status = &amp;#039;cancel&amp;#039;
    AND te.id IS NULL
```

**How it works:**
1. SELECT query uses JOINs + WHERE to fetch PKs
2. DELETE/UPDATE query only uses primary keys (no JOINs)

### Before SQL (Pre-operation Hook)

Execute SQL before each batch within the same transaction:

```yaml
adapter:
  table_name: items
  pk_columns: [&amp;quot;id&amp;quot;]
  operation: delete
  before_sql: |
    INSERT INTO deleted_item_ids (id, created, deleted)
    SELECT id, created, NOW() FROM items WHERE id IN (?)
  where_clause: &amp;quot;status = &amp;#039;cancel&amp;#039;&amp;quot;
```

**Notes:**
- Use &lt;code&gt;IN (?)&lt;/code&gt; placeholder - expanded to all PKs in the batch
- For composite keys: &lt;code&gt;(col1, col2) IN (?)&lt;/code&gt;
- Executed atomically with the main operation
- If &lt;code&gt;before_sql&lt;/code&gt; fails, entire transaction is rolled back

### Custom ORDER BY

Process records in a specific order:

```yaml
adapter:
  table_name: items
  table_alias: i
  pk_columns: [&amp;quot;id&amp;quot;]
  order_by: &amp;quot;i.created, i.id&amp;quot;
```

### Understanding update_sql

The &lt;code&gt;update_sql&lt;/code&gt; parameter specifies the SET clause. **Do not include trailing semicolons.**

```yaml
# Simple status update
update_sql: &amp;quot;status = &amp;#039;processed&amp;#039;&amp;quot;
# Results in: UPDATE users SET status = &amp;#039;processed&amp;#039; WHERE user_id IN (...)

# Multiple columns
update_sql: &amp;quot;status = &amp;#039;archived&amp;#039;, archived_at = NOW()&amp;quot;

# Using CASE statements
update_sql: |
  status = CASE
    WHEN last_login &amp;lt; NOW() - INTERVAL 30 DAY THEN &amp;#039;inactive&amp;#039;
    ELSE &amp;#039;active&amp;#039;
  END
```

**Important**:
- Do NOT include UPDATE keyword, table name, or WHERE clause
- The tool automatically adds WHERE pk IN (...) for batch updates

### Using where_clause for Idempotent Operations

Make updates safe to run multiple times:

```yaml
adapter:
  update_sql: &amp;quot;status = &amp;#039;processed&amp;#039;, processed_at = NOW()&amp;quot;
  where_clause: &amp;quot;status = &amp;#039;pending&amp;#039;&amp;quot;
# Results in: UPDATE users SET ... WHERE user_id IN (...) AND status = &amp;#039;pending&amp;#039;
```

## Command Line Options

- &lt;code&gt;--config, -c&lt;/code&gt;: Path to YAML configuration file (required for operation)
- &lt;code&gt;--debug, -d&lt;/code&gt;: Enable debug mode (SELECT only, no updates)
- &lt;code&gt;--resume-from&lt;/code&gt;: Manual resume from specific primary key(s)
- &lt;code&gt;--total-rows&lt;/code&gt;: Skip initial COUNT query and use provided value (e.g., &lt;code&gt;--total-rows 1000000&lt;/code&gt;). Also used as a stop condition based on &lt;code&gt;rows_handled&lt;/code&gt; (rows selected), not &lt;code&gt;rows_processed&lt;/code&gt; (rows affected by UPDATE)
- &lt;code&gt;--pk-source&lt;/code&gt;: Read PKs from file/directory instead of table (local path or &lt;code&gt;gs://bucket/path&lt;/code&gt;)
- &lt;code&gt;--version, -v&lt;/code&gt;: Show version information
- &lt;code&gt;--help, -h&lt;/code&gt;: Show help message

## Interactive Commands

Control the tool via Unix socket:

```bash
# Show status
echo &amp;quot;status&amp;quot; | nc -U /tmp/data-updater.sock

# Pause/resume processing
echo &amp;quot;pause&amp;quot; | nc -U /tmp/data-updater.sock
echo &amp;quot;resume&amp;quot; | nc -U /tmp/data-updater.sock

# Change batch size
echo &amp;quot;batch-size 5000&amp;quot; | nc -U /tmp/data-updater.sock

# Change interval
echo &amp;quot;interval 500ms&amp;quot; | nc -U /tmp/data-updater.sock

# Show help
echo &amp;quot;help&amp;quot; | nc -U /tmp/data-updater.sock

# Auto-interval: show status / enable / disable / set min
echo &amp;quot;auto-interval&amp;quot; | nc -U /tmp/data-updater.sock
echo &amp;quot;auto-interval on&amp;quot; | nc -U /tmp/data-updater.sock
echo &amp;quot;auto-interval off&amp;quot; | nc -U /tmp/data-updater.sock
echo &amp;quot;auto-interval min 200ms&amp;quot; | nc -U /tmp/data-updater.sock
```

## Debug Mode

Debug mode allows you to verify queries without executing updates:

```bash
data-updater --config config.yaml --debug
```

Example output:
```
INFO DEBUG: UPDATE query that would be executed query=&amp;quot;UPDATE users SET status = &amp;#039;processed&amp;#039; WHERE user_id IN (?,?,?)&amp;quot; args_count=3 primary_keys_count=3
```

## Resume Feature

### Automatic Resume (Default)
- Progress saved after each successful batch
- On restart, automatically resumes from last position
- Status files named: &lt;code&gt;data-updater-{table}-{adapter}.status&lt;/code&gt;

### Manual Resume
```bash
# Single primary key
data-updater --config config.yaml --resume-from &amp;quot;12345&amp;quot;

# Composite primary key
data-updater --config config.yaml --resume-from &amp;quot;tenant1,12345&amp;quot;
```

### Resume Priority
1. Manual &lt;code&gt;--resume-from&lt;/code&gt; (highest)
2. Status file (if exists)
3. Adapter&apos;s initial cursor (default)

### Skip COUNT Query
Use &lt;code&gt;--total-rows&lt;/code&gt; to skip the initial COUNT query:
```bash
# Useful for large tables or retries where you know the total
data-updater --config config.yaml --total-rows 1000000
```

This is particularly useful when:
- Retrying after interruption (you already know the count)
- Large tables where COUNT(*) is expensive
- Faster startup when exact count is not critical

**Stop condition:** &lt;code&gt;--total-rows&lt;/code&gt; stops the selector after handling (selecting) that many rows. The stop check uses &lt;code&gt;rows_handled&lt;/code&gt;, not &lt;code&gt;rows_processed&lt;/code&gt;. This means it works correctly even when UPDATE affects 0 rows (e.g., records already deleted by another process or filtered out by &lt;code&gt;where_clause&lt;/code&gt;).

## PK Source (Read PKs from File)

Read primary keys from a file instead of the database table.

**Important:** &lt;code&gt;--total-rows&lt;/code&gt; is required when using &lt;code&gt;--pk-source&lt;/code&gt; for accurate progress/ETA calculation.

```bash
# Count lines first
wc -l failed-ids.txt
# 1500 failed-ids.txt

# From local file (--total-rows is required)
data-updater --config config.yaml --pk-source &amp;quot;./failed-ids.txt&amp;quot; --total-rows 1500

# From local directory (processes all files)
data-updater --config config.yaml --pk-source &amp;quot;./failed-ids/&amp;quot; --total-rows 5000

# From GCS file
data-updater --config config.yaml --pk-source &amp;quot;gs://bucket/failed-ids.txt&amp;quot; --total-rows 1500

# From GCS directory
data-updater --config config.yaml --pk-source &amp;quot;gs://bucket/failed-ids/&amp;quot; --total-rows 10000
```

Or configure in YAML:
```yaml
pk_source:
  path: &amp;quot;gs://my-bucket/failed-ids/&amp;quot;
  gcs_project: &amp;quot;my-gcp-project&amp;quot;  # Required for GCS paths
  skip_header: true              # Skip first line (for BQ exports with header)
  prefetch_buffer: 5             # Number of GCS files to prefetch ahead (default: 5)
```

**GCS Authentication:**

GCS access uses Application Default Credentials (ADC). Set up with:
```bash
gcloud auth application-default login
gcloud auth application-default set-quota-project &amp;lt;project&amp;gt;
```

**File format (CSV):**
```
# Comments starting with # are ignored
12345
12346
tenant1,12345
&amp;quot;value,with,comma&amp;quot;,12346
```

**Skip header (for BigQuery exports):**

BigQuery exports include a header row with column names. Use &lt;code&gt;skip_header: true&lt;/code&gt; to skip it:
```csv
id
12345
12346
```

**Features:**
- Files are read line by line (streaming) to minimize memory usage
- GCS files are prefetched in the background to eliminate download latency (configurable buffer, default 5)
- Directory support: processes all files in sorted order
- Resume support: tracks progress per file and line number
- Can be combined with &lt;code&gt;where_clause&lt;/code&gt; to filter PKs from file

## Status Metrics

Status logs and the &lt;code&gt;status&lt;/code&gt; interactive command report two counters:

- **&lt;code&gt;rows_processed&lt;/code&gt;**: rows successfully affected by the UPDATE/DELETE operation (i.e., the database reported a row change)
- **&lt;code&gt;rows_handled&lt;/code&gt;**: rows selected and sent through the pipeline, regardless of whether the UPDATE/DELETE actually modified the row. This counter is used for progress percentage and ETA calculations

When &lt;code&gt;rows_handled&lt;/code&gt; is higher than &lt;code&gt;rows_processed&lt;/code&gt;, it typically means some rows were already in the desired state (e.g., already deleted or already updated by a previous run).

## Hibernate (Health-Check Based Pause)

The hibernate feature allows the processor to periodically run an external health-check script. If the script returns a non-zero exit code (indicating a problem), the processor pauses for a configurable period, then automatically resumes.

### Configuration

```yaml
processing:
  hibernate_script_path: &amp;quot;/path/to/check.sh&amp;quot;
  hibernate_pause_period: 30s
  hibernate_check_interval: 15s
```

- &lt;code&gt;hibernate_script_path&lt;/code&gt;: Path to an executable script. The script is run at the configured check interval (default 15s). Exit code 0 means healthy; any non-zero exit code triggers hibernation.
- &lt;code&gt;hibernate_pause_period&lt;/code&gt;: How long the processor pauses when the script signals a problem. Required when &lt;code&gt;hibernate_script_path&lt;/code&gt; is set.
- &lt;code&gt;hibernate_check_interval&lt;/code&gt;: How often the health-check script is executed. Defaults to &lt;code&gt;15s&lt;/code&gt;.

### Behavior

1. The health-check script is executed at the configured interval (default 15s) while the processor is running
2. If the script exits with code 0, processing continues normally
3. If the script exits with a non-zero code, the processor pauses for &lt;code&gt;hibernate_pause_period&lt;/code&gt;, then automatically resumes
4. The &lt;code&gt;hibernation_count&lt;/code&gt; metric tracks the total number of times hibernation was triggered (visible in &lt;code&gt;status&lt;/code&gt; command output and periodic logs)

### Use Cases

- Pause when database replication lag exceeds a threshold
- Pause when disk space is low
- Pause during maintenance windows
- Any custom operator-defined health check

## Hourly Summary Log

For long-running jobs, you can enable an hourly summary log that writes JSON entries to a dedicated file. A final summary is also written on shutdown, so short-lived runs still produce a report.

```yaml
processing:
  hourly_log_path: &amp;quot;/var/log/data-updater/hourly.log&amp;quot;
```

Each JSON line includes:
- &lt;code&gt;rows_processed_total&lt;/code&gt; / &lt;code&gt;rows_processed_delta&lt;/code&gt; — records processed in total and during the period
- &lt;code&gt;rows_failed_total&lt;/code&gt; / &lt;code&gt;rows_failed_delta&lt;/code&gt;
- &lt;code&gt;hibernation_count_total&lt;/code&gt; / &lt;code&gt;hibernation_count_delta&lt;/code&gt;
- &lt;code&gt;total_rows&lt;/code&gt;, &lt;code&gt;rows_remaining&lt;/code&gt;, &lt;code&gt;progress&lt;/code&gt; — overall progress
- &lt;code&gt;interactive_commands&lt;/code&gt; — commands issued via socket during the period (with timestamps)
- &lt;code&gt;summary_type&lt;/code&gt; — &lt;code&gt;&amp;quot;hourly&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;final&amp;quot;&lt;/code&gt;

If &lt;code&gt;hourly_log_path&lt;/code&gt; is not set, the reporter is not started.

## Auto-Interval Adjustment

Automatically adjusts the processing interval based on the hibernation ratio observed each hour. When many hibernate checks fail (high ratio), the interval increases (slows down). When the ratio is low, the interval decreases (speeds up).

```yaml
processing:
  auto_interval_enabled: true
  auto_interval_high_ratio: 0.3    # ratio &amp;gt;= this → slow down (default: 0.3)
  auto_interval_low_ratio: 0       # ratio &amp;lt;= this → speed up (default: 0)
  auto_interval_increase_factor: 1.25  # multiply interval by this to slow down (default: 1.25)
  auto_interval_decrease_factor: 0.8   # multiply interval by this to speed up (default: 0.8)
  auto_interval_min: 200ms         # floor for interval (default: initial interval)
  auto_interval_max: 30s           # ceiling for interval (default: 10x min)
```

Auto-interval can be toggled at runtime via socket commands (&lt;code&gt;auto-interval on/off&lt;/code&gt;). See [Interactive Commands](#interactive-commands).

## Pessimistic Locking

Prevent concurrent modifications with pessimistic locking:

```yaml
processing:
  pessimistic_locking: true  # default
  lock_retry_count: 3
```

Transaction pattern:
```sql
BEGIN;
SELECT ... FOR UPDATE WHERE ID IN (...);
UPDATE ... WHERE ID IN (...);
COMMIT;
```

- MySQL 8.0+: Uses &lt;code&gt;NOWAIT&lt;/code&gt; clause
- Sets &lt;code&gt;innodb_lock_wait_timeout=1&lt;/code&gt; to minimize lock wait

## Environment Variables

Use environment variables for sensitive data:

```yaml
database:
  host: &amp;quot;${DB_HOST}&amp;quot;
  user: &amp;quot;${DB_USER}&amp;quot;
  password: &amp;quot;${DB_PASSWORD}&amp;quot;
  database: &amp;quot;${DB_NAME}&amp;quot;
```

## Examples

See the &lt;code&gt;examples/&lt;/code&gt; directory for complete configuration files:

- &lt;code&gt;minimal-config.yaml&lt;/code&gt;: Bare minimum configuration
- &lt;code&gt;full-config.yaml&lt;/code&gt;: All available options with comments
- &lt;code&gt;production-config.yaml&lt;/code&gt;: Production-ready configuration
- &lt;code&gt;complex-update.yaml&lt;/code&gt;: Complex SQL with CASE statements
- &lt;code&gt;multiline-example.yaml&lt;/code&gt;: Multi-line SQL using YAML block scalars
- &lt;code&gt;update-sql-examples.yaml&lt;/code&gt;: Various update_sql patterns

## Production Tips

1. **Use environment variables** for sensitive data
2. **Enable status files** for automatic resume
3. **Set appropriate intervals** to avoid overwhelming the database
4. **Use pessimistic locking** for critical data consistency
5. **Configure replica** to offload SELECT queries from primary
6. **Test with debug mode** before running DELETE operations
7. **Use before_sql** to archive data before deletion

## Troubleshooting

### Common Issues

1. **Permission denied on socket**: Check socket path permissions
2. **Resume not working**: Verify status file path and permissions
3. **Slow processing**: Increase batch size or decrease interval
4. **Lock timeouts**: Enable pessimistic locking or increase retry count

## License

See LICENSE file in the repository root.
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>TiDB の DM 利用中に安全に DDL を実行する</title><link>https://engineering.mercari.com/blog/entry/20260319-2f5c9d904d/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260319-2f5c9d904d/</guid><description>&lt;p&gt;はじめに 現在メルカリでは CoreDB と呼ばれる巨大な MySQL を TiDB に移行しています[^1]. この記事内でも紹介されていますが, 私たちは移行するために MySQL と TiDB を DM というツー [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 23 Mar 2026 09:00:33 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;現在メルカリでは CoreDB と呼ばれる巨大な MySQL を TiDB に移行しています[^1]. この記事内でも紹介されていますが, 私たちは移行するために MySQL と TiDB を &lt;a href=&quot;https://github.com/pingcap/tiflow/tree/master/dm&quot;&gt;DM&lt;/a&gt; というツールで差分同期を行っています. 本記事ではこの DM を利用しつつ DDL(Data Definition Language) をどの様に実行しているかについて紹介します.&lt;/p&gt;
&lt;h2&gt;メルカリでの MySQL への DDL 実行&lt;/h2&gt;
&lt;p&gt;まず, メルカリにおける MySQL への DDL 実行は下記の通り場合分けして実行しています:&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/0e7b7f05-ddl_condition.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
それぞれの条件について簡単に解説しますが, 基本的に source &amp;#8211; replica の replication 遅延を最小限に抑えるために場合分けしています.&lt;/p&gt;
&lt;h3&gt;メタデータのみの変更&lt;/h3&gt;
&lt;p&gt;まず最初の条件は「メタデータのみの変更かどうか」です. これは Online DDL[^2] のページで &lt;code&gt;[In Place] &amp;amp; ![Rebuilds Table] &amp;amp; [Permit Concurrent DML] &amp;amp; [Only Modifies Metadata]&lt;/code&gt; なものが該当します. 例えば Table 名の変更や Column の default 値の変更, ENUM の追加[^3] などです. これはテーブルの再構築などが不要で一瞬で完了するためそのまま source 側で実行します.&lt;/p&gt;
&lt;h4&gt;metadata のみの変更でも注意すること&lt;/h4&gt;
&lt;p&gt;metadata のみの変更とはいえ注意すべきこと, それは DDL とクエリのロック競合です. 公式ドキュメント[^4]にあるとおり MySQL では  table へのアクセス/変更時に一貫性を保証するために metadata lock を取得しますが, この metadata lock が DDL とアプリケーションが発行するクエリで競合し意図しない影響を及ぼす可能性があります:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- session 1
mysql&amp;gt; BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql&amp;gt; SELECT * FROM foo;
Empty set (0.00 sec)
-- // このまま transaction を保持したままにする

-- session 2
mysql&amp;gt; ALTER TABLE foo ALTER COLUMN id SET DEFAULT 0, ALGORITHM=INSTANT;
-- // metadata のみの変更にもかかわらず実行がブロックされる

-- session 3
mysql&amp;gt; SELECT * FROM foo;
-- // DDL の後続のクエリも待たされる&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このように session 2 移行の同一テーブルへのアクセスがブロックされていますが, ここで processlist と metadata lock の関係を見てみます:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql&amp;gt; SELECT
    -&amp;gt;   t.PROCESSLIST_ID      AS process_id,
    -&amp;gt;   t.PROCESSLIST_USER    AS user,
    -&amp;gt;   t.PROCESSLIST_DB      AS db,
    -&amp;gt;   t.PROCESSLIST_TIME    AS time,
    -&amp;gt;   t.PROCESSLIST_STATE   AS state,
    -&amp;gt;   t.PROCESSLIST_INFO    AS query,
    -&amp;gt;   ml.LOCK_TYPE,
    -&amp;gt;   ml.LOCK_DURATION,
    -&amp;gt;   ml.LOCK_STATUS
    -&amp;gt; FROM performance_schema.metadata_locks ml
    -&amp;gt; JOIN performance_schema.threads t
    -&amp;gt;   ON ml.OWNER_THREAD_ID = t.THREAD_ID
    -&amp;gt; WHERE ml.OBJECT_TYPE = &amp;#039;TABLE&amp;#039;
    -&amp;gt;   AND ml.OBJECT_SCHEMA = &amp;#039;test&amp;#039;
    -&amp;gt;   AND ml.OBJECT_NAME = &amp;#039;foo&amp;#039;
    -&amp;gt;   AND t.PROCESSLIST_ID IS NOT NULL
    -&amp;gt; ORDER BY ml.LOCK_STATUS, process_id;
+------------+------+------+------+---------------------------------+------------------------------------------------------------------+-------------------+---------------+-------------+
| process_id | user | db   | time | state                           | query                                                            | LOCK_TYPE         | LOCK_DURATION | LOCK_STATUS |
+------------+------+------+------+---------------------------------+------------------------------------------------------------------+-------------------+---------------+-------------+
|          8 | root | test |  516 | NULL                            | NULL                                                             | SHARED_READ       | TRANSACTION   | GRANTED     |
|          9 | root | test |  514 | Waiting for table metadata lock | alter table foo alter column id set default 0, ALGORITHM=INSTANT | SHARED_UPGRADABLE | TRANSACTION   | GRANTED     |
|          9 | root | test |  514 | Waiting for table metadata lock | alter table foo alter column id set default 0, ALGORITHM=INSTANT | EXCLUSIVE         | TRANSACTION   | PENDING     |
|         16 | root | test |  512 | Waiting for table metadata lock | SELECT * FROM foo                                                | SHARED_READ       | TRANSACTION   | PENDING     |
+------------+------+------+------+---------------------------------+------------------------------------------------------------------+-------------------+---------------+-------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この様に DDL(id=9)が Exclusive lock を取ろうとして親の transaction(id=8)を待っていて, DDL の後続(id=16)が更に待たされている事がわかります. これを避けるために, たとえ metadata lock のものであっても下記のように &lt;code&gt;lock_wait_timeout&lt;/code&gt; を十分短い値に指定することでこの例の後続のクエリになるべく影響を与えない様に実行することが重要です. 例えばここでは 5s に設定した場合のそれぞれの挙動を確認します:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- session 1
mysql&amp;gt; BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql&amp;gt; SELECT * FROM foo;
Empty set (0.02 sec)

-- session 2
mysql&amp;gt; SET SESSION lock_wait_timeout=5;
mysql&amp;gt; SELECT NOW(); ALTER TABLE foo ALTER COLUMN id SET DEFAULT 0, ALGORITHM=INSTANT; SELECT NOW();
+---------------------+
| NOW()               |
+---------------------+
| 2026-03-05 01:59:32 |
+---------------------+
1 row in set (0.01 sec)

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
+---------------------+
| NOW()               |
+---------------------+
| 2026-03-05 01:59:37 |
+---------------------+
1 row in set (0.00 sec)

-- session 3
mysql&amp;gt; SELECT NOW(); SELECT * FROM foo; SELECT NOW();
+---------------------+
| NOW()               |
+---------------------+
| 2026-03-05 01:59:34 |
+---------------------+
1 row in set (0.01 sec)

Empty set (3.03 sec)

+---------------------+
| NOW()               |
+---------------------+
| 2026-03-05 01:59:37 |
+---------------------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1秒未満で完了するか&lt;/h3&gt;
&lt;p&gt;続いての条件は「1秒未満で完了するかどうか」です. これについては具体的なケースを紹介することは難しいのですが, 例えば &lt;code&gt;CREATE TABLE&lt;/code&gt; 文, 空もしくは十分少ないレコード数に対するメタデータの変更で完結しない DDL[^5] などがこれに当たります. ここでなぜ「1秒未満」という条件を付与しているかについてですが, MySQL の DDL はたとえ online としても replication を構成しているときには下記の制限があります[^6]:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Long running online DDL operations can cause replication lag. An online DDL operation must finish running on the source before it is run on the replica. Also, DML that was processed concurrently on the source is only processed on the replica after the DDL operation on the replica is completed.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;online DDL としても replica で実行される前に source で完了している必要があり, また source で並行で実行されている DML は replica での DDL が完了した後に実行されます. つまり, source で並行実行されている DML は replica では DDL が完了するまでブロックされるということです. これは source 側で(online) DDL による source 側の table definition の変更と並行実行されている DML の対応が replica 側でも同様にして再現される必要があります. そのため replica 側でも table defintion の変更と DML の整合性を担保するためにこのような仕組みとなっています.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/d72dc649-mysql_ddl.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;冒頭の記事のようにメルカリのデータサイズは非常に巨大で中には DDL 完了に1日以上かかるものもあり, そのようなテーブルへの DDL による replication 遅延を防ぐために, 基本的に即座に終わらない DDL はそのまま実行しないようにしています. 実際に DDL が 1s 未満で完了するかどうかは経験則に基づくことが多いですが, 例えばサービスで稼働していないホストで &lt;code&gt;SET sql_log_bin=0&lt;/code&gt; を実行してバイナリログに出力しないようにして実測するなどで計測可能です.&lt;br /&gt;
また, DDL 自体が 1s 未満で完了するとしても先の metadata lock による影響は考慮する必要があるため, この場合も同様に &lt;code&gt;lock_wait_timeout&lt;/code&gt; を調整する必要があります.&lt;/p&gt;
&lt;h3&gt;gh-ost で対応可能か&lt;/h3&gt;
&lt;p&gt;時間のかかる DDL を実行する場合, メルカリでは &lt;a href=&quot;%20https://engineering.mercari.com/blog/entry/20220217-d0d6aa21fe/%20&quot;&gt;gh-ost を採用しています&lt;/a&gt;. gh-ost を利用できるかどうかについてはリソース的な制限(テーブルコピーを伴うため余剰なディスクサイズが必要, 通常の更新と gh-ost による backfill/差分同期による replication 遅延影響)や機能的な制限(UNIQUE KEY の一部に ENUM が含まれる場合の性能劣化[^7])がない限り gh-ost を利用しており, DDL 実行オペレーションで最も数の多いケースです. もしそれらを満たさない場合には Rolling Upgrade, つまり全 replica に対して DDL を実行し最後に source を切り替えるといったことを行います.  &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/82f6366b-ghost.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;なぜ ENUM を含む場合に性能劣化するのか&lt;/h4&gt;
&lt;p&gt;MySQL の&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/enum.html#enum-sorting%20&quot;&gt;公式ドキュメント&lt;/a&gt;によると ENUM は文字列順ではなく内部 index 順でソートされますが, これは Go 言語[^8]の文字列によるハンドリングと異なります. この ENUM の取り扱いの不一致による iteration 時のデータ欠損を避けるために, gh-ost では UNIQUE KEY に ENUM が含まれる場合には &lt;code&gt;CONCAT(...)&lt;/code&gt; により明示的な文字列として取り扱われます[^9]. この時, &lt;code&gt;ORDER BY CONCAT(...) ASC&lt;/code&gt; が実行されることになり結果として iteration のたびに &lt;code&gt;Creating Sort Index&lt;/code&gt; が発生し性能, 負荷ともに劣化する可能性があります.&lt;/p&gt;
&lt;h2&gt;DM を用いた DDL 実行&lt;/h2&gt;
&lt;p&gt;ここから TiDB の話に移ります. 前述の通りメルカリでは移行に伴う停止時間をなるべく短くするために DM を用いて MySQL と TiDB で差分同期をしつつ切り替えを進めています:&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/576a0a94-slide.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
この時, 前述の条件 3 の場合にどのような挙動になるかを考えてみます. &lt;a href=&quot;https://docs.pingcap.com/tidb/stable/feature-online-ddl/&quot;&gt;こちらのドキュメント&lt;/a&gt;の通り, DM には &lt;code&gt;online-ddl&lt;/code&gt; というフラグがあり pt-osc や gh-ost といった online migration tool のユースケースをカバーしています. ここからは gh-ost を例にして説明していきます.&lt;br /&gt;
まず,  gh-ost で &lt;code&gt;test&lt;/code&gt; database の &lt;code&gt;foo&lt;/code&gt; table に DDL が適応される流れについて説明します:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;メタデータテーブル(ghc) を作成
&lt;ol&gt;
&lt;li&gt;&lt;code&gt; Create /* gh-ost */ table test._foo_ghc&lt;/code&gt;  &lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;realtable をもとに切り替え後のテーブル(gho)を作成
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Create /* gh-ost */ table test._foo_gho like test.foo&lt;/code&gt;  &lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;gho に DDL の適応
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ALTER /* gh-ost */ table test._foo_gho ...&lt;/code&gt;  &lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;realtable から gho への backfill  &lt;/li&gt;
&lt;li&gt;backfill 完了後 realtable から gho への差分同期  &lt;/li&gt;
&lt;li&gt;差分同期完了後 RENAME 文を使って cutover
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;RENABLE TABLE foo TO _foo_del, _foo_gho TO foo&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;このフローで &lt;code&gt;online-ddl&lt;/code&gt; が有効化されている場合に DM はどのような挙動になるでしょうか.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;del&gt;メタデータテーブル(ghc) を作成&lt;/del&gt; DM は ghc テーブルを作成しない  &lt;/li&gt;
&lt;li&gt;&lt;del&gt;realtable をもとに切り替え後のテーブル(gho)を作成&lt;/del&gt; DM は gho テーブルを作成しない, その代わりにメタデータテーブル &lt;code&gt;dm_meta.{task_name}_onlineddl&lt;/code&gt; を初期化する
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;DELETE FROM dm_meta.{task_name}_onlineddl WHERE id = {server_id} and ghost_schema = {ghost_schema} and ghost_table = {ghost_table};&lt;/code&gt;  &lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;del&gt;gho に DDL の適応&lt;/del&gt; DM が実行される DDL をメタデータテーブル  &lt;code&gt;dm_meta.{task_name}_onlineddl&lt;/code&gt; に保存する
&lt;ol&gt;
&lt;li&gt;この DDL は後に利用される  &lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;del&gt;realtable から gho への backfill&lt;/del&gt; DM は realtable への更新のみ TiDB に同期する
&lt;ol&gt;
&lt;li&gt;gho への更新はすべて破棄される  &lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;del&gt;backfill 完了後 realtable から gho への差分同期&lt;/del&gt; 4 と同様に realtable への更新のみ同期  &lt;/li&gt;
&lt;li&gt;&lt;del&gt;差分同期完了後 RENAME 文を使って cutover&lt;/del&gt; DM は cutover の RENAME 文を分割し, gho table から realtable への RENAME 実行の際に下記を実施する
&lt;ol&gt;
&lt;li&gt;3 で保存した DDL を取得  &lt;/li&gt;
&lt;li&gt;DDL の gho を realtable に置換  &lt;/li&gt;
&lt;li&gt;置換された DDL を実行
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ALTER table test.foo ...;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;このようにして DM は OnlineDDL ツールを利用する際に realtable から gho への同期に伴う処理を削減しています(&lt;code&gt;online-ddl=false&lt;/code&gt; を指定した場合は通常通り gho/ghc などが作成される).&lt;br /&gt;
まとめると下記のようになります:&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/7a33d0e3-onlineddl.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;MySQL&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;TiDB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;メタデータテーブル(ghc) を作成&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;ghc テーブルは作成しない&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;realtable をもとに切り替え後のテーブル(gho)を作成&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;gho テーブルは作成せず DM のメタデータテーブルを初期化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;gho に DDL の適応&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;実行予定の DDL をメタデータテーブルに保存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;realtable から gho への backfill&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;realtable への更新のみ TiDB に同期&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;backfill 完了後 realtable から gho への差分同期&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;realtable への更新のみ TiDB に同期&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;差分同期完了後 RENAME 文を使って cutover&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;RENAME 文を分割し保存していた DDL を実際に realtable に対して実行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;gh-ost 利用時の DM の挙動の比較&lt;/p&gt;
&lt;h2&gt;DDL 実行時のトラブル&lt;/h2&gt;
&lt;p&gt;メルカリでは  &lt;code&gt;online-ddl=true &amp;amp; gh-ost&lt;/code&gt; による DDL 実行で運用しており, 特に大きな問題もなく運用できていました. しかしある時の DDL 実行で問題が発生します. その問題とは DM の replication lag が通常 1~2s 未満だったものが突如 1h 以上遅延するという事象でした.&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/8b5be8f4-replication_lag.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
DM の status は RUNNING にも関わらずこの様に Replication QPS(DM syncer の完了した Job 数)が 16:00 頃から 0 になっており, DML が何も実行されなくなっていました:&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/1bd73a75-replication_qps.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;TiDB のサポートチームとともに調査したところ, この QPS が停止したタイミングで下記の DDL が実行されていたことがわかりました.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE `mercari`.`foo` MODIFY COLUMN `bar_id` int(10) UNSIGNED NOT NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/826dec53-ddl_running_job_count.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
この DDL は先の MySQL における DDL 実行パターンにおいて gh-ost を利用するものであったため, 先で紹介した cutover(&lt;code&gt;RENAME TABLE&lt;/code&gt;) 実行時に ALTER が実行されておりこの DDL により DM の遅延が発生していることがわかりました.&lt;br /&gt;
原因はわかりましたが, なぜ DM が遅延したのでしょうか. これは先に紹介した Online DDL の replica で実行される制限と同じ理由で, TiDB が直接 MySQL の binlog を読めないため DM が先の source/replica での table defnition と DML の整合性担保を保証するための仕組みとなっています. では v8.5.3 を例に実際にコードの中をみてみましょう[^10]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// dm/syncer/syncer.go#L713
func (s *Syncer) Process(ctx context.Context, pr chan pb.ProcessResult) {
    // ...
    err := s.Run(newCtx)
    if err != nil {
        // returned error rather than sent to runFatalChan
        // cancel goroutines created in s.Run
        cancel()
    }
    // ...
}

// dm/syncer/syncer.go#L1741
func (s *Syncer) Run(ctx context.Context) (err error) {
    // ...
    s.runWg.Add(1)
    go s.syncDML()
    s.runWg.Add(1)
    go func() {
        defer s.runWg.Done()
        // also need to use a different ctx. checkpointFlushWorker worker will be closed in the first defer
        s.checkpointFlushWorker.Run(s.tctx)
    }()
    s.runWg.Add(1)
    go s.syncDDL(adminQueueName, s.ddlDBConn, s.ddlJobCh)
    // ...
}

// dm/syncer/syncer.go#L1419
func (s *Syncer) syncDDL(queueBucket string, db *dbconn.DBConn, ddlJobChan chan *job) {
    defer s.runWg.Done()

    var err error
    for {
        ddlJob, ok := &amp;lt;-ddlJobChan
        if !ok {
            return
        }
    // ...
        if !ignore {
    // ...
            affected, err = db.ExecuteSQLWithIgnore(s.syncCtx, s.metricsProxies, errorutil.IsIgnorableMySQLDDLError, ddlJob.ddls)
    // ...
        }
    }
}

// dm/syncer/ddl.go#L221
func (ddl *DDLWorker) HandleQueryEvent(ev *replication.QueryEvent, ec eventContext, originSQL string) (err error) {
    // ...
    if err = ddl.flushJobs(); err != nil {
        return err
    }

    return ddl.strategy.handleDDL(qec)
}

// dm/syncer/syncer.go#L3210
func (s *Syncer) flushJobs() error {
    flushJobSeq := s.getFlushSeq()
    s.tctx.L().Info(&amp;quot;flush all jobs&amp;quot;, zap.Stringer(&amp;quot;global checkpoint&amp;quot;, s.checkpoint), zap.Int64(&amp;quot;flush job seq&amp;quot;, flushJobSeq))
    job := newFlushJob(s.cfg.WorkerCount, flushJobSeq)
    _, err := s.handleJobFunc(job)
    return err
}

// dm/syncer/syncer.go#L1115
func (s *Syncer) handleJob(job *job) (added2Queue bool, err error) {
    // ...
    s.addJob(job)
    // ...
}

// dm/syncer/syncer.go#L1016
func (s *Syncer) addJob(job *job) {
    // ...
    tp := job.tp
    switch tp {
    case flush:
        s.jobWg.Add(1)
        s.dmlJobCh &amp;lt;- job
    case ddl:
        s.updateJobMetrics(false, adminQueueName, job)
        s.jobWg.Add(1)
        startTime := time.Now()
        s.ddlJobCh &amp;lt;- job
        s.metricsProxies.AddJobDurationHistogram.WithLabelValues(&amp;quot;ddl&amp;quot;, s.cfg.Name, adminQueueName, s.cfg.SourceID).Observe(time.Since(startTime).Seconds())
    // ...
    }
}

// dm/syncer/syncer.go#L1617
func (s *Syncer) syncDML() {
    defer s.runWg.Done()

    dmlJobCh := s.dmlJobCh
    if s.cfg.Compact {
        dmlJobCh = compactorWrap(dmlJobCh, s)
    }
    causalityCh := causalityWrap(dmlJobCh, s)
    flushCh := dmlWorkerWrap(causalityCh, s)

    for range flushCh {
        s.jobWg.Done()
    }
}

// dm/syncer/syncer.go#L1419
func (s *Syncer) syncDDL(queueBucket string, db *dbconn.DBConn, ddlJobChan chan *job) {
    defer s.runWg.Done()

    var err error
    for {
        ddlJob, ok := &amp;lt;-ddlJobChan
        if !ok {
            return
        }
        // ...
        if !ignore {
            // ...
            affected, err = db.ExecuteSQLWithIgnore(s.syncCtx, s.metricsProxies, errorutil.IsIgnorableMySQLDDLError, ddlJob.ddls)
            // ...
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この部分で binlog から DDL イベントが来たときに, まず DML の flush を行います. これは DML queue に入っている DML を一度すべて下流(TiDB)にて実行するため, &lt;code&gt;s.jobWg.Wait()&lt;/code&gt; それらの完了を待機します.&lt;/p&gt;
&lt;p&gt;その後 DDL job を DDL queue に投入し, 再び &lt;code&gt;s.jobWg.Wait()&lt;/code&gt; で下流(TiDB)で DDL 実行が完了するまで待機します.&lt;br /&gt;
この DDL による wait で待機している間 binlog イベント処理ループはブロックされるため, DDL が完了するまで DM は後続のイベントを処理しないということになります.&lt;br /&gt;
&lt;code&gt;online-ddl=true&lt;/code&gt; 設定時に gh-ost を利用した DDL の実行は下記のようになります:&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/833a295e-tidb_ddl.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
この様に gh-ost の cut-over(RENAME) 実行時に downstream 側で実際の DDL が発行されそれが完了するまで DML はブロックされるため, DDL が完了するまで replication が遅延することになります.&lt;/p&gt;
&lt;h3&gt;DM を利用しつつ安全に DDL を実行するには&lt;/h3&gt;
&lt;p&gt;この様に &lt;code&gt;online-ddl&lt;/code&gt; は gh-ost など online schema change tool の利用時に同期に必要な無駄を削減させるためのものなため, 指定したとしても下流(TiDB)での DDL の実行完了を待つ必要があります. これは冒頭の無停止で段階的に移行していく際にいくつか問題がある場合があります. MySQL から TiDB へ移行する際にはサービスへの影響が少ないもの(分析基盤のための CDC など)から読み込みを移行させていきますが, これらの中には大きな遅延が許容できないものもあるため, 数時間実行にかかる DDL は影響を与える可能性があります. 一方でメルカリのデータベースはいずれも巨大であり &lt;a href=&quot;https://docs.pingcap.com/tidb/stable/dumpling-overview/&quot;&gt;TiDB Dumpling&lt;/a&gt; や &lt;a href=&quot;https://docs.pingcap.com/tidb/stable/tidb-lightning-overview/&quot;&gt;TiDB Lightning&lt;/a&gt; による export/import でも数日以上かかり, DDL 実行のために DM を停止, つまり TiDB Cluster を再作成するというオペレーションも現実的ではありません. そのため, メルカリでは下記のように DDL の実行方法をケース分けしています:&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/4dcd83f4-ddl_condition2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先の紹介のように &lt;code&gt;online-ddl&lt;/code&gt; を有効化すると MySQL 上で cutover(&lt;code&gt;RENAME TABLE&lt;/code&gt;) されたタイミングで TiDB 側で DDL が実行されます. 仮にこの DDL が例えば &lt;code&gt;MODIFY COLUMN&lt;/code&gt; のように何度でも実行可能な場合は, 先に TiDB 側のみ DDL を実行しておいて gh-ost をすることで2回目の(実質意味がない) DDL では即座に終了するため, 実際の DDL が長時間でも DM の遅延を最小に抑えることが可能です. &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20260304-b782487108/&quot;&gt;前のブログ&lt;/a&gt;にあるようなレコード数が多くさらに INDEX で多数利用されているような column に対する &lt;code&gt;MODIFY COLUMN&lt;/code&gt; などには有用と言えます. それ以外の場合(例えば INDEX 追加など)は実行時間やビジネス要件によってかわりますが, 遅延を許容して gh-ost を実行する, &lt;a href=&quot;https://docs.pingcap.com/tidb/stable/dm-binlog-event-filter/&quot;&gt;Binlog Event Filter&lt;/a&gt; により該当の DDL だけ除外するなど対応が考えられます.&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;今回は DM で MySQL と TiDB を同期しているときに DDL をなるべく安全に実行する方法を紹介しました. DM を利用する際に参考にしていただけると幸いです. 現在メルカリでは DBRE の EM を募集しています, 詳しくは&lt;a href=&quot;https://apply.workable.com/mercari/j/7AD4EF9218&quot;&gt;こちら&lt;/a&gt;をご覧ください.&lt;/p&gt;
&lt;p&gt;[^1]: ref: &lt;a href=&quot;https://pingcap.co.jp/case-study/mercari-tidb-cloud/&quot;&gt;https://pingcap.co.jp/case-study/mercari-tidb-cloud/&lt;/a&gt;　&lt;/p&gt;
&lt;p&gt;[^2]: ref: &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html&quot;&gt;https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html&lt;/a&gt;　&lt;/p&gt;
&lt;p&gt;[^3]: 末尾追加かつ要素の個数が byte を跨がない場合のみ該当&lt;/p&gt;
&lt;p&gt;[^4]: ref:  &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/metadata-locking.html&quot;&gt;https://dev.mysql.com/doc/refman/8.4/en/metadata-locking.html&lt;/a&gt;　&lt;/p&gt;
&lt;p&gt;[^5]: 例えば INDEX や column の操作(追加/削除/タイプ変更)&lt;/p&gt;
&lt;p&gt;[^6]: ref: &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-limitations.html&quot;&gt;https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-limitations.html&lt;/a&gt;　&lt;/p&gt;
&lt;p&gt;[^7]: ref: &lt;a href=&quot;https://github.com/github/gh-ost/blob/master/doc/requirements-and-limitations.md&quot;&gt;https://github.com/github/gh-ost/blob/master/doc/requirements-and-limitations.md&lt;/a&gt;　&lt;/p&gt;
&lt;p&gt;[^8]: ref: gh-ost は Go 言語, pt-osc は Perl で実装されている&lt;/p&gt;
&lt;p&gt;[^9]: ref: &lt;a href=&quot;https://github.com/github/gh-ost/issues/273&quot;&gt;https://github.com/github/gh-ost/issues/273&lt;/a&gt;　&lt;/p&gt;
&lt;p&gt;[^10]: ref: &lt;a href=&quot;https://github.com/pingcap/tiflow/blob/v8.5.3/&quot;&gt;https://github.com/pingcap/tiflow/blob/v8.5.3/&lt;/a&gt;　&lt;/p&gt;
</content:encoded></item><item><title>TiDBのmodify columnの大規模DDL実行</title><link>https://engineering.mercari.com/blog/entry/20260304-b782487108/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260304-b782487108/</guid><description>&lt;p&gt;MySQLと高い互換性を持つデータベースのTiDBでは、DDLが高速かつオンラインで実施されとても有用です。メルカリの運用における気付きとして得られた、主に実行の速度制御とmodify columnの完了時間見積もりの学 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 05 Mar 2026 07:00:28 GMT</pubDate><content:encoded>&lt;p&gt;MySQLと高い互換性を持つデータベースのTiDBでは、DDLが高速かつオンラインで実施されとても有用です。メルカリの運用における気付きとして得られた、主に実行の速度制御とmodify columnの完了時間見積もりの学びについてお伝えします。&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;メルカリではMySQLと高い互換性を持つTiDBを利用しているため、DDLはオンラインで実行でき、現状のところ大きな問題なく動作しています。&lt;/p&gt;
&lt;p&gt;先日、数十億レコード程度のテーブルのALTERを実施した際、実行の完了時刻が予測できない、と感じた事象がありました。この記事ではこの問題について調査して得られた学びを共有したいと思います。DDLにはさまざまなバリエーションがあり、本記事が適用できないケースがあることにご留意ください。&lt;/p&gt;
&lt;p&gt;なお、本記事中についてはTiDB CloudのDedicated(Serverlessでない方)を前提にしております。TiDB Cloudではなく、TiDBのSoftware版でもおそらく同じことがいえると思います。&lt;/p&gt;
&lt;h2&gt;実行中のTiDB障害&lt;/h2&gt;
&lt;p&gt;本記事では、DDLの実行完了の予測について取り扱いたいのですが、その前にDDLの進捗情報の永続化について整理をするために、まずはTiDBのDDLの障害耐性について整理したいと思います。&lt;/p&gt;
&lt;p&gt;長時間のDDL実行における関心事項として、DDL中に障害が発生するとどうなるか、という懸念があげられます。&lt;br /&gt;
結論から言えば、障害が発生してもDDLは継続しますが、これにはDDLをどこまで実行したかの状態を永続化して保持し、かつ障害発生時に他のノードで実行を再開する仕組みが必要です。&lt;/p&gt;
&lt;h3&gt;TiDBでのDDLおよびDDL owner&lt;/h3&gt;
&lt;p&gt;TiDBでのDDL実行においてはDDL ownerという概念があり、あるTiDBがDDL Ownerの役割を担い、そのTiDBが主体となり実際にデータを保存するTiKVに対するDDLの実処理を実行します&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingcap.com/best-practices/ddl-introduction/#tidb-ddl-module&quot;&gt;https://docs.pingcap.com/best-practices/ddl-introduction/#tidb-ddl-module&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://pingcap.github.io/tidb-dev-guide/understand-tidb/ddl.html#execution-in-the-tidb-ddl-owner&quot;&gt;https://pingcap.github.io/tidb-dev-guide/understand-tidb/ddl.html#execution-in-the-tidb-ddl-owner&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;DDL実行中にDDL ownerであるTiDBに障害が発生した場合どうなるでしょうか？&lt;/p&gt;
&lt;p&gt;DDL ownerは、etcdベースで選出がおこなわれ、通常はDDL ownerが定期的にKeepaliveのような死活情報をetcdに報告することにより、etcdリース期間が延長されています。したがって、DDL owner障害時には、このKeepaliveが途絶えることによりetcdリースが失効し、新ownerが選出されます。&lt;/p&gt;
&lt;p&gt;したがって、TiDB障害時にもDDLの処理は継続します。&lt;/p&gt;
&lt;p&gt;メルカリでも、長時間のDDL中に(意図せず)TiDBのスケールダウンを行ってしまう事象が発生しましたが、その際にも実際に処理が正常に継続することを確認できました。&lt;/p&gt;
&lt;p&gt;ただし、TiDBで実行中のプロセス一覧(&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/information-schema-processlist/#cluster_processlist&quot;&gt;information_schema.cluster_processlist&lt;/a&gt;)からは、ALTERのプロセスは確認できなくなる一方で、ADMIN SHOW DDL JOBSを確認すると処理が継続している、という状況でした。&lt;/p&gt;
&lt;h3&gt;DDLの実行状況の永続化&lt;/h3&gt;
&lt;p&gt;次に、DDLの状態の永続化です。DDLに関連するテーブルとしては&lt;br /&gt;
&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/mysql-schema/#system-tables-related-to-ddl-statements&quot;&gt;https://docs.pingcap.com/tidb/stable/mysql-schema/#system-tables-related-to-ddl-statements&lt;/a&gt;&lt;br /&gt;
があり、このうちバックフィルの情報はmysql.tidb_ddl_reorgに以下のように保持されます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&amp;gt; DESC mysql.tidb_ddl_reorg;
+-------------+----------+------+------+---------+-------+
| Field       | Type     | Null | Key  | Default | Extra |
+-------------+----------+------+------+---------+-------+
| job_id      | bigint   | NO   | MUL  | NULL    |       |
| ele_id      | bigint   | YES  |      | NULL    |       |
| ele_type    | blob     | YES  |      | NULL    |       |
| start_key   | blob     | YES  |      | NULL    |       |
| end_key     | blob     | YES  |      | NULL    |       |
| physical_id | bigint   | YES  |      | NULL    |       |
| reorg_meta  | longblob | YES  |      | NULL    |       |
+-------------+----------+------+------+---------+-------+
7 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これは次に処理すべきキーの値をstart_keyとして永続化する、という方法によっているようです。これにより処理が中断された場合、この情報を元に再開、継続することが可能となります。&lt;/p&gt;
&lt;p&gt;また、実行中の設定や進捗の確認に関しては、&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/mysql-schema/#system-tables-related-to-ddl-statements&quot;&gt;mysql.tidb_ddl_job&lt;/a&gt;に永続化されます。&lt;br /&gt;
こちらの詳細は記事の後半で再度内容を紹介します。&lt;/p&gt;
&lt;p&gt;なお、こちらはDDLの実行が完了すると、mysql.tidb_ddl_historyに移動するため注意が必要です。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&amp;gt; desc mysql.tidb_ddl_job;
+------------+------------+------+------+---------+-------+
| Field      | Type       | Null | Key  | Default | Extra |
+------------+------------+------+------+---------+-------+
| job_id     | bigint     | NO   | PRI  | NULL    |       |
| reorg      | int        | YES  |      | NULL    |       |
| schema_ids | mediumtext | YES  |      | NULL    |       |
| table_ids  | mediumtext | YES  |      | NULL    |       |
| job_meta   | longblob   | YES  |      | NULL    |       |
| type       | int        | YES  |      | NULL    |       |
| processing | int        | YES  |      | NULL    |       |
+------------+------------+------+------+---------+-------+
7 rows in set (0.00 sec)

mysql&amp;gt; desc mysql.tidb_ddl_history;
+-------------+------------+------+------+---------+-------+
| Field       | Type       | Null | Key  | Default | Extra |
+-------------+------------+------+------+---------+-------+
| job_id      | bigint     | NO   | PRI  | NULL    |       |
| job_meta    | longblob   | YES  |      | NULL    |       |
| db_name     | char(64)   | YES  |      | NULL    |       |
| table_name  | char(64)   | YES  |      | NULL    |       |
| schema_ids  | mediumtext | YES  |      | NULL    |       |
| table_ids   | mediumtext | YES  |      | NULL    |       |
| create_time | datetime   | YES  |      | NULL    |       |
+-------------+------------+------+------+---------+-------+
7 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;DDL実行中の速度制御&lt;/h2&gt;
&lt;p&gt;大容量テーブルに対する物理的なDDL（Reorg DDL）は、TiKVのリソース（CPUやI/O）を消費しながらデータのバックフィルを行うため、実行に時間がかかったり、リソースを過剰に消費するので、これらのコントロールが必要になることがあります。&lt;/p&gt;
&lt;p&gt;すでに実行中のDDLジョブの速度を動的にコントロールするにはADMIN ALTER DDL JOBSを使用します。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/sql-statement-admin-alter-ddl&quot;&gt;ADMIN ALTER DDL JOBS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;現在の設定値や、どのような操作をするかにより設定可能な項目などは異なりますが、MODIFY COLUMNに区分される実態の再構築(Full Reorg)が必要なDDLでは、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;THREAD: DDLジョブのワーカー数&lt;/li&gt;
&lt;li&gt;BATCH_SIZE: ワーカーが1回のバッチで処理する行数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;の変更が有効です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ADMIN ALTER DDL JOBS &amp;lt;job_id&amp;gt; THREAD = 16, BATCH_SIZE = 1024;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これらのパラメータはTiKVのリソースの消費量と、DDLの実行速度に影響し、負荷を抑えたい場合は、一時的なDDLの中断/再開も可能ですし、スレッド数を減らして実行することもできます。逆にTiKVのキャパシティーに余裕があり、高速に完了させたい場合にスレッド数やバッチサイズを増やしたりすることが有効です。&lt;/p&gt;
&lt;p&gt;また、これらの初期値として、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pingcap.com/ja/tidb/stable/system-variables/#tidb-ddl-reorg-worker-cnt&quot;&gt;tidb_ddl_reorg_worker_cnt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pingcap.com/ja/tidb/stable/system-variables/#tidb-ddl-reorg-batch-size&quot;&gt;tidb_ddl_reorg_batch_size&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;を変更することが可能です。&lt;/p&gt;
&lt;p&gt;通常、ADMIN ALTER DDL JOBS を実行するとその設定変更は即時で反映されることが期待されます。1点注意が必要なのは、特定のバージョン（v8.5.2, v8.5.3 など）において、特定のDDL（MODIFY COLUMN のcharからvarcharへの変更など）に対してADMIN ALTER DDL JOBSを実行しても、ワーカー数などの設定が実質的に反映されない不具合が報告されています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;関連Issue: &lt;a href=&quot;https://github.com/pingcap/tidb/issues/63201&quot;&gt;#63201 ADMIN ALTER DDL JOBS can&amp;#8217;t adjust the modify column job concurrency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;関連PR: &lt;a href=&quot;https://github.com/pingcap/tidb/pull/63605&quot;&gt;#63605 ddl: fix dynamic parameter adjustment failure in txn and local ingest mode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;こちらに関しては、設定変更後に、PAUSE/RESUMEをすれば設定が反映されることを確認しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PAUSE: &lt;a href=&quot;https://docs.pingcap.com/tidb/stable/sql-statement-admin-pause-ddl/&quot;&gt;https://docs.pingcap.com/tidb/stable/sql-statement-admin-pause-ddl/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;RESUME: &lt;a href=&quot;https://docs.pingcap.com/tidb/stable/sql-statement-admin-resume-ddl/&quot;&gt;https://docs.pingcap.com/tidb/stable/sql-statement-admin-resume-ddl/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DDLを実行の際はご利用のバージョンを確認し、必要に応じてPAUSE/RESUMEを試してみてください。&lt;/p&gt;
&lt;p&gt;また、現状設定している値の確認に関しては先ほどの、&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/mysql-schema/#system-tables-related-to-ddl-statements&quot;&gt;mysql.tidb_ddl_job&lt;/a&gt;から確認できます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;mysql&amp;gt; SELECT   job_id,   JSON_EXTRACT(CONVERT(UNHEX(TRIM(LEADING &amp;#039;0x&amp;#039; FROM HEX(job_meta))) USING utf8mb4), &amp;#039;$.reorg_meta.concurrency&amp;#039;) AS concurrency,   JSON_EXTRACT(CONVERT(UNHEX(TRIM(LEADING &amp;#039;0x&amp;#039; FROM HEX(job_meta))) USING utf8mb4), &amp;#039;$.reorg_meta.batch_size&amp;#039;) AS batch_size FROM mysql.tidb_ddl_job WHERE job_id = 149;
+--------+-------------+------------+
| job_id | concurrency | batch_size |
+--------+-------------+------------+
|    149 | 8           | 1024       |
+--------+-------------+------------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;DDL完了時間の見積もり&lt;/h2&gt;
&lt;p&gt;DDLについては実行完了時間の見積もりができることが望ましいと思います。&lt;/p&gt;
&lt;p&gt;まず、前提条件としてTiDBでDDLを発行した際、&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/sql-statement-admin-show-ddl/&quot;&gt;ADMIN SHOW DDL JOBS&lt;/a&gt;コマンドで、&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/information-schema-ddl-jobs/&quot;&gt;information_schema.ddl_jobs&lt;/a&gt;のROW_COUNTという値が観測可能であり、これはDDLの実行に伴って値が増えていきます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;: ALTER文の実行
mysql&amp;gt; ALTER TABLE test_alter_row_count MODIFY COLUMN value INT NOT NULL; Query OK, 0 rows affected (0.28 sec) 

: ADMIN SHOW DDL JOBSコマンドで実行結果/実行中の状況が確認可能
mysql&amp;gt; admin show ddl jobs 1;
+--------+---------+----------------------+---------------+--------------+-----------+----------+-----------+----------------------------+----------------------------+----------------------------+--------+----------+
| JOB_ID | DB_NAME | TABLE_NAME           | JOB_TYPE      | SCHEMA_STATE | SCHEMA_ID | TABLE_ID | ROW_COUNT | CREATE_TIME                | START_TIME                 | END_TIME                   | STATE  | COMMENTS |
+--------+---------+----------------------+---------------+--------------+-----------+----------+-----------+----------------------------+----------------------------+----------------------------+--------+----------+
|    151 | mercari | test_alter_row_count | modify column | public       |       116 |      143 | 100000000 | 2026-02-27 01:45:35.549000 | 2026-02-27 01:45:35.549000 | 2026-02-27 02:03:25.748000 | synced |          |
+--------+---------+----------------------+---------------+--------------+-----------+----------+-----------+----------------------------+----------------------------+----------------------------+--------+----------+
1 row in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この値を利用してどのように、完了時刻見積もりができるか、をバージョンごとに確認したのが本エントリの内容です。&lt;/p&gt;
&lt;h3&gt;v8.5.3まで&lt;/h3&gt;
&lt;p&gt;TiDB v8.5.3で下記のテーブルを作成し、1000行のデータにインデックスを作成していきました&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE test_alter_row_count (
   id BIGINT UNSIGNED NOT NULL,
   value INT UNSIGNED NOT NULL,
   label VARCHAR(255) NOT NULL DEFAULT &amp;#039;&amp;#039;,
   created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
   updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   PRIMARY KEY (id) CLUSTERED
);
-- 1000行を挿入後、value カラムを参照するセカンダリインデックスを 0〜4 本作成
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インデックス作成後に、INT UNSIGNEDから INTへの変更を伴う次のDDLを発行した際のROW_COUNTの変化を観測します。この変更は、UNSIGNEDとSIGNEDではバイト列のエンコーディングが異なるため、行データとインデックスの両方を書き換える必要があり、TiDB内部で、ModifyTypeReorg (Full Reorg)に分類されるものとなります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;ALTER TABLE test_alter_row_count MODIFY COLUMN value INT NOT NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果としては、テーブルの行数をR、変更対象のカラムを含むインデックス数をnとした際に、下記のように変更対象のカラムに関連するインデックス数の2乗のオーダーのROW_COUNTとなること（R x (1 + n²)）が観測されました。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;インデックス数 (n)&lt;/th&gt;
&lt;th&gt;ROW_COUNTの値&lt;/th&gt;
&lt;th&gt;R x (1 + n²)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;17,000&lt;/td&gt;
&lt;td&gt;17,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;複合インデックス（例: (value, created_at, updated_at)）におけるROW_COUNTに対する寄与も確認し、これは単一カラムインデックスと同じ寄与で、インデックスに含まれるカラム数は無関係であることを確認しました。&lt;/p&gt;
&lt;h3&gt;処理の実態と発生条件&lt;/h3&gt;
&lt;p&gt;ROW_COUNTの観測により、DDLの完了時刻をなるべく正確に推測することを目標とすると、&lt;br /&gt;
この R x (1 + n²) という式が、ROW_COUNT の集計上のバグなのか、それとも実際に n² に比例する処理が走っているのかを切り分ける必要があります。&lt;/p&gt;
&lt;p&gt;OSS の最新のmasterブランチを解析すると、masterのMODIFY COLUMNはカラム書き換えとインデックス再構築が別ステージに分離されており、n² パターンを説明できるコードパスは見つかりませんでした。&lt;/p&gt;
&lt;p&gt;8.5.3に関してはMODIFY COLUMN(Full Reorg)はupdateCurrentElementという関数で処理され、この関数にはインデックスを逐次的に処理するループがありました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/pingcap/tidb/blob/release-8.5-20251107-v8.5.3/pkg/ddl/column.go#L523-L597&quot;&gt;https://github.com/pingcap/tidb/blob/release-8.5-20251107-v8.5.3/pkg/ddl/column.go#L523-L597&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// pkg/ddl/column.go L523-596 (v8.5.3)
func (w *worker) updateCurrentElement(ctx context.Context, t table.Table, reorgInfo *reorgInfo) error {
   // Phase 1: カラム書き換え（全 R 行の値を変換）
   if bytes.Equal(reorgInfo.currElement.TypeKey, meta.ColumnElementKey) {
       err := w.updatePhysicalTableRow(ctx, t.(table.PhysicalTable), reorgInfo)
   }

   // Phase 2: インデックス再構築ループ（n 回実行）
   for i := startElementOffset; i &amp;lt; len(reorgInfo.elements[1:]); i++ {
       reorgInfo.currElement = reorgInfo.elements[i+1]  // 現在の要素を idx_i に設定
       err = w.addTableIndex(ctx, t, reorgInfo)          // インデックスを再構築
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここからインデックス再構築でRx n²の操作が発生することになっている実装上の要因2点を説明します。細かい話なので読み飛ばしていただいて構いません。&lt;/p&gt;
&lt;h4&gt;1. Worker が「現在のインデックス」ではなく「全インデックス」を受け取る&lt;/h4&gt;
&lt;p&gt;addTableIndexの内部ではbackfill workerが生成されます。このとき、reorgInfo.elements（カラム＋全 n インデックスを含む完全なリスト）がそのまま渡されます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// pkg/ddl/backfilling_scheduler.go L280-281 (v8.5.3)
idxWorker, err := newAddIndexTxnWorker(b.decodeColMap, b.tbl, backfillCtx,
    job.ID, reorgInfo.elements, reorgInfo.currElement.TypeKey)
//                 ^^^^^^^^^^^^^^^^^^
//                 「全要素リスト」が渡される&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Workerはこのリストからインデックス型の要素をすべて抽出して保持します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// pkg/ddl/index.go L1892-1900 (v8.5.3)
allIndexes := make([]table.Index, 0, len(elements))
for _, elem := range elements {
    if !bytes.Equal(elem.TypeKey, meta.IndexElementKey) {
        continue
    }
    indexInfo := model.FindIndexInfoByID(t.Meta().Indices, elem.ID)
    index := tables.NewIndex(t.GetPhysicalID(), t.Meta(), indexInfo)
    allIndexes = append(allIndexes, index)  // 全 n インデックスが入る
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;つまり、ループの i 番目のイテレーションでcurrElementがidx_iを指していても、Workerは idx₁ からidx_nまでの全インデックスを処理します。&lt;/p&gt;
&lt;p&gt;その結果、テーブルの全行をスキャンする際に各行からn個のidxRecordが生成されます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// pkg/ddl/index.go L2027-2032 (v8.5.3)
for _, index := range w.indexes {  // w.indexes は全 n 個
    idxRecord, _ := w.getIndexRecord(index.Meta(), handle, recordKey)
    w.idxRecords = append(w.idxRecords, idxRecord)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;R行 × nインデックス = n×R 個のレコード。これがn回のイテレーションそれぞれで生成されるため、合計 n × n × R = n²R 個になります。&lt;/p&gt;
&lt;h4&gt;2. DupKeyCheckSkip による重複書き込みの黙認&lt;/h4&gt;
&lt;p&gt;仮に1があっても、2 回目以降のイテレーションでは「すでに同じインデックスエントリが存在する」ため、重複キーエラーで処理がスキップされれば実害は小さいはずです。しかし、BackfillDataではDupKeyCheckSkipフラグが指定されています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// pkg/ddl/index.go L2349-2354 (v8.5.3)
handle, err := w.indexes[i%len(w.indexes)].Create(
    w.tblCtx, txn, idxRecord.vals, idxRecord.handle, idxRecord.rsData,
    table.WithIgnoreAssertion,
    table.FromBackfill,
    table.DupKeyCheckSkip,   // 重複チェックを完全にスキップ
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DupKeyCheckSkipはindex.Create内部で以下のように作用します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// pkg/table/tables/index.go L259-278 (v8.5.3)
skipCheck := opt.DupKeyCheck() == table.DupKeyCheckSkip  // true

if !distinct || skipCheck || untouched {
    err = txn.GetMemBuffer().Set(key, val)  // 既存エントリを無言で上書き
    continue                                 // ErrKeyExists は発生しない
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重複チェックをスキップしているため、MemBufferへのSetは既存エントリを上書きするだけでエラーになりません。BackfillDataに戻るとtaskCtx.addedCount++が無条件に実行され、全 n×Rレコードがカウントされます。&lt;/p&gt;
&lt;h3&gt;v8.5.4での修正（2025年11月）&lt;/h3&gt;
&lt;p&gt;PR &lt;a href=&quot;https://github.com/pingcap/tidb/pull/63970&quot;&gt;#63970&lt;/a&gt; &amp;quot;modify column: support ingest/DXF mode to recreate indexes&amp;quot; により、MODIFY COLUMNの処理アーキテクチャが刷新されたことにより、この問題は解消しました。&lt;/p&gt;
&lt;p&gt;TiDBの変更にはいくつかの種類があり、通常のインデックス追加はデフォルトではIngestという物理ImportであるTiDB lightningと同じ方式の高速な処理方法が適用されます。DXFというのは分散実行の仕組みで、これも通常Indexの追加に利用されます。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/tidb-distributed-execution-framework/&quot;&gt;https://docs.pingcap.com/tidb/stable/tidb-distributed-execution-framework/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;このPRの変更内容は、カラムの変更であるmodify columnの際のインデックス再構築にこの処理を利用可能にしたものです。&lt;/p&gt;
&lt;p&gt;これにより、修正後のアーキテクチャでは、MODIFY COLUMNが 3 つの明示的なステージに分離されました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Stage 1: ReorgStageModifyColumnUpdateColumn → カラム書き換えのみ
Stage 2: ReorgStageModifyColumnRecreateIndex → 全インデックスを一括再構築
Stage 3: ReorgStageModifyColumnCompleted → 完了&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;updateCurrentElementのインデックスループは完全に削除され、modifyTableColumnにリネームされた関数はカラム書き換えのみを担当します。インデックス再構築は doReorgWorkForCreateIndexに委譲され、全インデックスを1 回の呼び出しで一括処理します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// v8.5.4 pkg/ddl/column.go
func (w *worker) modifyTableColumn(...) error {
    if bytes.Equal(reorgInfo.currElement.TypeKey, meta.ColumnElementKey) {
        err := w.updatePhysicalTableRow(ctx, t, reorgInfo)  // カラムのみ
    }
    return nil  // インデックスループは存在しない
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この修正により n² 問題は解消され、doReorgWorkForCreateIndexはpickBackfillType でIngest / TxnMergeを動的に選択できるため、MODIFY COLUMNでもIngest（Lightning ベース）パイプラインが利用可能になりました。&lt;/p&gt;
&lt;p&gt;また、詳細は割愛しますが、本問題は7.4.0から発生していたように思われます。&lt;/p&gt;
&lt;h3&gt;v8.5.4以降&lt;/h3&gt;
&lt;p&gt;さて、8.5.4以降でのバージョンで、modify columnで必要以上に時間がかかる構造は解消されていそうなことがわかりました。どのようにカウントされるようになったのでしょうか。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;インデックス数 (n)&lt;/th&gt;
&lt;th&gt;ROW_COUNTの値&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;インデックス数に依存しないレコード数に等しい値となることが確認できました。&lt;/p&gt;
&lt;p&gt;それでは、これのROW_COUNTの値を利用して、どのように実行の完了時間を予測できるでしょうか。&lt;/p&gt;
&lt;p&gt;これを確認するため、1億レコードのテスト用のテーブルと、4つのインデックスを事前に作成、これらに対してDDLを実行し、ROW_COUNTの推移を実際に観測してみました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/f40fd98d-chart_timeline-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ROW_COUNTは最終的にはレコード数Rになるのですが、最初のカラムの書き換えがおわり、インデックスの再構成が発生する際に一度0にクリアされ、n本のindexが同時構築されるため再度増加しRに達する、という挙動が観測されました。また、Step 2のIngestは高速な処理が行われることも合わせて確認できると思います。&lt;br /&gt;
また、ROW_COUNTがRに達した後も何らかの処理が継続されますが、現状この状態の進捗を何らかの方法で確認する方法は確認できておりません。&lt;/p&gt;
&lt;p&gt;なお、このステージおよび、ROW_COUNTについては、先ほどの&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/mysql-schema/#system-tables-related-to-ddl-statements&quot;&gt;mysql.tidb_ddl_job&lt;/a&gt;により確認可能で、今回検証でこのDDLの進捗確認に利用したスクリプトを共有します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pymysql
import time
import csv
import sys

JOB_ID = 151
INTERVAL = 5

conn = pymysql.connect(
    host=&amp;quot;127.0.0.1&amp;quot;,
    port=3306,
    user=&amp;quot;root&amp;quot;,
    password=&amp;quot;&amp;quot;,
    database=&amp;quot;test&amp;quot;,
    autocommit=True
)

cursor = conn.cursor()
cursor.execute(&amp;quot;SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED&amp;quot;)

log_file = f&amp;quot;ddl_job_{JOB_ID}.csv&amp;quot;
with open(log_file, &amp;quot;w&amp;quot;, newline=&amp;quot;&amp;quot;) as f:
    writer = csv.writer(f)
    writer.writerow([&amp;quot;elapsed_sec&amp;quot;, &amp;quot;timestamp&amp;quot;, &amp;quot;row_count&amp;quot;, &amp;quot;state&amp;quot;, &amp;quot;schema_state&amp;quot;,
                      &amp;quot;stage&amp;quot;, &amp;quot;reorg_tp&amp;quot;, &amp;quot;concurrency&amp;quot;, &amp;quot;batch_size&amp;quot;,
                      &amp;quot;task_id&amp;quot;, &amp;quot;task_state&amp;quot;, &amp;quot;subtask_row_count&amp;quot;])

META_QUERY = &amp;quot;&amp;quot;&amp;quot;
SELECT
  JSON_EXTRACT(CONVERT(UNHEX(TRIM(LEADING &amp;#039;0x&amp;#039; FROM HEX(job_meta))) USING utf8mb4), &amp;#039;$.reorg_meta.stage&amp;#039;),
  JSON_EXTRACT(CONVERT(UNHEX(TRIM(LEADING &amp;#039;0x&amp;#039; FROM HEX(job_meta))) USING utf8mb4), &amp;#039;$.reorg_meta.reorg_tp&amp;#039;),
  JSON_EXTRACT(CONVERT(UNHEX(TRIM(LEADING &amp;#039;0x&amp;#039; FROM HEX(job_meta))) USING utf8mb4), &amp;#039;$.reorg_meta.concurrency&amp;#039;),
  JSON_EXTRACT(CONVERT(UNHEX(TRIM(LEADING &amp;#039;0x&amp;#039; FROM HEX(job_meta))) USING utf8mb4), &amp;#039;$.reorg_meta.batch_size&amp;#039;)
FROM mysql.tidb_ddl_job
WHERE job_id = %s
&amp;quot;&amp;quot;&amp;quot;

print(f&amp;quot;Monitoring JOB_ID={JOB_ID} every {INTERVAL}s → {log_file}&amp;quot;)
print(f&amp;quot;{&amp;#039;elapsed&amp;#039;:&amp;gt;8}  {&amp;#039;row_count&amp;#039;:&amp;gt;14}  {&amp;#039;state&amp;#039;:&amp;lt;10}  {&amp;#039;stage&amp;#039;:&amp;gt;5}  {&amp;#039;reorg_tp&amp;#039;:&amp;gt;8}  {&amp;#039;thr&amp;#039;:&amp;gt;3}  {&amp;#039;batch&amp;#039;:&amp;gt;6}  {&amp;#039;task_id&amp;#039;:&amp;gt;7}  {&amp;#039;task_state&amp;#039;:&amp;lt;10}  {&amp;#039;subtask_rows&amp;#039;:&amp;gt;14}&amp;quot;)
print(&amp;quot;-&amp;quot; * 110)

start = time.time()

while True:
    try:
        # ROW_COUNT (real-time from memory)
        cursor.execute(&amp;quot;&amp;quot;&amp;quot;
            SELECT JOB_ID, ROW_COUNT, STATE, SCHEMA_STATE, NOW()
            FROM information_schema.ddl_jobs
            WHERE JOB_ID = %s
            LIMIT 1
        &amp;quot;&amp;quot;&amp;quot;, (JOB_ID,))
        ddl_row = cursor.fetchone()

        # Stage/config from job_meta (persisted)
        stage = reorg_tp = conc = bs = &amp;quot;&amp;quot;
        try:
            cursor.execute(META_QUERY, (JOB_ID,))
            meta_row = cursor.fetchone()
            if meta_row:
                stage = meta_row[0] if meta_row[0] is not None else &amp;quot;&amp;quot;
                reorg_tp = meta_row[1] if meta_row[1] is not None else &amp;quot;&amp;quot;
                conc = meta_row[2] if meta_row[2] is not None else &amp;quot;&amp;quot;
                bs = meta_row[3] if meta_row[3] is not None else &amp;quot;&amp;quot;
        except Exception:
            pass

        # Dist task progress (only exists during Stage 2)
        task_id = &amp;quot;&amp;quot;
        task_state = &amp;quot;&amp;quot;
        subtask_row_count = &amp;quot;&amp;quot;
        try:
            cursor.execute(&amp;quot;&amp;quot;&amp;quot;
                SELECT id, state FROM mysql.tidb_global_task
                WHERE task_key LIKE CONCAT(&amp;#039;ddl/%%/&amp;#039;, %s, &amp;#039;%%&amp;#039;)
                ORDER BY id DESC LIMIT 1
            &amp;quot;&amp;quot;&amp;quot;, (JOB_ID,))
            task_row = cursor.fetchone()
            if task_row:
                task_id, task_state = task_row
                cursor.execute(&amp;quot;&amp;quot;&amp;quot;
                    SELECT COALESCE(SUM(summary-&amp;gt;&amp;#039;$.row_count&amp;#039;), 0)
                    FROM mysql.tidb_background_subtask
                    WHERE task_key LIKE CONCAT(&amp;#039;ddl/%%/&amp;#039;, %s, &amp;#039;%%&amp;#039;)
                &amp;quot;&amp;quot;&amp;quot;, (JOB_ID,))
                st_row = cursor.fetchone()
                if st_row:
                    subtask_row_count = st_row[0]
        except Exception:
            pass

        elapsed = int(time.time() - start)

        if ddl_row:
            job_id, row_count, state, schema_state, ts = ddl_row
            row_count = row_count or 0

            line = (f&amp;quot;{elapsed:&amp;gt;7}s  {row_count:&amp;gt;14,}  {state:&amp;lt;10}  {stage:&amp;gt;5}  {reorg_tp:&amp;gt;8}  &amp;quot;
                    f&amp;quot;{conc:&amp;gt;3}  {bs:&amp;gt;6}  {str(task_id):&amp;gt;7}  {str(task_state):&amp;lt;10}  {str(subtask_row_count):&amp;gt;14}&amp;quot;)
            print(line)

            with open(log_file, &amp;quot;a&amp;quot;, newline=&amp;quot;&amp;quot;) as f:
                writer = csv.writer(f)
                writer.writerow([elapsed, ts, row_count, state, schema_state,
                                  stage, reorg_tp, conc, bs,
                                  task_id, task_state, subtask_row_count])

            if state in (&amp;quot;synced&amp;quot;, &amp;quot;cancelled&amp;quot;, &amp;quot;paused&amp;quot;):
                print(f&amp;quot;\nJob finished: state={state}, final ROW_COUNT={row_count:,}&amp;quot;)
                break
        else:
            print(f&amp;quot;{elapsed:&amp;gt;7}s  job not found&amp;quot;)

    except Exception as e:
        print(f&amp;quot;  error: {e}&amp;quot;)
        conn.ping(reconnect=True)
        cursor = conn.cursor()
        cursor.execute(&amp;quot;SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED&amp;quot;)

    time.sleep(INTERVAL)

cursor.close()
conn.close()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なお、&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/mysql-schema/#system-tables-related-to-ddl-statements&quot;&gt;mysql.tidb_ddl_job&lt;/a&gt;テーブルのjob_meta属性にはJSONの値が格納されていますが、この中の構造に現れるreorg_meta.stageは8.5.4による、DDLの実行方式の変更に伴い新設されています。これにより、8.5.4以降ではStage 1完了後に一度ROW_COUNTがリセットされるものの、現状どちらのStageを処理しているかの判別ができます。&lt;/p&gt;
&lt;p&gt;最終的にバージョン間で差分が見られるインデックス再構築の箇所のみに焦点を当て、それぞれのバージョンでどうなっていたか、という結論を図にまとめたものが以下になります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/03/13385b23-diagram-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;今回はDDL実行中の速度制御の方法と、完了時間の見積もりについて紹介しました。&lt;/p&gt;
&lt;p&gt;速度制御については、&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/sql-statement-admin-alter-ddl&quot;&gt;ADMIN ALTER DDL JOBS&lt;/a&gt;で可能である一方、コマンド実行により変更が反映されないバグが含まれるバージョンがあり、PAUSE/RESUMEにてこれを反映できることを説明しました。&lt;/p&gt;
&lt;p&gt;完了時間見積もりについては、DDLがmodify columnに分類される場合、ROW_COUNTの値で見積もりが可能です。TiDB 8.5.3までは、対象のカラムを含むインデックス数の2乗のオーダーに比例する不具合があり、また、ROW_COUNTが統計上の値が増えているだけではなく、実際に発生するI/Oなどにも直接影響し注意が必要です。&lt;br /&gt;
また、TiDB 8.5.4以降では、インデックス再構築が、従来とは異なる方式で実施されるようになり、重複して実行される不具合は解消し、かつ高速になりました。ROW_COUNTは最終的には、レコード数に近い値になるものの、インデックス構築フェーズで値が一度ゼロにリセットされ、再度カウントアップする挙動となっていた検証結果を共有しました。&lt;/p&gt;
&lt;p&gt;マイナーバージョンによる不具合などについては、公式ドキュメントをよく読んでいるだけではわからないことが多く、他のDBMSなどをみても、この類の情報が充実してくると、利用者としても安心できる状況に近づくのではないかと考えています。&lt;/p&gt;
&lt;p&gt;メルカリではTiDBの運用を進めており、このような想定と異なる挙動の遭遇はまだまだ多く、避けられないものです。このような、想定外の挙動に対し、生成AIなどを活用し状況を理解し、世の中に発信しユーザーや製品に還元していけるような仲間を求めています。&lt;/p&gt;
&lt;p&gt;また、現在当チームのマネージャーポジションが募集中となります。興味ある方はぜひご応募ください。&lt;br /&gt;
&lt;a href=&quot;https://apply.workable.com/mercari/j/7AD4EF9218/&quot;&gt;https://apply.workable.com/mercari/j/7AD4EF9218/&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>「Go Conference mini in Sendai 2026」の参加レポート #sendaigo</title><link>https://engineering.mercari.com/blog/entry/20260225-go-conference-mini-in-sendai-2026/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260225-go-conference-mini-in-sendai-2026/</guid><description>&lt;p&gt;こんにちは。メルカリ Engineering Office の mikichin です。 2月21日に開催された「Go Conference mini in Sendai 2026」にメルカリはZunda Sponsor [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 25 Feb 2026 10:59:29 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリ Engineering Office の &lt;a href=&quot;https://x.com/chida_miki&quot;&gt;mikichin&lt;/a&gt; です。&lt;/p&gt;
&lt;p&gt;2月21日に開催された「Go Conference mini in Sendai 2026」にメルカリはZunda Sponsorをしておりました。今回は参加レポートをお届けします！&lt;/p&gt;
&lt;h2&gt;「Go Conference mini in Sendai 2026」について&lt;/h2&gt;
&lt;p&gt;「Go Conference mini in Sendai 2026」はプログラミング言語 Go に関する地域カンファレンスで、東北地方のエンジニアコミュニティの成長と連携を促進し、地域を超えて共に前進していくことを目指すイベントです。&lt;/p&gt;
&lt;h3&gt;開催概要&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;日時：2026年2月21日（土）&lt;/li&gt;
&lt;li&gt;場所：アーバンネットビル仙台中央 カンファレンスルーム&lt;/li&gt;
&lt;li&gt;公式サイト：&lt;a href=&quot;https://sendaigo.jp/&quot;&gt;https://sendaigo.jp/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;メルカリメンバーの登壇&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/02/f82ef3ce-hbpdkfrbgam8bqg.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;なんと、今回のカンファレンスでは4名の登壇がありました！&lt;br /&gt;
登壇後の感想とあわせて、登壇資料を共有します。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;はじめての Go 〜 きっかけは TinyGo だった / &lt;a href=&quot;https://x.com/chida_miki&quot;&gt;@mikichin&lt;/a&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;iframe class=&quot;speakerdeck-iframe&quot; frameborder=&quot;0&quot; src=&quot;https://speakerdeck.com/player/86f16f8d6cd343729008debf5e4a1771&quot; title=&quot;はじめてのGo〜きっかけはTinyGoだった&quot; allowfullscreen=&quot;true&quot; style=&quot;border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;&quot; data-ratio=&quot;1.7777777777777777&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;まさか Tech PR のわたしが、Go というプログラミング言語を中心としたカンファレンスに登壇する日が来るとは思っていませんでした。とても貴重な機会をいただき、ありがとうございました 🙌&lt;/p&gt;
&lt;p&gt;今回の発表が同じように開発をしてみたいと思う誰かの後押しになったり、エンジニアのみなさんが質問をされたときに非エンジニアの人のわからないレベル感が参考になったりしたらうれしいです。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Goに新機能を提案し実装されるまでのフロー徹底解説 〜将来、あなたのアイデアがGoに入るかもしれない。/ &lt;a href=&quot;https://x.com/t_yamatoya&quot; title=&quot;@pooh&quot;&gt;@pooh&lt;/a&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;iframe class=&quot;speakerdeck-iframe&quot; frameborder=&quot;0&quot; src=&quot;https://speakerdeck.com/player/d7448f2dae6848428c01e2513ea2f19e&quot; title=&quot;Go Conference mini in Sendai 2026 : Goに新機能を提案し実装されるまでのフロー徹底解説&quot; allowfullscreen=&quot;true&quot; style=&quot;border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;&quot; data-ratio=&quot;1.7777777777777777&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;登壇した時にもお伝えしたのですが、「なぜプロポーザルを出すのか？」「ワクワクしない！？自分の提案、自分のコードがGoに取り込まれるのは楽しいよね！」というのがこの登壇の動機でした。&lt;/p&gt;
&lt;p&gt;AI時代になり、自分でコードを書く機会が急激に減ってきています。&lt;br /&gt;
Goに貢献する、Goに自分の思いが入っているということを経験できる最後のタイミングかもしれません。&lt;/p&gt;
&lt;p&gt;ぜひ！興味を持った人がトライする機会になればいいなと思いました。&lt;/p&gt;
&lt;p&gt;仙台は牛タンが美味しかった！&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;AI時代を見据えたコードカバレッジ計測ツールの開発 / &lt;a href=&quot;https://x.com/goccy54&quot;&gt;@goccy&lt;/a&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;div style=&quot;position:relative;padding-bottom:56.25%;height:0;overflow:hidden;&quot;&gt;
  &lt;iframe
    src=&quot;https://docs.google.com/presentation/d/1S3Pp7pBHg8aLfO-tFw5EqB-GZvBfv_9Dq_Xdri8QBC4/embed?start=false&amp;#038;loop=false&amp;#038;delayms=3000&quot;
    frameborder=&quot;0&quot;
    allowfullscreen
    style=&quot;position:absolute;top:0;left:0;width:100%;height:100%;border:0;&quot;&gt;&lt;br /&gt;
  &lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;4年ぶりの仙台での登壇でした。出身地で登壇できるのは感慨深いもので、また地元を盛り上げるために良い発表ができるよう、頑張りたいと強く思いました。&lt;/p&gt;
&lt;p&gt;仙台はグルメも最高だし泊まりで来ている場合は終電も気にしなくていいのが最高なはずなのですが、毎回帰省を兼ねていて終電前で実家に帰ってしまうので、次回は会場の近くでホテルをとって朝まで飲み明かすのも良いなと思いつつ、体力が心配。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;TODO からはじまるコントリビュート 〜 TinyGo / &lt;a href=&quot;https://x.com/micchiebear&quot;&gt;@micchie&lt;/a&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/02/5d76c020--2026-02-25-11.02.39.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;資料リンクは&lt;a href=&quot;https://www.canva.com/design/DAHBNJb1vHQ/ZXT3cJFoj0bl4yqWKtKXlw/view&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;普段はカンファレンスの主催や運営に携わることが多いのですが、やはりスピーカーとして参加するのは格別です。この発表をきっかけに、5/30 に開催される Women Who Go Tokyo 10th Anniversary イベントの参加者も増えて、2度美味しいです。&lt;/p&gt;
&lt;p&gt;ちなみにこの LT のネタはまだ未解決なので、次の技術書典までには修正してマージされたいところです。&lt;/p&gt;
&lt;h2&gt;会場内の様子&lt;/h2&gt;
&lt;p&gt;今回のイベントでは、スポンサーブースだけではなくKitchen Senoue（実行委員長 Senoueさんが振る舞う芋煮）や にがおえりんごさんがイラストを書いてくれるブースなどもありました！&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/02/b7c9dbbd--2026-02-24-11.35.34.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;TinyGo Keeb のブースでは、「電車でTinyGO!」の展示があり遊んでみました。わたしは「電車でGO!」自体遊んだことがなかったので、初体験。全然うまく駅に停車できませんでしたｗ&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/02/01198547-img_1779-scaled.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;「電車でTinyGO!」はプレステの電車でGO用に作られたコントローラーをBVEというPCゲームで遊べるように TinyGo でキーボード化したものとのことです。（参照元：&lt;a href=&quot;https://tinygo-keeb.org/blog/?p=191&quot;&gt;Go Conference mini in Sendai 2026に参加しました&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;セッションだけではなく、いろいろ楽しめるコンテンツがたくさんありました！&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;いままで、カンファレンススタッフやスポンサーブースの担当として参加することが多かったのですが、今回スピーカーとしての参加ということで、自分の発表がおわるまではそわそわしたり感想を直接いただいたりと、いつもとは違った経験ができてよかったです！&lt;/p&gt;
&lt;p&gt;また、今回4名の登壇者がいたので、各セッションの応援に行ったりと終始カンファレンスを楽しむことができました。&lt;/p&gt;
&lt;p&gt;そして、なんといっても地方カンファレンスというのはいいですね！その土地ならではのごはんも堪能して大満足です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/02/0e69b730--2026-02-24-11.58.39.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最後に、「Go Conference mini in Sendai 2026」の企画運営、おつかれさま &amp;amp; ありがとうございました！また、次回を楽しみにしています！&lt;/p&gt;
</content:encoded></item><item><title>TTLを利用したTiDBの効率的なインスタンス活用</title><link>https://engineering.mercari.com/blog/entry/20260105-0fedbf6898/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20260105-0fedbf6898/</guid><description>&lt;p&gt;MySQLに高い互換性を持つデータベースのTiDBには、古いデータを自動的に削除するTTL(Time to Live)の機能があります。本記事では、これを活用しコスト削減および障害耐性の向上を実現した事例を紹介します 背 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 06 Jan 2026 07:00:46 GMT</pubDate><content:encoded>&lt;p&gt;MySQLに高い互換性を持つデータベースのTiDBには、古いデータを自動的に削除するTTL(Time to Live)の機能があります。本記事では、これを活用しコスト削減および障害耐性の向上を実現した事例を紹介します&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;メルカリでは、商品に対してコメントを付与することができ、値引きの依頼だったり、さまざまなやり取りを行うことができます。このコメントが、例えば公序良俗に違反すると想定される場合、お客さまがそれを「通報」する機能がありますが、メルカリ内部でも自動的にこれらを検知する仕組みが古くから実装され、運用に利用されています。&lt;/p&gt;
&lt;p&gt;これらのコメントのデータは、歴史的経緯から古くはMySQL、現在はTiDBに格納されています。&lt;/p&gt;
&lt;p&gt;自動検知の機能自体は、事前定義されたあるキーワードがコメントに含まれるかどうか、いわゆる「全文検索」によって実現されています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;: 全文検索クエリのイメージ
SELECT table1.* FROM table1 FORCE INDEX(idx_xxx_xxx) \
WHERE (column1 like &amp;#039;&amp;#039;%a%&amp;#039;&amp;#039; OR column1 like &amp;#039;&amp;#039;%b%&amp;#039;&amp;#039; … ) \
AND (column1 not like &amp;#039;&amp;#039;%c%&amp;#039;&amp;#039; OR column1 not like &amp;#039;&amp;#039;%d%&amp;#039;&amp;#039; … )
AND ... \
ORDER BY created DESC LIMIT 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;またこの機能を動作させるには、多くのコメントに対し高頻度で多数の全文検索を実行する必要があり、このような使い方はもちろんMySQLにはあまり向かないものです。しかし、TiDBに移行した際にさらにこの負荷の影響が目立つようになりました。&lt;/p&gt;
&lt;h2&gt;初期の対応&lt;/h2&gt;
&lt;p&gt;TiDB User Day 2025でResource Controlの導入について紹介(&lt;a href=&quot;https://static.pingcap.co.jp/files/2025/10/20165547/TiUD2025-Mercari.pdf&quot;&gt;発表資料&lt;/a&gt;)しており、詳細は割愛しますが、対象のクエリにヒント句でResource Groupを割当て、Burstableでない制約を加えることにより、TiKVでの優先度の制御と、TiDBでの多少の負荷の平滑化を実現していました。&lt;/p&gt;
&lt;p&gt;しかしこの負荷は、定期的に実行されかつ実行間隔が短いものでしたので、実行を完了しなければいけないタイムウィンドウの制約が比較的厳しいものであることから、平滑化による制御の効果が限定的でした。&lt;/p&gt;
&lt;p&gt;また、Resource Controlによる制御は、TiDBへのOLTPのワークロードに関しては比較的うまくいくものの、TiKVに対しては主に優先度でしか制御できず、リソースの隔離が不十分であるため、アプローチを変えて対応することにしました。&lt;/p&gt;
&lt;h2&gt;検討したアプローチ&lt;/h2&gt;
&lt;p&gt;問題のあるコメントの検知に関しては、コメントのデータが追記式で、後から編集できない(更新されない)というデータの性質上、ある程度最近のデータにのみクエリを発行できれば良いため、最近のデータだけをうまく処理するという方針を軸に、主に下記の観点から2点のアプローチを主に比較しました&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;既存のTiDBからデータが簡易に生成できる&lt;/li&gt;
&lt;li&gt;コストが妥当&lt;/li&gt;
&lt;li&gt;クライアントの変更が少ない&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;a. TiDBからTiCDCへ流通させたデータにKSQLを発行する&lt;/h3&gt;
&lt;figure id=&quot;attachment_36433&quot; aria-describedby=&quot;caption-attachment-36433&quot; style=&quot;width: 620px&quot; class=&quot;wp-caption aligncenter&quot;&gt;&lt;img loading=&quot;lazy&quot; src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/14ee415a-cleanshot-2025-12-25-at-16.47.27.png&quot; alt=&quot;KSQL downstream&quot; width=&quot;620&quot; height=&quot;203&quot; class=&quot;size-full wp-image-36433&quot; srcset=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/14ee415a-cleanshot-2025-12-25-at-16.47.27.png 620w, https://storage.googleapis.com/prd-engineering-asset/2025/12/14ee415a-cleanshot-2025-12-25-at-16.47.27-300x98.png 300w&quot; sizes=&quot;(max-width: 620px) 100vw, 620px&quot; /&gt;&lt;figcaption id=&quot;caption-attachment-36433&quot; class=&quot;wp-caption-text&quot;&gt;TiCDCダウンストリームをKSQL DBとして活用&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;TiDBには&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/ticdc-overview/&quot;&gt;TiCDC&lt;/a&gt;という、TiDBの実データを保存するTiKVの変更を取得し、ダウンストリームのデータベースに変更を伝播させるための、いわゆるCDC(Change Data Capture)の便利な機能があります。&lt;/p&gt;
&lt;p&gt;MySQLであれば、例えば&lt;a href=&quot;https://github.com/debezium/debezium&quot;&gt;debezium&lt;/a&gt;といったソフトウェアがこれにあたり、TiCDCは変更イベントを以下のような様々なプロトコルで出力可能な柔軟さを持っています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/ticdc-avro-protocol/&quot;&gt;Avro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/ticdc-debezium/&quot;&gt;Debezium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/ticdc-canal-json/&quot;&gt;Canal-JSON&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このTiCDCを使って、Debezium形式で下流のKafkaにデータを流せば、&lt;a href=&quot;https://www.confluent.io/blog/ksql-streaming-sql-for-apache-kafka/&quot;&gt;KSQL&lt;/a&gt;というストリームデータに対してSQLを発行できる機能を活用し、インメモリでクエリを発行できる可能性があります。&lt;/p&gt;
&lt;p&gt;KSQLの発行先であるKSQL DBのレコードはイミュータブルになります。&lt;br /&gt;
また、KSQLは全文検索に特化した機能はありませんが、全文検索のクエリも発行できます。&lt;/p&gt;
&lt;p&gt;KSQL自体はすでに社内で活用/運用実績がある状況でした。&lt;/p&gt;
&lt;h3&gt;b. TTLにより一部のデータを保持する専用のTiDB Clusterを作成する&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/0faa6a8a-cleanshot-2025-12-25-at-16.45.40.png&quot; alt=&quot;TTL downstream&quot; /&gt;&lt;/p&gt;
&lt;p&gt;まずは、必要なテーブルのみを持つTiDB Clusterを作成します。&lt;br /&gt;
TiCDCのレプリケーションタスクはChangefeedと呼ばれますが、テーブルの絞り込みには、このChangefeedの&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/ticdc-filter/#table-filter&quot;&gt;Table Filter&lt;/a&gt;という機能が活用できます。本ケースでは、コメントを保存するテーブルのみを保持するClusterを用意することになります。&lt;/p&gt;
&lt;p&gt;次に、このテーブルのデータを最新のデータのみ保持するようにします。&lt;br /&gt;
TiDBには、Spanner同様、古いデータを自動的に削除する&lt;a href=&quot;https://docs.pingcap.com/tidbcloud/time-to-live/&quot;&gt;TTLの機能&lt;/a&gt;があります。(MySQLにはありません)。この機能を活用すると最近のデータ以外は不要、という要件を簡易に達成できます。&lt;br /&gt;
TTLの機能をCluster単体で利用した場合は、特に追加の考慮は必要ありませんが、TiCDCを経由したデータに対して利用した場合は、追加の考慮が必要です。&lt;br /&gt;
すなわち、上流には存在する一方、下流には(TTLで削除されたため)存在しない、というデータが発生し、全てのイベントを伝播した場合、例えば、下流に存在しないデータに対しての削除が伝播し、エラーになるといったことが発生する可能性に対処する必要があります。&lt;br /&gt;
この問題に対しては、Changefeedに&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/ticdc-filter/#event-filter-rules&quot;&gt;Event Filter&lt;/a&gt;という機能があり、特定のDELETEやUPDATEを無視する、といった設定が可能です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/579860f1-cleanshot-2025-12-26-at-10.22.53.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;: 上記のデータセットでEvent Filterがないとエラーになるクエリの例
DELETE FROM comments WHERE id=1 /* すでに下流にはデータがない */;
UPDATE comments SET comment=&amp;quot;good morning&amp;quot; where id=2 /* すでに下流にはデータがない */; &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;さらに、TTLパラメータにより、保持するデータがメモリに収まるようコントロールできれば、実質的に多くのケースでインメモリに近い形で処理可能です。&lt;/p&gt;
&lt;p&gt;なお、メルカリではTiDBのマネージドサービスである、TiDB Cloudを利用しており、ここで紹介したChangefeedのTable Filterおよび Event FilterはTiDB Cloudでも利用可能な機能です。&lt;/p&gt;
&lt;h2&gt;手法の決定&lt;/h2&gt;
&lt;p&gt;主にクライアントへの変更要求が少ないことから、TTLを用いる手法を選択することにしました。&lt;br /&gt;
万が一問題が発生した場合の切戻し、および暫定対処に関しても、クエリの発行先を、元のClusterに戻し、元のClusterのリソースを増やしてあげれば良いだけである、という便利さもあります。&lt;/p&gt;
&lt;p&gt;ここで改めて、隔離した一部のデータセットを作成し、そこにクエリを発行するというアプローチを取った利点を確認します。&lt;/p&gt;
&lt;h3&gt;利点1： 構築が容易/短期で可能&lt;/h3&gt;
&lt;p&gt;特筆すべきこととして、TTLが十分短ければ、初期構築が非常に迅速、かつ容易に行えることが言えます。&lt;/p&gt;
&lt;p&gt;既存のデータベースからある一部のデータを保持する別のデータベースを作成する際に、最も手間となるのは、既存のデータベースからのエクスポート/リストアから始まる初期データの作成ではないかと思います。&lt;/p&gt;
&lt;p&gt;しかし、Event Filterで所定の構成をすれば、TTLが短い場合、下流に空のデータベースを用意し、その時点からデータの複製を始めれば、保持すべきTTLを待つだけでデータの用意が完了します。&lt;/p&gt;
&lt;h3&gt;利点2： 障害単位が独立し個別調整の余地が出る&lt;/h3&gt;
&lt;p&gt;リソースを完全に独立させた専用のClusterを用意することで、障害の単位も分離でき、さらに、決まった使われ方をする独立したClusterに対して、積極的にリソースを活用するようチューニングが可能です。&lt;/p&gt;
&lt;p&gt;もちろん、設定が同一ではない、非対称なものが生まれるのは運用における認知負荷が上がるという問題はあり、構成変更により得られる効果とのバランスをチームで納得のいくものにする必要があります。&lt;/p&gt;
&lt;h2&gt;結果&lt;/h2&gt;
&lt;p&gt;変更前後のTiKVの負荷について示します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A) 変更前のTiKVの負荷&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/5bc093c7-cleanshot-2025-10-30-at-12.11.18-1.png&quot; alt=&quot;before&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;B) 変更後のTiKVの負荷(ピーキーな負荷が隔離され利用量が平滑化された)&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/01/b86b34aa-cleanshot-2026-01-05-at-09.19.24@2x.png&quot; alt=&quot;after&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;C) 新規TiKV with TTLの負荷&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2026/01/cd1a7078-cleanshot-2026-01-05-at-09.28.59.png&quot; alt=&quot;new cluster&quot; /&gt;&lt;/p&gt;
&lt;p&gt;このように負荷は別のClusterに分離され、上位のスペックで運用していた既存のClusterの負荷が減少し、かつ安定したことから、既存のClusterのノード数を減らすことができました。&lt;br /&gt;
新規に別のClusterを作成しているものの、トータルコストとしても削減でき、障害に関するリスクも分離できました。&lt;/p&gt;
&lt;p&gt;追記：BのグラフのCPU利用率はAより高くなっていますが、BのグラフをTiKVノードを減らした後に作成したためです。負荷が平滑化され、上下の激しい負荷が別Clusterに分離されたという文脈でご参照ください&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;本記事では、Changefeedのフィルター機能(Table Filter, Event Filter)、TiDBのTTLを活用し、一部のデータに対して、高頻度で発行される全文検索の負荷を、独立したClusterに分離することでコスト削減をした事例を紹介しました。&lt;/p&gt;
&lt;p&gt;このアプローチでは、アプリケーションの変更および、構築、共に少ない変更ですみ、結果として早期に移行を完了することができました。&lt;/p&gt;
&lt;p&gt;メルカリでは、TiDBを利用していますが、初期導入のフェーズから、この記事のような継続的改善のフェーズに入っています。現在、様々な取り組みをしており、近々別の記事で紹介していく予定です。&lt;/p&gt;
&lt;h2&gt;関連/補足&lt;/h2&gt;
&lt;p&gt;本論とは少しそれる、細かな検討事項について補足します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TiDBの全文検索については、一部のリージョン、一部のサービスプロバイダ、一部のサービス提供形態で、試験的に利用できるものが提供されています。現状では選択肢には入りませんでした。また、商品に対するコメントについては、ある商品のコメントを表示する、コメントを書き込むというのが主な用途であり、そういった点からも元データとしてはこのデータのみを全文検索エンジンに格納するのは相応しくないと考えています。&lt;br /&gt;
&lt;a href=&quot;https://docs.pingcap.com/tidbcloud/vector-search-full-text-search-sql&quot;&gt;https://docs.pingcap.com/tidbcloud/vector-search-full-text-search-sql&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/tikv-in-memory-engine/&quot;&gt;TiKV MVCC In-Memory Engine&lt;/a&gt;については、B案の一部として試しました。今回のケースが、In Memory Engineに向いたケースではなく、「構成変更により得られる効果とのバランス」が悪かったため、今回は採用には至りませんでした。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>AI-Nativeという選択 ー 正解のない時代に、メルカリが選んだ指針</title><link>https://engineering.mercari.com/blog/entry/20251225-mercari-ai-native-company/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251225-mercari-ai-native-company/</guid><description>&lt;p&gt;こんにちは、メルカリCTOの木村(@kimuras)です。 今年は、ついに開催されたメルカリ主催のエンジニアイベント 「mercari GEARS 2025」 にて、Keynoteを担当しました。本記事では、その内容を改 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 25 Dec 2025 11:00:55 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、メルカリCTOの木村(@kimuras)です。&lt;br /&gt;
今年は、ついに開催されたメルカリ主催のエンジニアイベント 「&lt;a href=&quot;https://gears.mercari.com/&quot; title=&quot;mercari GEARS 2025&quot;&gt;mercari GEARS 2025&lt;/a&gt;」 にて、Keynoteを担当しました。本記事では、その内容を改めて文章としてまとめ、皆さんにお伝えしたいと思います。メルカリがAI-Native Companyになることを宣言して以来、エンジニアリング組織に限らず、全社としてどのようにAI-Native化を進めていくのか、その指針についてお話しします。&lt;/p&gt;
&lt;p&gt;ご存じのとおり、AI活用による生産性向上は、まだまだ不確実性の高い領域です。さらに新たな技術が次々と登場することで、今日述べる内容も、近い将来には更新が必要になるかもしれません。しかし、だからこそ「現時点で我々がどこを目指し、どのような方向性で進んでいるのか」を言語化し、共有しておくことは、この変革期において非常に重要だと考えています。&lt;/p&gt;
&lt;p&gt;AI-Native化の推進は決して簡単ではありませんが、本記事の内容が、同じように挑戦されている皆様の一助になれば幸いです。&lt;/p&gt;
&lt;h2&gt;目次&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;全社をあげたAI-Native化とは&lt;/li&gt;
&lt;li&gt;現状: 確かな変化、しかし「まだ道半ば」&lt;/li&gt;
&lt;li&gt;発想の転換:「人間前提」から「AI前提」へ&lt;/li&gt;
&lt;li&gt;AI-Native化の前提条件:Knowledge Management&lt;/li&gt;
&lt;li&gt;AI-Centricな開発:Agent-Spec Driven Development(ASDD)&lt;/li&gt;
&lt;li&gt;全社的なAI化:AI Task Force&lt;/li&gt;
&lt;li&gt;未来のビジョン:AI化の先にあるもの&lt;/li&gt;
&lt;li&gt;今後に向けて&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;全社をあげたAI-Native化&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;「プロダクト、仕事のやり方、組織すべてをAI中心に再構築し、AIの進化を最大限に活用することで、これまでにない成果を目指す」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(参考: &lt;a href=&quot;https://careers.mercari.com/mercan/articles/53674/&quot; title=&quot;新年度のテーマは「Back to Startup」と「AI-Native」。12周年を迎えたメルカリが目指すこれからの姿 | mercan (メルカン)&quot;&gt;新年度のテーマは「Back to Startup」と「AI-Native」。12周年を迎えたメルカリが目指すこれからの姿 | mercan (メルカン)&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;これは、社長である山田からの強い決意表明でした。&lt;br /&gt;
AIへの対応が遅れれば、競争環境の中で後れを取るリスクは避けられません。同時に、私たち一人ひとりの成長においても、大きな変化が求められています。AIによる生産性向上は、従来の評価基準では実現が難しかった新たな価値創造を可能にします。変化を柔軟に受け入れ、成長し続けられる人にとって、これは自身の価値を飛躍的に引き上げる絶好の機会でもあります。&lt;/p&gt;
&lt;p&gt;そして、AI-Nativeな働き方を全社に浸透させるということは、単にAIツールを一律に導入することではありません。これまでの働き方をゼロベースで見直し、業務そのものをAIを前提とした形へ抜本的に進化させていくことが重要だと考えています。&lt;/p&gt;
&lt;h1&gt;現状: 確かな変化、しかし「まだ道半ば」&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/14d23d0a--2025-12-23-11.03.05.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリではすでに、社員の95％がAIツールを活用し、コード生成の約70％をAIが担うまでに進化しています。開発スピードも前年比で64％向上しました。数字だけを見れば、AIは確実に浸透しているように思えるかもしれません。&lt;/p&gt;
&lt;p&gt;しかし、私たちは現在の状態を“AI-Native”だとは捉えていません。むしろ、ここからこそ本当の変革が始まると考えています。&lt;/p&gt;
&lt;p&gt;最近よく議論されているように、「本当に生産性は飛躍的に向上したのか？」という問いに対して、私自身もまだ改善の余地は大きく残っていると感じています。そして、コーディングの生産性が向上しただけでは、組織全体の生産性は十分には高まりません。同じくらい重要なのは、コーディング以外の業務プロセスも含めて、AI前提の働き方に転換していくことです。&lt;/p&gt;
&lt;h1&gt;発想の転換:「人間前提」から「AI前提」へ&lt;/h1&gt;
&lt;p&gt;前述のとおり、AIを前提とした働き方を実現するためには、単にツールを導入するだけでは不十分です。私たちは、仕事に対する考え方そのものを根本から塗り替える必要があると考えています。&lt;/p&gt;
&lt;p&gt;これまで私たちが直面してきた限界は、「人が行うこと」を前提に組み立てられたプロセスや仕組みによって生じていました。AI-Nativeな働き方とは、そうした前提条件そのものを問い直し、業務を“人が実行するタスク”から“AIと人が最適に協働するタスク”へと再設計していくことにほかなりません。&lt;/p&gt;
&lt;h2&gt;これまでの限界&lt;/h2&gt;
&lt;p&gt;これまでの仕事や組織のデザインは、多くが“人間前提”で設計されてきました。&lt;/p&gt;
&lt;p&gt;つまり、人間の時間・集中力・処理能力といった制約を起点に、「その条件の中でどう成果を最大化するか」を軸に最適化されてきたのです。&lt;/p&gt;
&lt;p&gt;たとえば、1日8時間労働、週休2日、1チーム8名構成、週次の定例会議、こうした組織の“当たり前”はすべて、人間の能力と限界を前提として形づくられてきました。現在も一般的な働き方であり、私たち自身もその枠組みの中で働いています。&lt;/p&gt;
&lt;p&gt;しかし、AIを前提とした働き方とは本来どういう姿なのか。&lt;/p&gt;
&lt;p&gt;そこを抜本的に考え直し、AI活用を進めながら、これまでの常識を一つひとつ疑い、新しい標準をつくっていく必要があります。&lt;/p&gt;
&lt;p&gt;たとえば、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1人が複数ロールを効率的にこなせるようになれば、チームは8人よりも少ない人数のほうが、むしろ高い成果を出せるかもしれません。&lt;/li&gt;
&lt;li&gt;AIによって単純作業が減り、人はより連続的で深い思考に集中できるようになると、脳の疲労はこれまで以上に高まる可能性があります。その場合、1日8時間労働ではなく、短時間で高集中の働き方のほうが高い生産性を生み出すことも考えられます。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このように、AI前提の働き方は、従来の“当たり前”を根本から再設計することを意味します。&lt;/p&gt;
&lt;h2&gt;AI前提の世界観&lt;/h2&gt;
&lt;p&gt;では、AIを前提とした働き方とは、どのような発想の転換なのでしょうか。&lt;/p&gt;
&lt;p&gt;これまで新たなプロジェクトや事業を立ち上げる際には、前述のような「人間の限界」を前提として設計するのが一般的でした。特に人的リソースは最も大きな制約であり、どれだけ人を確保できるかが計画の起点になっていました。&lt;/p&gt;
&lt;p&gt;しかし、AIが前提となる世界では、この出発点が根本的に変わります。&lt;/p&gt;
&lt;p&gt;仕事や組織のあり方を、“人の限界から逆算する”のではなく、ビジョンや提供したい価値から逆算して設計することが重要になります。&lt;/p&gt;
&lt;p&gt;すなわち、&lt;/p&gt;
&lt;p&gt;「何を実現し、どんな価値をお客さまに届けたいのか」&lt;/p&gt;
&lt;p&gt;という問いからスタートし、その実現のためにAIをワークフローへ組み込み、最適な仕組み・チーム構成・役割・データのあり方などを再設計していくという考え方です。AIはミッションを前進させる創造的なパートナーであり、チームの一員として存在します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/b60bc67f--2025-12-23-11.04.56.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これこそが、私たちが目指す“AI-Native”の世界です。&lt;/p&gt;
&lt;h1&gt;AI-Native化の前提条件:Knowledge Management&lt;/h1&gt;
&lt;h2&gt;なぜKnowledge Managementが重要なのか&lt;/h2&gt;
&lt;p&gt;私たちもAI Coding Assistantをはじめ、さまざまなAIツールを導入してきましたが、当初想定していたほど生産性が向上しないという課題がありました。&lt;/p&gt;
&lt;p&gt;その背景には、AI化を進める前に取り組むべき、より本質的な要素が欠けていたことがあります。それが、適切な情報、すなわちコンテクストの整備です。&lt;/p&gt;
&lt;p&gt;特にAI Agentは、十分なコンテクストが与えられなければ期待どおりに機能しません。単純な指示だけでは精度の高い成果物を生成できず、手直しが何度も発生し、かえって時間がかかってしまうことすらあります。最終的には品質が低いアウトプットになってしまうケースも少なくありません。だからこそ、タスクに関わる背景知識、関連するレギュレーション、過去の意思決定プロセスや議論のログなどをコンテクストとして適切に提供することが不可欠です。&lt;/p&gt;
&lt;p&gt;そうすることでAI Agentは、状況や歴史的文脈、そしてAI Agent利用者が求めるゴールを正確に理解し、より期待に近い成果物を生成できるようになります。結果として、AIを効果的かつ継続的に活用できる環境が実現します。&lt;/p&gt;
&lt;h2&gt;どのようなコンテクストが必要か&lt;/h2&gt;
&lt;p&gt;必要となるコンテクストは、AI Agentに解かせたい課題によって大きく異なります。したがって、「この情報さえあればよい」という万能のテンプレートは存在しません。ただし、コーディングを例に挙げると、以下のような情報は特に有効です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microservicesの依存関係&lt;/li&gt;
&lt;li&gt;コーディング規約&lt;/li&gt;
&lt;li&gt;設計書&lt;/li&gt;
&lt;li&gt;関連する開発における過去の意思決定ログ&lt;/li&gt;
&lt;li&gt;コードレビューでの議論内容&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらのコンテクストをAI Agentに提供することで、依存関係を正しく考慮しながら、これまでの方針や意思決定と整合性のある設計やコード生成が可能になります。&lt;/p&gt;
&lt;h2&gt;最も重要なコンテクスト:意思決定情報&lt;/h2&gt;
&lt;p&gt;私たちが最も重要だと考えるコンテクストの一つが、意思決定情報です。&lt;/p&gt;
&lt;p&gt;意思決定に関するコンテクストは本来きわめて重要ですが、実際には十分に整理されていないケースが多く見られます。SlackやGitHub、ミーティングメモなど複数のツールに議論が散在し、必要な情報を必要なときに取り出すことが困難になっているのが現状です。会議で決まった内容が適切に共有されず、後から意思決定の経緯をたどれない場合も少なくありません。&lt;/p&gt;
&lt;p&gt;当社でもDesign Docにアーキテクチャや意思決定事項をまとめていますが、その判断に至るまでの議論は依然としてさまざまなツールに分散しています。その結果、「なぜその判断に至ったのか」という重要な背景が抜け落ちるリスクがあり、散在した情報を後から統合するには非常に大きな手間がかかります。&lt;/p&gt;
&lt;p&gt;しかし、こうした意思決定情報はプロジェクトを適切に進めるうえで不可欠であり、AI Agentにとっても極めて重要なコンテクストです。AI Agentは、今後コーディングだけでなく、仕様書作成、デザイン、QA、リーガルチェック、セキュリティチェックなど、より広範な領域で活用されるようになります。その際に必要なのは、次のような情報です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;このプロジェクトの目的・狙いは何か&lt;/li&gt;
&lt;li&gt;何が許容でき、何が許容できないのか&lt;/li&gt;
&lt;li&gt;過去にどのような議論があり、何を重視して意思決定してきたのか&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI Agentがこうした背景を理解しているかどうかで、アウトプットの品質は大きく変わります。適切な意思決定コンテクストが提供されれば、AI Agentは状況を正確に把握し、より高品質で一貫性のある成果を生成できるようになります。&lt;/p&gt;
&lt;p&gt;この後に紹介するAI Agent Spec Driven(ASDD)でもSpecを決定した議論を録音しておき、それをコンテクストとしてSpecに提供することで、より高性能にAI Agentを活用できると紹介されています。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;最初のSpecだけでは表現しきれていなかった背景やニュアンスが、レビュー時の対話には多く含まれています。この文字起こしログをCoding Agentに読ませてSpecを改善させることで、当初の記述では表現できていなかった文脈や設計の抜け漏れを補足し、より精度の高いSpecへと昇華させることができます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(参考: &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251209-d0de07214d/&quot; title=&quot;Agent Specで小さく素早く回すメルカリモバイル開発現場&quot;&gt;Agent Specで小さく素早く回すメルカリモバイル開発現場&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;Knowledge Managementへの取り組み&lt;/h2&gt;
&lt;p&gt;現実には、こうした情報は十分に整理されておらず、多くが暗黙知のまま埋もれてしまっています。そこで私たちは、議論の記録や意思決定をできる限り構造化し、いつでもコンテクストとして活用できる状態にするため、Knowledge Managementの強化に取り組んでいます。&lt;/p&gt;
&lt;p&gt;AIを前提とした働き方をつくると同時に、情報管理のあり方そのものを抜本的に見直しているところです。これが実現すれば、トップダウンの意思決定と、現場からの学びや提案といったボトムアップの動きがよりスムーズにつながり、全社としての意思決定速度も大きく向上します。&lt;/p&gt;
&lt;p&gt;こうしたAI-Readable(本稿では、データが整備されており、AIエージェントが容易にコンテクストとして参照できる状態のデータを「AI-Readable」と定義します)なデータマネジメントを実現するため、私たちは現在、Notionに情報を極力集約する取り組みを進めています。&lt;/p&gt;
&lt;p&gt;目的は二つあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;散在している情報を一つの文書管理基盤に集約し、必要なときにコンテクストを簡単に取り出せる状態にすること&lt;/li&gt;
&lt;li&gt;情報の残し方そのものを再設計し、AIによる議事録作成の標準化や、多様な情報資産の構造化などを通じて、AIが扱いやすいナレッジ体系をゼロから構築すること&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これにより、人間にとってもAI Agentにとっても理解しやすく再利用しやすい組織の記憶をつくることを目指しています。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;この方向性を元に、現在、メルカリはNotionをCentral Knowledge Baseとして位置付け、ナレッジの中央管理型への移行を進めています。本記事の主旨と離れるので、細かくは記載しませんが、ツール選定に関しては、フロー情報（議事録など、メンテしない情報）とストック情報の両方に強いという点や、AIとの親和性の高さが大きなポイントでした&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(参考: &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251213-96e00d1d91/&quot; title=&quot;メルカリが、AI時代にナレッジマネジメントに投資したわけ&quot;&gt;メルカリが、AI時代にナレッジマネジメントに投資したわけ&lt;/a&gt;)&lt;/p&gt;
&lt;h1&gt;AI-Centricな開発:Agent-Spec Driven Development(ASDD)&lt;/h1&gt;
&lt;h2&gt;現状の課題&lt;/h2&gt;
&lt;p&gt;私たちはすでに、ほぼ100％のエンジニアがAI Coding Assistantを活用しており、コード生成量も60％以上増加しています。しかし、冒頭でも述べたように、これはあくまでスタート地点にすぎません。&lt;/p&gt;
&lt;p&gt;本質的にAIを活用するためには、開発プロセス全体をゼロベースで見直し、AIを前提とした開発プロセスへと再構築する必要があります。AI Coding Assistantの活用を進める中で、利用率は大きく向上したものの、以下のような課題が明らかになりました。&lt;/p&gt;
&lt;h3&gt;プロンプト品質のばらつき&lt;/h3&gt;
&lt;p&gt;レギュレーションがないため、人によってプロンプトの質が大きく異なり、短時間で高品質なコードを生成できるケースがある一方、指示の反復が必要で、結果としてAIなしより遅くなるケースもありました。&lt;/p&gt;
&lt;h3&gt;コンテクスト収集の困難さ&lt;/h3&gt;
&lt;p&gt;プロンプトの書き方を理解していても、必要なコンテクストを正確に収集できず、適切な情報量をAIに与えられないことが多く発生しました。&lt;/p&gt;
&lt;h3&gt;生成コード品質のばらつき&lt;/h3&gt;
&lt;p&gt;一部ではレビューしやすい高品質なコードが生成される一方で、品質が低くレビューが困難だったり、バグの温床になりやすいコードが出力されるケースも見られました。&lt;/p&gt;
&lt;h3&gt;使用ツールのばらつき&lt;/h3&gt;
&lt;p&gt;当初はCursor を全社導入しましたが、その後Claude Codeなど新たなCoding Assistantが登場し、エンジニアによって利用ツールが異なる状況が生まれました。そのため、ベストプラクティスを集約し、共有することが難しくなりました。&lt;/p&gt;
&lt;p&gt;これらの課題の根底にあるのは、AIの使い方に関する共通レギュレーションが存在しないことです。そのため、チームや個人によってアウトプットの品質が大きくばらついてしまう状況が生まれていました。&lt;/p&gt;
&lt;h2&gt;目指す開発プロセス&lt;/h2&gt;
&lt;p&gt;私たちが目指す開発プロセスは、先に挙げた課題を解決したうえで、すべての開発プロセスにAIのポテンシャルが最大限活かされている状態です。&lt;/p&gt;
&lt;p&gt;具体的には、次のような姿を想定しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;コーディングだけでなく、スペック作成、デザイン、コードレビュー、QA／テストなど、あらゆる工程でAI Agentが活用され、開発全体が最適化されていること&lt;/li&gt;
&lt;li&gt;各プロセスに必要なコンテクストが適切に整理・提供され、AI Agentが効率よくタスクを遂行できること&lt;/li&gt;
&lt;li&gt;前述の課題が解消され、統一された開発プロセスとして標準化され、すべてのエンジニアが安定してAgentic Codingを実践できること&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらを実現することで、特定のツールに依存しない、共通化されたAgentic Codingのワークフローが全エンジニアに浸透している状態をゴールとしています。&lt;/p&gt;
&lt;h2&gt;Doubleプロジェクト:ASDDの実現&lt;/h2&gt;
&lt;p&gt;私たちが現在進めているのが、DoubleプロジェクトにおけるAgent-Spec Driven Development（ASDD）です。「Double」という名称は、生産性を“2倍にする”というプロジェクトの目的に由来しています。&lt;/p&gt;
&lt;p&gt;ASDDは、AI Agentに必要なコンテクストを正しく与え、適切なプロンプトで誰でも開発を進められるようにするための、AIフレンドリーな仕様フォーマットを整備する取り組みです。&lt;/p&gt;
&lt;p&gt;そしてこれは、単なる仕様書ではありません。AI Agentやエンジニアが実際にコードを書けるレベルまで落とし込むための、実装指向の設計書を作成するためのテンプレートです。抽象的なアーキテクチャ設計ではなく、既存のコードベースやプロジェクトの流儀に完全にフィットした形で、以下のような具体要素を明確に定義します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API定義&lt;/li&gt;
&lt;li&gt;データモデル&lt;/li&gt;
&lt;li&gt;DBスキーマ&lt;/li&gt;
&lt;li&gt;処理フロー&lt;/li&gt;
&lt;li&gt;テストシナリオ&lt;/li&gt;
&lt;li&gt;具体的な実装手順（TODO）&lt;/li&gt;
&lt;li&gt;マイクロサービス間の依存関係&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このテンプレートの目的は、「誰が・どのタスクを・どのファイルで・どのコードスタイルで実装するか」を明確にし、誰がAI Agentを使っても、同じ品質・同じ規約で実装できる状態をつくることです。言い換えれば、ASDDはチーム全体の実装プロセスを“再現可能な工程”にするための詳細仕様テンプレートです。&lt;/p&gt;
&lt;p&gt;また、AI Agentが正確にコーディングできるよう、タスクの粒度を小さく保つなどの工夫もテンプレートに組み込んでいます。&lt;/p&gt;
&lt;p&gt;(参考: &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251209-d0de07214d/&quot; title=&quot;Agent Specで小さく素早く回すメルカリモバイル開発現場&quot;&gt;Agent Specで小さく素早く回すメルカリモバイル開発現場&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;ASDDの活用範囲&lt;/h2&gt;
&lt;p&gt;ASDDのポイントは、コーディングだけのSpecではないということです。このSpecは全プロセスでAgentをフルに活用するためのベースとなります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/373833e0--2025-12-23-11.25.17.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;開発領域:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;バックエンド開発&lt;/li&gt;
&lt;li&gt;フロントエンド開発&lt;/li&gt;
&lt;li&gt;モバイル開発&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;品質保証:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;QAのテストケース自動生成&lt;/li&gt;
&lt;li&gt;AI Review&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;その他の領域:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;カスタマーサポートのスペック調査&lt;/li&gt;
&lt;li&gt;リスクマネジメント&lt;/li&gt;
&lt;li&gt;コンプライアンスチェック&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;PJ Aurora:UI自動生成&lt;/h2&gt;
&lt;p&gt;さらに、このAgent Specは、UIを自動生成するためのAI Agentの仕様としても活用されます。UI向けのAI Agentは「PJ Aurora」というプロジェクトとして開発が進められています。&lt;/p&gt;
&lt;p&gt;Auroraは、プロンプトを与えるだけで、内製のデザインシステムを活用しながらUIを自動生成できる、非常に先進的な取り組みです。デザインの一貫性を保ちつつ、UI作成にかかる工数を大幅に削減できる点が大きな特徴です。&lt;/p&gt;
&lt;p&gt;また本プロジェクトでは、生成されたUIが社内のデザイン規約に準拠しているかを自動でチェックするAI Agentの開発も進行しています。これにより、Agenticなデザインプロセスによる効率化と品質担保の両立が期待されています。&lt;/p&gt;
&lt;p&gt;(参考: &lt;a href=&quot;https://speakerdeck.com/mercari/mercari-gears-2025-pj-auroras-vision-and-automated-ui-quality-evaluation-agents&quot; title=&quot;PJ-Auroraが描く未来と、UI品質評価を自動化するエージェント開発&quot;&gt;PJ-Auroraが描く未来と、UI品質評価を自動化するエージェント開発&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;コンテクストの重要性&lt;/h2&gt;
&lt;p&gt;ここで改めて強調したい重要なポイントがあります。Agent Specを作成する際に与えるコンテクストの質は、極めて重要です。ASDDの品質は、このコンテクストの品質に大きく依存しており、適切なコンテクストが与えられなければ、期待通りに機能しません。逆に言えば、質の高いコンテクストが整理・提供されてはじめて、ASDDは本来の効果を発揮します。&lt;/p&gt;
&lt;p&gt;そのため、前述のとおり、このコンテクストを体系的に整備するためのKnowledge Managementプロジェクトも並行して進めています。これまでに作成された仕様書や意思決定の履歴、経営会議における意思決定情報などを整理・蓄積し、AI Agentにとっても人にとっても適切なコンテクストとして活用できる状態を整えています。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;メルカリのナレッジ基盤に最適化されたエージェントが自動的に一次情報にアクセスし、詳細な実装計画を生成します。また別のエージェントが、実装計画がサービスのコーディング規約に沿っているか、計画が所定のセキュリティ観点をクリアしているかなどのさまざまな調査を行います。この2つのエージェントが交互に修正と評価を繰り返し、最後に要件や仕様における不確実要素が残った場合には、開発者に追加の質問を行います。このように、「AIのコンテキストに適切な一次情報を与える」ことをプロセスによって保証しました。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(参考: &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251201-pj-double-towards-ai-native-development/&quot; title=&quot;pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌&quot;&gt;pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;ASDDがもたらす価値(Customer-Centricを極める)&lt;/h2&gt;
&lt;p&gt;ASDDの本質的なポイントは、いわゆる直感的なVibe Codingではなく、Agenticなアプローチによって、誰でも再現性のある開発レギュレーションを実現できることにあります。私たちは社内に蓄積されたベストプラクティスを継続的に収集し、このAgent Specを育て続けることで、AI Agentのポテンシャルを最大限に引き出し続けられる開発環境を構築していきたいと考えています。&lt;/p&gt;
&lt;p&gt;これが実現すれば、エンジニアに限らず、すべての開発に関わるメンバーが次のような状態を手に入れることができます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;より多くのタスクをこなせるようになる&lt;/li&gt;
&lt;li&gt;Trial &amp;amp; Errorを高速に回せるようになる&lt;/li&gt;
&lt;li&gt;解決が難しいデザインや設計に、より多くの時間を使えるようになる&lt;/li&gt;
&lt;li&gt;お客さまに提供すべき新しい価値について、深く考える時間を確保できるようになる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これまで私たちは「AI-Centric」という表現を使ってきましたが、人がよりお客さまの価値に集中できるようになるという意味において、これは本来私たちが大切にしてきたCustomer-Centricの究極の形であると、私は考えています。&lt;/p&gt;
&lt;h1&gt;全社的なAI化:AI Task Force&lt;/h1&gt;
&lt;h2&gt;開発プロセス以外にも最適化の必要性&lt;/h2&gt;
&lt;p&gt;ここまでは主に、開発プロセスを中心としたAI Agent活用によるプロセス改善についてお話ししてきました。これにより、プランニングからコーディング、リリースに至るまでのスピード向上が期待できます。&lt;/p&gt;
&lt;p&gt;しかし、実際のリリースまでを見渡すと、開発以外にも複数のプロセスが存在します。全体最適ができていなければ、どこかで必ず待ちが発生し、結果としてリリース全体のスピードは改善しません。&lt;br /&gt;
その代表例が、開発プロセスに付随して発生する関連チームによる各種チェックプロセス です。例えば当社では、プロジェクトの内容に応じて、リリース前に以下のような確認を行い、新しく開発したサービスの安全性や品質を担保しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;リーガルレビュー&lt;/li&gt;
&lt;li&gt;PRレビュー&lt;/li&gt;
&lt;li&gt;セキュリティレビュー&lt;/li&gt;
&lt;li&gt;コンプライアンスチェック&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;究極的には、全社レベルでのプロダクティビティが向上しなければ、AIを活用してもリリース速度は頭打ちになります。そのため、開発プロセスに限らず、全社のあらゆるワークフローをAgenticなワークフローへと進化させていく必要があります。&lt;/p&gt;
&lt;p&gt;理想的には、ASDDのSpecを活用し、これらの関連ワークフローについてもAI Agentが並列に処理を担うことで、大きなボトルネックを生むことなく、高速なリリースを実現できる状態です。その実現に向けて、私たちはコーディング以外の業務にも目を向け、Agenticなワークフローを横断的に構築していかなければならないと考えています。&lt;/p&gt;
&lt;h2&gt;PJ Socrates:Agenticな働き方の先駆け&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/2a7295a8--2025-12-23-11.23.03.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Agenticな開発を実現するうえで、その働き方の方向性をいち早く示してくれたのがPJ Socratesでした。Socratesは、いわばBI領域のAI Agentであり、社内に存在するさまざまなデータの取得や分析を支援する役割を担っています。&lt;/p&gt;
&lt;p&gt;これまで、施策のアイデア検討やビジネス戦略を考える際には、お客さまの動向や売れ行きなどを把握するために、DB上の複数テーブルをJoinした複雑なクエリを書く必要がありました。こうした作業には職人的な知識や経験が求められ、BIやAnalyticsチームのサポートなしにデータを取得することは難しいのが実情でした。Socratesの導入により、必要な分析内容を自然言語でAI Agentに依頼するだけで、データ取得から分析までを自動で行えるようになりました。&lt;/p&gt;
&lt;p&gt;当時はまだ「AI Agent」という考え方自体が明確でなかったこともあり、この体験は社内に大きなインパクトをもたらしました。AIは単なるタスクの自動化にとどまらず、データを取得し、分析し、さらには示唆を提示するところまで担える。つまり、AI Agentは自動化だけでなく、意思決定を含む高度なタスクも遂行できる存在であることが、具体的に示されたのです。&lt;/p&gt;
&lt;p&gt;そして、これまで人が担ってきたタスクを可能な限りAI Agentに委ね、人は本来より多くの時間を使うべき領域に集中していく。そのような働き方の方向性を、全社として実感する大きなきっかけとなりました。&lt;/p&gt;
&lt;p&gt;(参考: &lt;a href=&quot;https://speakerdeck.com/mercari/llms-creating-an-analytics-platform-for-humans-and-data-analysis-ai-agents-to-collaborate&quot; title=&quot;AI/LLMが拓くデータ活用の新時代：人間とデータ分析AI エージェントが協業する分析基盤へ&quot;&gt;AI/LLMが拓くデータ活用の新時代：人間とデータ分析AI エージェントが協業する分析基盤へ&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;AI Task Forceの発足&lt;/h2&gt;
&lt;p&gt;このAgenticな働き方を全社で実現するために発足したのが、AI Task Forceです。AI Task Forceは2025年7月に活動を開始しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/14f62a9a--2025-12-23-11.27.32.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;AI Task Forceでは、全社を33のドメインに分け、それぞれの領域のワークフローをAI化することを目的に、各ドメインへエンジニア1〜2名、PjM 1〜2名をアサインしました。さらに、各ドメインにはドメインオーナーを配置し、AI導入に向けた方向性や方針に関する意思決定を担っています。&lt;/p&gt;
&lt;p&gt;その結果、総勢約100名規模のメンバーがAI Task Forceに参画する体制となりました。ここでアサインされたエンジニアは、必ずしもAIのバックグラウンドを持つ人材に限定していません。あえてさまざまな領域のエンジニアを各部署から選抜し、互いに学び合いながら進める形をとっています。AI Task Force立ち上げ時の様子については、以下の記事でも紹介しています。記事中の写真からも分かるとおり、100名規模となると相当な体制です。&lt;/p&gt;
&lt;p&gt;活動開始当初は、AI-Native化という不確実性の高いテーマに取り組むことから、目的や目標の明確化・文書化を重視し、全社およびTask Force向けにAll Handsでの説明会を高頻度で実施することを強く意識して進めてきました。&lt;/p&gt;
&lt;p&gt;(参考: &lt;a href=&quot;https://careers.mercari.com/mercan/articles/53708/&quot; title=&quot;メルカリが本気で始めた「AI-Native」化。100名規模のタスクフォースが立ち上がるまで | mercan (メルカン)&quot;&gt;メルカリが本気で始めた「AI-Native」化。100名規模のタスクフォースが立ち上がるまで | mercan (メルカン)&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;AI Task Forceの3つの責務&lt;/h2&gt;
&lt;p&gt;前述の参照記事にも記載されているとおり、AI Task Forceには大きく分けて3つの責務があります。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;各ドメインにおけるすべての業務の棚卸し&lt;/li&gt;
&lt;li&gt;棚卸し結果を踏まえた、将来のAI化に向けたビジョンおよびロードマップの策定&lt;/li&gt;
&lt;li&gt;ロードマップに基づくAI化の実行&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;AI Task Forceは7月に発足し、現在までに33すべてのドメインにおける業務の棚卸しとロードマップ策定を完了しました。そして今まさに、各領域でAI Agent化に向けた具体的な開発フェーズに入っています。&lt;/p&gt;
&lt;p&gt;33領域を横断して業務を棚卸しした結果、定義されたワークフローの数は約4,000にも上りました。もちろん、これらすべてを一律にAI化するわけではありませんが、各ドメインで策定されたロードマップに基づき、中長期的な視点で段階的にAI化を推進していく計画です。&lt;/p&gt;
&lt;h2&gt;AI Task Forceからの学び&lt;/h2&gt;
&lt;p&gt;AI Task Forceはまだ道半ばではありますが、ここまでの取り組みを通じて、すでに多くの学びを得ることができました。ここでは、その中でも特に重要だと感じているポイントを振り返ります。&lt;/p&gt;
&lt;h3&gt;複数ロール間の連携強化&lt;/h3&gt;
&lt;p&gt;今回のAI Task Forceでは、従来の役割分担を超えた取り組みが生まれました。通常、Software Engineerはプロダクト開発を主に担い、リーガルやファイナンスといったコーポレート領域の業務に深く関わることは多くありません。しかし今回、そうした領域にエンジニアが入り込み、業務の棚卸しを行ったことで、次のような成果が生まれました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;エンジニアのフレッシュな視点により、現場では当たり前になっていた冗長な業務に対して、効率化のアイデアを提示できた&lt;/li&gt;
&lt;li&gt;場合によっては、その業務自体を根本的に不要にできる可能性を示すことができた&lt;/li&gt;
&lt;li&gt;エンジニア自身にとっても、これまでにない経験となり、新しいチャレンジへのモチベーションにつながった&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このように、ロールを越えた協働は、業務改善だけでなく、人の成長や組織の活性化にも大きく寄与したと感じています。&lt;/p&gt;
&lt;h3&gt;不確実性との向き合い方&lt;/h3&gt;
&lt;p&gt;AI Task Forceでは、各領域ごとに業務の棚卸しを行い、AI-Native化に向けたビジョンやロードマップを策定してきました。しかし、無数に存在するタスクの中から大きな方針を定め、優先順位を決めることは決して簡単ではありません。実際、領域によっては、この意思決定に大きく悩み、時間を要したケースもありました。&lt;/p&gt;
&lt;p&gt;別のADVENT CALENDAR記事の中で、@panoramaさんは次のように述べています。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;しかし最終的には&lt;br /&gt;
「誰も正解がわからない。でも、手探りで失敗を繰り返したとしても進める価値がある」&lt;br /&gt;
のがAI-Native化だと考え直しました。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(参考: &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251210-ai-tf-facing-uncertainty/&quot; title=&quot;AI Task Forceで学んだ「不確実性との向き合い方」&quot;&gt;AI Task Forceで学んだ「不確実性との向き合い方」&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;私自身、メンバーに苦労をかけてしまったのではないかと反省する気持ちもあります。一方で、たとえ時間がかかったとしても、この不確実性と真正面から向き合い、じっくり考えて答えを出すプロセスを経たことが、AIを単なるツールとして導入するのではなく、働き方そのものを見直すきっかけになったと信じています。&lt;/p&gt;
&lt;p&gt;AI Task Forceをリードする立場として、私自身も不安を感じる場面は多くありましたが、悩みや不安を抱えながらも前に進んでくれたメンバーを、心から誇りに思っています。AI Task Forceのように不確実性の高いプロジェクトを進めることは容易ではありません。これから同様の取り組みを始める方には、ぜひ先ほどの記事も参考にしていただければと思います。&lt;/p&gt;
&lt;h3&gt;情報共有の難しさ&lt;/h3&gt;
&lt;p&gt;今回、33の領域をそれぞれ独立して進めたことで、各領域において迅速な意思決定が可能になった一方、反省点もありました。それは、各領域で得られたベストプラクティスやレトロスペクティブを、十分に共有できなかった点です。&lt;/p&gt;
&lt;p&gt;33領域すべてで定例ミーティングを行うのは現実的ではありませんが、もし領域間でスムーズに知見を共有できる仕組みがあれば、全体の効率はさらに高められたはずです。各領域のロードマップが出揃った今だからこそ、進捗や学びをよりオープンに共有し、全社としてより洗練された進め方を模索していきたいと考えています。&lt;/p&gt;
&lt;p&gt;同時に、この「情報共有の難しさ」もまた、Agentベースのアプローチによって効果的に解決できる課題の一つだと捉えています。&lt;/p&gt;
&lt;p&gt;(参考:  &lt;a href=&quot;https://www.youtube.com/watch?v=Sz2-eVnOtbQ&quot; title=&quot;【メルカリ本気の全社AI化】100人で2000人を改革するAIタスクフォースの全貌 / すべての仕事をAI-Native化 / 鍵は&quot;&gt;【メルカリ本気の全社AI化】100人で2000人を改革するAIタスクフォースの全貌 / すべての仕事をAI-Native化 / 鍵は&lt;/a&gt;)&lt;/p&gt;
&lt;h1&gt;未来のビジョン:AI化の先にあるもの&lt;/h1&gt;
&lt;h2&gt;メルカリのミッションと現在地&lt;/h2&gt;
&lt;p&gt;ここまで、私たちはAI Task Forceを通じて全社のあらゆる業務をAI-Native化する取り組みを進めていること、そして並行して、特に開発プロセスにおいてはASDDを軸にしたAgent化を推進していることをお話ししてきました。では、このようにすべてをAI-Nativeへと進化させた先に、私たちは何を目指しているのでしょうか。&lt;/p&gt;
&lt;p&gt;メルカリは、「あらゆる価値を循環させ、あらゆる人の可能性を広げる」というミッションのもと、マーケットプレイス、フィンテックサービス、そして新たな事業を展開してきました。そしてこのミッションを真に実現するために、私たちにはまだ取り組むべきことが数多く残されています。&lt;/p&gt;
&lt;h2&gt;世界展開への道&lt;/h2&gt;
&lt;p&gt;近年、メルカリは&lt;a href=&quot;https://about.mercari.com/press/news/articles/20250930_crossborder/&quot; title=&quot;国際展開を本格的に進めており、台湾をはじめ、香港でのサービス提供も開始&quot;&gt;国際展開を本格的に進めており、台湾をはじめ、香港でのサービス提供も開始&lt;/a&gt;しました。私たちはマーケットプレイスを世界へと広げることで、より多くのモノの価値を循環させ、世界中の人々の可能性を広げていくというビジョンを掲げ、着実に国際展開の基盤を築いてきました。一方で、実際にサービスをグローバルに展開しようとすると、どれだけスケーラブルな仕組みを構築したとしても、各国ごとのローカライズ対応や日々の運用を考えたとき、現在の社員数のままでは限界があるという課題意識も持っていました。&lt;/p&gt;
&lt;p&gt;しかし、これまでに述べてきたように、AI Agentを最大限に活用し、一人ひとりの生産性や能力を拡張できれば、現実的な人数で世界展開を本当に実現することも決して夢ではない と、今では考えています。&lt;/p&gt;
&lt;p&gt;これはマーケットプレイスに限った話ではありません。フィンテック領域においても、グローバルに展開される金融サービスを提供し、私たち独自の与信モデルを世界に広げていくことで、世界中の人々の可能性をさらに広げられるはずだと考えています。&lt;/p&gt;
&lt;p&gt;@deeeetさんの記事にもあるとおり、現在メルカリでは、国際展開におけるサービスリリースと並行して、基盤開発にも非常に力を入れています。AI-Native化を進めるうえで、このようなアーキテクチャ整備を同時に行うことは、AI前提の開発プロセスをより効率的かつ持続可能なものにするために極めて重要です。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;先日の事業戦略発表会において共有しましたが，今後更にメルカリの海外展開を加速させるためにグローバル版のメルカリアプリを先日リリースしました．このアプリは現在提供してる日本版・アメリカ版のメルカリとは異なる新しいアプリであり，またアプリだけではなくその裏側のバックエンド基盤も新たに再構築しています．&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(参考: &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251007-a09afcd49b/&quot; title=&quot;グローバル展開にむけたアプリと基盤の再構築&quot;&gt;グローバル展開にむけたアプリと基盤の再構築&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;新たな価値の創造&lt;/h2&gt;
&lt;p&gt;そして、世界中の人々の可能性をさらに広げていくためには、既存のサービスにとどまらず、これまでにない新たな価値を提供するサービスを生み出し続ける必要があります。&lt;/p&gt;
&lt;p&gt;これまでは、新規プロジェクトや新規事業を立ち上げる際、まず人的リソースが大きな制約となり、構想段階で断念せざるを得ないケースも少なくありませんでした。しかしこれからは、そうした前提となっていた限界を一度手放し、「実現したい価値」から発想し、次々と形にしていける世界へと変わっていくと考えています。&lt;/p&gt;
&lt;p&gt;その中で、新しい事業づくりにおけるAI Agent の活用は、今後ますます重要なテーマになります。すでに私たちは、並行してさまざまな実験的な取り組みを進めていますが、その成果については、来年のメイントピックとして改めてお伝えしたいと思います。ぜひ楽しみにしていてください。&lt;/p&gt;
&lt;h2&gt;Customer-Centricの究極形&lt;/h2&gt;
&lt;p&gt;そして、先ほども触れたとおり、AI-Centricな組織を築いていくことで、私たちはより多くの時間とエネルギーを、「お客さまに提供したい本質的な価値」に向けられるようになります。これは単なる効率化ではなく、Customer-Centricをさらに突き詰めていくための進化だと考えています。&lt;/p&gt;
&lt;p&gt;AIを前提に、より深くお客さまの価値に向き合う。それこそが、私たちが目指す「AI-Native Company」の姿です。&lt;/p&gt;
&lt;h2&gt;今後に向けて&lt;/h2&gt;
&lt;p&gt;メルカリでは、AI-Native Companyを目指すことを全社方針として明確に打ち出しました。その取り組みの中核として、ASDDを中心にAI Agentを前提とした開発プロセスの再構築を進めています。さらに、開発領域にとどまらず、全社のあらゆる業務をAI-Native化していくために AI Task Forceを立ち上げ、組織横断での変革にも着手しました。&lt;/p&gt;
&lt;p&gt;これらの取り組みは、まだ始まったばかりで道半ばです。しかし、不確実性と向き合いながらも一歩ずつ前進することで、人がより本質的な価値創出に集中できる組織を実現していきたいと考えています。&lt;/p&gt;
&lt;p&gt;これは私たちに限った話ではなく、社会全体としてもAI-Native化は確実に進みつつあります。すでに多くの業務をAI Agent化している方も多いでしょう。一方で、会社の規模が大きくなるほど、全社的にAIを本質的に浸透させる難易度は高まると、個人的には感じています。だからこそ、来年は社会にとって「AI Agentをいかに本質的に定着させるか」が問われる一年になると考えています。&lt;/p&gt;
&lt;p&gt;私たち自身も現在、AI Agentを効果的に活用するための実験を日々重ねていますが、来年はそれを一部の取り組みにとどめず、より広く、より深く浸透させていくフェーズに入ります。すでに始まってはいますが、改めて「AI-Native、すなわちAgentベースでまずプロセスを考える」という姿勢を、組織全体で言い続け、実践し続ける一年にしていきたいと思います。&lt;/p&gt;
&lt;p&gt;また、本稿では十分に触れられませんでしたが、来年はAIに関わるセキュリティ、ガバナンス、コンプライアンスも、より重要なテーマになっていくでしょう。これらは、AIを安全かつ持続的に活用し、全社に浸透させていくうえで欠かせない要素です。&lt;/p&gt;
&lt;p&gt;今後も、メルカリにおけるAI-Native化の進捗については、継続的に発信していきたいと考えています。最後までお読みいただき、ありがとうございました。そして今年も大変お世話になりました。どうぞ良いお年をお迎えください。&lt;/p&gt;
</content:encoded></item><item><title>メルペイ インターンでの挑戦と学び：EGP Cardsと向き合った3ヶ月間</title><link>https://engineering.mercari.com/blog/entry/20251225-building-egp-cards-at-merpay/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251225-building-egp-cards-at-merpay/</guid><description>&lt;p&gt;メルペイ インターンでの挑戦と学び：EGP Cardsと向き合った3ヶ月間 こんにちは。メルペイのGrowth Platformでフロントエンド・エンジニアとしてインターンをしている@Yusaku（宮田 優作）です。 こ [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 25 Dec 2025 10:00:57 GMT</pubDate><content:encoded>&lt;h1&gt;メルペイ インターンでの挑戦と学び：EGP Cardsと向き合った3ヶ月間&lt;/h1&gt;
&lt;p&gt;こんにちは。メルペイのGrowth Platformでフロントエンド・エンジニアとしてインターンをしている&lt;a href=&quot;https://x.com/pkmiya__&quot;&gt;@Yusaku&lt;/a&gt;（宮田 優作）です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の25日目の記事です。&lt;br /&gt;
私は2025年10月からインターンを開始し、今月で3ヶ月目になりました（図1）。&lt;br /&gt;
この記事では、インターン期間中に取り組んだタスクと得た学びについて紹介します。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/cf971c51-img_selfie-mercari-landscape.png&quot; alt=&quot;図１：オフィスで撮影した私の写真&quot; /&gt;&lt;br /&gt;
図１：オフィスで撮影した私の写真&lt;/p&gt;
&lt;h2&gt;チームについて&lt;/h2&gt;
&lt;p&gt;私が所属する Growth Platform Frontend チームは、Engagement Platform（通称EGP）という社内向けマーケティングツールを開発しています。&lt;br /&gt;
このツールを使うことで、マーケターや プロジェクトマネージャーは、ポイントやクーポンなどのインセンティブ配布、ランディングページ（LP）の作成・公開、キャンペーン作成といった CRM業務をコーディング不要で簡単に行うことができます（図2）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/20251225-building-egp-cards-at-merpay_fig-2.jpg&quot; alt=&quot;図2：EGPのノーコードエディタ（EGP Content）&quot; /&gt;&lt;br /&gt;
図2：EGPのノーコードエディタ（EGP Content）&lt;/p&gt;
&lt;p&gt;今回のインターンではEGP Cardsという機能の向上に取り組みました。EGP Cardsは、Web・iOS・Androidのクロスプラットフォームで利用できるカード型のコンポーネントを作成・公開する機能です。&lt;br /&gt;
EGP Cardsは、いわゆるWebページのエディタ機能（EGP Pages）とは異なり、サーバがUIの構造を返却するというServer Driven UIアーキテクチャを採用しています。エディタ上で作成されたコンポーネントのコンテンツはJSONファイルとして保存され、各プラットフォームで共通のUIとして描画されます（図3）。&lt;br /&gt;
Server Driven UIとEGP Cardsのアーキテクチャについては、同じくGrowth Platformチームの@togamiさん、@Stefanさんの記事をそれぞれご覧ください。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241210-f7c478382a/&quot;&gt;WYSIWYGウェブページビルダーを支える技術とServer Driven UIへの拡張&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251214-supercharging-user-engagement-how-mercari-is-using-server-driven-ui-to-reduce-time-to-market/&quot;&gt;Supercharging User Engagement: How Mercari is Using Server-Driven UI to Reduce Time-to-Market&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/20251225-building-egp-cards-at-merpay_fig-3.jpg&quot; alt=&quot;図3：EGP Cardsの編集画面&quot; /&gt;&lt;br /&gt;
図3：EGP Cardsの編集画面&lt;/p&gt;
&lt;h2&gt;タスク1 &amp;#8211; Dry Run for EGP Cards&lt;/h2&gt;
&lt;h3&gt;タスク概要：Dry Runとは？&lt;/h3&gt;
&lt;p&gt;Dry Runとは、変数を設定し、そこにデータを挿入することで、状態をエミュレートできる機能のことです。これにより、API呼び出しを記述したり実機で動作確認を行ったりする前に、コンテンツの挙動を確認することができます。&lt;br /&gt;
このタスクでは、EGP CardsでDry Run機能を利用できるようにする実装を行いました（図4）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/20251225-building-egp-cards-at-merpay_fig-4.jpg&quot; alt=&quot;図4：今回実装した、EGP CardsのDry Run機能&quot; /&gt;&lt;br /&gt;
図4：今回実装した、EGP CardsのDry Run機能&lt;/p&gt;
&lt;h3&gt;動作の仕組み&lt;/h3&gt;
&lt;p&gt;Dry Run機能は、以下の流れで動作します。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;利用者がDry Runを有効化し、フィールドにモックデータを入力する&lt;/li&gt;
&lt;li&gt;エディタが構造ツリーを再帰的に走査し、動的にJavaScriptのコードを評価して変数を値に置換する&lt;/li&gt;
&lt;li&gt;置換された値がキャンバス上に表示される&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;実装の流れ&lt;/h3&gt;
&lt;p&gt;以下の流れで実装を進めました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;EGP Pagesに既に実装されていたDry Run機能について、コードリーディングやロギングを行い、実装ロジックを理解する&lt;/li&gt;
&lt;li&gt;EGP Cards特有の仕様を理解した上で、同様に利用できるDry Run機能を実装する&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;コーディングの際には、EGP PagesとEGP Cardsで共通利用できそうな処理を探し、適度にファイルを切り出すことで、可読性・保守性を意識した実装を心がけました。&lt;/p&gt;
&lt;h2&gt;タスク2 &amp;#8211; Content Agent Improvement for EGP Card&lt;/h2&gt;
&lt;h3&gt;背景：Content Agentの課題&lt;/h3&gt;
&lt;p&gt;EGPのノーコードエディタ（EGP Content）は、CardsのほかにもPagesやE-mailsなど、複数の種類のコンテンツを扱うことができます。&lt;br /&gt;
また、EGP ContentにはAIエージェント（通称 Content Agent）が導入されており、対話を通じてコンテンツの要約や書き換えを行うことができます（図5）。&lt;br /&gt;
一方で、当時のContent Agentは、コンテンツ種別ごとのエディタ仕様を十分に理解していないという課題がありました。その結果、UIが崩れたコンテンツを生成してしまい、利用者の期待通りのアウトプットを提供できない可能性がありました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/20251225-building-egp-cards-at-merpay_fig-5.jpg&quot; alt=&quot;図5：Content Agentの会話処理パイプライン&quot; /&gt;&lt;br /&gt;
図5：Content Agentの会話処理パイプライン&lt;/p&gt;
&lt;h3&gt;実装の流れ&lt;/h3&gt;
&lt;p&gt;この課題を解決するため、以下の流れで実装を進めました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;EGP Cardsの仕様やデータ構造を記述したプロンプトを作成する&lt;/li&gt;
&lt;li&gt;作成したプロンプトを、Content AgentのAgent Layerで条件付きで注入する&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;EGP Cardsには、メディアクエリに対応していないことや、すべての要素がFlexで構成されていることなど、いくつかの制約があります。これらの制約や期待される出力をプロンプトに明示することで、Content AgentがCardsに適したコンテンツを生成できるようにしました（図6）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/20251225-building-egp-cards-at-merpay_fig-6.jpg&quot; alt=&quot;図6：EGP CardsでContent Agentを利用する様子&quot; /&gt;&lt;br /&gt;
図6：EGP CardsでContent Agentを利用する様子&lt;/p&gt;
&lt;h2&gt;学んだこと&lt;/h2&gt;
&lt;h3&gt;チーム開発におけるアウトプットの出し方を学んだこと&lt;/h3&gt;
&lt;p&gt;Dry Run 機能の実装を通じて、チーム開発におけるアウトプットの出し方について多くの学びがありました。実装の正しさや機能の完成度だけでなく、Pull Request（PR）の切り方やレビューの受け方が、チーム全体の開発効率や生産性に大きく影響することを実感しました。&lt;br /&gt;
具体的には、バグ修正やリファクタリングであっても、PRのサイズが大きくなりすぎる場合やタスクのスコープ外に及ぶ場合には、別のPRとして切り出すことでレビューコストを抑えられることを学びました。また、コードやPRコメントには、なぜその実装にしたのか、どのような選択肢があり、何をしない判断をしたのかといった実装意図を明示することが重要です。これにより、レビュワーとの認識齟齬を防ぎ、建設的なレビューにつながると感じました。&lt;br /&gt;
レビューを受けた際にも、指摘内容をすぐに修正として反映するのではなく、まずはレビュワーの意図を正しく理解することが重要です。場合によっては背景や前提条件をすり合わせた上で議論することで、より良い設計や実装にたどり着けることを学びました。これらの経験を通じて、個人としてコードを書く力だけでなく、チームで価値を届けるためのコミュニケーションや姿勢の重要性を強く認識しました。&lt;/p&gt;
&lt;h3&gt;メルカリカルチャーを実体験として理解できたこと&lt;/h3&gt;
&lt;p&gt;メルカリでは、情報の透明性やフラットな意思疎通によって、個人に大きな裁量が与えられていると言われることが多いです。実際にインターンを通じて、その点を強く実感しました。一方で、個人的に特に印象に残ったのは、英語を前提としたグローバルな開発環境です。&lt;br /&gt;
これまで参加してきたインターンはすべて日本語環境だったため、ドキュメントやコミュニケーション、議論の場がすべて英語になる経験は非常に新鮮でした。グローバルなチームである以上、英語でのスムーズな意思疎通が求められることは理解していましたが、実際に業務の中でそれを実践し、議論や開発を進められたことに大きなやりがいを感じました。&lt;br /&gt;
実際の業務では、英語で書かれたREADMEや仕様書を読み込んだ上でPull Requestを作成し、設計意図や懸念点を英語で説明・議論しました。認識の齟齬を防ぐため、必要に応じて日本語での補足も行いながら、主体的にコミュニケーションを取ることを意識しました。この経験を通じて、メルカリのカルチャーは単なるスローガンではなく、日々の業務に根付いたものだと感じました。&lt;/p&gt;
&lt;h3&gt;技術的挑戦を通じて、学びの広がりを再認識できたこと&lt;/h3&gt;
&lt;p&gt;これまで私は、フロントエンドを主な技術領域として、インターンや個人開発に取り組んできました。そのため、フロントエンド領域における学びは、徐々に頭打ちになりつつあるのではないかと感じていました。&lt;br /&gt;
しかし、EGPというツールに触れたことで、その考えは大きく変わりました。EGPは非常にインタラクティブでリッチなUIを持つだけでなく、その裏側では、ノーコードによるコンテンツの作成・配信の仕組みや、安全かつ効率的にAI Agentとやりとりを行うための設計など、複雑で奥深いロジックが支えられていることを知りました。&lt;br /&gt;
タスクでは、ある程度抽象度のある状態で要件を受け取り、自分で具体的な実装タスクへ分解した上で設計・実装を進めました。また、EGPの使い方をキャッチアップしている最中に、イメージのプレビュー機能があることで利用者体験が向上するのではないかといった改善提案も行いました。&lt;br /&gt;
さらに、Content Agentの改善では、今回のCards向け実装に閉じることなく、将来的にPagesやE-mailsなど他のContent typeにも展開しやすいよう、Typeごとにプロンプトを切り出す設計とし、可読性や拡張性を意識しました。エンジニアがプロダクトの将来を見据えて設計・実装することが、利用者体験の向上や業務効率化につながり、結果として事業価値に直結する点は、メルカリならではの魅力だと感じています。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;今回のインターンでは、EGP Cardsという機能の向上に取り組みました。インターンを通じて、技術的なスキルだけでなく、プロダクトの価値やチームとの関わり方を含めてエンジニアリングに向き合う姿勢を学ぶことができました。&lt;br /&gt;
実務を通して得たこれらの学びを、今後の開発や自身の成長につなげていきたいと考えています。最後までお読みいただきありがとうございました。&lt;/p&gt;
</content:encoded></item><item><title>「AIが学習しやすいナレッジ基盤」メルカリが全社で導入したNotion Architecture ver1.0</title><link>https://engineering.mercari.com/blog/entry/20251224-mercariadventcalendar/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251224-mercariadventcalendar/</guid><description>&lt;p&gt;こんにちは！メルカリ Engineering Office チームの@kikoと@aisakaです。 この記事は、Mercari Advent Calendar 2025 の24日目の記事です。 先日の t-hiroi  [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 24 Dec 2025 12:00:29 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは！メルカリ Engineering Office チームの&lt;a href=&quot;https://x.com/kiko_tw&quot;&gt;@kiko&lt;/a&gt;と&lt;a href=&quot;https://x.com/ai_sakamoto921&quot;&gt;@aisaka&lt;/a&gt;です。&lt;/p&gt;
&lt;p&gt;この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の24日目の記事です。&lt;/p&gt;
&lt;p&gt;先日の t-hiroi の記事「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251213-96e00d1d91/&quot;&gt;メルカリが、AI時代にナレッジマネジメントに投資したわけ&lt;/a&gt;」では、メルカリが推進するナレッジマネジメント戦略について紹介がありました。その中で特に重要なのが、AI-Native な会社を実現するためには 「AI が正しく学習できるコンテクスト（文脈）を整える」ことが不可欠であるという点です。&lt;/p&gt;
&lt;p&gt;この考え方を軸に、メルカリでは全社を挙げてナレッジマネジメントを再設計してきました。&lt;br /&gt;
また先日も発表がありましたが、ナレッジの蓄積・共有基盤としてNotion の全社導入を進めています。&lt;br /&gt;
&lt;a href=&quot;https://prtimes.jp/main/html/rd/p/000000064.000088144.html&quot;&gt;メルカリがNotion 全社導入で「AI-Native」企業への変革を加速&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;私たちは、このNotionを活用した全社ナレッジマネジメント基盤の構築・運用を担当しています。&lt;/p&gt;
&lt;p&gt;本記事では、メルカリがどのように Notion 上でナレッジを整理しているのか、そしてその基盤となる Notion Architecture ver1.0をご紹介します！&lt;/p&gt;
&lt;h1&gt;Notion Architectureとは&lt;/h1&gt;
&lt;p&gt;Notion Architectureは、Mercariグループにおける情報を&lt;strong&gt;「構造的に」「一貫して」「AIが活用しやすい形で」&lt;/strong&gt;管理するための設計指針です。&lt;/p&gt;
&lt;p&gt;日々蓄積されるナレッジを会社の資産として活用するためには、散在しやすい社内の情報を極力構造化し、AIがコンテクストとして活用できる状態にすることが最も重要です。&lt;/p&gt;
&lt;p&gt;そのため Notion Architectureでは、組織ごとの柔軟性は確保しながらも、グループ全体では共通のデータベース構造・テンプレート・メタデータを用いて標準化を行いました。&lt;/p&gt;
&lt;p&gt;これにより次の状態を実現します：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;情報が散在せず、全社横断で検索・参照できる&lt;/li&gt;
&lt;li&gt;ドキュメント構造とメタデータをなるべく統一し、AIが学習しやすく&lt;/li&gt;
&lt;li&gt;メンバーが日々記録した情報が、自然とグループ全体の資産へと統合される&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Notion上でのKnowledgeの置き場所&lt;/h1&gt;
&lt;p&gt;今後も引き続きアップデート予定ですが、まずver1.0として整理した構造を紹介します。&lt;/p&gt;
&lt;h2&gt;Mercari WorkspaceにおけるTeamspace設計&lt;/h2&gt;
&lt;p&gt;MercariのNotion Workspace配下には下記２種類のTeamspaceを設計しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Group-wide Teamspace（全社員向け）→ グループ共通のナレッジ基盤を構成する領域&lt;/li&gt;
&lt;li&gt;Business / Domain Teamspace（事業・ドメインごと）→ 各チームが自律的に運用しながらナレッジを管理する領域&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/bf72b268--2025-12-22-16.49.44.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この2層構造を採用した背景には、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事業・ドメインごとの文化に合わせた柔軟性&lt;/li&gt;
&lt;li&gt;全社では AI が学習しやすい統一された構造の維持&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という両立を実現したい、という狙いがあります。&lt;/p&gt;
&lt;p&gt;また、メルカリは約 2,100 名規模のグループ会社であり、Teamspace が増えすぎると管理コストが跳ね上がります。そのため、Teamspace の種類を最小限に絞ることも重要なポイントでした。&lt;/p&gt;
&lt;h2&gt;Teamspace間のKnowledge構造&lt;/h2&gt;
&lt;p&gt;各 Business / Domain Teamspace は、それぞれが独立運用できるよう設計しつつ、Group-wide Teamspace とリンクした状態で情報が流れる構造 になっています。&lt;/p&gt;
&lt;p&gt;こうすることで「柔軟な運用 × 集約されたナレッジ基盤」の両立を実現しています。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/2e92494a--2025-12-22-16.49.58.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;では、それぞれのTeamspaceが具体的にどのような役割を担い、どう連携しているのかを説明していきます。&lt;/p&gt;
&lt;p&gt;以下では、Mercari Group Teamspaceと事業・ドメイン別Teamspaceそれぞれの目的と役割を整理しています。この2つのTeamspaceは補完し合い、全社のナレッジ基盤を支えています。&lt;/p&gt;
&lt;h3&gt;Mercari Group Teamspace&lt;/h3&gt;
&lt;h4&gt;目的&lt;/h4&gt;
&lt;p&gt;全社共通のナレッジ基盤として、Mercari Group Teamspaceは、グループ全体の中核となる複数の共通データベースを保持しハブとして機能することを目的としています。&lt;/p&gt;
&lt;h4&gt;役割&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;共通データベースを保持し、文書を一元管理する&lt;/li&gt;
&lt;li&gt;ドキュメントテンプレート、メタデータ設計、カテゴリ定義など「全社標準」を提供する&lt;/li&gt;
&lt;li&gt;事業・ドメイン別Teamspaceからのページ作成・参照を受け入れるハブとして機能する&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;事業・ドメイン別 Teamspace&lt;/h3&gt;
&lt;h4&gt;目的&lt;/h4&gt;
&lt;p&gt;事業やドメインごとの運用自由度を確保しつつ、Group-wide構造とリンクされた状態で情報を発信・管理することを目的としています。&lt;/p&gt;
&lt;h4&gt;役割&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Group-wide共通DBへの新規ページ作成を行う（＝情報を正しい構造に流す）&lt;/li&gt;
&lt;li&gt;Group-wide DBのViewを表示・参照し、自部門に関連する情報を整理・活用する&lt;/li&gt;
&lt;li&gt;必要に応じて事業独自のDBを保持し、ローカルでの文書管理を支援する&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ナレッジはどのように管理されるのか&lt;/h2&gt;
&lt;h3&gt;標準化されたドキュメント構造とデータベース管理&lt;/h3&gt;
&lt;h4&gt;なぜデータベースとテンプレートによる標準化が必要なのか&lt;/h4&gt;
&lt;p&gt;おそらく多くの企業さんでも同様の課題を持っているかと思いますが、メルカリでは長年ナレッジが散在してしまう課題を抱えています。&lt;br /&gt;
例えば：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;プロジェクト資料の保存場所が統一されておらず、探すのに時間がかかる&lt;/li&gt;
&lt;li&gt;ドキュメントの構造や粒度が人によって異なり、AIが文脈を正しく理解できない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;こうした状態では記録した情報が活用されず、「書いたけど探せない」「探せないから新しく作る」という負のサイクルに陥ります。&lt;/p&gt;
&lt;h4&gt;メルカリのアプローチ：共通データベース × テンプレートによる自然な標準化&lt;/h4&gt;
&lt;p&gt;これを解決するため、Notion 上に作成されるすべてのドキュメントは、用途別に整理された共通データベース上で管理されます。&lt;/p&gt;
&lt;p&gt;各データベースには目的に応じたテンプレートを用意し、文書構造・メタデータ・プロパティの標準化をしています。&lt;/p&gt;
&lt;p&gt;こうすることにより 「記録すれば自然と整理される」ナレッジ基盤 の実現を目指しています。&lt;/p&gt;
&lt;h3&gt;Group-wide Databaseの設計思想&lt;/h3&gt;
&lt;p&gt;では、具体的にどのようなデータベース群を設計したのか紹介します。&lt;br /&gt;
Group-wide Databaseは、Mercariグループ全体で共通化されたナレッジ基盤の中核を担うデータベース群です。&lt;br /&gt;
情報をタイプ別に整理し、横断検索・AI活用・全社統一運用を支える目的で設計されています。&lt;/p&gt;
&lt;h4&gt;設計思想&lt;/h4&gt;
&lt;h5&gt;目的&lt;/h5&gt;
&lt;p&gt;ドキュメント構造を統一し、あらゆる情報を一貫したフォーマットで再利用・検索できる状態を作る&lt;/p&gt;
&lt;h5&gt;方針&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;各DBは「用途単位」で分割する&lt;/li&gt;
&lt;li&gt;テンプレートを通じて文書構造・メタデータを標準化&lt;/li&gt;
&lt;li&gt;同じフォーマットで記録・共有できるようにする (AIが学習しやすくする)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;テンプレート利用によるドキュメント標準化の推進&lt;/h3&gt;
&lt;p&gt;文書作成時は、各データベース（DB）に紐づいた新規ページ作成テンプレートを使用します。&lt;br /&gt;
※ ここでのテンプレートは、Notion公式マーケットプレースのものではなく、Mercari Workspace内で管理される共通テンプレートを指します。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/973a494c--2025-12-22-16.50.11.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;テンプレートを利用することで、文書構成、メタデータ、プロパティを標準化し、全員が同じ形式と用語でナレッジを共有できるようにします。&lt;/p&gt;
&lt;p&gt;下記のような効果やメリットを見込んでいます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;コラボレーションの推進&lt;/li&gt;
&lt;li&gt;プロセス変更への柔軟対応&lt;/li&gt;
&lt;li&gt;AI が学習しやすい文書構造&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Notion Architecture ver1.0のチュートリアル&lt;/h1&gt;
&lt;p&gt;ここまで、Notion Architecture ver1.0の構造とその設計背景などを紹介してきました。&lt;/p&gt;
&lt;p&gt;メルカリがどのように Notion 上でナレッジを整理しているのか、Notionの画面を投影しながら説明します。これからNotionを導入する方は、参考程度に。既にNotionを導入している方からは、先駆者として「おいおいこういう事が起こるから気をつけると良いよ」といったアドバイスをいただけると嬉しいです！&lt;br /&gt;
&lt;iframe loading=&quot;lazy&quot; title=&quot;メルカリNotion設計サンプル&quot; width=&quot;580&quot; height=&quot;326&quot; src=&quot;https://www.youtube.com/embed/GnHDu1oRs58?feature=oembed&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;h1&gt;おわりに&lt;/h1&gt;
&lt;p&gt;本記事では、メルカリが全社で取り組むナレッジマネジメントの基盤として構築しているNotion Architecture ver1.0の概要とチュートリアルを紹介しました。&lt;/p&gt;
&lt;p&gt;Notion Architectureはまだ進化の途上であり、今後も組織の拡大や AI 活用の進展に合わせて継続的にアップデートしていきます！&lt;/p&gt;
&lt;p&gt;最後になりますが、こうした全社規模のプロジェクトには、様々な方によるサポート、貢献が不可欠です。今回のNotion導入も組織、チーム、会社を跨いだ多くの関係者の皆さんの協力のおかげで実現できました。&lt;/p&gt;
&lt;p&gt;特に、Notion導入を先にされていたSansanさんには、構造やテンプレート作成の際に大変お世話になりました。会社をまたいだ繋がりがあったのも本プロジェクトの醍醐味でした。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/0f393bd4--2025-12-22-16.50.30.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;明日の記事はメルカリのCTO &lt;a href=&quot;https://x.com/kimuras&quot;&gt;@kimuras&lt;/a&gt;さんによる最終記事です。引き続き最後までお楽しみください！&lt;/p&gt;
</content:encoded></item><item><title>なぜ再発防止は、思ったように機能しないのか。メルカリのプロダクト開発でCAST分析が必要だった理由</title><link>https://engineering.mercari.com/blog/entry/20251224-mercari-cast/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251224-mercari-cast/</guid><description>&lt;p&gt;こんにちは。メルカリで暗号資産交換業を提供しているメルコインCTOのpoohです。 この記事は、Mercari Advent Calendar 2025 の24日目の記事です。 メルカリグループ内で、CAST分析の取り組 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 24 Dec 2025 10:00:19 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリで暗号資産交換業を提供しているメルコインCTOのpoohです。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の24日目の記事です。&lt;/p&gt;
&lt;p&gt;メルカリグループ内で、CAST分析の取り組みが広がりつつあります。メルコインではすでに導入しており、他のプロダクトチームでも検討や試行が始まっています。&lt;/p&gt;
&lt;p&gt;私はこれまで10年以上、SREとして障害対応やオンコール運用に関わってきました。 インフラ、SRE、データ基盤と領域は違っても、やってきたことは同じです。&lt;/p&gt;
&lt;p&gt;障害が起きる。 振り返る。 再発防止策を考える。&lt;/p&gt;
&lt;p&gt;それなりに真剣にやってきたはずなのに、 しばらくすると、よく似た構図の問題がまた起きる。&lt;/p&gt;
&lt;p&gt;この違和感を、長い間うまく説明できずにいました。&lt;/p&gt;
&lt;h2&gt;再発防止は「サボられている」わけではない&lt;/h2&gt;
&lt;p&gt;最初に強調しておきたいのは、 多くの現場で再発防止はちゃんと考えられている、ということです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;レビューで拾えたはずだった&lt;/li&gt;
&lt;li&gt;QAの観点が足りなかった&lt;/li&gt;
&lt;li&gt;想定が甘かった&lt;/li&gt;
&lt;li&gt;次はもう少し丁寧にやろう&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;どれも正論です。 真面目に向き合っている証拠でもあります。&lt;/p&gt;
&lt;p&gt;それでも、思ったように再発防止が効かない。&lt;/p&gt;
&lt;p&gt;これは意識や姿勢の問題ではない、と感じていました。&lt;/p&gt;
&lt;h2&gt;原因は、いつも「最後」に集まる&lt;/h2&gt;
&lt;p&gt;振り返りをすると、原因はどうしてもリリース直前や本番直前に寄りがちです。&lt;/p&gt;
&lt;p&gt;QAで見落とした。 本番確認が足りなかった。&lt;/p&gt;
&lt;p&gt;でも少し引いて見ると、違和感があります。&lt;/p&gt;
&lt;p&gt;仕様を考えた人がいて、 実装した人がいて、 レビューした人がいて、 QA判断をした人がいる。&lt;/p&gt;
&lt;p&gt;そのすべてを通り抜けて、 最後の工程だけを「根本原因」と呼ぶのは、無理がある。&lt;/p&gt;
&lt;p&gt;この違和感に、はっきり名前をつけてくれたのがCAST分析でした。&lt;/p&gt;
&lt;h2&gt;Google SREが直面している「エラーバジェット0」の世界&lt;/h2&gt;
&lt;p&gt;CAST分析に興味を持った大きなきっかけの一つが、 Google SREが「エラーバジェット0のシステム」を前提に考え始めている、という話(&lt;a href=&quot;https://www.usenix.org/publications/loginonline/evolution-sre-google&quot; title=&quot;The Evolution of SRE at Google&quot;&gt;The Evolution of SRE at Google&lt;/a&gt;)です。&lt;/p&gt;
&lt;p&gt;SREの世界では、 エラーバジェットを使ってスピードと安全性のバランスを取ります。&lt;/p&gt;
&lt;p&gt;一方で、Google自身が向き合っている現実もあります。&lt;/p&gt;
&lt;p&gt;金融、規制対応、社会インフラ。 サービスが落ちてはいけない。 失敗が許されない。 それでも人が設計し、人が運用する。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;today our products have losses that must never occur—error budgets of zero. The types of failures we need to prevent have evolved beyond what error budgets can effectively address.&lt;br /&gt;
現在では私たちの製品には絶対に発生してはならない損失、つまりエラーバジェットがゼロという状況が存在します。防止すべき障害の種類は、エラーバジェットが効果的に対処できる範囲を超えています。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;エラーバジェットを持てないシステムでは、 「失敗は起きる前提でうまく付き合う」というモデルだけでは足りない。&lt;/p&gt;
&lt;p&gt;その文脈で使われていたのが、CAST分析でした。&lt;/p&gt;
&lt;h2&gt;CAST分析は、問いを変える&lt;/h2&gt;
&lt;p&gt;CAST分析が前提にしている考え方は、とてもシンプルです。&lt;/p&gt;
&lt;p&gt;障害は、誰かの判断ミスで起きたのではない。 その判断を「正しい」と思わせる状況が積み重なった結果として起きる。&lt;/p&gt;
&lt;p&gt;だから問うべきなのは、&lt;/p&gt;
&lt;p&gt;なぜその判断をしたのか。 なぜその選択が合理的に見えたのか。&lt;/p&gt;
&lt;p&gt;結果を知ったあとに 「あの時こうすべきだった」と言うのは簡単ですが、 それは後知恵バイアスです。&lt;/p&gt;
&lt;p&gt;CAST分析は、 当時の情報、制約、前提の中での合理性を丁寧に扱います。&lt;/p&gt;
&lt;h2&gt;実際のCAST分析は、こう進む（抽象化した例）&lt;/h2&gt;
&lt;p&gt;ここから、実際にメルカリで行っているCAST分析の一部を、 特定につながらない形で紹介します。&lt;/p&gt;
&lt;p&gt;題材にしたのは、 &lt;strong&gt;既存のプロダクト施策に対する、ごく小さな運用変更&lt;/strong&gt;でした。&lt;/p&gt;
&lt;p&gt;変更内容自体はシンプルで、 「影響範囲は広いが、作業内容は限定的」 と判断されていたものです。&lt;/p&gt;
&lt;p&gt;結果としては、 お客さまがサービスを正しく利用できない状態が一定時間続いてしまいました。&lt;/p&gt;
&lt;h3&gt;従来の振り返りだと、こうなる&lt;/h3&gt;
&lt;p&gt;普通に振り返ると、こう整理できます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必要な設定の一部が更新されていなかった&lt;/li&gt;
&lt;li&gt;レビューやQAで気づけなかった&lt;/li&gt;
&lt;li&gt;本番確認が十分ではなかった&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;事実としては正しい。 でも、ここから出てくる対策はだいたい同じです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;チェックリストを作る&lt;/li&gt;
&lt;li&gt;レビューを強化する&lt;/li&gt;
&lt;li&gt;QAを必須にする&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「もっとちゃんとやろう」で終わってしまう。&lt;/p&gt;
&lt;h3&gt;CAST分析で見えた構造&lt;/h3&gt;
&lt;p&gt;CAST分析では、原因を一点に絞りません。 システム全体の構造を見ます。&lt;/p&gt;
&lt;p&gt;このケースで見えてきたのは、こんな状況でした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;過去は特定の設定だけ更新すれば問題なかった時代があった&lt;/li&gt;
&lt;li&gt;仕様は段階的に変化していたが、手順や認識は更新されていなかった&lt;/li&gt;
&lt;li&gt;PM・エンジニア・QAがそれぞれ異なる前提をもとに判断していた&lt;/li&gt;
&lt;li&gt;PMが自分で設定や状態を確認する手段を持っていなかった&lt;/li&gt;
&lt;li&gt;システム上は「仕様通りの挙動」が続き、監視では異常と判定されなかった&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;誰かがサボったわけでも、 誰かが雑だったわけでもありません。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;その構造の中にいれば、同じ判断をするのが自然&lt;/strong&gt; という状態ができていました。&lt;/p&gt;
&lt;h2&gt;出てきた再発防止策が、まったく違う&lt;/h2&gt;
&lt;p&gt;ここが、CAST分析の一番大きな違いです。&lt;/p&gt;
&lt;p&gt;出てきた提言は、 「誰が気をつけるか」ではありません。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不正な設定の組み合わせは、そもそも保存できないようにする&lt;/li&gt;
&lt;li&gt;状態を人に聞かなくても確認できる仕組みを用意する&lt;/li&gt;
&lt;li&gt;「施策が意図通りに機能しているか」を直接捉える指標を持つ&lt;/li&gt;
&lt;li&gt;深夜や休日の切り替えを原則避ける&lt;/li&gt;
&lt;li&gt;「軽微な変更」の定義を、工数ではなく失敗時の損失で考える&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;すべて、&lt;strong&gt; 個人の注意力に依存しない改善&lt;/strong&gt;です。これらの提言には従来実施してきたretrospectiveででたものもあります。網羅性ある振り返りができるというのがお気に入りです。&lt;/p&gt;
&lt;h2&gt;経営陣の視点で見たときの意味&lt;/h2&gt;
&lt;p&gt;このCAST分析が、 経営陣が読んだときに意味を持つ理由はここにあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;現場の誰かを責める話にならない&lt;/li&gt;
&lt;li&gt;なぜその判断が起きたのかを、構造として説明できる&lt;/li&gt;
&lt;li&gt;再発防止が「人の問題」ではなく「組織と仕組みの話」になる&lt;/li&gt;
&lt;li&gt;同じことが別のプロダクトでも起こりうると理解できる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CAST分析は、 障害対応の手法というより、 &lt;strong&gt;プロダクト開発を安全にスケールさせるための考え方&lt;/strong&gt; だと感じています。&lt;/p&gt;
&lt;h2&gt;再発防止を、本当に機能させたいなら&lt;/h2&gt;
&lt;p&gt;もし、&lt;/p&gt;
&lt;p&gt;振り返りをちゃんとやっているのに、 同じ構図の問題が繰り返される。 対策が注意喚起やチェックリストに寄っている。&lt;/p&gt;
&lt;p&gt;そんな感覚があるなら、 CAST分析は一度きちんと調べてみる価値があります。&lt;/p&gt;
&lt;p&gt;銀の弾丸ではありません。 でも、問いの立て方が変わります。&lt;/p&gt;
&lt;p&gt;それだけで、 再発防止は少しずつ、 本当に機能し始めます。&lt;/p&gt;
&lt;p&gt;CAST分析、 ぜひ一度触れてみてください。&lt;/p&gt;
&lt;p&gt;参考資料&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?si=6wZF39NKlnl-BQCH&amp;amp;v=2ChpAi5mcnI&amp;amp;feature=youtu.be&quot; title=&quot;チュートリアル&quot;&gt;チュートリアル&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;最初の15分ぐらいおすすめ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://psas.scripts.mit.edu/home/get_file9.php?name=CAST_Handbook_Japanese.pdf&quot; title=&quot;ハンドブック&quot;&gt;ハンドブック&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;1-2章までは読みやすく、先を読みたくなります。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;明日の記事は kimurasさんとYusakuさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>Cursorでプログラミング言語を学び直す方法——AI駆動学習の4ステップ</title><link>https://engineering.mercari.com/blog/entry/20251223-ai-driven-learning/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251223-ai-driven-learning/</guid><description>&lt;p&gt;こんにちは。メルペイ Payment Mobileチームで iOSエンジニアをしている @kubomi です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の23日目の [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 23 Dec 2025 10:00:27 GMT</pubDate><content:encoded>
&lt;p&gt;こんにちは。メルペイ Payment Mobileチームで iOSエンジニアをしている &lt;a href=&quot;https://x.com/kubomi____&quot;&gt;@kubomi&lt;/a&gt; です。&lt;/p&gt;
&lt;p&gt;この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の23日目の記事です。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;はじめに&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;最近、自分の手でコードを書いていますか？&lt;/p&gt;
&lt;p&gt;AIに任せれば動くコードが出てくる「Vibe Coding」時代。便利な反面、私はこんな不安を感じるようになりました。&lt;/p&gt;
&lt;p&gt;「このコード、本当に理解できてる？」 「雰囲気で理解しているつもりになってるのでは？」&lt;/p&gt;
&lt;p&gt;同じ不安を感じているエンジニアの方々は、きっと多いのではないでしょうか。 &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AIがコードを書いてくれる時代だからこそ、AIを使って「学ぶ」ことは重要ではないか&lt;/strong&gt;——そう考えて私が試したのが、「AIにコードを書かせる」のではなく「AIに学習を設計させる」というアプローチです。&lt;/p&gt;
&lt;p&gt;この記事では、私が実践した「AI駆動学習」の方法を、具体的なプロンプトとともに紹介します。AIを使ってオーダーメイドの学習計画・教材を生成し、プログラミング言語を体系的に学び直す方法です。 &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;こんな方におすすめ：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AIに頼りきりで自分の理解度に不安がある  &lt;/li&gt;
&lt;li&gt;新しい言語を体系的に学びたい  &lt;/li&gt;
&lt;li&gt;学習を始めても途中で挫折しがち&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;私の「AI駆動学習」サイクル&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/38e67f83-20250821_tech-talk-kubomi-ai-driven-learning_ai駆動の学習サイクル-.png&quot; alt=&quot;ai-driven-learning-steps&quot; /&gt;&lt;/p&gt;
&lt;p&gt;私が実践しているのは、学習のあらゆるステップでAIをフル活用する「AI駆動学習サイクル」です。このサイクルは4つのステップで構成されています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step 1: Plan — AIが学習プランを作る&lt;/strong&gt;&lt;br /&gt;
公式ドキュメントを参照し、体系的な学習計画を作成してもらいます。何を・いつ・どの順番で学ぶかが明確になります。  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 2: Learn — AIが教材を作って、人が学ぶ&lt;/strong&gt;&lt;br /&gt;
学習プランに沿って、解説・サンプルコード・演習課題を含む教材を生成してもらいます。そのまま実行できるファイル形式で出力させるのがポイントです。  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 3: Practice — 人が課題を実装して、AIがレビュー&lt;/strong&gt;&lt;br /&gt;
教材の演習課題を自分で実装し、AIにコードレビューしてもらいます。ここが一番学びが深まるステップです。  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 4: Track — AIが進捗管理&lt;/strong&gt;&lt;br /&gt;
学習プランからTODOリストを生成し、進捗を可視化します。次にやることが常に明確になり、継続しやすくなります。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;つまり、ほとんどAI任せの学習サイクルです。人間である私がやることは、教材を読んで理解し、課題を実装することだけ。それ以外の「プランニング」「教材作成」「レビュー」「進捗管理」はすべてAIに任せています。&lt;/p&gt;
&lt;p&gt;ここからは、各ステップの詳細と、実際に効果的だったプロンプトを共有していきます。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;準備するもの&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;今回使用するのは &lt;a href=&quot;https://cursor.com/&quot;&gt;&lt;strong&gt;Cursor&lt;/strong&gt;&lt;/a&gt; です。&lt;/p&gt;
&lt;p&gt;CursorはVS Codeベースのエディタで、AIとの対話機能が統合されています。なぜCursorを選んだかというと、&lt;strong&gt;学習プランの作成から、教材の生成、コードの実行、レビューまでがすべて1つの環境で完結する&lt;/strong&gt;からです。&lt;/p&gt;
&lt;p&gt;ChatGPTなどのチャットUIでも学習プランは作れますが、生成されたコードをコピペして別のエディタで動かす手間が発生します。Cursorなら、AIが生成したファイルをそのまま保存し、その場で実行して動作確認できます。この「シームレスさ」が学習の継続には重要でした。&lt;/p&gt;
&lt;p&gt;Cursor以外でも、ClineなどのIDE統合型AIエージェントであれば同様のワークフローが実現できると思います。&lt;/p&gt;
&lt;p&gt;準備ができたら、空のディレクトリを1つ作成してCursorで開きましょう。このディレクトリに、学習プラン・教材・進捗管理ファイルをすべて格納していきます。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Step 1: Plan — 学習プランを作る&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;まずはCursorに学習計画を作ってもらいます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用したプロンプト:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;公式ドキュメント（https://docs.swift.org/swift-book/）を元に、
10日でSwiftをマスターする学習計画を作って、mdファイルで出力して。&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここでのポイントは2つあります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ポイント１：公式ドキュメントを参照させること。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;これによって、断片的ではなく体系的な学習プランを組み立ててくれます。公式ドキュメントを基準にすることで、正確で網羅的なカリキュラムが得られます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ポイント２：期間を区切ること&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;「10日でマスターする」といった期間を指定することで、学習量や内容を自然に調整してくれます。無理のないペースで進められるプランが、あっという間に完成しました。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;生成された学習プランの例（抜粋）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/2293fea0-image1.png&quot; alt=&quot;leaning-plan&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これだけで、何を・いつ・どの順番で学ぶかが明確になります。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Step 2: Learn — 教材を作る&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;次に、先ほど作った学習プランをもとに、日ごとの教材をCursorに作らせます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用したプロンプト:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;学習プランを元に、各日の教材を作って。
各日ごとにディレクトリを作成して、その中に解説・サンプルコード・演習課題を含む実行可能な .swift ファイルを含めて。&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このプロンプトのポイントは2つあります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ポイント１：日ごとにディレクトリを分ける&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;日ごとにディレクトリを分けることで、学習プランと教材が対応し、管理しやすくなります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ポイント２：実行可能なファイル形式で出力させること&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;これによって教材が「ただの読み物」で終わらないのです。生成されたファイルをそのまま実行して試せる。すぐに手を動かして学習できる。ちょうど&lt;strong&gt;教材とIDEが合体したような体験&lt;/strong&gt;になりました。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;生成されるディレクトリ構成&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;swift-learning/
├── README.md（学習プラン）
├── Day1/
│   └── 01_Basics.swift
├── Day2/
│   └── 02_ControlFlow.swift
├── Day3/
│   └── 03_Optionals.swift
...&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;生成された教材ファイルの例（抜粋）&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/*
 Day 1: Swiftの基礎と基本構文
 Swift公式ドキュメント: https://docs.swift.org/swift-book/documentation/the-swift-programming-language
 */

// MARK: - 1. 変数と定数
print(&amp;quot;=== 変数と定数 ===&amp;quot;)

// 変数（変更可能）
var myVariable = 42
myVariable = 50
print(&amp;quot;変数: \(myVariable)&amp;quot;)

// 定数（変更不可）
let myConstant = 42
// myConstant = 50  // エラー: 定数は変更できません
print(&amp;quot;定数: \(myConstant)&amp;quot;)

...

/*
 演習1: 変数と定数
 - あなたの名前を定数として定義してください
 - あなたの年齢を変数として定義してください
 - 年齢を1つ増やして表示してください
 */

// ここにコードを書いてください

/*
 演習2: 文字列操作
 - あなたの名前と年齢を使って「私の名前は[名前]で、[年齢]歳です」というメッセージを作成してください
 - 文字列補間を使用してください
 */

// ここにコードを書いてください
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コメントで解説が書かれており、サンプルコードのあとに演習問題が用意されています。コードを読んで、動かして、自分で書いてみる。この一連の流れが1つのファイルで完結します。&lt;/p&gt;
&lt;p&gt;教材を読んでいて分からないことがあれば、その場でCursorに質問できるのもこの学習スタイルの強みです。「なぜここでXXXを使うの？」「他の書き方はある？」など、疑問点をどんどん聞くことで理解が深まります。&lt;/p&gt;
&lt;p&gt;ただし、AIはハルシネーション（誤った情報の生成）を起こすこともあるので、必要に応じて公式ドキュメントと照らし合わせながら学ぶことをおすすめします。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Step 3: Practice — 演習課題をレビューしてもらう&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;3つ目のステップは、最も効果を実感できた「Practice」です。&lt;/p&gt;
&lt;p&gt;教材に含まれる演習課題を自分で実装したあと、そのコードをCursorにレビューしてもらいます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用したプロンプト:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;課題をレビューして、改善点を教えて。もっとSwiftらしい書き方ある？&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;「Swiftらしい書き方」というように聞くことで、その言語ならではの洗練された表現やベストプラクティスを提案してくれます。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;理解度クイズ&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;また、Cursorにクイズを出題させて理解度をチェックするのも効果的でした。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用したプロンプト:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;今日学んだ内容から5問クイズを出して。&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;すると、すぐに確認テストを作ってくれます。このように、アウトプット中心で学ぶことで、確実に身につく実感がありました。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Step 4: Track — 進捗管理&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;最後のステップは、進捗管理の自動化です。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用したプロンプト:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;学習プランをもとにTODOリストを作って進捗管理して。&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;学習プランからTODOリストを生成させ、1つ終わったらCursorに報告して更新してもらいます。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;生成された進捗管理ファイルの例&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Swift学習 進捗管理

## 📊 進捗サマリー
- **完了**: 3/10日
- **進捗率**: 30%

## ✅ TODOリスト
- [x] Day 1: Swiftの基礎と基本構文
- [x] Day 2: 制御フローと関数  
- [x] Day 3: オプショナルとエラーハンドリング
- [ ] Day 4: コレクション型
- [ ] Day 5: クラスと構造体
- [ ] Day 6: プロトコルと拡張
- [ ] Day 7: ジェネリクス
- [ ] Day 8: クロージャと関数型プログラミング
- [ ] Day 9: エラーハンドリングと非同期処理
- [ ] Day 10: 実践的なSwiftアプリケーション&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;進捗が可視化されるとモチベーションも保ちやすいですし、次にやることが常に明確になります。「今日はどこまでやったか」「明日は何をすればいいか」を考える手間がなくなり、学習のハードルが下がりました。&lt;/p&gt;
&lt;p&gt;学習で挫折しがちなポイントの1つが「どこまでやったか分からなくなること」なので、ここをCursorに任せてしまうのはかなりおすすめです。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;AI駆動学習のメリット&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;この学習サイクルを実践してみて、いくつかのメリットを実感しました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;オーダーメイドの教材が手に入る&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;自分の理解度や使える時間に合わせて、カスタマイズされた学習プランと教材を作れます。「1日30分しか時間が取れない」「Python中級者のためのJava入門」など、自分の状況をプロンプトに書くだけで、かなりパーソナライズされた教材が出てきます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;アウトプット中心で定着する&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;教材を読むだけでなく、実際にコードを書き、AIにレビューしてもらうことで、理解が深まります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;継続しやすい&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;進捗管理をAIに任せることで、「今日は何をすればいいか」を考える手間がなくなります。学習のハードルが下がり、継続しやすくなりました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;新しい領域に踏み出しやすい&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;体系的に学べる安心感があると、これまで手を出しにくかった新しい言語やフレームワークにも挑戦しやすくなります。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;まとめ&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;AIの進化によって、私たちが自分の手でコードを書く機会はこれからますます減っていくでしょう。だからこそ、学ぶ時間を意識的に確保することが大事だと改めて感じました。&lt;/p&gt;
&lt;p&gt;AIを使えば、自分の理解度や時間に合わせたオーダーメイドの教材を作れます。そのおかげで、新しい言語にも挑戦しやすくなり、担当領域の外へも踏み出せると実感しました。&lt;/p&gt;
&lt;p&gt;私自身は、次のステップとしてAndroid開発に挑戦するためにKotlinを学ぼうと思っています。&lt;/p&gt;
&lt;p&gt;Vibe Codingでコードを書かなくなった方、AIに頼りっぱなしで不安を感じている方、ぜひこの「AI駆動学習サイクル」を試してみてください。AIを使って学ぶという、新しい学習体験が待っています。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;付録：そのまま使えるプロンプト集&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;実際に使ったプロンプトをまとめました。&lt;code&gt;[プログラミング言語]&lt;/code&gt; や &lt;code&gt;[N]&lt;/code&gt; の部分を置き換えてすぐに試せます。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Step 1: 学習計画生成&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;公式ドキュメント（[公式ドキュメントのURL]）を元に、[N]日で[プログラミング言語]をマスターする学習計画を作って、mdファイルで出力して。&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;Step 2: 教材生成&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;学習プランを元に、各日の教材を作って。各日ごとにディレクトリを作成して、その中に解説・サンプルコード・演習課題を含む実行可能なファイルを含めて。&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;Step 3: レビュー&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;課題をレビューして、改善点を教えて。もっと[プログラミング言語]らしい書き方ある？&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;理解度クイズ&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;今日学んだ内容から5問クイズを出して。&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;Step 4: 進捗管理&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;学習プランをもとにTODOリストを作って進捗管理して。&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;明日の記事は poohさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>インターン生が挑んだ認証方式のマイグレーション──メルペイ加盟店管理画面へのOAuth 2.0導入</title><link>https://engineering.mercari.com/blog/entry/20251219-029c76e123/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251219-029c76e123/</guid><description>&lt;p&gt;はじめに こんにちは。メルペイPartner Platformチームでバックエンドエンジニアのインターンをしている@takiと申します。 この記事は、Merpay &amp;amp; Mercoin Advent Calenda [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 22 Dec 2025 10:00:32 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。メルペイPartner Platformチームでバックエンドエンジニアのインターンをしている&lt;a href=&quot;https://www.linkedin.com/in/%E8%B5%B7%E6%A8%B9-%E5%B7%9D%E4%B8%8A-b48b37324?utm_source=share&amp;amp;utm_campaign=share_via&amp;amp;utm_content=profile&amp;amp;utm_medium=ios_app
&quot;&gt;@taki&lt;/a&gt;と申します。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の22日目の記事です。&lt;/p&gt;
&lt;p&gt;Partner Platformチームではメルペイの加盟店向けの管理画面を開発しています。本記事では、インターン期間中に取り組んだタスクの一つである、管理画面の認証方式をマイグレーションした経験について紹介します。&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;メルペイでは、加盟店向けに管理画面を提供しています。この画面では、店舗でのメルペイの取引履歴の確認など、さまざまな機能を利用できます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/bb6a683a-partner_dashboard.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;既存の認証方式&lt;/h3&gt;
&lt;p&gt;現在の管理画面では、以下のようなフローの認証方式を採用しています。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ログイン&lt;/strong&gt;: お客さまが送信したメールアドレス・パスワードを用いて、加盟店管理画面側で認証を行います。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;リソース取得&lt;/strong&gt;: 認証が成功するとアクセストークンが発行され、そのトークンを用いて依存先の各マイクロサービスにリクエストを行います。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/36e27015-screenshot-2025-12-19-at-13.50.28.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;このロジックはシンプルですが、大きな課題があります。それは、他サービス（例えばメルカリShopsやメルカリAdsなど）の加盟店管理画面とは独立した認証体系になってしまっているということです。&lt;/p&gt;
&lt;p&gt;そのため、例えばメルペイとメルカリShopsの両方を利用している加盟店のスタッフは、それぞれのサービスの管理画面で異なるアカウントを発行する必要があります。&lt;/p&gt;
&lt;h3&gt;新しい認証方式&lt;/h3&gt;
&lt;p&gt;そこで今回、この課題の解決に向けて、認証方式のマイグレーションを行いました。新しい方式の基本的なフローは以下の通りです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ログイン&lt;/strong&gt;: お客さまはまず認可サーバにメールアドレス・パスワードを送信し、認証を行います。ここで使用する認可サーバは、メルカリのIDPチームが提供しているものです。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;トークンを要求&lt;/strong&gt;: 加盟店管理画面側から、認可用のトークンを認可サーバにリクエストします。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;トークンを発行&lt;/strong&gt;: 認可サーバがトークンを発行します。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;リソース取得&lt;/strong&gt;: 発行されたトークンを用いて、加盟店管理画面から依存先のマイクロサービスにアクセスします。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/88ba5085-screenshot-2025-12-19-at-13.51.24.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この方式では、認証・認可のフローが各サービスから分離され、共通の認可サーバに集約されます（下図参照）。これにより、将来的にはお客さまがさまざまなサービスの加盟店管理画面に、共通のアカウントでログインできるようになります。（メルカリShopsに関してはOAuthへのマイグレーションが別途で必要になります。）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/40420e23--2025-12-19-14.39.30.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この仕組みは、技術的にはOAuth 2.0およびOIDC（OpenID Connect）というプロトコルを使用しています。以下では、OAuth 2.0について簡単に説明します。&lt;/p&gt;
&lt;h2&gt;OAuth 2.0とは&lt;/h2&gt;
&lt;p&gt;OAuth 2.0は、サードパーティアプリケーションがお客さまのリソースに安全にアクセスするための認可（Authorization）プロトコルです。&lt;/p&gt;
&lt;h3&gt;OAuth 2.0の登場人物&lt;/h3&gt;
&lt;p&gt;OAuth 2.0では、以下の4つの登場人物が出てきます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;リソースオーナー（Resource Owner）&lt;/strong&gt;: リソースの所有者であるお客さま。今回のケースでは加盟店のスタッフです。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;クライアント（Client）&lt;/strong&gt;: リソースにアクセスしたいアプリケーション。今回のケースでは加盟店管理画面（のBFF：Backend For Frontend）です。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;認可サーバ（Authorization Server）&lt;/strong&gt;: お客さまを認証し、アクセストークンを発行するサーバ。今回のケースではIDPチームが提供する認可サーバです。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;リソースサーバ（Resource Server）&lt;/strong&gt;: 保護されたリソースを保持するサーバ。今回のケースでは加盟店管理画面から呼び出される各種マイクロサービスです。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;OAuth 2.0のフロー&lt;/h3&gt;
&lt;p&gt;OAuth 2.0にはいくつかのフロー（グラントタイプ）があります。その中で、Webアプリケーションで最も一般的なのはAuthorization Code Flowというものです。今回実装したフローも、このフローを採用しています。&lt;/p&gt;
&lt;p&gt;基本的な流れは以下の通りです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;認可リクエスト&lt;/strong&gt;: お客さまがログインを試みると、クライアントはお客さまを認可サーバにリダイレクトし、認可を要求します。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ログイン&lt;/strong&gt;: 認可サーバでお客さまがログインします。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;認可コードの発行&lt;/strong&gt;: 認可サーバは認可コードを発行し、クライアントにリダイレクトします。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;トークンを要求&lt;/strong&gt;: クライアントは認可コードを認可サーバに送信し、アクセストークンを要求します。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;トークンを発行&lt;/strong&gt;: 認可サーバは、正当なクライアントからのリクエストかどうかを検証した上で、アクセストークンを発行します。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;リソース取得&lt;/strong&gt;: クライアントはアクセストークンを使用して、リソースサーバのAPIにアクセスします。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;先ほどの図ではこのフローを一部省略して描いていましたが、OAuth 2.0のフローを踏まえてちゃんと描くと以下のような形になります。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/0a14ec2e-screenshot-2025-12-19-at-13.51.06.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;今回の開発内容&lt;/h2&gt;
&lt;h3&gt;実装前の準備&lt;/h3&gt;
&lt;p&gt;実装に入る前に、まず要件を整理する必要がありました。具体的には、既存のロジックのどの部分をどの順序で変更するべきか調査し、チケットを切るところから始めました。&lt;/p&gt;
&lt;p&gt;この際、参考にしたのがスキマバイト「メルカリ ハロ」の加盟店管理画面です。メルカリ ハロは最も早くOAuthによる認可を導入していたサービスでした。実際にメルカリ ハロの管理画面を触ってみた上で、メルカリ ハロへのOAuth導入時のドキュメント、および実装を照らし合わせながら理解することで、実装の進め方の解像度を大きく高めることができました。&lt;/p&gt;
&lt;p&gt;残念ながらメルカリ ハロは2025年12月をもってサービスが終了しましたが、メルカリ ハロで行われたさまざまな技術的挑戦で得られた知見は、こうして他のプロダクトに生かし続けられるということを実感しました。&lt;/p&gt;
&lt;h3&gt;実装&lt;/h3&gt;
&lt;p&gt;実装の方針を立てた後、実際の開発に取り掛かりました。認可サーバ側の認証・認可のロジックはIDPチーム側で既に実装されていたため、今回の開発では加盟店管理画面のBFFにおける処理の実装がメインとなりました。具体的には、上の図の③のリダイレクトを受け付ける部分や、④⑤のトークン発行の部分などです。&lt;/p&gt;
&lt;p&gt;OAuthのフローでは、加盟店管理画面のBFFは、ブラウザとの直接通信を多用します。例えば、ブラウザにリダイレクトを行わせたり、⑤で受け取ったアクセストークンをブラウザのCookieにセットしたりする必要があります。&lt;/p&gt;
&lt;p&gt;そのため、これらの処理はサーバ間通信の仕組みであるgRPCサーバではなく、HTTPサーバで行うのが適切です。しかし、元々のBFFにはgRPCサーバしか存在しなかったため、新たにHTTPサーバを構築する必要がありました（下図の赤色部分）。&lt;/p&gt;
&lt;p&gt;実装では、gRPCサーバと同一プロセス内の別ポートでHTTPサーバを立ち上げるようにしました。さらに、Kubernetesのポート公開の設定やGatewayのプロキシ設定を変更することで、無事にHTTPサーバを疎通させることができました。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/602d2853-screenshot-2025-12-19-at-13.50.52.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;続いて、新しく立てたHTTPサーバに、③や④⑤などのフローを行うためのエンドポイントを実装し、テスト環境でOAuthによる認可を動かすところまでを実現できました。&lt;/p&gt;
&lt;h2&gt;インターンの感想&lt;/h2&gt;
&lt;p&gt;メルペイでの約3ヶ月間のインターンを通して、強く感じたことは、メルペイのエンジニアは一般的なエンジニアの担当領域にとどまらず、事業側へのインパクトを解像度高く把握してプロダクトに関わっているということです。&lt;/p&gt;
&lt;p&gt;実際、自分の周りのメンターの方々は、単に与えられた仕様を実装するのではなく、「自分の開発しているものが、お客さまにどのような価値をもたらし、その価値が事業の成長や収益にどうつながるのか」まで踏み込んで議論しながらプロダクトに向き合っていると感じました。&lt;/p&gt;
&lt;p&gt;そういった視点を前提に、必要に応じて周囲のチームと連携しながら、仕様の見直しなども含めて主体的に開発を進めていく──このスタイルがマネージャー層だけでなく、一人ひとりのエンジニアにまで根付いていることは、自分にとって大きな驚きであり、とても魅力的な環境だと感じました。&lt;/p&gt;
&lt;p&gt;そして、メルペイのエンジニアがこのような動きをできる理由の一つとして、メルカリ・メルペイにはVibe CodingやAgentic Codingが深く浸透しているということがあると感じました。「Coding Agentの登場によって、エンジニアの役割はアーキテクトへと変化していく」とはよく言われていることですが、メルカリではこの変化がすでにかなり進んでいると実感すると共に、自分もこのようなエンジニアを目指したいと感じる経験となりました。&lt;/p&gt;
&lt;h2&gt;終わりに&lt;/h2&gt;
&lt;p&gt;この記事では、メルペイのインターンで取り組んだ認証方式のマイグレーション、そしてインターンを通して感じたことをまとめました。&lt;/p&gt;
&lt;p&gt;インターン期間中は、この記事で述べた内容に限らず、技術的な面でも、それ以外の面でも、さまざまな学びを得ることができました。&lt;/p&gt;
&lt;p&gt;残りのインターン期間では、OAuthの導入に関するQAを実施し、本番環境へのリリースまで完了させたいと考えています。&lt;/p&gt;
&lt;p&gt;明日の記事は@kubomiさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>Non-AI tasks in the AI task force：AIツール開発の現場でこそ必要な「AI以外の」技術選定</title><link>https://engineering.mercari.com/blog/entry/20251217-2204b3261b/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251217-2204b3261b/</guid><description>&lt;p&gt;こんにちは、メルカリのAI Task Forceでイネーブラー（Enabler）をしている @akkie です。 この記事は、Mercari Advent Calendar 2025の21日目の記事です。 すでにご存知の [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sun, 21 Dec 2025 11:00:16 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、メルカリのAI Task Forceでイネーブラー（Enabler）をしている &lt;a href=&quot;https://x.com/akkie30&quot; title=&quot;@akkie&quot;&gt;@akkie&lt;/a&gt; です。&lt;/p&gt;
&lt;p&gt;この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt;の21日目の記事です。&lt;/p&gt;
&lt;p&gt;すでにご存知の方も多いかもしれませんが、現在メルカリは「AI-Native」をテーマに掲げ、AIを基盤として組織とプロダクトを抜本的に変革する&lt;a href=&quot;https://careers.mercari.com/mercan/articles/53708/&quot;&gt;取り組み&lt;/a&gt;を進めています。AI Task Forceは、メルカリをAI Nativeな組織へと変革するために立ち上がった100名規模のチームで、Enablerは「各領域でAI Nativeな業務変革を主導する役割」とされています。&lt;/p&gt;
&lt;p&gt;既存のAI/LLMツールの活用はもちろん、プロダクトへのAI組み込みや、マニュアル作業が多い業務のDX推進など、その活動は多岐にわたります。&lt;/p&gt;
&lt;p&gt;最近では、特に開発における生産性向上を目的として&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251201-pj-double-towards-ai-native-development/&quot;&gt;pj-double&lt;/a&gt;に多くのエンジニアが参加しています。&lt;/p&gt;
&lt;p&gt;しかしこの記事では、生産性を上げるために「どうAIに効率的に仕事をさせるか」ではなく、AIを使わない部分の仕事について話したいと思います。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;システムにAIを使わないに越したことはない&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;AI Task Forceに居ながらこんなこと言っていいのか？と思うかもしれませんが、多くのエンジニアが、目的を達成するために「AIを使わないで済むなら使わないに越したことはない」に同意するのではないでしょうか。&lt;/p&gt;
&lt;p&gt;よくあるジョークとして、コーディングしているエンジニアの様子を想像してみて、と非エンジニアの人に聞くと、目まぐるしく手を動かしてキーボードにコードを打ちこんでいる様子を想像する、けれど実際のエンジニアを見てみると、キーボードから手を離し頭を抱えながら、画面に大量に表示されたエラーをジッと見つめ原因を探っている時間の方が何十倍も長いということがあります。これは事実です。&lt;/p&gt;
&lt;p&gt;最近ではまずAIが70%正しいコードを書いてくれるようになった分、なおさら人間はコードを打ち込むよりも「頭を抱えてコードが動かない問題の原因を探っている」時間の方が長くなったかもしれません。使っているライブラリの古いバージョンを前提にAIがコードを書いてきたり、サードパーティのAPI仕様を理解していなかったり、間違える要素はいくらでもありますし、AIの出力は下手に正しそうに見える分デバッグにも時間がかかります。&lt;/p&gt;
&lt;p&gt;システム開発はいわば人間が行ってきたことを自動化することが仕事です。メルカリのアプリ開発も、これまで街中のフリーマーケットで人と人との間で行われていた商売をシステムで行うための大きな自動化に取り組んでいるとも言えるでしょう。&lt;/p&gt;
&lt;p&gt;AIはその大きな助けになってくれますが、人間がAIに正しく意図を伝えられていないだけというケースも含めてまだ「70％正しい」くらいの出力を返してくるのが現状です。&lt;/p&gt;
&lt;p&gt;それと比べて、シェルにコマンドを打ち込んだり、コードをDockerイメージ上で動かす作業はほぼ100%(もちろん環境要因で動かなくなるときはあるでしょうが)常に同じレスポンスを返してくれます。ターミナルを開いて “echo $(( 100 + 200 ))” と打てばいつだって画面には300と表示されるし、お金もかかりません。&lt;/p&gt;
&lt;p&gt;「100+200の答えが知りたい」ときに、ChatGPTを開いて「100+200は？」と聞く人はいませんよね。たとえ聞いてもChatGPTは多くの場合正解を返してくるでしょうが、コストパフォーマンスや精度を考えるとこんなことをAIに聞くコードをシステムに組み込む人は居ないわけです。&lt;/p&gt;
&lt;p&gt;また、数珠つなぎにAIに仕事を連携させていくと、その掛け算で精度は下がり、すぐに使い物にならなくなるでしょう。&lt;/p&gt;
&lt;p&gt;つまり、仕事(特にシステム内で行うもの)は「AIを使わずとも自動化できるのであれば、使わないに越したことはない」ということが前提にあるはずです。もちろんAIの精度を上げる努力も必要ですが、決定論的なロジックで解決できる課題にAIを持ち込むべきではありません。AIは確率論だけれど、システムはできる限り決定論であるべきです。&lt;/p&gt;
&lt;p&gt;AI Task Forceというチームに所属しているので、色んな組織から「これ、AIで解決できませんか？」という相談を受けます。しかし具体的に話を聞いてみると、「それはAI使わなくても解決できますね」という回答になることがよくありました。「アクセスログから◯◯の売上分析をしたい」、「人事システムに溜まった△△のデータを可視化・検索したい」、などはその例でしょう。AIにそれらをさせるのではなくそれらを実現するコードをAIに書かせる、という意味ではたくさんAIを利用することにはなりますが、一度きりでは済まないような作業をAI自身にやらせるのは悪手だと考えます。AIに魚を持ってこさせるのではなく、釣り竿を作らせるのがAIの良い使い方と考えます。&lt;/p&gt;
&lt;p&gt;とにかく、どうしてもAIにしか解決が難しい課題にのみAIを使いたい。私にとってこの半年の仕事は、いわば「いかにAIを使わないで済むかを考える」ことでした。&lt;/p&gt;
&lt;p&gt;また、AIを使うかどうかは内部の実装の話だけではなく、ユーザとのインタフェースにも同じことが言えます。何にでもAIを使うことがもてはやされる時代ですが、あえてAIを使わない選択肢をとることで向上する生産性もあります。あなたが提供しようとしている価値に、本当に自然言語によるユーザとのインタフェースは必要でしょうか？mcpサーバは必要でしょうか？&lt;/p&gt;
&lt;p&gt;UNIXコマンドの多くが対話型のコマンドを避け、パイプされた他のコマンドとの組み合わせによって自動化が容易になったように、自分の作っているものに自然言語による対話型インタフェースが必要かは常に慎重な見定めが必要です。  &lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;社内ツール開発における「9割」の正体&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;たとえAIを使って業務改善のためのツールを開発することが決まっても、大半の時間はAIと全く関係ないところに使われていることにも気が付きました。特にインフラ周りです。&lt;/p&gt;
&lt;p&gt;LLMが当たり前に使われるようになるずっと前から、AIを使ったプロダクト開発においては**「データの収集やインフラ整備が工数の9割を占め、モデル自体の設計・改善は1割程度である」**と言われてきました。&lt;/p&gt;
&lt;p&gt;この現実は、LLMが登場しAI技術が進化しても変わってないように感じています。&lt;/p&gt;
&lt;p&gt;たとえば機械学習モデルが実際に価値を生むAPIとして、高可用性・低遅延・スケーラビリティを維持して稼働し続けるためには、モデルの学習や評価よりも多くの、複雑なインフラおよびMLOps作業が不可欠になります。&lt;/p&gt;
&lt;p&gt;具体的にタスクを挙げてみます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基盤・ネットワーク:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;コンテナ化  &lt;/li&gt;
&lt;li&gt;Kubernetes/サーバレス設定  &lt;/li&gt;
&lt;li&gt;DNS  &lt;/li&gt;
&lt;li&gt;SSL証明書  &lt;/li&gt;
&lt;li&gt;ロードバランサ  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;セキュリティ&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;ネットワークセキュリティ  &lt;/li&gt;
&lt;li&gt;認証認可  &lt;/li&gt;
&lt;li&gt;シークレット管理  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;運用・監視&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;オートスケーリング  &lt;/li&gt;
&lt;li&gt;ヘルスチェック  &lt;/li&gt;
&lt;li&gt;Datadog設定  &lt;/li&gt;
&lt;li&gt;ログ収集  &lt;/li&gt;
&lt;li&gt;ABテスト  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevOps&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;CI/CDパイプライン  &lt;/li&gt;
&lt;li&gt;IaC (Terraform/CloudFormation)  &lt;/li&gt;
&lt;li&gt;モデルのバージョン管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然、これに加えて機械学習に与えるためのデータの収集のパイプラインや、BigQueryなどのData Ware House整備も必要になります。&lt;/p&gt;
&lt;p&gt;こうしたタスクも当然AIの力で楽にはなるのですが、インフラ周りの設定は複数のGithub Repository、手動作業、目視での確認、試行錯誤などが必須となり、どうしても時間がかかってしまいます。GitHub Actionsを1行直しては試す、Terraformの設定を1行直しては試す、Kubernetesマニフェストを1行直しては試す、のような作業に膨大な時間を使った経験があるエンジニアも少なくないでしょう。&lt;/p&gt;
&lt;p&gt;つまり、AIによって業務を改善しようと考えるときに、アプリケーションロジックをAIに効率的に書かせることだけではなく、こうしたインフラ・基盤周りの作業を決定論的に効率化しないことには生産性は大きく上がらないのではないかと考えています。&lt;/p&gt;
&lt;p&gt;メルカリのAI Task Forceでは、各組織（エンジニアのいないチームを含む）にEnablerが1〜2名アサインされ、組織の業務を改善する助けをするというスタイルをとっています。&lt;/p&gt;
&lt;p&gt;つまり、上記のタスクを基本的にはたった1〜2名で完遂しなければなりません。&lt;/p&gt;
&lt;p&gt;「ここはSREにお願いして…」「ここはMLエンジニアに…」と専門家に依頼している時間はありませんし、非エンジニアの方々の課題を解きほぐし、仕様に落とし込むPMのような役割も求められます。そもそも自分の知らない領域のチームのドメイン知識を手に入れるためにも多くの時間が必要でしょう。&lt;/p&gt;
&lt;p&gt;ここにさらに「AI固有の悩み」が乗ってきます。モデル選定、コンテキストエンジニアリング、そもそもGoogle ADKやPydantic AIなどライブラリの進化が早くて追いつくのが大変…など。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;社内ツール開発の技術選定&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;そこで、私が半年間AI Task Forceでイネーブラーとして活動している間、そうした少ない人数で社内ツールを作るために頻繁に用いた構成を紹介します。&lt;/p&gt;
&lt;p&gt;前提として、社内ツールにはメルカリのメインプロダクト、Microservicesほどのスケーラビリティや厳密な可用性は求められません。求められるのは**「開発スピード」「メンテのしやすさ」「低コスト」**です。&lt;/p&gt;
&lt;p&gt;Google WorkspaceとGoogle Cloudを社内で使っていることが前提になっています。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;1. Cloud Run + IAP (Identity-Aware Proxy)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Google Cloud環境において、私が社内ツール基盤として圧倒的に頻繁に用いたのが &lt;strong&gt;Cloud Run&lt;/strong&gt; と &lt;strong&gt;IAP (Identity-Aware Proxy)&lt;/strong&gt; の組み合わせです。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;なぜCloud Runなのか？&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Cloud Run を利用する主な理由は、その優れたコスト効率、スケーラビリティ、そしてシンプルなデプロイに集約されます。&lt;/p&gt;
&lt;p&gt;Cloud Run の最大の利点の一つは、その高いコスト効率です。リクエストがないときは実行インスタンスがゼロになり、その間は課金が一切発生しません。これは、夜間や休日などアクセスが少なくなる時間帯がある社内ツールなどにとって、特に大きなメリットとなります。&lt;/p&gt;
&lt;p&gt;また、どれだけ利用者が集中したとしても、Cloud Run は自動で必要な数のコンテナを起動し、トラフィックの負荷に瞬時に対応するスケーラビリティを備えています。これにより、ピーク時のアクセス集中によるサービス停止やパフォーマンス低下の心配をする必要がなくなります。&lt;/p&gt;
&lt;p&gt;Cloud Run を使えばシンプルなデプロイが可能です。アプリケーションをパッケージ化した Dockerfileさえ用意すれば、あとは簡単なコマンド一つでデプロイが完了します。「レプリカセット！」「ライブネスプローブ！」「ホリズンタルポッドオートスケーラー！」といったKubernetesの呪文も覚える必要はありません。&lt;/p&gt;
&lt;p&gt;また、DNSやSSL証明書の管理から解放されるだけでなく、サービスで重要な基本的なメトリクスやログも自動で収集されます。これらはすべてGCPコンソールからすぐに確認できるため、運用の負担が大幅に軽減されます。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;IAPで認証を「実装しない」&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;社内ツールで最も重要なことの１つはセキュリティですが、認証ロジックを自前で実装するのは大変です。&lt;/p&gt;
&lt;p&gt;IAP (Identity Aware Proxy) を使えば、Google Workspaceアカウントに基づいたアクセス制御をインフラ層で強制できます。アプリ側では特定のヘッダを見るだけでユーザーを特定でき、認証ロジックを書く必要がなくなります。&lt;/p&gt;
&lt;p&gt;TerraformでLoad Balancerやバックエンドサービスを構成するための複雑なネットワーク設定を組むと長い時間のかかる作業が、Cloud Runなら以下のコマンドだけで「社内限定公開」の環境が整います。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Cloud Runへのデプロイ（IAP有効化オプション付き）
gcloud beta run deploy ${SERVICE_NAME} \
 --no-allow-unauthenticated --iap \
 --platform managed \
 --project=${PROJECT_ID} \
 --service-account=${SA_EMAIL} \
 --region=asia-northeast1 \
 --image=${IMAGE_ID}

# IAPへのアクセス権限付与（社内ドメインや特定グループのみ許可）
gcloud beta iap web add-iam-policy-binding \
 --project=${PROJECT_ID} \
 --member=&amp;quot;例:group:all-members@mercari.com&amp;quot; \
 --role=roles/iap.httpsResourceAccessor \
 --region=asia-northeast1 \
 --resource-type=cloud-run \
 --service=${SERVICE_NAME}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これだけで、Artifact Registryに上げてあるコンテナが、社内メンバーだけがアクセス可能な状態で立ち上がります。&lt;br /&gt;
(betaなので今後GAになった際にはコマンドが変わる可能性があります)&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;Vertex AI (Gemini) へのアクセスも簡単&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;AIをツールから使う上で面倒なことの一つが、APIトークンの管理です。&lt;/p&gt;
&lt;p&gt;Cloud Runならば、Cloud Runの実行Service Accountに roles/aiplatform.user を付与しておくだけで、APIキーの管理やローテーションを気にすることなくGeminiを利用可能です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from google import genai

# 認証情報が環境から自動取得されるためAPI Keyの指定は不要
client = genai.Client(vertexai=True, project=&amp;quot;your-project-id&amp;quot;, location=&amp;quot;us-central1&amp;quot;)
model = &amp;quot;gemini-2.5-flash-lite&amp;quot;

response = client.models.generate_content(
    model=model, 
    contents=&amp;quot;元気ですか?&amp;quot;
)
print(response.text)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;n8nやDifyなどのノーコードツールも魅力的ですが、将来的な要件変更の柔軟性を考えると、Cloud Runによるコードベースの開発は「セットアップコストを抑えつつ、自由度を確保する」ための最適解の1つだと感じています。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;2. データベース：Firestore と Google Spreadsheet&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;「高負荷なトランザクションが不要な社内ツールにおいて、月額数千円から1万円以上のコストがかかる Cloud Spanner や Cloud SQL はオーバースペックになりがちです。そこで、コストを最小限に抑える選択肢として Firestore と Google Spreadsheet が非常に役立ちました。&lt;/p&gt;
&lt;p&gt;Firestore は、スキーマレスかつサーバーレスなデータベースで、完全従量課金のためスモールスタートに最適です。一方で、意外にも優秀な選択肢となるのが Google Spreadsheet です。&lt;/p&gt;
&lt;p&gt;Spreadsheet を利用する最大の利点は、事実上のゼロコストで運用できる点にあります。Google Workspace 環境であれば追加費用はかかりません。Cloud Run の実行サービスアカウントに権限を付与するだけで、プログラムから容易に読み書きが可能です。&lt;/p&gt;
&lt;p&gt;さらに、管理画面（Admin UI）を作る手間を省ける点も大きな魅力です。非エンジニアの担当者が直接データを閲覧・編集できるため、現場からは「使い慣れたツールで管理できてありがたい」という声も得られました。データ不整合のリスクという側面はありますが、数千行程度のマスタデータ管理などであれば、開発スピードとメンテナンス性を最優先した合理的な選択だと言えます。」&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;3. Streamlit&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;ツール開発において、現在は Next.js がデファクトスタンダードと言えますが、社内ツールであれば Streamlit を採用することにも大きなメリットがあります。&lt;/p&gt;
&lt;p&gt;最大の利点は、開発速度の圧倒的な速さと学習コストの低さです。Streamlit を使えば、データアナリストやエンジニアが Python を書く延長線上で UI を構築できます。フロントエンドとバックエンドの境界を意識せず、入力ウィジェットやグラフ、チャットインターフェースなどを数行で実装できるため、迅速な改善が求められる現場で真価を発揮します。将来的に非エンジニアがツール制作に携わる際や、AI エージェントが台頭して「ロジックさえあれば UI はチャットで十分」となる時代を見据えても、Python で完結するライブラリを選んでおくのは有利な選択です。&lt;/p&gt;
&lt;p&gt;また、Python エコシステムとのシームレスな統合も無視できません。Pandas などのデータ処理ライブラリと標準で連携できるため、Next.js のように Python バックエンドとの間に複雑な通信レイヤーを設ける手間が省けます。同一の Python 環境内でデータを直接扱い、即座に UI へ反映できるシンプルさは、デバッグやメンテナンスの工数削減に直結します。&lt;/p&gt;
&lt;p&gt;他の Python 製 UI ライブラリも検討しましたが、現時点では Streamlit が最も完成度のバランスに優れていると感じています。もちろん API 開発が必要なケースでは他の選択肢が必要になりますが、「開発効率」を最優先する社内ツールにおいて、Streamlit は非常に強力な武器になります。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;諦めたこと&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;もちろん、上記の技術を選んだことで諦めることにした部分もあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ミリ秒単位の「サクサク」した操作感
&lt;ul&gt;
&lt;li&gt;Cloud Runがコールドスタートするときには数秒の遅延が発生します。また、Streamlitで複雑なことをし始めるとどうしても動きがモッサリして、CacheやSessionの管理が厄介にもなります。  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;厳密なトランザクション管理や集計クエリ
&lt;ul&gt;
&lt;li&gt;アクセス数が少ない社内ツールでは大きな問題は起きないだろうと考え、諦めました。  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;サービスの常時稼働
&lt;ul&gt;
&lt;li&gt;Cloud Runには常時稼働するオプションもありますが、一気にコストが上がり、従量課金のメリットが消え去ります。常にeventをListenするアプリやバックグラウンドでの重い非同期処理にはコストパフォーマンスが良くないでしょう。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;まとめ・今後の展望&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;AI-Nativeな組織変革を達成するには、エンジニアと非エンジニア組織の協力が不可欠です。&lt;/p&gt;
&lt;p&gt;この半年間、私はコードを書くだけでなく、現場の課題を聞き出し、それを技術で解決する「技術コンサルタント」のように働く時間が多かったように思います。&lt;/p&gt;
&lt;p&gt;非エンジニアと密に連携し、効率よく価値を届けるためには、彼らにとってもフレンドリーな技術を選択肢に入れ、柔軟性の高い技術を選択し、また常に「本当にAIを組み込むことは必要なのか」を見極めることも大事です。&lt;/p&gt;
&lt;p&gt;また、上述のような技術を使っても、まだ厄介なところは「CI/CD」で、個人的に改善しようとしているところです。&lt;br /&gt;
プルリクエストが作られれば自動的にPreview URLが生成され、マージしたら本番環境にデプロイされる。環境変数の値を変更したいときに数クリックでサクサクと変更ができる。まだまだ今は手動デプロイをすることも多いですが、そういったPaaS体験を上述の技術と合わせて達成することで格段に生産性が上がるだろうなと感じています。&lt;/p&gt;
&lt;p&gt;進化の早いAIを活用し、現場の業務を最速で効率化していくフェーズにおいては、サーバレスソリューションをフル活用して「明日から使えるツール」を届けてみるのも悪くないのではないでしょうか。&lt;/p&gt;
&lt;p&gt;明日の記事は@Kahlaさんです。引き続きお楽しみください。  &lt;/p&gt;
</content:encoded></item><item><title>メルカリグループのDevEx改善、9ヶ月間の実践</title><link>https://engineering.mercari.com/blog/entry/20251219-nine-months-of-devex-improvement-at-mercari-group/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251219-nine-months-of-devex-improvement-at-mercari-group/</guid><description>&lt;p&gt;はじめに この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の21日目の記事です。 ntk1000です。MerpayのKYCおよびPartner Platformチームのエン [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sun, 21 Dec 2025 10:00:43 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;はじめに&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の21日目の記事です。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/ntk1000&quot;&gt;ntk1000&lt;/a&gt;です。MerpayのKYCおよびPartner Platformチームのエンジニアリングマネージャーを務めています。 半年前、メルカリグループにおける開発者体験（DevEx）改善のための&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250624-building-a-company-wide-framework-for-improving-devex-in-mercari-group/&quot;&gt;全社的な取り組みを紹介&lt;/a&gt;しました。四半期ごとの改善サイクルを設計し、エンジニアとEMから100%の参加を得て、Deep Work（集中するための中断されない時間）やCross-team Collaborationといった構造的な課題を特定しました。&lt;/p&gt;
&lt;p&gt;最初の6ヶ月（FY25 Q4 → FY26 Q1）では、総合的な開発者体験指標は大きな変化がありませんでした。一部のチームで大幅な改善が見られたものの、多くのチームは停滞していました。この状況を踏まえてアプローチを見直した結果、直近の四半期（FY26 Q2）では全社的に指標が大幅に改善し、特にDeep Workの領域で顕著な向上を達成しています。通常は年間目標とされる改善水準も、今回は1四半期で達成できており、アプローチ転換の効果が明確に表れました。&lt;/p&gt;
&lt;p&gt;本記事では、この9ヶ月間の実践と成果、そして組織全体でDevEx改善をスケールさせるための取り組みについて共有します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/4c7ddc4b-chatgpt-image-2025年12月16日-20_00_48-1024x683.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;strong&gt;改善のスケール：チームから組織全体へ&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;第1段階（FY25 Q4 → FY26 Q1）：一部成功と全体停滞&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;FY26 Q1のサーベイでは、開発者体験指標が横ばいの組織がほとんどでした。 しかし、特定の課題領域やチーム単位でのデータを分析すると、改善に成功している事例も確認することができました。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;成功事例の分析&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Deep Workを例にとると、大幅に改善したチームの取り組みを調査した結果、以下の共通パターンがありました：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;集中時間を確保する明確なポリシーを確立
&lt;ul&gt;
&lt;li&gt;ミーティングの整理、統合ルールの策定と実施  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;チーム全体の「ノーミーティングタイム」ブロックの設定
&lt;ul&gt;
&lt;li&gt;エンジニアの集中作業Dayを定期的に開催—月1回、2日間など完全にミーティングのない期間の確保  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AI活用によるルーチンタスクの自動化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの取り組みに共通するのは、&lt;strong&gt;チーム全体のコミットメントに支えられた、ポリシーベースの改善&lt;/strong&gt;です。 個人レベルの工夫ではなく、チーム全体で合意した構造的な変更として実装されており、&lt;strong&gt;ポリシーベースのため素早い展開・適用が可能&lt;/strong&gt;という特徴があります。 一方でチーム単位での適用となるため、改善対応にばらつきがある・成功パターンの横展開が自然には起きないという課題もありました。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;第2段階（FY26 Q1 → FY26 Q2）：ボトムアップとトップダウンの融合&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;上記の課題を踏まえ、チーム単位の改善だけでは限界があることが明らかになったため、&lt;strong&gt;ボトムアップとトップダウン、両方のアプローチを同時に機能させる&lt;/strong&gt;体制を構築しました。 2層構造での改善サイクルをまとめると下記になります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ボトムアップ:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;FY25 Q4から確立していたチームレベルの改善サイクル：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主体&lt;/strong&gt;: Individual Contributor（IC）とEngineering Manager（EM）  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特徴&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;小回りが効き、チーム固有の課題に素早く対応  &lt;/li&gt;
&lt;li&gt;チームがコントロールでき、かつ素早い対応ができる領域（Deep Work、Documentation、Code Maintainabilityなど）で高い成功率&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;トップダウン:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;FY26 Q2から本格的に構築した組織レベルの改善サイクルです：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主体&lt;/strong&gt;: VPoE、Director、Manager of Managers（MoM）  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目的&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;個別チームでは解決困難な構造的課題への対応  &lt;/li&gt;
&lt;li&gt;組織横断での方針決定とリソース配分  &lt;/li&gt;
&lt;li&gt;成功パターンの標準化と横展開&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/e600e6e3-chatgpt-image-2025年12月16日-20_04_18-1024x683.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;具体的な取り組み事例&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Fintech組織に所属するMerpay/MercoinではDeep Workへの改善要望が継続して高く、スコアも低い状況でした。 VPoE主導でDeep Work改善を組織としての注力課題に定め、下記のような取り組みを行いました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fintech内の各組織におけるDeep Work関連指標の可視化（Deep Workスコア、ミーティングが多い日や割り込みの割合）  &lt;/li&gt;
&lt;li&gt;Deep Work改善事例の横展開とローカライズ
&lt;ul&gt;
&lt;li&gt;組織内での定例会議の見直しと統合  &lt;/li&gt;
&lt;li&gt;チーム間での成功事例の共有・標準化  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;追加サーベイを用いたより詳細な課題分析  &lt;/li&gt;
&lt;li&gt;EMへの改善実行サポート  &lt;/li&gt;
&lt;li&gt;進捗トラッキング  &lt;/li&gt;
&lt;li&gt;経営層へのレポート、非エンジニア組織への取り組み共有&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;個々のチームの努力だけでは変化しなかった組織平均が、VPoE主導のトップダウン施策とEM/IC主導のボトムアップ実行を組み合わせることで、&lt;strong&gt;1四半期で約16%の改善を実現&lt;/strong&gt;することができました。この結果は経営会議でも報告しており、全社集会でも共有を予定しています。&lt;/p&gt;
&lt;p&gt;Deep Work改善に有効であった集中時間を確保する明確なポリシーは、非エンジニア組織にも有効と考えられます。全社的なDeep Work改善につながることを期待しています。&lt;/p&gt;
&lt;p&gt;また、今回は素早い展開・適用が可能なポリシーベースの改善がメインだったため、組織横断で効果が発揮されやすいということもあったと思います。今後は着手しやすい短期の施策だけでなく、長期的な対応を必要とするような難易度の高い課題にも取り組んでいく必要があると考えています。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;strong&gt;DXからの学び&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;今回の取り組み方針を再設計する上で、&lt;a href=&quot;https://getdx.com/&quot;&gt;DX&lt;/a&gt;（私たちのDevExプラットフォームを提供する会社）によるプレゼンテーションで得られた知見が参考になったため簡単に共有したいと思います。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;イニシアチブには構造が必要&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;プレゼンテーションで強調されたのは、多くのDevEx改善が失敗する原因は、チームの関心の欠如だけではなく適切な構造の欠如であるという点です。 成功には3つの要素が不可欠とされていました：&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;1：ビジネスケースの構築&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;開発者の痛みを説明するだけでなく、ビジネス成果に翻訳する：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;組織のKPIに接続&lt;/strong&gt;: 時間損失、コスト、品質、定着率  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;規模での影響を定量化&lt;/strong&gt;: 例えば、1人の開発者がビルドごとに20分損失 × 700エンジニア = 重大なコスト  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;価値の解放を示す&lt;/strong&gt;: 痛みの除去だけでなく、可能になること（より速い機能、より高い信頼性、より良い回復）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;2：イニシアチブを適切に構造化&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;6-12ヶ月以上でタイムボックス&lt;/strong&gt;: 本当の変化には時間がかかる。スプリントは迅速な成果を生むが習慣を形成しない。  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自然なチェックポイントを設定&lt;/strong&gt;: ベースライン → 中間点 → 終了測定  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;チームが動員できるようにする&lt;/strong&gt;: コミュニケーション、計画、実践の埋め込みに時間を与える&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;3：適切な指標を定義&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Northstar KPI（組織の最重要指標）&lt;/strong&gt;: 生産性、満足度、品質、定着率  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;リーディング指標&lt;/strong&gt;: チームが直接影響できるもの（例：ビルド時間、レビュー時間）  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ガードレール&lt;/strong&gt;: ゲーミングを防止（例：PR数単独では人為的に膨張可能）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;3つの不可欠な役割&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;すべての成功したイニシアチブには必要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;エグゼクティブスポンサー&lt;/strong&gt;: トップダウンのリーダーシップとビジネスアライメントを提供（例：VPoEの意思決定）  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;チャンピオン&lt;/strong&gt;: データで問題を枠組み化し、戦術的ガイダンスを提供（例：DirectorやMoMによる組織データ理解と方針決定）  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;マネージャー&lt;/strong&gt;: 時間を配分し、イニシアチブをチームレベルのアクションに翻訳（例：EMやICによるチーム改善）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;いずれかの役割が欠けていると、イニシアチブは停滞します。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;可視性による持続可能性&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;イニシアチブが途中で停滞することを防ぐため、以下の仕組みが推奨されています：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可視化&lt;/strong&gt;: 進捗/進捗不足を示すダッシュボード  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;運営レビュー&lt;/strong&gt;: 課題/アクション/成果を伴う定期的なミーティング  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;明確なリーダーシップ&lt;/strong&gt;: 進捗の説明責任、ビジネスアラインメント&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;strong&gt;今後の課題&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;現時点で解決できていない課題があり、引き続き取り組みを進めています。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;横展開の難しさ&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;成功パターンは自動的に広がりません。特にメルカリグループではプロダクトのフェーズや組織サイズがさまざまなので、例えば成熟した大規模組織へのアプローチとスタートアップフェーズの小規模組織へのアプローチが同じになるとは限りません。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;現在の取り組み&lt;/strong&gt;: 単なる成功事例の寄せ集めではなく、組織サイズやプロダクトのフェーズといった情報も併記することで、各組織が自律して適用可能なアクションを判別できるようにナレッジ整備を進めています。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;長期的影響の測定&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;サーベイスコアと即時指標（ミーティング時間、中断頻度）は測定できますが、DevEx改善をビジネス成果（提供速度、品質、定着率）に接続することは依然として困難です。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;現在の取り組み&lt;/strong&gt;: DXスコア改善と各種サーベイ結果および定量データとの相関分析を進めています。まだ確定的な結論は出ていません。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;複数四半期にわたる継続性&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;高い参加率は継続できていますが、サーベイ疲れや改善が進まない課題への不満も散見されるようになってきました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;現在の取り組み&lt;/strong&gt;: DXのサーベイが肥大化しないように毎回調整、DX関連の社内イベントを実施して意義を定期的に伝える取り組みを続けています。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;&lt;strong&gt;まとめ&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;9ヶ月前、この取り組みを開始した時点では、強いエンゲージメントを達成し、構造的な課題を特定し、アクションプランを作成しました。&lt;/p&gt;
&lt;p&gt;現在は、初期の勢いを持続的な組織変革に変える段階にあります。一部のチームで大きな成果が出ている一方、課題に直面しているチームもあるというばらつきがある状況から、組織横断での改善を進めることができるようになってきました。&lt;/p&gt;
&lt;p&gt;DevEx改善を組織全体にスケールさせるために必要な要素をまとめます：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;構造的サポート&lt;/strong&gt;（改善体制、役割の明確化）  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文化的コミットメント&lt;/strong&gt;（多方面でのリーダーシップ、定期的な可視性）  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;実用的なフレームワーク&lt;/strong&gt;（チームが適応できる成功事例の展開）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/920b115e-chatgpt-image-2025年12月16日-20_11_28-1024x683.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;改善スコアは指標であり目的ではないことにも引き続き注意する必要があります。 より重要なのは、チームが以下の能力を獲得したかどうかであり、結果としてビジネス成果を最大化することです：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日々の仕事での摩擦を特定する  &lt;/li&gt;
&lt;li&gt;リーダーシップに明確に表現する  &lt;/li&gt;
&lt;li&gt;改善のために集団的アクションを取る  &lt;/li&gt;
&lt;li&gt;結果を測定し振り返る&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;継続的な実践としてのDevEx&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;プロダクトの進化や組織サイズ・構造の変化、AIによる開発手法の変容といった動きに応じて、いち早く課題を特定して対処するためには継続的な実践が不可欠です。&lt;/p&gt;
&lt;p&gt;目標は完璧なスコアを達成することではなく、開発者体験の問題を継続的に感知し対応できる組織能力を構築し、生産性を上げることです。&lt;/p&gt;
&lt;p&gt;第1段階では全社的な開発者体験指標は横ばいでしたが、第2段階では大幅な改善を達成しました。 この変化が一時的なものでなく継続的な改善プロセスとして定着するように、引き続きこの取り組み自体も改善を進めていきます。&lt;/p&gt;
&lt;p&gt;明日の記事は @takiさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>PR駆動の変更、CI/CDでOS設定を自動反映 — Terraformで実現するJamf ProのIaC＋GitOps基盤</title><link>https://engineering.mercari.com/blog/entry/20251220-jamf-terraform-gitops/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251220-jamf-terraform-gitops/</guid><description>&lt;p&gt;こんにちは。メルカリのセキュリティエンジニアの@yuです。 この記事は、Mercari Advent Calendar 2025 の20日目の記事です。 従業員の業務端末(MacBook)に利用しているデバイス管理ツール [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sat, 20 Dec 2025 11:00:31 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリのセキュリティエンジニアの@yuです。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の20日目の記事です。&lt;/p&gt;
&lt;p&gt;従業員の業務端末(MacBook)に利用しているデバイス管理ツールであるJamf Proの構成管理にTerraformを活用し始めた際の所見について紹介します。&lt;/p&gt;
&lt;h2&gt;概要&lt;/h2&gt;
&lt;p&gt;Jamf Proの構成変更は、macOS, iOSデバイス管理の改善に欠かせません。一方で、変更が手作業中心だと、レビューが重くなったり、作業ミスの余地が残ったり、変更の根拠や履歴が散らばったりしがちです。特にセキュリティの観点では「何時、誰が、何故、どのようなプロセスで、何を変更できるのか」のような5W1Hを明確にし続けることが重要になります。&lt;/p&gt;
&lt;p&gt;そこで本記事では、Open Source Communityが開発・保守しているTerraform provider (※)を用いてJamf ProのリソースをIaC (Infrastructure as Code)化し、さらにGitHub上のPR (Pull request)を起点に&lt;code&gt;terraform plan&lt;/code&gt;をレビューしてから&lt;code&gt;terraform apply&lt;/code&gt;を実行する、PR駆動のGitOpsフローへ移行した取り組みを紹介します。&lt;/p&gt;
&lt;p&gt;※: &lt;a href=&quot;https://registry.terraform.io/providers/deploymenttheory/jamfpro/latest/docs&quot;&gt;https://registry.terraform.io/providers/deploymenttheory/jamfpro/latest/docs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;この手法自体は主要なクラウドサービスの構成管理では一般的な手法ではありますが、本件の様なデバイス管理ツール上での適用については例が少ないのが現状です。本記事では、この試みをいかにして実現したのかを紹介します。&lt;/p&gt;
&lt;p&gt;この仕組みによって、変更内容はコード差分とplan出力として事前に確認できるようになり、適用は自動化されます。結果として「手順書ベースのレビューや手作業変更」から、「差分とplanを一次情報としてレビューし、承認後に安全に反映する」形へ移行できました。&lt;/p&gt;
&lt;h2&gt;背景：なぜ“セキュアな変更管理”が必要だったのか&lt;/h2&gt;
&lt;p&gt;もともとJamf Proの変更は、手順書に沿った手作業が中心でした。この方法は丁寧に運用すれば回りますが、変更のたびに設計から実装までのリードタイムが伸びやすく、オペミスの余地も残ります。また、Jamf Proのリソースによっては変更履歴が十分に追えず、申請フローと実際の変更を機械的に突合できない、といった課題もありました。さらに、従来の申請フローでは「何をどう変えるか」が自然言語やスクリーンショットで記述されることが多く、変更内容が曖昧になりやすいという問題もありました。申請者・承認者・作業者の間で解釈が分かれた結果、意図と異なる設定変更やレビュー漏れが起きる余地が残ります。&lt;/p&gt;
&lt;p&gt;（こちらの課題の一例については昨年の &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241220-mscp-jamf-api-macos-security-configs-iac/&quot; title=&quot;mSCPとJamf Pro APIによるmacOSセキュリティ設定の手動IaC化の試行 | メルカリエンジニアリング&quot;&gt;mSCPとJamf Pro APIによるmacOSセキュリティ設定の手動IaC化の試行 | メルカリエンジニアリング&lt;/a&gt;  も併せてご参照下さい）&lt;/p&gt;
&lt;p&gt;さらに運用が進むにつれて、リソースの所管や背景（なぜこの設定が必要か）が 変更管理ツール、SlackやNotion、Google Docsなどに散在し、後から辿るコストが増えていきます。結果として、定常メンテナンスのために高い操作権限を人に付与し続ける必要が出やすく、攻撃面管理の観点でも悩ましい状態になりがちです。一方で、私たちはお客さま向けのプロダクト環境へ設定しているものと同じ高いセキュリティ及び管理基準を当社のコーポレート環境に対しても適用したいと考えております。&lt;/p&gt;
&lt;p&gt;この状況を改善するために、私たちはJamf Proの変更を「コード化」し、「PRとレビューのプロセスに載せる」、そして「承認後に自動適用する」構造へ切り替えました。以降の章では、この GitOpsフローをどう設計し、どのように権限分離・承認・変更範囲のコントロールを実現したかを中心に説明します。&lt;/p&gt;
&lt;h2&gt;コードオーナーで「変更範囲」と「承認責務」を結びつける&lt;/h2&gt;
&lt;p&gt;GitOpsでは、構成変更の入口がPRになります。つまり、レビューと承認の設計が、そのまま変更管理の統制になります。Jamf Proのように、設定変更が端末群に広く影響し得る仕組みでは、誰がどの範囲を見て、どの基準で承認するかを、運用ルールだけでなく “仕組み” に落とすことが重要です。&lt;/p&gt;
&lt;p&gt;そこで有効なのが コードオーナー (※)です。コードオーナー を使うと、特定のディレクトリやファイルに変更が入った際に、指定したロール（チーム）がレビューに入ることを、リポジトリのルールとして強制できます。これにより「所管が曖昧」「気づいた人が見る」状態から、「この範囲はこのロールが責任を持つ」状態へ寄せられます。&lt;/p&gt;
&lt;p&gt;※: &lt;a href=&quot;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners&quot;&gt;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;（掲載例）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# CODEOWNERS (example)

# Policies: local admin management
/terraform/sandbox/policies/local-admin/**                        @sec-reviewers

# Application update settings: owned by IT Dept.
/terraform/sandbox/configuration-profiles/update-settings/**          @endpoint-admins

# Production: require multiple approvers
/terraform/prod/**               @sec-reviewers @endpoint-admins

# Workflows
/.github/workflows/**                         @sec-reviewers&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここでのポイントは、ディレクトリ構造自体をレビュー境界に合わせることです。&lt;/p&gt;
&lt;h2&gt;GitHub Actions workflowの統制：applyの実行条件を絞る&lt;/h2&gt;
&lt;p&gt;PR駆動のGitOpsでは、&lt;code&gt;terraform plan&lt;/code&gt;が “事前確認” で、&lt;code&gt;terraform apply&lt;/code&gt;が “本番反映” です。この2つを分けて考え、applyが実行され得る条件を狭くすることで、変更管理をセキュアにできます。&lt;/p&gt;
&lt;p&gt;本件での設計の要点は&lt;code&gt;terraform plan&lt;/code&gt;と&lt;code&gt;terraform apply&lt;/code&gt;の実行ワークフローを分離し、承認前に本番反映 (apply)が起きない状態をBranch Protectionで担保することです。&lt;/p&gt;
&lt;p&gt;具体的にはPR 作成時にはplanを実行し、レビュワーはコード差分とplan出力を一次情報として確認します。一方でapplyはmainブランチへのマージ後にのみ実行されるようにし、「承認済みの変更だけが反映される」状態をワークフロー条件とします。Branch Protectionに関わるセキュリティ上の論点については、補足として「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241217-github-branch-protection/&quot; title=&quot;GitHubのBranch Protectionの突破方法 | メルカリエンジニアリング&quot;&gt;GitHubのBranch Protectionの突破方法 | メルカリエンジニアリング&lt;/a&gt;」も併せて参照ください。&lt;/p&gt;
&lt;p&gt;さらに、ワークフロー自体を保護することも重要です。GitOpsにおいてCI/CDは実質的に“実行主体”であり、ここが最重要の保護対象になります。そのため、本件ではplanとapplyのワークフローを分離し、それぞれが参照できるAPI権限も分離します。具体的には、planは読み取りに必要な範囲に限定し、applyは反映に必要な範囲に限定します。加えて、認証情報は特定のワークフローが特定の GitHubイベントで実行された場合にのみ、CI上で一時的に参照できるようアクセス制御を適用し、不要な露出を避けます。&lt;/p&gt;
&lt;p&gt;最後に、&lt;code&gt;.github/workflows/**&lt;/code&gt;配下の変更についても勿論レビューを必須にすることで、CI/CDの改変による統制の迂回を防ぎます。&lt;/p&gt;
&lt;p&gt;この設計によって、「強い操作権限を常に人へ配り続け、手動での変更管理」状態から、承認プロセスに通った変更だけが自動で反映される状態への移行準備が整いました。Jamf Proは端末に対して大きな影響を与え得るため、変更管理をこの構造に乗せること自体がリスク低減になります。&lt;/p&gt;
&lt;h2&gt;移行：既存JamfリソースをIaCへ取り込む&lt;/h2&gt;
&lt;h3&gt;Import block / generate-config-out で「ゼロから書かない」移行にする&lt;/h3&gt;
&lt;p&gt;Jamf Pro には既に多くの設定が存在しているため、IaC化を “ゼロから書く” 方式で始めると、次の問題が起きがちです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;現行設定の再現に時間がかかる&lt;/li&gt;
&lt;li&gt;再現ミス（設定の抜け漏れ）が起きる&lt;/li&gt;
&lt;li&gt;結果として、移行プロジェクトが止まりやすい&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;そのため、本取り組みではTerraformのImport blockと &lt;code&gt;-generate-config-out&lt;/code&gt;を活用し、既存のJamfリソースを起点にtfファイルを生成し、stateに取り込む流れを中心に据えました。&lt;/p&gt;
&lt;p&gt;（コマンド例）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;terraform plan -parallelism=1 -generate-config-out=&amp;lt;generated&amp;gt;.tf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このアプローチの利点は、移行を“正確性”と“スピード”の両立で進めやすい点です。運用移行では、最初から完璧な整理（理想のディレクトリ構成やモジュール化）を目指すより、まず「planが安定して読める状態」まで持っていく方が、その後の GitOps 運用に繋がりやすいと考えています。&lt;/p&gt;
&lt;p&gt;なお、メルカリでは100を超えるJamf Proリソースがあるため、手動でimportブロック書くのも一定時間を要する可能性があり、事前に「Jamf Proのリソース情報を取得してimportブロックを自動生成する」プログラムを準備して臨みました。但し、こちらは Terraform provider側で将来&lt;code&gt;terraform query&lt;/code&gt;へ対応した場合は、該当コマンドへ置き換えが可能となります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/25ea0303-20251220_mercari_engineering_blog_jamf_terraform_gitops-1024x381.png&quot; alt=&quot;Jamf terraform plan result&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;つまずきどころ1：構成プロファイルの差分が毎回出る問題&lt;/h3&gt;
&lt;p&gt;構成プロファイルでは、初回に&lt;code&gt;*.mobileconfig&lt;/code&gt;をペイロードとしてJamf Proへ作成した際に、Jamf Pro側で&lt;code&gt;PayloadIdentifier&lt;/code&gt;などの付加情報が追加されることがあります。結果として、次回の&lt;code&gt;terraform plan&lt;/code&gt;で差分が必ず出る、という状況が起こり得ます。&lt;/p&gt;
&lt;p&gt;例えば、GitHubプロジェクト側のmSCP (macOS Security Compliance Project)側で自動生成されるmobileconfigファイルをそのまま使う場合は本事象が発生します。&lt;/p&gt;
&lt;p&gt;この差分は “危険な変更” というより、管理システム側の正規化（自動付加）により起きているため、運用としては次のように整理しておくと混乱が減ります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「差分が出る＝即apply」ではなく、まず差分の性質を分類する。&lt;/li&gt;
&lt;li&gt;既存リソースは importを起点に扱い、変更時に差分を取り込む方針に寄せる&lt;/li&gt;
&lt;li&gt;新規登録リソースも既存の正規化（自動付加）をsandbox環境などで検証の上、正規化された状態で本番へ適用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これにより、GitOpsの価値である「planを一次情報としてレビューする」を維持しつつ、ノイズ由来の差分で運用が疲弊することを避けられます。&lt;/p&gt;
&lt;h3&gt;つまずきどころ2：Policy更新時の差分の見え方&lt;/h3&gt;
&lt;p&gt;Policy 更新時は、plan/applyで例えば Self Serviceに関する属性差分が表示されるなど、UI上の変更と一致していても“コード上の差分”として表現されることがあります。将来的にTerraform provider側の改修に伴い解消する可能性がありますが、それまでは差分が「意図した変更か」を判断するためのチェック観点を押さえ、Terraformを利用する関係者で共有出来る形にすることで、円滑な運用に繋げることが出来ます。&lt;/p&gt;
&lt;h2&gt;運用：属人性を減らすために“プロセスで残す”&lt;/h2&gt;
&lt;h3&gt;PRが「背景・差分・plan」をまとめた一次情報になる&lt;/h3&gt;
&lt;p&gt;従来、変更の背景（なぜ必要か）や手順、結果はSlackやGoogle Docs、Notionなどに散らばりがちでした。GitOpsへ寄せると、少なくとも変更の入口がPRに集約されるため、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PR記述 (なぜ変えるのか)&lt;/li&gt;
&lt;li&gt;コード差分 (何を変えるか)&lt;/li&gt;
&lt;li&gt;plan 出力 (何が起きるか)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;が、1つの場所に残ります。これは、コンテキスト散在や所管不明瞭の改善に直結します。&lt;/p&gt;
&lt;h3&gt;コードオーナー＋レビューにより「気づいた人」から「責務」へ&lt;/h3&gt;
&lt;p&gt;コードオーナーは単に“レビューを依頼する仕組み”ではなく、責務境界をコードに埋め込む仕組みとして機能します。結果として、特定のリソースに対して「どのロールが見るべきか」を継続的に維持しやすくなります。&lt;/p&gt;
&lt;h3&gt;変更履歴の可視化（通知）で“あとから追える”を当たり前にする&lt;/h3&gt;
&lt;p&gt;GitOps では mainブランチが事実上の “唯一の正” になるため、mainの変更履歴を関係者が追える形にするのが重要です。mainの変更履歴をポストするチャンネルを用意してGitHubにアクセスせずともどのような変更が行われているかについてリーチし易い環境を用意しました。&lt;/p&gt;
&lt;h2&gt;導入効果&lt;/h2&gt;
&lt;p&gt;ここまでの取り組みを経て、私たちの運用が具体的にどのように変わったのかを紹介します。&lt;/p&gt;
&lt;h3&gt;1.ClickOpsからの解放&lt;/h3&gt;
&lt;p&gt;これまでの変更手順書に対するレビューでは、手順のWeb UIを頭でトレースしてレビューを行っていました。「この操作で意図した設定に変わるのかな？」「前提のステップや併せて変更するべきリソースはどこだろう？」という、ある意味想像力を働かせたレビュー必要でした。仮にsandbox環境でのテストをパスしていたとしても本番環境との僅かな差異によって予期せぬ挙動を招くリスクがあり、手順書作成からテスト、本番実装までのリードタイムも膨らんでいました。また、仮に完璧な手順書があったとしても、手作業中心では最後の最後でのオペミス（クリックやコピペ内容）というヒューマンエラーのリスクを内包していました。&lt;/p&gt;
&lt;p&gt;移行後はPRのコード差分とterraform planの出力結果という一次情報が目の前にあるため、レビュー品質とスピードがあがりました。また、変更の反映も手作業では無く「プログラムによる確実な実行」がもたらした安心感はとても良いです。&lt;/p&gt;
&lt;h3&gt;2.監査・トラブルシューティングが容易に&lt;/h3&gt;
&lt;p&gt;PRそのものが「変更背景・差分・実行結果」をすべて内包するログとなっています。いつ、誰が、何の目的・背景で変更したのかが main ブランチの履歴と紐付いて残るため、監査やトラブルシューティングの際も、迷わず一次情報へ辿り着けるようになりました。&lt;/p&gt;
&lt;h3&gt;3.仕組みで責任と権限の担保&lt;/h3&gt;
&lt;p&gt;コードオーナーによって、ディレクトリ構造とレビュー（承認）責務が紐付いています。また、「承認された変更のみが自動適用される」フローを構築したことで、将来的に「最小権限の原則」の実現に向けたよい準備となりました。&lt;/p&gt;
&lt;h2&gt;今後の展望&lt;/h2&gt;
&lt;h3&gt;1. 管理対象リソースの拡大&lt;/h3&gt;
&lt;p&gt;Jamf ProのTerraform providerは有用ですが、開発途上の側面もあり、リソースタイプやリソースの構成によっては”差分ノイズ”と継続的に付き合う可能性があります。ノイズが多いとplanレビューの焦点がぶれ、レビュワーが疲弊する状況になりますので、まずはsandboxでの検証を継続しつつ、本番での管理(import)は段階的に広げる運用を継続予定です。&lt;/p&gt;
&lt;h3&gt;2. Jamfの機能追加に伴う拡張&lt;/h3&gt;
&lt;p&gt;将来的には通常のJamf Proリソース管理に加え、Jamf社が公式で開発している別provider (※)で扱える領域 (例: blueprintやcompliance benchmark)を含めて、デバイスの管理運用やセキュリティコントロールを改善できる余地があります。&lt;/p&gt;
&lt;p&gt;※: &lt;a href=&quot;https://registry.terraform.io/providers/Jamf-Concepts/jamfplatform/latest/docs&quot;&gt;https://registry.terraform.io/providers/Jamf-Concepts/jamfplatform/latest/docs&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;2025年12月上旬現在、この機能を用いるJamf Platform APIはpublic betaのアナウンス待ちとなり、それまではWeb UIでの変更管理となります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.jamf.com/platform-api/reference/getting-started-with-platform-api&quot;&gt;https://developer.jamf.com/platform-api/reference/getting-started-with-platform-api&lt;/a&gt; &lt;/p&gt;
&lt;h3&gt;3. 運用メンバー向けOnboarding&lt;/h3&gt;
&lt;p&gt;日常的に開発を行っているメンバーはGitOpsの活用が容易ですが、そうでないメンバーや別チームの関係者に対しても「使ってみようかな」と考えてもらえる仕掛けや、取りかかりし易い環境は大切です。エディタやGitHubの使い方や、Terraform特有の記法のガイドなど、一方的なドキュメントの連携だけで終わらせるのではなく、メンバーからのフィードバックによる改善など、関係者で利用促進のポイントを継続検討予定です。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Jamf Proの変更管理を、手順書・手作業中心の運用から、TerraformによるIaC＋PR駆動GitOps (planレビュー→承認→apply) へ移行しました。&lt;/li&gt;
&lt;li&gt;変更はPRの差分と&lt;code&gt;terraform plan&lt;/code&gt;出力を一次情報としてレビューできるため、レビューの質と速度を上げつつ、適用は自動化することでオペミスの余地を減らす設計にできました。&lt;/li&gt;
&lt;li&gt;コードオーナーやワークフローの実行条件で「変更範囲」と「承認責務」を結びつけ、所管の曖昧さやコンテキスト散在に対しても、プロセスと履歴が残る形で改善を狙えるようになりました。&lt;/li&gt;
&lt;li&gt;一方で、差分ノイズが出やすいリソース（例: 構成プロファイル）などの課題もあるため、importの方針や運用ルールを整備しながら、段階的に改善していきます。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;近年、Jamf Proだけでなく法人向け他製品についても前述のようなセキュリティやガバナンス上の利点からGitOpsやIaCに関わる要素技術 (APIやTerraform provider)へ多大な投資が行われています。将来の製品選定基準の一つとして本要素を含めるのも良いかもしれません。&lt;/p&gt;
&lt;p&gt;本記事が各組織のmacOS, iOS構成管理の課題検討の一助になれば幸いです。&lt;/p&gt;
&lt;p&gt;最後に、メルカリでGitOpsをPolicy as Codeとして活用し、セキュリティとガバナンスを拡張することにご興味がありましたら、セキュリティチームの採用情報について&lt;a href=&quot;https://careers.mercari.com/&quot; title=&quot;mercari careers&quot;&gt;mercari careers&lt;/a&gt;をご覧ください。&lt;/p&gt;
&lt;p&gt;明日の記事は akkie さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>LLMを用いたおしゃべり機能「しゃべるおさいふ」のバックエンド設計</title><link>https://engineering.mercari.com/blog/entry/20251219-f15f686553/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251219-f15f686553/</guid><description>&lt;p&gt;こんにちは。メルペイバックエンドエンジニアの @kobaryo です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の20日目の記事です。 はじめに メルカリグループは [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sat, 20 Dec 2025 10:00:40 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイバックエンドエンジニアの &lt;a href=&quot;https://x.com/artoy5884&quot;&gt;@kobaryo&lt;/a&gt; です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の20日目の記事です。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;メルカリグループは現在、AI Nativeな会社を目指しており、開発プロセスへの活用はもちろん、社内のさまざまな業務においてAIを使った効率化の取り組みが進んでいます。それ以前にも、プロダクト面ではメルカリの「&lt;a href=&quot;https://about.mercari.com/press/news/articles/20240910_aisupport/&quot;&gt;AI出品サポート&lt;/a&gt;」やメルカリ ハロの「&lt;a href=&quot;https://careers.mercari.com/mercan/articles/51281/&quot;&gt;かんたん求人作成&lt;/a&gt;」とAIを活用した機能がこれまでに複数リリースされてきました。&lt;/p&gt;
&lt;p&gt;そして今回、メルペイでもAI Nativeなプロダクトを実現すべく、2025年9月に「しゃべるおさいふ」機能のPoCを開始しました。この機能は一部のお客さま向けに限定提供しているため、体験できていない方もいらっしゃるかもしれませんが、本記事では、この「しゃべるおさいふ」機能のバックエンド設計について解説します。&lt;/p&gt;
&lt;p&gt;まずは機能の概要を紹介し、その後にアーキテクチャや、LLMを利用するがゆえに発生する特有のポイントについて説明していきます。&lt;/p&gt;
&lt;h2&gt;「しゃべるおさいふ」機能とは&lt;/h2&gt;
&lt;p&gt;「しゃべるおさいふ」は、メルカリアプリのおさいふタブを開くと、LLMがお客さま一人一人の決済履歴やポイントの有効期限などをもとに、ひとことコメントを返してくれる機能です。&lt;br /&gt;
「◯◯ポイントがあと×日で失効するので、よく行くカフェで使いませんか？」といった実用的な提案から、画像のようにちょっとした雑談めいたコメントまで返してくれます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/71b7a742--2025-12-19-16.02.27.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;近年のLLM活用はチャットUIのようにお客さまが能動的に行動する形式が主流ですが、この機能は「日常的なメルペイの利用の中でAIからのコメントが届く体験」がアプリ訪問や決済行動に寄与するのかを検証する狙いがあります。この機能によって「今日はいつもと違う買い物をしたけれど、どんなコメントが返ってくるのだろう？」と、決済体験を少しでも楽しくしたいと考えています。&lt;/p&gt;
&lt;h2&gt;アーキテクチャ&lt;/h2&gt;
&lt;p&gt;ここからは、具体的な設計について説明します。PoCであることから、少人数・短期間での開発を前提とし、シンプルな構成を重視しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/5ad101e1-wallet-ai-architecture.drawio.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;短期間でのリリースを実現するため、新たなマイクロサービスは作らず、既存サービスにエンドポイントを追加する形を採用しました。ただし、LLMへのリクエストはレイテンシが大きくなることが予想され、さらにPoCのため解放人数が動的に変化し、負荷の変動が大きくなり得ます。そのため、APIサーバーとは独立したworkerを設けて処理を非同期化し、他機能への影響を避けつつ、「しゃべるおさいふ」だけを容易にスケールできるよう設計しました。この構成によって、設計開始から社内テスト用の実装まで1ヶ月弱、そこからリリースまでにもおよそ1ヶ月とスムーズに進めることができ、短期間での立ち上げに大きく寄与しました。&lt;/p&gt;
&lt;p&gt;大まかなフローは次のとおりです。&lt;br /&gt;
&amp;nbsp;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;メルカリアプリがエンドポイントを叩くと、jobがPub/Sub経由でworkerに送信され、アプリ側はjob完了までポーリングする&lt;/li&gt;
&lt;li&gt;workerがお客さまの情報をもとにLLMに問い合わせ、生成したコメントを保存する&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;保存したコメントは、後述するコスト削減のためのキャッシュとしても活用されます。&lt;/p&gt;
&lt;h2&gt;実装におけるポイント&lt;/h2&gt;
&lt;p&gt;ここでは、LLMを用いることで生じた課題と、その解決方法について解説します。LLMの設定により解決したものもあれば、そうではないものもあります。&lt;/p&gt;
&lt;h3&gt;フィルタリング&lt;/h3&gt;
&lt;p&gt;LLM活用における最大の懸念点は、不適切または意図しない出力がそのままお客さまに表示されてしまうことです。LLM自身がある程度有害な内容を抑制するとはいえ、プロダクトとして避けたい表現は依然として存在します。&lt;/p&gt;
&lt;p&gt;そのため生成コメントに対して、次の3段階のフィルタリングを行い、いずれかに該当した場合は再生成する仕組みにしています。&lt;br /&gt;
&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文字数制限
&lt;ul&gt;
&lt;li&gt;表示上の都合から、決済に関わる重要情報（バーコードや残高など）と干渉しない長さに収める必要があります。トークン数制限もしていますが、文字数とトークン数は必ずしも比例しないため文字数チェックも併用しています。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;単語単位でのフィルタリング
&lt;ul&gt;
&lt;li&gt;明らかにNGな語句については、後述のLLMによるフィルタを通す前に弾くことで、レイテンシとコストを抑えています。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LLMによるフィルタリング
&lt;ul&gt;
&lt;li&gt;設定したポリシーを添えて、生成コメントを再度LLMに渡し、内容がポリシーに沿っているかチェックしています。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;さらに、これらのフィルタリングが有効に機能しているかを検証するため、バックエンドでは任意のお客さまデータやプロンプトをrequest bodyとして渡し、それを基にコメントを生成できるQA用エンドポイントも用意しました。QAではポリシーに抵触しそうなプロンプトやデータを入力し、その返答を確認するサイクルを繰り返すことで、フィルタリングの有効性を検証しています。&lt;/p&gt;
&lt;h3&gt;コスト・レイテンシ&lt;/h3&gt;
&lt;p&gt;LLMを利用するうえで、コストとレイテンシの問題は避けられません。この機能はお客さまの操作なしで自動表示されるため、リクエスト数が増えることが予想されますし、表示が遅れるとおさいふタブからの離脱につながります。&lt;/p&gt;
&lt;p&gt;コスト観点では、LLM側の設定と仕様側の工夫の両面から対処しています。LLM側の設定としては、プロンプトの静的な部分と動的な部分を切り分け、前者をプロンプトキャッシュすることでコストを削減しています。仕様面では、1人のお客さまに対して1日に生成するコメント数に上限を設けることで、過剰な生成を抑えています。上限を超えた場合は、既に生成済みのそのお客さまに向けたコメントを再利用するようにし、推論コストを減らしています。ただし、体験面では「コメント→決済→コメント」というサイクルが心地よく回ることが重要なため、決済が行われた際にはこの上限を緩和する仕組みを取り入れています。これにより過剰な生成は抑えつつ、決済行動に応じたコメント更新の楽しさは損なわないよう工夫しています。&lt;/p&gt;
&lt;p&gt;レイテンシ観点では、事前にバッチでコメントを生成しておく方法も検討できるものの、決済直後の情報がすぐにコメントへ反映される体験を重視し、リアルタイム生成を採用しています。ただし表示までに時間がかかると離脱につながるため、モデルやlocationの選定により、概ね3〜4秒以内に返答されるよう調整しています。&lt;/p&gt;
&lt;h3&gt;その他&lt;/h3&gt;
&lt;p&gt;これらの課題以外にも、いくつか工夫が必要な点があります。&lt;/p&gt;
&lt;p&gt;1つ目は、お客さまにとってコメントが自然に感じられるかどうかです。たとえば、レイテンシの項で述べたように、決済情報を即座にコメントへ反映する仕組みはその一例です。また、LLMは現在の日付や時刻、日付から曜日への変換をしばしば誤るため、日時情報はバックエンド側で生成し、プロンプトとして明示的に与えるようにしています。さらに、ポイントのように時間経過で値が変わる情報を使用するコメントについては、キャッシュとして表示しないようにしており、自然さが損なわれないよう配慮しています。&lt;/p&gt;
&lt;p&gt;2つ目は、モデルやプロンプトなどの設定を容易に変更できるようにすることです。しゃべるおさいふでは複数の LLMプロバイダのリクエスト形式に対応しつつ、コメント生成とフィルタリングの双方について、リリース作業なしでモデルやtemperatureなどの設定値を変更できる仕組みを備えています。また、ランダムに選択されるコメント種別、そのプロンプト、お客さまデータの組み合わせを宣言的に記述できるようにすることで、変更のしやすさも確保しています。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;本記事では、メルペイのPoC機能「しゃべるおさいふ」について、その概要と設計上のポイントを紹介しました。&lt;/p&gt;
&lt;p&gt;LLMモデルが強化され、幅広いタスクを汎用的にこなせるようになってきた今、LLMを使ったプロダクトの差別化にはUXの工夫がますます重要になっていると感じます。この「しゃべるおさいふ」プロジェクトで得られたLLM のプロダクト活用に関する知見を基盤に、今後もLLM をプロダクトにどのように組み込むべきかを継続して探求していきます。&lt;/p&gt;
&lt;p&gt;明日の記事はntkさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>AI-Native 開発を加速する AWS Kiro の導入と、Okta を活用したアカウント管理の自動化</title><link>https://engineering.mercari.com/blog/entry/20251211-4cfd1db1bf/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251211-4cfd1db1bf/</guid><description>&lt;p&gt;こんにちは。メルカリのバックエンドエンジニアの @amenbo と、プラットフォームチームの @siroken3 です。この記事は、この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 19 Dec 2025 11:00:45 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリのバックエンドエンジニアの @amenbo と、プラットフォームチームの @siroken3 です。この記事は、この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の19日目の記事です。&lt;/p&gt;
&lt;p&gt;メルカリでは AI-Native company を目指し、さまざまな AI エージェントの導入や運用改善に取り組んでいます。開発者の生産性向上もその一環であり、コーディング支援ツールの積極的な活用を進めています。そんな中、今年の7月にAWSからPreview公開された「Kiro」の試験導入を進めています。&lt;/p&gt;
&lt;p&gt;Kiro は従来の「vibe coding」を超えた、仕様書駆動の開発を実現する IDE です。ただ、新しいツールを組織に導入する際には、技術的な評価だけでなく、アカウント管理や課金管理といった運用面での課題にも向き合う必要があります。&lt;/p&gt;
&lt;p&gt;本記事では、メルカリが Kiro をどのように導入し、Okta と AWS IAM Identity Center を連携させたアカウント管理の自動化をどのように実現したかを紹介します。&lt;/p&gt;
&lt;h2&gt;Kiro とは&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Kiroの概要&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Kiro は、2025年7月に AWS からパブリックプレビューとしてリリースされ、11月に一般提供（GA）が開始された spec-driven&lt;br /&gt;
development を特徴とする agentic IDE です。VS Code のオープンソース版である Code OSS をベースに開発されています。&lt;/p&gt;
&lt;p&gt;従来の AI コーディングアシスタントは、その場その場で「なんとなくいい感じに」コードを生成する「vibe coding」と呼ばれるアプローチが主流でした。一方、Kiro は仕様書を中心に据えた開発フローを実現します。開発者は以下の3つのマークダウンファイルで構成される「Kiro Specs」を作成し、これを基に AI エージェントがコード生成や実装を支援します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requirements.md&lt;/code&gt;:  ユーザーストーリーと受け入れ基準を定義&lt;/li&gt;
&lt;li&gt;&lt;code&gt;design.md&lt;/code&gt;: 技術設計とアーキテクチャを記述&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tasks.md&lt;/code&gt;: 実装タスクをチェックリスト形式で管理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Kiroの料金プラン&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Kiro の有償プランを組織で利用するには、AWS の AI コーディングアシスタントサービスである AWS Q Developer の Pro サブスクリプションプランを使用します。具体的には、AWS IAM Identity Center を通じてユーザーを管理し、Q Developer Proのライセンスを割り当てる仕組みになっています。&lt;/p&gt;
&lt;p&gt;なお、2025年11月の GA 以降、Kiro は独自のサブスクリプション体系に移行しましたが、引き続き IAM Identity Center によるユーザー管理が必要です。このKiro 独自サブスクリプション体系への移行については、この記事の後半に述べます。&lt;/p&gt;
&lt;h2&gt;アカウント管理における課題と解決策&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;既存のOkta連携基盤&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;組織で Q Developer Pro のライセンス管理を行うには、AWS IAM Identity Center との連携が不可欠です。メルカリでは Kiro の登場以前から、Okta と Identity Center の連携を構築しており、社内のさまざまな AWS リソースへのアクセス管理に活用していました。&lt;/p&gt;
&lt;p&gt;この連携では SCIM 2.0（System for Cross-domain Identity Management）プロトコルによる自動プロビジョニングを採用しています。Okta 上でユーザーやグループを追加・変更すると、その情報が自動的に Identity Center へ同期される仕組みです。&lt;/p&gt;
&lt;p&gt;Kiro のアカウント管理にもこの仕組みを活用しました。Okta に Kiro 利用者用のグループ（ &lt;code&gt;AWS_kiro_JP_Users&lt;/code&gt; ）を作成し、そのグループに Q Developer Pro のサブスクリプションを割り当てます。これにより、Okta のグループにユーザーを追加するだけで、そのユーザは Kiro が利用可能になります。&lt;/p&gt;
&lt;p&gt;ただし、この時点では Okta へのユーザー追加自体は手動で行っており、利用申請があるたびに AWS 管理担当者がコンソールからユーザーを登録する運用でした。作業自体は数分で終わるものの、担当者の手が空いていなければ対応は後回しになり、申請者を待たせてしまうケースが発生しました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI エージェントによるアカウント発行の自動化&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Okta との連携をさらに一歩進め、すでに Okta と連携している社内の AI エージェントを Kiro のグループを扱えるように設定し、申請プロセスそのものも自動化しました。&lt;/p&gt;
&lt;p&gt;ユーザーは Slack から「I want kiro」のように AI エージェントに伝えるだけで、以下のフローが自動的に実行されます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AI エージェントがリクエストを受け取る&lt;/li&gt;
&lt;li&gt;AI エージェントが Okta の API を呼び出し、ユーザーを Kiro グループ（ &lt;code&gt;AWS_kiro_JP_Users&lt;/code&gt; ）に登録&lt;/li&gt;
&lt;li&gt;Okta が SCIM プロトコルで Identity Center にユーザー情報を同期&lt;/li&gt;
&lt;li&gt;Identity Center で Q Developer Pro アカウントが有効になる&lt;/li&gt;
&lt;li&gt;ユーザーが Kiro を使い始めた瞬間から従量制課金が開始される&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/c839d6e7-system_overview.png&quot; alt=&quot;▲kiro アカウント申請の仕組み&quot;  /&gt; ▲kiro アカウント申請の仕組み&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/ad65329b-agent.png&quot; alt=&quot;▲AIエージェントを用いたアカウント申請&quot;  /&gt; ▲AIエージェントを用いたアカウント申請&lt;/p&gt;
&lt;p&gt;この仕組みにより、AWS 管理担当者の手を一切煩わせることなく、ユーザーが自律的に Kiro を利用開始できるようになりました。&lt;/p&gt;
&lt;h2&gt;Q Developer Pro から Kiro への移行&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;2025年11月のアップデートと移行の必要性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2025年11月、Kiro は大幅なアップデートを経て一般提供（GA）が開始されました。このアップデートにより、Kiro は Amazon Q Developer から独立した製品となり、独自のサブスクリプション体系が導入されました。&lt;/p&gt;
&lt;p&gt;先ほど説明した仕組みは GA 以前に実施したものであったため、既存の Q Developer Pro サブスクリプションを Kiro プランに移行する必要がありました。移行しない場合でも Q Developer Pro のサブスクリプションは引き続き有効ですが、Kiro 専用の新機能（Property-based Testing、Checkpointing など）は利用できない可能性があります。&lt;/p&gt;
&lt;p&gt;移行手順自体は非常に簡単で、AWS Management Console から対象グループ（今回の例では &lt;code&gt;AWS_kiro_JP_Users&lt;/code&gt; ）を選択し、希望の Kiro プランを指定するだけです。グループ単位またはユーザー単位で柔軟に移行でき、段階的な移行も可能です。&lt;/p&gt;
&lt;p&gt;なお、一度 Kiro サブスクリプションにアップグレードすると、Q Developer Pro には戻せません。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;本記事では、メルカリにおける AWS Kiro の導入と、Okta、AWS IAM Identity Center、AI エージェントを組み合わせたアカウント管理の自動化について紹介しました。&lt;/p&gt;
&lt;p&gt;手動運用から始まった Kiro のアカウント管理は、既存の Okta と Identity Center の SCIM 連携を活用し、さらに AI エージェントと組み合わせることで、完全に自動化されました。この取り組みにより、以下を実現できました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;管理負荷の大幅な削減&lt;/strong&gt;: AWS 管理担当者の手作業がゼロに&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;スケーラブルな運用&lt;/strong&gt;: ユーザー数が増えても運用コストが増加しない&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;セキュアなアクセス管理&lt;/strong&gt;: Okta を中心とした統一された ID 管理とアクセス制御&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;また、2025年11月の Kiro GA に伴う Q Developer Pro からの移行もスムーズに完了し、新しい課金体系のもとで運用を継続しています。&lt;/p&gt;
&lt;p&gt;今後、AI ツールの導入はさらに加速していくと考えられます。その際、技術的な評価だけでなく、運用面での自動化をどう実現するかが重要になってきます。本記事で紹介した Okta と Identity Center の連携パターンは、他の AI ツール導入においても参考になる取り組みではないでしょうか。&lt;/p&gt;
&lt;p&gt;メルカリは引き続き AI-Native company としての歩みを進めていきます。今後も、開発者の生産性向上とスケーラブルな運用を両立させる取り組みを続けていきたいと思います。&lt;/p&gt;
&lt;p&gt;明日の記事は @Yu Sasaki さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>メルカリ内部の Dynamic Client Registration 活用事例</title><link>https://engineering.mercari.com/blog/entry/20251218-26bcec59ba/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251218-26bcec59ba/</guid><description>&lt;p&gt;こんにちは。メルカリ IDP チームの @task(mima) です。本記事はメルカリアドベントカレンダー 2025 の 19 日目の記事です。 はじめに 株式会社メルカリには、グループ全体のシステムの認証と認可を統括す [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 19 Dec 2025 11:00:05 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリ IDP チームの &lt;a href=&quot;https://x.com/task4233&quot; title=&quot;@task(mima)&quot;&gt;@task(mima)&lt;/a&gt; です。本記事は&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251126-mercari-advent-calendar-2025/&quot; title=&quot;メルカリアドベントカレンダー 2025&quot;&gt;メルカリアドベントカレンダー 2025&lt;/a&gt; の 19 日目の記事です。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;株式会社メルカリには、グループ全体のシステムの認証と認可を統括する IdP が存在します。本記事では、この IdP における Dynamic Client Registration (以下 DCR とする) の活用事例をご紹介します。活用事例は大きく分けて以下の2つです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Terraform を用いた OAuth / OIDC クライアントの宣言的な管理&lt;/li&gt;
&lt;li&gt;MCP 認可フローにおける動的なクライアント生成&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これらの紹介のために、本記事では DCR と関連仕様について触れた後、それぞれの事例について背景とメルカリでの利用例を紹介します。&lt;/p&gt;
&lt;p&gt;いずれも認証認可の領域に携わっているエンジニア向けの題材ですが、調査した限りでは DCR 自体の利活用をしたケースは少なかったため、少しでも参考になれば幸いです。なお、認証認可における基本的な概念や用語については説明を割愛します。ご承知おきください。&lt;/p&gt;
&lt;h2&gt;従来の OAuth / OIDC クライアント管理の課題&lt;/h2&gt;
&lt;p&gt;メルカリの IdP は、メルカリグループの各サービスや、&lt;a href=&quot;https://help.jp.mercari.com/guide/articles/1476/&quot; title=&quot;メルカリと公式連携した越境EC事業者&quot;&gt;メルカリと公式連携した越境EC事業者様&lt;/a&gt;に応じて OAuth / OIDC クライアントを登録・管理する必要があります。これらの登録・管理に際して、従来以下のような手作業が必要でした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;クライアント情報を登録・編集を目的とした IdP のデータベースに対する SQL 実行&lt;/li&gt;
&lt;li&gt;作成されたクライアント ID / シークレットの Vault での管理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;そのため、変更時の人的ミスやオペレーションコストが従来の課題でした。これらの課題を解決するために、OAuth / OIDC クライアントを機械的に登録・管理する仕組みが必要でした。&lt;/p&gt;
&lt;p&gt;そこで白羽の矢が立ったのが DCR でした。&lt;/p&gt;
&lt;h2&gt;Dynamic Client Registration (DCR) とは&lt;/h2&gt;
&lt;p&gt;DCR は、OAuth / OIDC クライアントを動的に登録するための標準仕様です。これにより、API 経由での登録・参照・更新・削除が可能になります。&lt;/p&gt;
&lt;p&gt;DCR に関連する仕様は以下の通りです。必要に応じて一次情報を参照してください。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;クライアント登録&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OAuth 2.0: &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7591&quot; title=&quot;RFC 7591 - OAuth 2.0 Dynamic Client Registration Protocol&quot;&gt;RFC 7591 &amp;#8211; OAuth 2.0 Dynamic Client Registration Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OIDC: &lt;a href=&quot;https://openid.net/specs/openid-connect-registration-1_0.html&quot; title=&quot;OpenID Connect Dynamic Client Registration 1.0 incorporating errata set 2&quot;&gt;OpenID Connect Dynamic Client Registration 1.0 incorporating errata set 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;クライアント管理&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OAuth 2.0: &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7592&quot; title=&quot;RFC 7592 - OAuth 2.0 Dynamic Client Registration Management Protocol&quot;&gt;RFC 7592 &amp;#8211; OAuth 2.0 Dynamic Client Registration Management Protocol&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;続いて、クライアント登録およびクライアント管理のための概要を紹介します。&lt;/p&gt;
&lt;h2&gt;クライアント登録フロー&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7591&quot; title=&quot;RFC 7591&quot;&gt;RFC 7591&lt;/a&gt; で定義されている 抽象的な DCR のフローは以下の図 1 の通りです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;    +--------(A)- Initial Access Token (OPTIONAL)
    |
    |   +----(B)- Software Statement (OPTIONAL)
    |   |
    v   v
+-----------+                                      +---------------+
|           |--(C)- Client Registration Request --&amp;gt;|    Client     |
| Client or |                                      | Registration  |
| Developer |&amp;lt;-(D)- Client Information ------------|   Endpoint    |
|           |                                      +---------------+
+-----------+&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;center&gt;&lt;b&gt;図1: Abstract Dynamic Client Registration Flow(出典: &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7591#section-1.3&quot;&gt;RFC 7591 Section 1.3&lt;/a&gt;)&lt;/b&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;この図の「Client Registration Endpoint」は、クライアントを認可サーバーに登録できるように設計された OAuth 2.0 のエンドポイントです。そして、このエンドポイントとの &lt;strong&gt;(C) のリクエストと (D) のレスポンスがDCR のメイン処理&lt;/strong&gt;です。(C) で「Client or Developer」が 「Client Registration Endpoint」 に対して、希望する登録メタデータを含むリクエストを送信します。認可サーバは、OAuth クライアントを登録し、(D) でクライアント ID、クライアントシークレット、登録メタデータなどを返却します。&lt;/p&gt;
&lt;p&gt;上記フロー図の &lt;strong&gt;(A) と (B) はオプショナルなセキュリティ機構&lt;/strong&gt;です。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(A) Initial Access Token&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「Client Registration Endpoint」 へのアクセスを制御するためのトークン&lt;/li&gt;
&lt;li&gt;誰でも自由にクライアントを登録できる状態を防ぐアクセス制御機構&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;(B) Software Statement&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;クライアントのメタデータを含む署名付き JWT&lt;/li&gt;
&lt;li&gt;事前承認されたソフトウェアであることを証明&lt;/li&gt;
&lt;li&gt;詳細は &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7591#section-3.1.1&quot; title=&quot;RFC 7591 Section 3.1.1&quot;&gt;RFC 7591 Section 3.1.1&lt;/a&gt; を参照&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらは (C) のリクエストに含めて送信できます。&lt;/p&gt;
&lt;p&gt;具体的には、(C) と (D) の基本的なリクエストとレスポンスは以下のようになります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;リクエスト例:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /register HTTP/1.1
Content-Type: application/json
Accept: application/json
Host: server.example.com

{
 &amp;quot;redirect_uris&amp;quot;: [
   &amp;quot;https://client.example.org/callback&amp;quot;,
   &amp;quot;https://client.example.org/callback2&amp;quot;],
 &amp;quot;client_name&amp;quot;: &amp;quot;My Example Client&amp;quot;,
 &amp;quot;client_name#ja-Jpan-JP&amp;quot;: &amp;quot;クライアント名&amp;quot;,
 &amp;quot;token_endpoint_auth_method&amp;quot;: &amp;quot;client_secret_basic&amp;quot;,
 &amp;quot;logo_uri&amp;quot;: &amp;quot;https://client.example.org/logo.png&amp;quot;,
 &amp;quot;jwks_uri&amp;quot;: &amp;quot;https://client.example.org/my_public_keys.jwks&amp;quot;,
 &amp;quot;example_extension_parameter&amp;quot;: &amp;quot;example_value&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;レスポンス例&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTTP/1.1 201 Created
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
 &amp;quot;client_id&amp;quot;: &amp;quot;s6BhdRkqt3&amp;quot;,
 &amp;quot;client_secret&amp;quot;: &amp;quot;cf136dc3c1fc93f31185e5885805d&amp;quot;,
 &amp;quot;client_id_issued_at&amp;quot;: 2893256800,
 &amp;quot;client_secret_expires_at&amp;quot;: 2893276800,
 &amp;quot;redirect_uris&amp;quot;: [
   &amp;quot;https://client.example.org/callback&amp;quot;,
   &amp;quot;https://client.example.org/callback2&amp;quot;],
 &amp;quot;grant_types&amp;quot;: [&amp;quot;authorization_code&amp;quot;, &amp;quot;refresh_token&amp;quot;],
 &amp;quot;client_name&amp;quot;: &amp;quot;My Example Client&amp;quot;,
 &amp;quot;client_name#ja-Jpan-JP&amp;quot;: &amp;quot;クライアント名&amp;quot;,
 &amp;quot;token_endpoint_auth_method&amp;quot;: &amp;quot;client_secret_basic&amp;quot;,
 &amp;quot;logo_uri&amp;quot;: &amp;quot;https://client.example.org/logo.png&amp;quot;,
 &amp;quot;jwks_uri&amp;quot;: &amp;quot;https://client.example.org/my_public_keys.jwks&amp;quot;,
 &amp;quot;example_extension_parameter&amp;quot;: &amp;quot;example_value&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;クライアント管理&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7592&quot; title=&quot;RFC 7592&quot;&gt;RFC 7592&lt;/a&gt; は、登録されたクライアント情報（Client Metadata）を管理（参照 / 更新 / 削除）する仕組みを定義しています。表1に示す通り、クライアント ID をパスに含む REST API として構成することができます。&lt;/p&gt;
&lt;p&gt;表1: Client Metadata を管理する用途とリクエスト例&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;th&gt;リクエスト例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;参照&lt;/td&gt;
&lt;td&gt;GET /register/${clientID}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;更新&lt;/td&gt;
&lt;td&gt;PUT /register/${clientID}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;削除&lt;/td&gt;
&lt;td&gt;DELETE /register/${clientID}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;これらの仕様を踏まえた上で、活用事例の紹介に移りましょう。&lt;/p&gt;
&lt;h2&gt;活用事例1: Terraform を用いた OAuth / OIDC クライアントの宣言的な管理&lt;/h2&gt;
&lt;h3&gt;背景&lt;/h3&gt;
&lt;p&gt;前述の通り、従来はメルカリグループの OAuth / OIDC クライアントを作成・運用するために、データベースに直接 SQL を実行してクライアント情報を登録していましたが、以下のような課題がありました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;変更履歴の追跡が困難&lt;/li&gt;
&lt;li&gt;レビュープロセスの欠如&lt;/li&gt;
&lt;li&gt;人的ミスによるセキュリティリスク&lt;/li&gt;
&lt;li&gt;複数環境（開発・ステージング・本番）での一貫性の維持が困難&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの課題に対処すべく、OAuth / OIDC クライアントを宣言的に管理する仕組みとして、Terraform と DCR を組み合わせた仕組みを導入しました。&lt;/p&gt;
&lt;h3&gt;実装概要&lt;/h3&gt;
&lt;p&gt;前提として、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/mercari-microservices-platform-terraform-0-12/&quot; title=&quot;Mercari Microservices Platform における Terraform 0.12 対応 &quot;&gt;Mercari Microservices Platform における Terraform 0.12 対応 &lt;/a&gt;で紹介されているように、メルカリでは社内のインフラストラクチャを microservices-terraform と呼ばれる1つのリポジトリで管理しています。この仕組みと DCR を組み合わせることで、Terraform の HCL (HashiCorp Configuration Language) で OAuth / OIDC クライアントを定義し、PR をマージすると自動的にクライアントが生成・更新・削除されます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/24c0e16b-untitled-diagram.drawio.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;center&gt;&lt;b&gt;図2. Terraform を用いた OAuth / OIDC クライアントの宣言的な管理の概要図&lt;/b&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;例えば、以下の図 3 に示す通り、Terraform の HCL で宣言すると Plan 結果が GitHub 上にコメントとして表示されます。後はマージするだけで自動的に OAuth / OIDC クライアントが作成される仕組みです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/0a5665cd-thoughts.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;center&gt;&lt;b&gt;図3: 新しい OAuth / OIDC クライアント登録のための Terraform の HCL 表現とGitHub 上に表示される Terraform の Plan 結果&lt;/b&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;この仕組みにより、以下のメリットが得られました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;変更履歴の可視化&lt;/strong&gt;: Git によるバージョン管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;レビュープロセスの確立&lt;/strong&gt;: PR ベースのワークフロー&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自動化&lt;/strong&gt;: マージ後の自動適用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IaC&lt;/strong&gt;: 宣言的な管理による一貫性の確保&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;技術詳細&lt;/h3&gt;
&lt;p&gt;Terraform Custom Provider の実装などの技術詳細については、昨年の技術書典16で「OAuth 2.0 Client を Terraform Custom Provider で宣言的に管理してみた」と題して、&lt;a href=&quot;https://techbookfest.org/product/4JE8riJdXX5y1vBEYq7v8L&quot; title=&quot;Unleash Mercari Tech! vol.3&quot;&gt;Unleash Mercari Tech! vol.3&lt;/a&gt; に寄稿しておりますので、興味のある方はそちらをご覧ください。Terraform の Custom Provider 開発については、&lt;a href=&quot;https://developer.hashicorp.com/terraform/plugin&quot; title=&quot;公式ドキュメント&quot;&gt;公式ドキュメント&lt;/a&gt; も参考になります。&lt;/p&gt;
&lt;h2&gt;活用事例2: MCP 認可フローにおける動的なクライアント生成&lt;/h2&gt;
&lt;h3&gt;背景&lt;/h3&gt;
&lt;p&gt;メルカリでは、AI-Native な会社への変貌のため、様々な取り組みを実施しています。その取り組みの一環として Mercari MCP サーバを立ち上げました。この MCP サーバは、2025年11月13日に開催された &lt;a href=&quot;https://gears.mercari.com/&quot; title=&quot;mercari GEARS 2025&quot;&gt;mercari GEARS 2025&lt;/a&gt; にて、 ChatGPT からメルカリの機能を利用するデモが発表されました。&lt;/p&gt;
&lt;p&gt;&lt;iframe loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/IIz0DH-wbKQ?si=OAr7FmOt5RtPjgPE&amp;amp;start=1673&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;このデモでは、MCP サーバの認可のために DCR が利用されています。&lt;/p&gt;
&lt;h3&gt;メルカリでDCR を採用するに至った経緯&lt;/h3&gt;
&lt;p&gt;MCP の最新仕様である &lt;a href=&quot;https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization&quot; title=&quot;Authorization - Model Context Protocol - Version 2025-11-25&quot;&gt;Authorization &amp;#8211; Model Context Protocol &amp;#8211; Version 2025-11-25&lt;/a&gt; を追っている方は、「DCR は &lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00&quot; title=&quot;Client ID Metadata Document (CIMD)&quot;&gt;Client ID Metadata Document (CIMD)&lt;/a&gt; のフォールバックという位置付けなのでは？」と思われたかもしれません。その通りです。&lt;/p&gt;
&lt;p&gt;実際に、  &lt;a href=&quot;https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization&quot; title=&quot;Authorization - Model Context Protocol - Version 2025-11-25&quot;&gt;Authorization &amp;#8211; Model Context Protocol &amp;#8211; Version 2025-11-25&lt;/a&gt; では、クライアント登録のアプローチは次の優先度に従うべき（SHOULD）とされています。 &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;クライアント側で利用可能なサーバの事前登録済情報がある場合、それを利用&lt;/li&gt;
&lt;li&gt;認可サーバがサポートしている場合、&lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00&quot; title=&quot;Client ID Metadata Document (CIMD)&quot;&gt;Client ID Metadata Document (CIMD)&lt;/a&gt;  を利用&lt;/li&gt;
&lt;li&gt;認可サーバがサポートしている場合、フォールバックとして DCR を利用&lt;/li&gt;
&lt;li&gt;他に手段がない場合は、ユーザにクライアント情報の入力を催促&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;DCR がこのような位置付けになっている理由として、以下の理由などが挙げられます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;登録されるクライアントが無制限に増えるため&lt;/li&gt;
&lt;li&gt;登録エンドポイント自体が攻撃に利用されるリスクがあるため&lt;/li&gt;
&lt;li&gt;クライアントの有効性検証ができないため&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;詳しくは、&lt;a href=&quot;https://blog.modelcontextprotocol.io/posts/client_registration/&quot; title=&quot;Evolving OAuth Client Registration in the Model Context Protocol | Model Context Protocol Blog&quot;&gt;Evolving OAuth Client Registration in the Model Context Protocol | Model Context Protocol Blog&lt;/a&gt; をご参照ください。&lt;/p&gt;
&lt;p&gt;この優先順位にもかかわらず、メルカリで DCR を選択した理由は以下の通りです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;実装時期の問題&lt;/strong&gt;: MCP サーバの実装を開始した時点では、CIMD の仕様がまだ確定していなかったため(&lt;a href=&quot;https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization&quot; title=&quot;Authorization - Model Context Protocol - Version 2025-06-18&quot;&gt;Authorization &amp;#8211; Model Context Protocol &amp;#8211; Version 2025-06-18&lt;/a&gt; では DCR は SHOULD 扱いでした)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;既存実装の活用&lt;/strong&gt;: メルカリの IdP では、Terraform による管理のために既に DCR を実装済みであり、新規実装コストが低かったため&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これらの理由で当時は DCR を採用しましたが、複数選択肢のある今、それぞれの長所短所を考慮して選定することが好ましいでしょう。&lt;/p&gt;
&lt;h3&gt;実装フロー&lt;/h3&gt;
&lt;p&gt;MCP サーバでの DCR 利用フローは以下の図 4 の通りです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/0ddb0524-mermaid-diagram-2025-12-18-182151.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;center&gt;&lt;b&gt;図4: DCR を利用する場合の認可フローを表すシーケンス図&lt;/b&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;図 4 のフローは以下の通り実行されます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.MCP クライアント（例: ChatGPT）がメルカリの MCP サーバへの接続を開始する&lt;/li&gt;
&lt;li&gt;2.MCP サーバは、WWW-Authenticate ヘッダとともにステータスコード 401 を返却する&lt;/li&gt;
&lt;li&gt;3-4. MCP クライアントは、MCP サーバから Protected Resource Metadata を取得し、認可サーバの情報を取得する&lt;/li&gt;
&lt;li&gt;5-6. MCP クライアントが 認可サーバのメタデータエンドポイントを呼び出して、DCR エンドポイントの情報を取得する&lt;/li&gt;
&lt;li&gt;7-8. MCP クライアントが、DCR エンドポイント経由で OAuth / OIDC クライアントを作成して、Client Metadata を取得する&lt;/li&gt;
&lt;li&gt;9-14-. MCP クライアントは、生成された Client Metadata を使用して認可サーバとの間で認可コードフローを実行し、アクセストークンを取得する&lt;/li&gt;
&lt;li&gt;15-16. MCP クライアントは、取得したアクセストークンでMCP サーバの機能にアクセスしてレスポンスを受け取る&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この仕組みにより、各 MCP クライアントに対して動的にクライアントを作成し、適切なスコープで認可を行うことが可能になりました。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;本記事では、DCR の活用事例として&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Terraform を用いた OAuth / OIDC クライアントの宣言的な管理&lt;/li&gt;
&lt;li&gt;MCP 認可フローにおける動的なクライアント生成&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;の2点を紹介しました。&lt;/p&gt;
&lt;p&gt;DCR は IdP を内製化している組織だけでなく、外部 IdP を利用する場合でも有用な仕様です。一方で、MCP 認可フローにおいては先述した優先順位を鑑みて選定すると良いと思います。&lt;/p&gt;
&lt;p&gt;本記事が皆様の OAuth / OIDC クライアント管理の参考になれば幸いです。ここまでお読みいただきありがとうございました。&lt;/p&gt;
&lt;p&gt;明日の記事は @yuさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>Payment Platform の 2025 年: メルカリグループを支える決済基盤のこれまでとこれから</title><link>https://engineering.mercari.com/blog/entry/20251219-payment-platform-2025/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251219-payment-platform-2025/</guid><description>&lt;p&gt;こんにちは、Merpay の Payment Core チームで EM をしている komatsu です。普段はメルカリグループ全体の決済基盤や銀行接続まわりを担当しています。 この記事は Merpay &amp;amp; Me [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 19 Dec 2025 10:00:59 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、Merpay の Payment Core チームで EM をしている komatsu です。普段はメルカリグループ全体の決済基盤や銀行接続まわりを担当しています。&lt;br /&gt;
この記事は &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の 19 日目の記事です。&lt;/p&gt;
&lt;p&gt;年末ということで、今回は Payment Platform がこの 1 年で取り組んできた 2 つの大きな進化の方向性を振り返ります。1 つは Checkout Solution による統合決済体験の提供、もう 1 つは 2B (法人向け) 決済への展開です。&lt;/p&gt;
&lt;h2&gt;背景: 決済ニーズの多様化&lt;/h2&gt;
&lt;p&gt;メルカリグループでは、C2C マーケットプレイスを起点としながらも、さまざまなプロダクトが展開されています。メルカリShops やメルカリNFT、さらには直近リリースした&lt;a href=&quot;https://about.mercari.com/press/news/articles/20250930_crossborder/&quot;&gt;メルカリ グローバルアプリ&lt;/a&gt;など、2C の提供先が広がると同時に、スキマバイトアプリであるメルカリ ハロや広告事業であるメルカリAds、外部事業者がメルカリの商品をオファー経由で購入・再販する C2B (Consumer-to-Business) パートナーシップなど、2B の決済スキームも登場し、多様化しています。&lt;/p&gt;
&lt;p&gt;Payment Platform は、こうした多様な決済ニーズに応えるため、これまでマイクロサービスアーキテクチャによる決済基盤を構築し、gRPC API を通じて各プロダクトに決済機能を提供してきました。&lt;/p&gt;
&lt;p&gt;この 1 年では、さらに 2 つの軸で大きな進化を遂げています。&lt;/p&gt;
&lt;h2&gt;Checkout Solution &amp;#8211; UI を含む統合決済ソリューションへ&lt;/h2&gt;
&lt;h3&gt;背景: gRPC API を超えた、統合ソリューションという選択肢&lt;/h3&gt;
&lt;p&gt;従来、Payment Platform は gRPC API を通じて決済のバックエンド機能を提供していました。各プロダクトチームは、この API を利用して独自に決済フローや UI を実装していました。&lt;/p&gt;
&lt;p&gt;この方式は柔軟性が高い一方で、いくつかの課題もありました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;開発負荷の重複&lt;/strong&gt;: 新しい決済手段を追加する際、各プロダクトが個別に UI や決済フローを実装する必要がある&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UX の分断&lt;/strong&gt;: プロダクトごとに決済体験が異なり、統一された UX を提供しにくい&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ローンチ速度&lt;/strong&gt;: 新規プロダクトが決済機能を実装する際、フロントエンドからバックエンドまで一から構築する必要がある&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの課題を解決するため、gRPC API に加えて、&lt;strong&gt;UI を含む統合決済ソリューション&lt;/strong&gt;という新しい選択肢を提供し始めました。これが Checkout Solution です。&lt;/p&gt;
&lt;h3&gt;Checkout Solution のアーキテクチャ&lt;/h3&gt;
&lt;p&gt;Checkout Solution は、決済のフロントエンド (UI) とバックエンド (API) を一体として提供する統合ソリューションです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/06/ee417b90-checkout_background.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上図のように、プロダクトは Checkout Solution の UI を組み込むだけで、決済手段の選択から決済完了までの体験を提供できます。バックエンドでは Payment Service をはじめとする既存の決済基盤と連携し、各決済手段の処理を実行します。&lt;/p&gt;
&lt;p&gt;詳しいアーキテクチャについては、以下の記事をご覧ください。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250605-bf42ce60cf/&quot;&gt;決済基盤の新たな挑戦: 決済チェックアウトソリューションの開発&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250606-7afd82e718/&quot;&gt;チェックアウトソリューションのバックエンドアーキテクチャ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;プロダクトチームは Checkout Solution を組み込むだけで、決済フローや UI を一から実装する必要がなくなりました。新しい決済手段の追加も、Payment Platform 側で対応すれば全プロダクトで使えます。&lt;/p&gt;
&lt;p&gt;従来のプロダクトは引き続き gRPC API を使いながら、新しいプロダクトは Checkout Solution で迅速な開発とリリースを実現しています。&lt;/p&gt;
&lt;h3&gt;展開の歩み&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;メルカリNFT での初導入&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Checkout Solution は、まずメルカリNFT で初めて導入されました。新しいプロダクトのローンチにあたり、MVP として決済機能を素早く統合できることが実証されました。ユースケースごとの設定の切り替えや、プロダクト固有のコンポーネントの描画も含め、基礎となる機能が実現できました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;メルカリ グローバルアプリへの拡大&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;その後、先日公開されたメルカリ グローバルアプリでも Checkout Solution のサポートが始まりました。&lt;/p&gt;
&lt;p&gt;グローバルアプリでは、日本のお客さま向けに提供しているメルペイの残高やポイント、メルペイのあと払いといった決済手段ではなく、海外のお客さま向けのクレジットカード決済機能を新たに開発しました。Cross Border (XB; 越境販売) 事業に必要な決済機能も、Checkout Solution を通して提供することでスピーディーに立ち上げることができました。&lt;/p&gt;
&lt;p&gt;こうして、異なるプロダクト間で統一された決済体験を提供できるようになりました。&lt;/p&gt;
&lt;p&gt;また、このタイミングは Payment Platform にとって初めての日本円以外の決済ユースケースでした。&lt;br /&gt;
日本円前提だったシステムの多通貨対応も行い、決済やその裏にある会計や帳簿も含めて今後のグロースに耐えうる基盤に進化を遂げました。&lt;/p&gt;
&lt;p&gt;多通貨対応の詳細は近日公開予定の &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251003-mercari-crossborder/&quot;&gt;Guest post from FT payment platform — Engineering for Multi-Currency and Multi-Provider Payments&lt;/a&gt; をお楽しみに。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;メルカリモバイルへの導入、サブスクリプション対応: Mandate の導入&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;グローバルアプリとは別の新しい導入先として、既存のメルカリモバイルの決済方法登録画面の置き換えも行いました。&lt;br /&gt;
これまでとは異なり、サブスクリプション型の決済スキーム対応のため、お客さま不在時に自動課金を行うための決済手段選択モードを追加し、&lt;strong&gt;Mandate&lt;/strong&gt; (継続的な課金への同意) という概念を導入しました。これにより、タイミングに依らないより自由な決済スキームの構築が可能となりました。Mandate の詳細設計については以下の記事をご覧ください。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251216-mandates-for-recurring-payments/&quot;&gt;多様な支払い手段と継続課金を安全に扱う「Mandate」の設計&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2C から 2B へも拡張可能なアーキテクチャ&lt;/h3&gt;
&lt;p&gt;また、Payment Platform には以前から、メルカリShops やメルペイ加盟店向けの 2B 精算の仕組みがありました。とはいえ、これらは法人が決済すると言うより、お客さまの決済によって資金が移動する先、というユースケースにとどめていました。そこにメルカリ ハロやメルカリAds が登場し、法人自身が決済を行うケース&lt;a href=&quot;ハロではアルバイトの募集時に法人に紐づく与信枠を利用した「決済」が走る仕組みです&quot;&gt;^1&lt;/a&gt;も出てきたことで、2B 決済のニーズがさらに多様化してきています。&lt;/p&gt;
&lt;p&gt;Checkout Solution は現在 2C をメインに展開していますが、将来的にはこうした 2B 向けの決済でも活用できるアーキテクチャになっています。&lt;/p&gt;
&lt;p&gt;Payment Platform があらゆる決済の裏側に加えて、カスタマイズ可能な UI も提供することで、新しい決済手段を追加するたびに各プロダクトが個別に開発する必要がなくなり、統一した UX をお客さまや加盟店に届けられます。&lt;/p&gt;
&lt;h2&gt;2B 決済への展開&lt;/h2&gt;
&lt;h3&gt;2C 決済と 2B 決済の違い&lt;/h3&gt;
&lt;p&gt;Checkout Solution でも 2B プロダクトのサポートを視野に入れているように、メルカリ ハロやメルカリAds をはじめとして、メルカリグループでは 2B の決済ニーズも増えています。&lt;/p&gt;
&lt;p&gt;2B の決済体験を考えるうえで重要なのは、2C との違いを理解することです。&lt;br /&gt;
特に 2C と 2B では、アカウントや決済の座組が大きく異なります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2C: シンプルな構造&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 人 = 1 アカウント&lt;/li&gt;
&lt;li&gt;与信、請求、決済の単位が基本的に一致&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2B: 複雑な構造&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/1726c47a-tree-structure.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;法人の下に支社・店舗・部署が存在し、木構造での管理が必要&lt;/li&gt;
&lt;li&gt;与信、請求、決済の単位が異なる場合がある (例: 与信は店舗別、請求は支社単位)&lt;/li&gt;
&lt;li&gt;座組が複雑で、柔軟な対応が求められる&lt;/li&gt;
&lt;li&gt;インボイス制度など 2B 固有の要件が存在&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2B 決済のユースケース&lt;/h3&gt;
&lt;p&gt;メルカリグループでは、すでにいくつかの 2B 決済のユースケースが存在しています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;メルカリ ハロ&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;「メルカリ ハロ」では事業者掛け払いというスキームを提供していました。メルカリが事業者の代わりにクルーに仕事完了時に給料を支払い、月末締め翌月払いで事業者から給与および手数料を回収する仕組みです。&lt;/p&gt;
&lt;p&gt;詳しくは以下の記事をご覧ください。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241221-invoice-payment/&quot;&gt;メルカリ ハロにおける事業者請求払いの内製化&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;メルカリAds&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;メルカリAds では、パートナー企業の広告をメルカリアプリに掲載し、月末締め翌月払いを請求書経由で行う仕組みを提供しています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C2B 決済&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;C2B パートナーシップでは、外部事業者がメルカリの商品をオファー経由で購入・再販するビジネスモデルを提供しています (&lt;a href=&quot;https://about.mercari.com/press/news/articles/20241219_kaitorirequest/&quot;&gt;大黒屋とのパートナーシップ事例&lt;/a&gt;)。&lt;/p&gt;
&lt;p&gt;メルカリ出品者が販売しても売れ残った商品に対して、パートナー企業から買取リクエストが送信されます。出品者が承諾すると、メルカリが出品者から商品を購入し、その後パートナー企業に販売する C2B モデルです。&lt;/p&gt;
&lt;p&gt;パートナー企業からメルカリへの決済は、与信に基づくオファーで与信枠内で行われ、月末締め翌月払いで請求されます。現在、パートナー数が限定的であることから与信枠の管理は Finance チームがスプレッドシートを中心に手動で行っています。&lt;/p&gt;
&lt;h3&gt;2B 決済基盤に求められる要素&lt;/h3&gt;
&lt;p&gt;2B の決済基盤、特に事業者請求払い (掛け払い) のような与信を伴う決済では、以下の要素が不可欠となります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2024/12/030d3444-relations.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上図のように、決済から入金、会計処理まで一連のフローを支える必要があります。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;決済および債権の管理&lt;/strong&gt;: 決済実行と売掛金の計上&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;与信管理&lt;/strong&gt;: 事業者ごとの与信枠の設定と残高管理、木構造での柔軟な管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;請求書の発行・送付・消込&lt;/strong&gt;: 適格請求書の自動発行と送付、入金管理と売掛金の消し込み (入金消込)、事業者ごとに異なる管理単位への対応&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;入金の受け取り&lt;/strong&gt;: 入金管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;精算処理&lt;/strong&gt;: 月次での精算と会計処理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;督促&lt;/strong&gt;: 未払いに対する督促処理&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;これまでの取り組み&lt;/h3&gt;
&lt;p&gt;Payment Platform では、これらの要素を段階的に構築してきました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;外部サービスの活用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;メルカリ ハロやメルカリAds のローンチ時には、外部企業が提供する掛け払いサービスを採用しました。ローンチの速度や与信管理の容易性・貸倒リスクを加味した結果、与信審査や貸倒リスクを外部に委譲し、スピーディーにビジネスを立ち上げることにフォーカスしました。Payment Platform としては、これまでの決済 API のインターフェースを保ったまま法人の与信を利用した決済手段である Invoice Payment を Payment Service に導入しました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一部内製化と手動運用の組み合わせ&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;メルカリAds では一部の企業に対して、外部サービスを使わない内製掛け払いソリューションを提供しました。決済時に Debt Service で債権を計上し、月次で Settlement Service を通じて精算する仕組みです。利用先を限定していたため、請求書の発行や消込は手動運用で対応しました。このタイミングでは、中長期の内製化や掛け払いプロバイダの多様化を見越して、外部サービスの掛け払いを利用する場合と内製掛け払いを利用する場合を抽象化した概念として Invoice Payment API を提供しました。&lt;/p&gt;
&lt;p&gt;この取り組みにより、既存の Payment Service と Debt Service を活用した 2B 決済の基礎が築かれました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Invoice Service のローンチ&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2025 年には、2B に対する請求書の発行・管理・送付などを行う &lt;strong&gt;Invoice Service&lt;/strong&gt; システムをローンチしました。これはメルカリAds やその他 2B プロダクトで内製掛け払いに移行していく際に必要となるだけでなく、メルカリ &amp;#8211; メルペイ間の精算など、他のビジネスでも利用するための基盤として構築されました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ここまでで実現できたこと&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;これまでの取り組みにより、事業者請求払いに必要な要素のうち、以下がシステムとして整いました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 決済トランザクションの管理 (Payment Service)&lt;/li&gt;
&lt;li&gt;✅ 債権の管理 (Debt Service)&lt;/li&gt;
&lt;li&gt;✅ 請求書の発行・送付・消込 (Invoice Service)&lt;/li&gt;
&lt;li&gt;✅ 精算処理 (Settlement Service)&lt;/li&gt;
&lt;li&gt;✅ バーチャル口座 (Virtual Account) による入金管理 (Bank Service)&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;与信管理&lt;/strong&gt; ← まだシステム化されていない&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;督促&lt;/strong&gt; ← まだユースケースがない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;重要なのは、これらの多くがこれまでのメルカリのプロダクトのために構築した既存マイクロサービスを活用できている点です。汎用性の高い設計が、新しい決済スキームへの迅速な対応を支えています。&lt;/p&gt;
&lt;h3&gt;これから: 与信管理の内製化&lt;/h3&gt;
&lt;p&gt;前述の通り、現在 C2B の与信枠は Finance チームがスプレッドシートで手動管理しています。パートナー数が限定的な間はこの運用で対応できていますが、規模拡大に向けてシステム化が必要です。&lt;/p&gt;
&lt;p&gt;手動管理では与信残高の即時更新が困難で、ヒューマンエラーのリスクや運用負荷が増大します。また C2B、メルカリAds など複数プロダクトで独自に与信管理を行うと、メンテナンスコストが増大し一貫性も保ちにくくなります。同じ法人が複数プロダクトを利用する場合、与信枠の重複管理や過剰付与のリスクもあります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;与信管理サービスのシステム化&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;これらの課題を解決するため、統一的な与信管理基盤をシステム化する計画があります。&lt;strong&gt;与信管理サービス&lt;/strong&gt;として、リアルタイムの与信残高照会・更新、複数事業で再利用可能な汎用設計、法人・支社・店舗といった木構造での柔軟な管理、既存決済基盤との一貫した会計処理を提供していく予定です。&lt;/p&gt;
&lt;p&gt;与信管理が内製化されると、以下のように Payment Platform のマイクロサービス群が連携して 2B 決済を支えることになります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/b732ae13-architecture.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この図から見えるのは、&lt;strong&gt;既存マイクロサービスと新規サービスを組み合わせて全体のソリューションを構築している&lt;/strong&gt;点です。&lt;/p&gt;
&lt;p&gt;これまでに構築してきた Payment Service、Debt Service、Settlement Service、Bank Service といった共通サービスは、それぞれが明確なドメイン責務を持つため、新しいユースケースでもそのまま活用できます。2B 特有の与信管理や請求書発行だけを新しいサービスとして追加し、請求書送付には Payment Platform 外の Notification Service を活用します。&lt;/p&gt;
&lt;p&gt;各サービスは API を通じて疎結合に連携し、Payment Service が決済のオーケストレーションを担うことで、全体として 2B 決済ソリューションを実現しています。このアーキテクチャにより、新しい決済スキームにも柔軟に対応できる拡張性を持っています。&lt;/p&gt;
&lt;h2&gt;Payment Platform が目指す姿&lt;/h2&gt;
&lt;p&gt;Payment Platform は、メルカリグループにおける既存サービスのグロース・新規サービスのローンチを簡単に・早く・効率的にできるようにすることを目指しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;スピード&lt;/strong&gt;: 新規プロダクトが決済機能を素早く統合できる&lt;br /&gt;
Checkout Solution のような統合ソリューションを提供することで、プロダクトチームは決済フローや UI を一から実装する必要がありません。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;効率&lt;/strong&gt;: 各プロダクトの開発負荷を削減&lt;br /&gt;
新しい決済手段を追加する際、Payment Platform 側で対応すれば、すべてのプロダクトで使えるようになります。各プロダクトが個別に実装する必要がなくなり、開発効率が向上します。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一貫性&lt;/strong&gt;: 統一された UI/UX の提供&lt;br /&gt;
UI を含む統合ソリューションを提供することで、異なるプロダクト間でも統一された決済体験を届けられます。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拡張性&lt;/strong&gt;: 2C から 2B まで、あらゆる決済シーンをカバー&lt;br /&gt;
2B の決済ニーズをキャッチしながら機能を育て、汎用的なソリューションにすることで、メルカリグループの多様な決済ニーズに応えていきます。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;この 1 年、Payment Platform は 2 つの大きな軸で進化してきました。&lt;/p&gt;
&lt;p&gt;1 つ目は &lt;strong&gt;Checkout Solution による統合決済体験の実現&lt;/strong&gt;です。gRPC API に加えて、UI を含む統合ソリューションという新しい選択肢を提供することで、プロダクトチームの開発負荷を削減し、統一された UX を提供できるようになりました。また、多通貨対応も合わせて行い、今後のグローバルなプロダクト展開に耐えうる決済基盤の仕組みも加わりました。&lt;/p&gt;
&lt;p&gt;2 つ目は &lt;strong&gt;2B 決済への展開&lt;/strong&gt;という新たな挑戦です。2B 決済は始まったばかりの領域ですが、既存の 2C で培った強固なマイクロサービス群を活用しながら、段階的に基盤を構築しています。与信管理サービスによる与信管理の内製化により、2B 決済基盤が完成に近づいていきます。&lt;/p&gt;
&lt;p&gt;Payment Platform は、これまで多様な決済ニーズに応えてきた実績を基盤に、今後もメルカリグループの成長を決済で支えていきます。&lt;/p&gt;
&lt;p&gt;明日の記事は kobaryo さんです。引き続きお楽しみください。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;関連記事&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250605-bf42ce60cf/&quot;&gt;Checkout Solution を活用したメルカリの決済体験&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250606-7afd82e718/&quot;&gt;Checkout Solution: Backend 編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251216-mandates-for-recurring-payments/&quot;&gt;Mandates for Recurring Payments: サブスクリプション決済を支える仕組み&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241221-invoice-payment/&quot;&gt;メルカリ ハロにおける事業者請求払いの内製化&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://about.mercari.com/press/news/articles/20241219_kaitorirequest/&quot;&gt;大黒屋とのパートナーシップ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Kubernetes環境におけるパケットキャプチャ</title><link>https://engineering.mercari.com/blog/entry/20251218-capturing-network-packets-in-kubernetes/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251218-capturing-network-packets-in-kubernetes/</guid><description>&lt;p&gt;こんにちは。メルカリのPlatform Network team/SREの@mshibuyaです。 この記事は、Mercari Advent Calendar 2025の18日目の記事です。 年の瀬って忙しくなりがちです [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 18 Dec 2025 11:00:19 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリのPlatform Network team/SREの&lt;a href=&quot;https://x.com/m4buya&quot;&gt;@mshibuya&lt;/a&gt;です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt;の18日目の記事です。&lt;/p&gt;
&lt;p&gt;年の瀬って忙しくなりがちですよね。ただでさえ忙しいのに、Advent Calendar記事執筆の予定まで入れてしまい、半泣きでこの記事を書いています。いや、こうなるのはわかってはいたんですが、一年を振り返ってアウトプットの少なさに焦ってしまうとやはり記事執筆に名乗りをあげてしまうのですよね…。&lt;/p&gt;
&lt;p&gt;さて、今回はKubernetes環境におけるネットワークのパケットキャプチャの話をします。筆者は現在前述の通り&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20220209-introduction-of-the-network-team/&quot;&gt;Network team&lt;/a&gt;に所属しており、そこではメルカリのプロダクト開発を支える様々なPlatformとしてのコンポーネント群のうち、ネットワークに関わるものを中心に扱っています。&lt;/p&gt;
&lt;p&gt;メルカリでは数百を超える規模のマイクロサービスが稼働しており、そのサービス内外におけるネットワークコミュニケーションも複雑多岐にわたります。Network teamはその性質上、そういった環境で発生したネットワークに起因すると思われる不具合・問題の調査を依頼されることが多くあります。もちろん単純な設定不備などが原因の場合もありますが、問題が複雑で解決の糸口がなかなか見出せないような局面において、深い分析を行う手段を必要としていました。そこでパケットキャプチャが登場します。&lt;/p&gt;
&lt;p&gt;こういった問題調査のための手法は、それ自身の実行手順が明確になっていなければいざ問題が発生した際に役立てることはできません。緊急度の高い障害が発生しているような状況下であればなおさらでしょう。今回確立したこの手法は、みなさまの環境においてそのまま適用可能とは限りませんが、それでも安定して実行できる調査手順を紹介しておくことは、みなさまがご自身の組織で同様の手順を作る際にも役立つであろうと考えたのがこの記事を公開する狙いです。&lt;/p&gt;
&lt;h2&gt;なぜKubernetesでのパケットキャプチャは難しいのか&lt;/h2&gt;
&lt;p&gt;みなさまご存知の通り、KubernetesはハードウェアやOSといった様々なレイヤにおいて抽象化を提供しており、それによって開発者が生のリソースに煩わされることなくワークロードを実行可能な環境を提供しています。セキュリティ上、一般には利用者である開発者は生のノードにはアクセスできないようになっており、またその上で動くPodのようなワークロードもMulti-tenancyとして互いに隔離されています。そのため、昔のように単純にサーバ上でtcpdumpを叩けばおっけー、とはいかないわけですね。&lt;/p&gt;
&lt;p&gt;また、Service Meshにまつわる難しさもあります。メルカリではIstioを導入していますが、クラスタ内の通信は基本的にmTLSによって暗号化されているため、そのままでは通信の中身を見ることができません。この点を考慮した手法を確立する必要があります。&lt;/p&gt;
&lt;p&gt;こうしたKubernetesクラスタを含め、開発者が機能を簡単に素早くデリバリーするために必要な道具立て一式を提供しているのが我々Platformとしての立ち位置です。このようなネットワークにおける深いトラブルシューティングの必要性がいつ生じるかは予想できません。特別なPlatform特有の権限によることなく、必要であれば開発者がセルフサービスによってこのような調査を行えることも大事な要件としました。&lt;/p&gt;
&lt;h2&gt;Ephemeral Containersを活用したPodレベルでのキャプチャ&lt;/h2&gt;
&lt;p&gt;前述の条件を満たす手法として我々が確立したのが、Kubernetesの機能である&lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/&quot;&gt;Ephemeral Containers&lt;/a&gt;を活用した手法です。&lt;/p&gt;
&lt;p&gt;Ephemeral Containers（エフェメラルコンテナ）は、Kubernetes v1.25でGAとなった機能です。ノード全体へのアクセス権限を持つことなく、実行中のPodのネットワーク名前空間などの共有リソース内に、一時的なデバッグ用コンテナを「外付け」する形でアタッチできます。これにより、tcpdumpなどのデバッグツールをアプリケーションコンテナ内に含める必要なくトラブルシューティングを行えるため、このようなパケットキャプチャに用いるにはうってつけです。NodeやCluster全体への特別な権限を必要としない点も大きなメリットで、Platformメンバーも開発者も同じ方法によって調査を行うことができます。&lt;/p&gt;
&lt;p&gt;具体的な手順としては、下記のようになります。&lt;/p&gt;
&lt;h3&gt;Step 1. 必要な権限の取得&lt;/h3&gt;
&lt;p&gt;メルカリでは、&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20220201-promote-zero-touch-production-further-features-of-carrier/&quot;&gt;Carrier&lt;/a&gt;と呼ばれる一時的な権限取得を可能にする内製ツールによって、通常時は本番環境に対する操作権限を持たないZero Touch Productionを実現しています。&lt;br /&gt;
このため、本番環境における問題調査においてパケットキャプチャを実施する際には、対象となるPodに対する操作権限を持つRoleをまず取得する必要があります。&lt;/p&gt;
&lt;p&gt;上記Roleには、Ephemeral Containersに対する操作を行うために必要なpermissionを事前に付与しておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: example-role
rules:
# ...
- apiGroups: [&amp;quot;&amp;quot;]
  resources: [&amp;quot;pods/ephemeralcontainers&amp;quot;]
  verbs: [&amp;quot;create&amp;quot;, &amp;quot;delete&amp;quot;, &amp;quot;deletecollection&amp;quot;, &amp;quot;patch&amp;quot;, &amp;quot;update&amp;quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 2. Ephemeral Containerの起動&lt;/h3&gt;
&lt;p&gt;権限が得られたら、対象となるPodに対してEphemeral Containerをアタッチします。ここではパケットキャプチャも含む、ネットワーク全般のトラブルシューティングに対応したリッチなツールセットがインストール済みのnetshootを用いています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl debug -it -n &amp;lt;your-namespace&amp;gt; &amp;lt;target-pod&amp;gt; \
  --image=nicolaka/netshoot \
  --custom=./root.yaml --container=netshoot-debug&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここで、ファイル &lt;code&gt;./root.yaml&lt;/code&gt; には事前に以下の内容を準備しておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;securityContext:
  runAsUser: 0
  runAsNonRoot: false&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これにより、コンテナ内でtcpdumpを実行するための必要条件である「rootとしてnetshootコンテナを実行する」を実現します。大して長い内容ではないので、本当はコマンドラインにインラインで書いてしまいたいのですが、今のところkubectl debugの引数としてはファイルを渡すことしかできないようです…。&lt;/p&gt;
&lt;h3&gt;Step 3. キャプチャの実施&lt;/h3&gt;
&lt;p&gt;無事にnetshootコンテナのシェルが開いたら、キャプチャをスタートできます。ここではファイル &lt;code&gt;/tmp/capture.pcap&lt;/code&gt; に書き込んでいます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tcpdump -i any -w /tmp/capture.pcap&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Istioが有効化された環境化においては、この &lt;code&gt;-i any&lt;/code&gt; がポイントとなります。トラフィックは eth0 だけでなく、iptablesによってリダイレクトされた仮想インターフェースをも通るため、これらを取りこぼさないように全インターフェースを対象にしています。eth0のみをキャプチャすると、mTLSによって暗号化済みの内容しか取れないため、調査の目的に対し不足することが多いでしょう。&lt;/p&gt;
&lt;p&gt;単純に全トラフィックをキャプチャするとデータが膨大となってしまい得ます。ここでは詳細は触れませんが、tcpdumpのオプションによってキャプチャするパケットをフィルタできるため、調査しようとしている問題に関係するパケットにできるだけ絞り込んでキャプチャするとその後の調査が楽です。もちろん、絞り込みすぎると「必要なデータが取れてなかった」というトレードオフになります。&lt;/p&gt;
&lt;h3&gt;Step 4. ファイルの回収&lt;/h3&gt;
&lt;p&gt;上記によってEphemeral Containerにファイルが作成されるので、ローカルよりkubectl cpでダウンロードすれば完了です。Step 2でつけたコンテナ名を指定するのをお忘れなく。&lt;br /&gt;
これでキャプチャしたデータの分析に移れます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl cp -n &amp;lt;your-namespace&amp;gt; &amp;lt;target-pod&amp;gt;:/tmp/capture.pcap ./capture.pcap -c netshoot-debug&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;慣れてきたら、Step 2-4までをワンラインで行ってしまってもいいでしょう。このような見た目になります。余計な出力がファイルに混入しないよう &lt;code&gt;-iq&lt;/code&gt; を使い、また標準エラー出力を捨てています。 &lt;code&gt;-G 10&lt;/code&gt; でキャプチャを行う秒数を指定しています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl -n &amp;lt;your-namespace&amp;gt; debug &amp;lt;target-pod&amp;gt; -iq --image=nicolaka/netshoot --custom=./root.yaml -- bash -c &amp;#039;tcpdump -i any -G 10 -W 1 -s0 -w - 2&amp;gt;/dev/null&amp;#039; &amp;gt; tcpdump.pcap&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Nodeレベルでのキャプチャ&lt;/h2&gt;
&lt;p&gt;上記Podレベルでのキャプチャ方法とは別に、GKE NodeにSSHし&lt;a href=&quot;https://docs.cloud.google.com/container-optimized-os/docs/how-to/toolbox&quot;&gt;CoreOS Toolbox&lt;/a&gt;を用いてパケットキャプチャを行う手順も整備済みです。が、Nodeに対しSSHを行える権限を持つ必要があること、また前述のようにIstioのトラフィックは暗号化されたものしか取得できないこともあり、あくまで補助的な位置づけです。主にPlatformメンバーがNodeレベルでしか観測できないようなトラブルシューティングに用いることを想定しています。&lt;/p&gt;
&lt;h3&gt;Step 1. 必要な権限の取得&lt;/h3&gt;
&lt;p&gt;メルカリではKubernetesクラスタをGoogle Kubernetes Engineによって構築・運用しています。まず、GKEノードにSSHするために必要な権限を、前掲のCarrierを用いて取得する必要があります。該当プロジェクトに対し下記の権限があれば十分なはずです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;roles/compute.instanceAdmin.v1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;roles/iam.serviceAccountUser&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;roles/iap.tunnelResourceAccessor&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Step 2. Nodeの特定&lt;/h3&gt;
&lt;p&gt;kubectl get podコマンドで、対象Podがホストされているノード名を確認します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ kubectl get pod -n &amp;lt;your-namespace&amp;gt; your-app-pod-7f5b7f7d9f-abcde -o wide
NAME                           READY   STATUS    RESTARTS   AGE    IP           NODE                                NOMINATED NODE   READINESS GATES
your-app-pod-7f5b7f7d9f-abcde   2/2     Running   0          2d1h   10.1.2.3     gke-cluster-1-node-pool-1-a1b2c3d4   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 3. toolbox環境に入る&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;gcloud compute ssh&lt;/code&gt; を使用してノードにSSHし、&lt;code&gt;toolbox&lt;/code&gt; コマンドで、デバッグツールが揃ったシェル環境に入ります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcloud compute ssh --project &amp;lt;your-project&amp;gt; gke-cluster-1-node-pool-1-a1b2c3d4&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# On the GKE node
$ toolbox&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 4. キャプチャの実施&lt;/h3&gt;
&lt;p&gt;toolboxシェル内でtcpdumpを実行します。ホストのルートファイルシステムは &lt;code&gt;/media/root&lt;/code&gt; にマウントされているため、キャプチャファイルはノードの &lt;code&gt;/tmp&lt;/code&gt; に相当する &lt;code&gt;/media/root/tmp/&lt;/code&gt; に保存します。&lt;code&gt;-i any&lt;/code&gt; で全インターフェースからキャプチャを行うことを指定し、フィルタとしてStep 2で確認したPodのIPアドレスを指定します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Inside the toolbox shell
$ tcpdump -i any -w /media/root/tmp/node_capture.pcap host 10.1.2.3&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 5. ファイルの回収&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;toolbox&lt;/code&gt; シェル (&lt;code&gt;exit&lt;/code&gt;)、SSHセッション (&lt;code&gt;exit&lt;/code&gt;) の順で終了し、ローカルマシンからgcloud compute scpでファイルを手元にコピーします。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcloud compute scp --project &amp;lt;your-project&amp;gt; gke-cluster-1-node-pool-1-a1b2c3d4:/tmp/node_capture.pcap ./node_capture.pcap&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こちらのNodeレベルでのキャプチャはまだ実際の調査で利用した実績はありませんが、こうして手順として整備しておけばいざ問題が発生した際も落ち着いて調査に入れます。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;この記事では、メルカリにおけるKubernetesパケットキャプチャのプラクティスについて紹介しました。 とりわけPodレベルにおいては、Ephemeral Containersを活用することで、セキュリティと利便性のバランスを取りながら開発者が自身でトラブルシューティングを行える手順を整えています。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Podレベル (Ephemeral Containers)&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Nodeレベル (Toolbox)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;主な用途&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;アプリケーション固有の問題調査、mTLS化された通信内容の確認&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Node全体に関わるネットワーク問題（CNI、iptablesルールなど）の調査&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;必要な権限&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;比較的低い (Podレベルの権限)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;高い (NodeへのSSHアクセス権限)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;Istio環境でのトラフィックの可視性&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;暗号化前の平文トラフィックをキャプチャ可能&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;暗号化後のトラフィックしかキャプチャできない&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;対象の絞り込みやすさ&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;調査対象のPodに直接アタッチするため、トラフィックの特定が容易&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;複数Podが稼働するため、対象Podのトラフィックを分離するのが比較的困難&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;推奨される利用者&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;アプリケーション開発者、SRE&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Platformチーム、SRE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;セルフサービスへの親和性&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;高い (開発者が自身で調査可能)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;低い (強い権限が必要なため限定的)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;今回ご紹介した内容をさらに発展・一般化したお話を来年3月開催の&lt;a href=&quot;https://www.usenix.org/conference/srecon26americas&quot;&gt;SRECon26 Americas&lt;/a&gt;において「It&amp;#8217;s Not Always the Network (But Here&amp;#8217;s How to Prove It): Kubernetes Packet Capture for SREs」というタイトルで発表予定です。いや、まだ実感がないというか信じられないのですが、ProposalがAcceptされたという連絡はいただいたので、登壇しにシアトルに行ってくると思います…。&lt;/p&gt;
&lt;p&gt;パケットキャプチャを行った次のステップとしては、実際にキャプチャしたデータを分析するフェーズが待っています。紙面の都合上、また筆者がその分野ではまだまだ修行中ということもあり今回は触れませんでしたが、今後またなんらかの知見を共有していけたらと思います。&lt;/p&gt;
&lt;p&gt;最後までお読みいただきありがとうございました。明日の記事はamenboさん・siroken3さんによる「AI-Native開発を加速する AWS Kiro の導入と、Okta を活用したアカウント管理の自動化」となります！引き続きお楽しみくださいー&lt;/p&gt;
</content:encoded></item><item><title>メルペイでの3ヶ月間：非同期処理への移行とASDD開発の学び</title><link>https://engineering.mercari.com/blog/entry/20251217-ce2057d0ad/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251217-ce2057d0ad/</guid><description>&lt;p&gt;はじめに こんにちは。メルペイの Balance チームでインターンをしている @minato です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の18日目の記事です [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 18 Dec 2025 10:00:09 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。メルペイの Balance チームでインターンをしている &lt;a href=&quot;https://linkedin.com/in/湊士-山口-60b86b277&quot;&gt;@minato&lt;/a&gt; です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の18日目の記事です。&lt;/p&gt;
&lt;p&gt;2025年10月からメルペイでバックエンドのインターンを始め、3ヶ月目になります。&lt;br /&gt;
本記事では、インターン期間中に取り組んだタスク、得ることのできた学びについて紹介します。&lt;/p&gt;
&lt;h2&gt;取り組んだタスク&lt;/h2&gt;
&lt;p&gt;今回のインターンでは、債権データを管理するマイクロサービスにおいて、これまで同期的に行っていた債権残高の更新処理を非同期処理へと移行するタスクに取り組みました。&lt;br /&gt;
以下、非同期処理へ移行するに至った背景、設計、実装、負荷試験について説明していきます。&lt;/p&gt;
&lt;h3&gt;背景&lt;/h3&gt;
&lt;p&gt;メルペイでは現在、世界共通のプラットフォームを提供する「&lt;a href=&quot;https://about.mercari.com/press/news/articles/20250930_crossborder/&quot;&gt;メルカリ グローバルアプリ&lt;/a&gt; 」プロジェクトが進められています。これまで国ごとに限定されていたプロダクトをグローバルに展開するにあたり、これまでになかった量の Transaction が集中することが想定され、負荷分散機能の検討・開発が必要となりました。&lt;/p&gt;
&lt;p&gt;このような理由から、僕は、負荷分散を必要とする機能の一つである債権の作成機能の改善に取り組みました。&lt;br /&gt;
債権データの作成プロセスでは、債権を作成した後、該当する債務者の債権残高を更新する必要があります。なので、債権データの作成リクエストが連続して行われた際、特定の行やカラムに更新が集中してしまい、その箇所が hotspot となり、以下の問題を起こします。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;高負荷が特定の行に集中し、ロック競合が多発する&lt;/li&gt;
&lt;li&gt;更新待ちが発生し、スループットが低下する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この問題を解決するために、債権残高をリクエスト中に同期的に更新するのではなく、非同期で更新することにしました。非同期化することで、本来複数の Transaction で処理されるはずの複数の Request による債権残高の更新を、一つの Transaction でまとめて処理することができ、hotspot 問題を解決することができます。&lt;/p&gt;
&lt;h3&gt;設計&lt;/h3&gt;
&lt;p&gt;大まかな設計としては、債権残高の更新処理が発生した際に債権更新イベントテーブルへレコードを作成し、worker がそのテーブルを取得して Job Queue に push、Job Processor が Job Queue 内のアイテムに基づいてアカウントを更新するという流れとしました。&lt;/p&gt;
&lt;p&gt;また、Job Queue については、債権更新イベントテーブルに、Job Queue に入っているかを表すフィールドを設けることで、DB を Job Queue として使用できる仕組みとしました。なぜイベントの管理を、外部サービスではなく DB で管理としたのかというと、外部サービスへの依存を避けられること、同様の処理を行うマイクロサービスにおいて DB で管理する構成で正常に機能しているためになります。&lt;br /&gt;
以下の画像は、非同期イベントをどのように処理しているのかについて表したものです。Workerは DB から処理されていないイベントレコードを取得し、server上のメモリ(以下の図の Queue) に保存します。その後、Job Processor が Queue 内のイベントをまとめて処理するという構成になります。&lt;/p&gt;
&lt;p&gt;実装では、Worker、Queue、Job Processor のそれぞれについてカスタマイズしやすい struct を定義し、他の非同期処理において再利用できるようにしました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/eef4978d-screenshot-2025-12-15-at-16.25.04.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;実装&lt;/h3&gt;
&lt;p&gt;今回の実装では、社内で活用が推進されている ASDD(Agent Spec Driven Development) という方法を使用しました。ASDD とは、AI agent が仕様書を作成し、別agentがその仕様書に基づいて実装を進めるという手法です。詳しくは、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251201-pj-double-towards-ai-native-development/&quot;&gt;pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌&lt;/a&gt; の記事をご覧ください。&lt;br /&gt;
アカウントの非同期化は、システム全体に関わる非常に大きな機能改善であったため、厳密に設計をDocsとして記録すること、レビューのしやすさを考慮し、仕様書自体は僕が手作業で作成したため、時間を消費してしまいましたが、その後の実装はあっという間にAIによって完了し、とても驚きました。&lt;br /&gt;
「もし社内の全員が ASDD を使いこなす未来が来たらどうなるのか」 と想像するだけでワクワクする体験でした。&lt;/p&gt;
&lt;h3&gt;負荷試験&lt;/h3&gt;
&lt;p&gt;負荷試験については、まだ行っていないため、これから取り組む予定のことを説明します。今回の負荷試験では、想定される平均リクエスト数と最大リクエスト数をテスト環境に対して送信し、システムがどの程度安定して処理できるかを確認する予定です。&lt;/p&gt;
&lt;p&gt;また、債権残高の更新イベントが生成されてから、非同期的に実際に更新されるまでの処理時間についても計測しようと思います。というのも、債権残高の更新される速度は、債権の処理を行う上流のサービスにとって非常に重要なものになるためです。&lt;/p&gt;
&lt;p&gt;テスト計画を立てる中、驚いたことは、負荷試験のためのリポジトリが整備されていたことです。専用のリポジトリが存在することで、誰でも容易に負荷試験シナリオを作成でき、さらに他者が同じ条件で負荷を再現できるため、検証の透明性と再現性を保つことができます。&lt;/p&gt;
&lt;h2&gt;インターンを通して得た学び&lt;/h2&gt;
&lt;p&gt;今回のインターンでは、日常の開発ではなかなか扱うことのできない規模のリクエスト数を処理するシステムの開発を経験することができ、技術的な学びはもちろん、コーディングに対する姿勢やプロダクトへの向き合い方について学びました。&lt;/p&gt;
&lt;h3&gt;コーディングへの取り組み方&lt;/h3&gt;
&lt;p&gt;インターンを通じて特に印象的だったのは、コメントを書かずとも、関数名、コードから類推がつき、後から読んだ際に一目で何をする処理なのかを理解できるレベルを目指す姿勢です。保守・運用のことまで考えたコードを書くことは普段の開発においても、心掛けているのですが、普段の自分を見つめ直し、コーディングへの姿勢を改めるきっかけになりました。&lt;br /&gt;
また、レビューの際に、ファイル内のコメント一つに対しても、どのような意図を持って書いたのか、また、そもそもそのファイルは何のためにあるのかといったことを指摘していただき、開発においてメタ的に考えることと具体的に考えることの両方を行うことの重要性を実感しました。&lt;/p&gt;
&lt;h3&gt;プロダクトのことを第一に考える開発&lt;/h3&gt;
&lt;p&gt;また、プロダクトに対する心構えについても、非常に刺激を受けました。チームのメンバー一人一人が、プロダクトの未来を考えながら、意思決定をしており、「将来的にこう使われる可能性があるから、今の内にこの手法を採用しておく」、「お客さま体験を良くするためにこのデータ構造はこうあるべき」といった議論が開発中だけではなく食事中にも頻繁に行われており、プロダクトへの愛とメルカリの Values の一つである Be a Pro の精神を強く感じました。&lt;/p&gt;
&lt;h2&gt;その他の活動と経験&lt;/h2&gt;
&lt;p&gt;インターン期間中は、開発のタスクにのみ取り組むのではなく、Qに1回開催されている社内イベントのTech Talkに登壇してみたり、他のチームの方と1on1やランチに行ったり、技術書やテックブログの読み合わせ会に参加したりと、いろいろな活動をしました。&lt;/p&gt;
&lt;p&gt;ランチではバックエンドエンジニアの方だけではなく、セキュリティエンジニアの方とも話す機会をいただき、なぜその職種を選んだのかというキャリアの話から、最近の攻撃手法、日々の開発で意識すべきセキュリティ観点まで、バックエンドに閉じない視点を得ることができました。&lt;/p&gt;
&lt;p&gt;また、Tech Talk や読み合わせ会に参加し、メルカリは、自発的に技術を学びに行ったり、技術について情報発信する環境が本当に整っていることを強く実感し、エンジニアにとって最高の環境だと思いました。&lt;/p&gt;
&lt;h2&gt;さいごに&lt;/h2&gt;
&lt;p&gt;インターンを通して、タスクに取り組む中で技術的な学びだけではなく、AIの活用方法、エンジニアとしての姿勢についても多くの学びを得ることができました。特に、エンジニアとしての姿勢は、これからの働き方に活かしていこうと思います。インターンとして参加できる残りの期間では、負荷試験までやり切り、可能であればリリースまで完了させたいと考えています。&lt;/p&gt;
&lt;p&gt;今回の開発をリードしてくださった@yutaroさん、メンターの@kobaryoさん、そしてチームの皆さんを始め関わってくださった全ての方々、本当にありがとうございました！&lt;/p&gt;
&lt;p&gt;明日の記事は@komatsuさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>多様な支払い手段と継続課金を安全に扱う「Mandate」の設計</title><link>https://engineering.mercari.com/blog/entry/20251216-mandates-for-recurring-payments/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251216-mandates-for-recurring-payments/</guid><description>&lt;p&gt;はじめに こんにちは。メルペイのPayment Core team/Backend Engineerの@tomoです。 この記事は、 Merpay &amp;amp; Mercoin Advent Calendar 2025の1 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 17 Dec 2025 10:00:15 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。メルペイのPayment Core team/Backend Engineerの@tomoです。&lt;br /&gt;
この記事は、 &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot; title=&quot;Merpay &amp;amp; Mercoin Advent Calendar 2025&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt;の17日目の記事です。&lt;/p&gt;
&lt;h2&gt;都度払いから継続決済へ&lt;/h2&gt;
&lt;p&gt;これまでメルペイの決済は、メルカリでの買い物や、お客さまがスマホを取り出してバーコードを提示したりボタンを押したりすることで完了する、いわゆる都度決済が中心でした。しかし、メルカリのエコシステムが拡大するにつれ、決済のあり方は大きく変化しています。&lt;/p&gt;
&lt;p&gt;例えば、私たちが提供を開始したメルカリモバイルでは、毎月の利用料金を支払うためにお客さまが都度アプリを開くことはありません。システムがバックグラウンドで自律的に決済を実行します。 このように、決済はもはや単発のイベントではなくなりつつあります。お客さまの操作を伴わずに実行される オフセッション決済──サブスクリプション課金のように、継続的に行われる支払い形態が増えてきています。&lt;/p&gt;
&lt;p&gt;そして、メルカリ特有の多様な支払い手段を組み合わせつつ、これらの継続決済を大規模に安全に実行するために、私たちは &lt;strong&gt;Mandate&lt;/strong&gt;（マンデート） と呼ばれる新しい概念を導入しました。&lt;/p&gt;
&lt;h2&gt;Mandateとは&lt;/h2&gt;
&lt;p&gt;「Mandate」という言葉は日常では馴染みが薄いかもしれませんが、Fintech の領域では口座振替依頼や自動引き落としの同意を表す一般的な用語です。Stripe の SetupIntent や、インド UPI の AutoPay など、世界中の決済プラットフォームで同様の仕組みが提供されています。&lt;/p&gt;
&lt;p&gt;身近な例として、動画配信サービスのサブスクリプションがあります。お客さまは初回登録時にクレジットカード情報を登録し、「毎月このカードから定額を引き落としてよい」という包括的な許可を与えます。この「将来の支払いに関する包括的な同意」こそが Mandate の本質です。これらは一般に「オフセッション決済」と呼ばれます。請求の瞬間にお客さまが操作を行わずに決済が実行される決済形態を指します。&lt;/p&gt;
&lt;p&gt;メルカリにおける Mandate も同じ考え方で、お客さまがサービス（例：メルカリモバイルサービス）に対して「メルペイ残高やポイントなどを用いて、未来の支払いを行ってよい」と認可するデジタルな契約を示します。&lt;/p&gt;
&lt;p&gt;一般的なMandateは、特定の「クレジットカード」や「銀行口座」に1対1で紐づきます。たとえば、あるサブスクAの支払いに対してクレジットカードBで契約するためのMandateを作成するという具合です。しかし、メルカリのお客さまは以下のような多様な決済手段を持っています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;売上金・残高&lt;/li&gt;
&lt;li&gt;無償ポイント・有償ポイント&lt;/li&gt;
&lt;li&gt;メルペイのあと払い&lt;/li&gt;
&lt;li&gt;クレジットカード&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「ポイントが余っていればポイントを優先し、足りなければ残高から、それでも足りなければあと払いで」――このような複合決済を毎回お客さまの操作なしに行うには、単一のカードへの紐付けでは不十分です。そのため、Payment Platformでは、複数の決済手段に対してMandateを作成できるようにしました。つまり Mandate は、「多様な支払い手段を組み合わせて継続的に課金する」というメルカリ特有の要件を、安全に実現するための基盤として設計されています。&lt;/p&gt;
&lt;h2&gt;メルペイにおける Mandate&lt;/h2&gt;
&lt;h4&gt;Mandate を構成する 3 つの基本要素&lt;/h4&gt;
&lt;p&gt;オフセッション決済では、誰が → 誰に → どう支払うか の三要素がそろって初めて正しい権限判定ができます。この三要素によって Mandate のスコープを定めます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Customer（誰が支払うか）：お客さま&lt;/li&gt;
&lt;li&gt;Partner（誰に支払うか）：料金を受け取るサービス（例: メルカリモバイル）&lt;/li&gt;
&lt;li&gt;Payment Method（どう支払うか）：ポイント、残高、あと払い、クレジットカードなどの組み合わせ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mandate をこの三軸の組み合わせで表すことで、余計な権限を持たせることなく、監査に耐える説明可能性を確保し、かつ決済実行時の判定を機械的に行えるようになります。&lt;br /&gt;
Mandateの管理はWallet Serviceによって行われます。Wallet Service は、あんしん支払い設定など、お客さま固有の設定や支払い権限を管理する基盤サービスです。&lt;/p&gt;
&lt;h4&gt;決済サービスによる必須の Mandate 検証&lt;/h4&gt;
&lt;p&gt;オフセッション決済はお客さまの操作を伴わないため、何よりも安全性が求められます。Mandate が存在しない、あるいは範囲外の決済が誤って実行されることは絶対に避けねばなりません。&lt;br /&gt;
私たちはその安全性を担保するため、決済作成 API（&lt;code&gt;CreateCharge&lt;/code&gt;）に Mandate 検証ロジックを統合しました。&lt;br /&gt;
クライアントはオフセッションとして決済を実行する意図を示すために &lt;code&gt;mode=off_session&lt;/code&gt; を指定して &lt;code&gt;CreateCharge&lt;/code&gt; を呼び出し、Mandate の有無を事前に確認する必要はありません。&lt;br /&gt;
Payment Service は &lt;code&gt;mode=off_session&lt;/code&gt; を受け取ると、Wallet Serviceの&lt;code&gt;CheckMandateExistence&lt;/code&gt; APIを呼び出し同期的に検証を問い合わせます。Mandate が存在し、スコープ内で有効なものであれば決済を実行し、そうでなければ即座にエラーを返して処理を中断します。&lt;br /&gt;
こうしてプラットフォームが Gatekeeper として機能することで、サービス側の実装に依存しない堅牢な安全性を確保しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/561bc8fd--2025-12-16-18.34.52.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;決済チェックアウトソリューションで Mandate-Free な開発者体験を実現する&lt;/h2&gt;
&lt;p&gt;Off-session modeのCreateChargeでは、クライアントはMandateの存在を意識せず使用することができます。しかし、サービス契約時には Mandate関連のAPI 呼び出しを実装する必要があります。つまり、サービス側のエンジニアはmandateの作成・削除などライフサイクルに関する仕様を理解し、実装しなければいけません。&lt;/p&gt;
&lt;p&gt;そこで Payment Platform 側では、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250605-bf42ce60cf/&quot;&gt;決済チェックアウトソリューション&lt;/a&gt;と組み合わせることで、クライアントが Mandate の API 仕様を意識せずに済む「Mandate-Free」な開発体験の実現を目指しました。&lt;br /&gt;
メルペイのチェックアウトソリューションは、決済向けの共通チェックアウト画面を提供する仕組みとして開発されました。決済 UI や 3DS 対応をプロダクトが個別に実装する必要がなくなり、基盤側で統一的に提供できるようになったものです。&lt;br /&gt;
さらに今回、このチェックアウトソリューションに &lt;code&gt;setup mode&lt;/code&gt; を新設し、支払い手段の登録フローも一括で管理できるようにしました。setup mode のチェックアウトソリューションを通じてお客さまがサービス利用のための支払い手段を登録すると、内部でMandate関連APIを呼び出し、Wallet Serviceに Mandate を作成・更新します。&lt;/p&gt;
&lt;p&gt;結果として、クライアントはお客さまに支払い方法を設定させるために チェックアウトソリューション を呼び出すだけでよく、以降の継続課金も &lt;code&gt;mode=off_session&lt;/code&gt; の &lt;code&gt;CreateCharge&lt;/code&gt; を呼ぶだけで完結します。Mandateの検証やスコープチェックは Payment Platform 側が強制するため、クライアントは決済実行時に必要となる細かな権限管理ロジックを扱う必要がなくなります。&lt;/p&gt;
&lt;h2&gt;メルカリモバイルにおける実践&lt;/h2&gt;
&lt;p&gt;Mandate と チェックアウトソリューションの統合は、現在メルカリモバイルのクレジットカード決済で本番運用されています。&lt;br /&gt;
お客さまはサービス契約時に一度だけ チェックアウトソリューション  を通じてクレジットカードを支払い手段として登録します。登録後、クレジットカードは内部的に Mandate と紐づけられ、以降の毎月の料金請求はオフセッションで自動的に処理されます。お客さまが特別な操作をすることはありません。&lt;br /&gt;
メルカリモバイル開発者にとっても、複雑なカード登録フローや Mandate 管理といった重たい実装から解放され、「契約時に チェックアウトソリューション を使用する」「毎月 &lt;code&gt;off_session&lt;/code&gt; で &lt;code&gt;CreateCharge&lt;/code&gt; を呼ぶ」という最小限の実装だけで安全な継続課金が実現できています。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;本記事では、メルカリの多様な支払い手段と複雑なビジネス要件を背景に、将来の支払い権限を管理する基盤としての Mandate と、その Mandate をお客さまにも開発者にも意識させない形で統合した チェックアウトソリューション（setup mode） の実践を紹介しました。&lt;/p&gt;
&lt;p&gt;明日の記事は@Minatoさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>TiDB Resource Groupでワークロードを制御する</title><link>https://engineering.mercari.com/blog/entry/20251211-3846ed440d/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251211-3846ed440d/</guid><description>&lt;p&gt;こんにちは。Mercari Ads teamのEngineering Managerの@ogataka50です。 この記事は、Mercari Advent Calendar 2025 の15日目の記事です。 1. はじめ [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 15 Dec 2025 11:00:06 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。Mercari Ads teamのEngineering Managerの&lt;a href=&quot;https://x.com/ogataka50&quot;&gt;@ogataka50&lt;/a&gt;です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の15日目の記事です。&lt;/p&gt;
&lt;h2&gt;1. はじめに&lt;/h2&gt;
&lt;p&gt;Mercari Ads では、メインの DB として TiDB を採用しています。HTAP（Hybrid Transactional and Analytical Processing）により、広告設定などのオンライン処理（OLTP）と、impression・conversion の集計などのバッチ処理（OLAP）を単一クラスタで運用しています。しかし、ワークロードの多様化とトラフィック増加に伴い、リソース競合による性能劣化が次第に顕在化しました。具体的には次のような事象です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;バッチ処理や集計処理が TiDB に過度な負荷をかけると、広告管理ツール（Ads Manager）の操作のレスポンスが悪化し、サービス品質（UX）に悪化&lt;/li&gt;
&lt;li&gt;逆に、オンライン処理が重くなったときにバックグラウンドのレポート集計やバッチ処理が大幅に遅延。これによりレポートの生成やデータ更新が遅れ、期待どおりの動作にならない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このような異なるワークロード同士のリソース競合によって、システムの安定性やパフォーマンスが低下する課題に直面していました。&lt;/p&gt;
&lt;p&gt;そこで、TiDB の Resource Group を使って、用途（オンライン／バッチ／レポートなど）ごとにリソースを論理分離し、干渉しないよう制御することにしました。&lt;/p&gt;
&lt;p&gt;本記事では、Resource Group の概要と、どのように導入を進めたかを共有したいと思います。&lt;/p&gt;
&lt;h2&gt;2. Resource Control の概要&lt;/h2&gt;
&lt;p&gt;Resource Group は、TiDB におけるリソース制御機能のひとつで、論理的な「グループ 」に分け、各グループに対して CPU／I/O／ストレージへのアクセス量を制御できます。&lt;br /&gt;
この制御の単位となるのが Request Unit (RU)になります。RU は CPU 時間やディスク I/O、IOPS など複数リソースの消費量を統合して評価する抽象的な単位です。&lt;br /&gt;
TiDB の公式ドキュメントでは以下のように &lt;a href=&quot;https://docs.pingcap.com/tidb/stable/tidb-resource-control-ru-groups/#what-is-request-unit-ru&quot;&gt;RU の消費定義&lt;/a&gt;が示されています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/66ee25a0-mermaid-chart-create-complex-visual-diagrams-with-text.-2025-12-11-021308.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Resource Group を使い、複数のワークロードで必要なTiDBのリソースを論理的に分離して、リソース競合を防ぎつつ、必要に応じてリソースを割り当て直すことができます。具体的には以下のような処理です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;オンライン処理には高めの RU を割り当てる + 高優先度で処理させる&lt;/li&gt;
&lt;li&gt;バッチ／分析には必要最小限の RU を割り当て、低優先度で処理させる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この仕組みによって、オンライン処理のレイテンシを維持しつつ、バッチや分析処理も同時に安定して実行できるようにすることを目指しました。&lt;/p&gt;
&lt;h2&gt;3. 実際の導入方法&lt;/h2&gt;
&lt;h3&gt;3.1 サービスごとに SQL ユーザーと Resource Group を分離&lt;/h3&gt;
&lt;p&gt;Mercari Ads では、各マイクロサービス (広告管理 UI、レポート生成バッチ、分析サービスなど) ごとに SQL ユーザーを分け、それぞれに専用の Resource Group を割り当てるようにしました。&lt;br /&gt;
クエリ単位でヒントを使って Resource Group を設定することも可能ですが、多数のサービス／クエリが混在すると運用が煩雑になりやすいため、SQLユーザー単位での分離することから始めました。&lt;/p&gt;
&lt;h3&gt;3.2 Resource Group の定義例&lt;/h3&gt;
&lt;p&gt;まずは Resource Group を作り、それを SQLユーザーに紐付けるような流れになります。&lt;br /&gt;
次の SQL で Resource Group を作成します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- create Resource Group
CREATE RESOURCE GROUP IF NOT EXISTS rg_online
  RU_PER_SEC = 2000
  PRIORITY = HIGH
  BURSTABLE;

CREATE RESOURCE GROUP IF NOT EXISTS rg_batch
  RU_PER_SEC = 500
  PRIORITY = LOW;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;RU_PER_SEC: 1 秒あたりに補充される RU の量 (トークン・バケットの回復速度) を指定&lt;/li&gt;
&lt;li&gt;PRIORITY: ストレージ層 (TiKV / TiFlash) 側でのタスク優先度 — 高優先度なら他より先に処理される&lt;/li&gt;
&lt;li&gt;BURSTABLE: 状況に応じて余剰リソースの利用を許可する設定。負荷が低いタイミングでは余裕を使える&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;どの Resource Group をどの SQL ユーザーに割り当てるかを、以下の SQL で設定します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- bind Resource Group and SQL User
ALTER USER &amp;#039;ads_service_sql_user&amp;#039; RESOURCE GROUP rg_online;
ALTER USER &amp;#039;batch_job_sql_user&amp;#039; RESOURCE GROUP rg_batch;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;optimizer hintsを追加することでSQL単位でResource Group を指定することも可能です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- SQL 単位で Resource Group を指定する例
SELECT /*+ RESOURCE_GROUP(rg_name) */ * FROM table_name;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 モニタリング → 設定の見直しサイクル&lt;/h3&gt;
&lt;p&gt;次のようなサイクルで適切な設定値の調整をしていきました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;まずは制限なしのRU_PER_SEC (default / unlimited 相当) での Resource Group を作成し、各サービスの「通常負荷時」のリソース消費を観測
&lt;pre&gt;&lt;code&gt;CREATE RESOURCE GROUP IF NOT EXISTS rg_monitoring
RU_PER_SEC = 2147483647;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;1週間程度監視し、通常時のRU 使用量、I/O／CPU 負荷、クエリ数などを把握する&lt;/li&gt;
&lt;li&gt;用途 (オンラインかバッチなど) に応じて RU_PER_SEC・PRIORITY・BURSTABLE を設定し直す
&lt;ul&gt;
&lt;li&gt;オンライン処理: RU_PER_SEC 高め / HIGH 優先度、必要なら BURSTABLE を使うことを検討&lt;/li&gt;
&lt;li&gt;バックグラウンド(バッチ／分析)処理: RU_PER_SEC 控えめ / LOW 優先度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;このサイクルを継続し、各 Resource Group の RU_PER_SEC を徐々に適正化しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/92c57137-screenshot_2025-12-11_at_6_28_44.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;4. 平均値ベースだけではなく、瞬間的な RU の消費も考慮する&lt;/h2&gt;
&lt;p&gt;Resource Group を SQL ユーザーごとに分け、RU_PER_SEC の設定を進めていく中で、意図通りにいかなかったケースがありました。あるサービスは、平常時はほとんど TiDB にアクセスしないのですが、ユーザが期間の長い広告レポートをリクエストした際にだけ、大量のデータを読み取る集計処理が発生し、瞬間的に多くの RU を必要とするケースがありました。このような場合平均値ベースの RU 消費は小さく見えます。しかし実際には、1 回のリクエストで数千〜数万 RU を消費するようなスパイクがあり、平均値を基準に RU_PER_SEC を設定していたため、このスパイク時に上限に達してしまい、必要な RU を確保できず著しくパフォーマンスが低下する状況が生まれてしまいました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/1ffdc554-screenshot_2025-12-11_at_6_34_24.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;そのため、RU_PER_SEC の設定では平均値だけではなく、瞬間的なスパイクにも対応できるよう考慮する必要があります。こうしたユースケースでは、必要に応じて BURSTABLE を有効化し、一時的に余剰リソースを利用できるようにすることも効果的です。&lt;/p&gt;
&lt;h2&gt;5. Runaway Query の制御と管理&lt;/h2&gt;
&lt;p&gt;Resource Group によるリソース分離だけでなく、想定以上にリソースを消費するクエリを制御、管理する機能があります。&lt;br /&gt;
Resource Group 定義に、QUERY_LIMIT オプションを追加することで、あるクエリが長時間実行される、または過剰にリソースを消費するような クエリを検知し、強制終了 (KILL)、優先度変更といった制御が可能です。&lt;br /&gt;
TiDB では、このような予想以上にリソースを消費するクエリを Runaway Query と呼んでいます。&lt;br /&gt;
たとえば、以下のように定義することで、「30秒以上実行されるクエリは自動的にkillする」などの制御ができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- configure QUERY_LIMIT on resource group
CREATE RESOURCE GROUP rg_online_limited
  RU_PER_SEC = 10000
  QUERY_LIMIT = (EXEC_ELAPSED = &amp;#039;30s&amp;#039;, ACTION = KILL);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こうした制御を入れることで、意図せぬ重たいクエリや無限ループ／誤った SQL による極端なリソース消費や、他ワークロードへの影響を防ぐ一助になります。&lt;/p&gt;
&lt;p&gt;発生した Runaway Query は、次の SQL で確認できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- select Runaway Query by Resource Group
SELECT *
FROM mysql.tidb_runaway_queries
WHERE resource_group_name = &amp;#039;rg_online_limited&amp;#039;
ORDER BY start_time DESC
LIMIT 100;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. まとめ &amp;amp; 今後の展望&lt;/h2&gt;
&lt;p&gt;Resource Groupの導入によって、オンライン処理に必要な RU を安定して確保できるようになり、Ads Managerのレイテンシがバックグラウンド処理の影響を受けにくくなりました。また、バックグラウンド処理側も過剰にリソースを奪わない範囲で着実に実行されるようになり、ワークロード間の競合による性能劣化が減少しました。&lt;br /&gt;
実施した内容をまとめると、以下の通りです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;現状のワークロード把握
&lt;ul&gt;
&lt;li&gt;各SQL user(≒サービス)ごとにどのようなシステム要求があるかリストアップ (オンライン／バッチ／Background／分析用途など)&lt;/li&gt;
&lt;li&gt;最初は制限なし (default / UNLIMITED 相当) で動かし、どれくらい RU を消費するか観測&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;モニタリング期間 (数日〜1週間)
&lt;ul&gt;
&lt;li&gt;通常時のRU 消費を計測&lt;/li&gt;
&lt;li&gt;特にバッチや分析クエリのピーク時の消費量に注意する&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ワークロードごとに Resource Group の設定値を設定
&lt;ul&gt;
&lt;li&gt;オンライン処理: 応答性重視 → RU_PER_SEC を比較的高め、PRIORITY = HIGH、必要なら BURSTABLE を使うことを検討&lt;/li&gt;
&lt;li&gt;バックグラウンド処理: RU_PER_SEC を控えめ、PRIORITY = LOWで必要以上のリソースを消費しないようにした&lt;/li&gt;
&lt;li&gt;必要に応じて Runaway 制御 (QUERY_LIMIT) を設定&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;継続的な運用とレビューサイクル
&lt;ul&gt;
&lt;li&gt;定期 (週次／月次) で RU 消費状況、遅延・タイムアウト・失敗のログをチェック&lt;/li&gt;
&lt;li&gt;発生したRunaway Queryの確認&lt;/li&gt;
&lt;li&gt;必要に応じてResource Group 設定を調整&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;今後は引き続き下記のような改善、調整を行っていこうと考えています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;スパイクが発生するケースでは、一時的に割当を調整するなど、Resource Group と Runaway 制御を組み合わせた運用改善&lt;/li&gt;
&lt;li&gt;定期レビューの自動化。どのサービスがどれくらい RU を使っているか／どの程度余裕があるかなどを可視化と調整&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本記事が TiDB 運用の一助となれば幸いです。&lt;/p&gt;
&lt;p&gt;明日の記事は @Snehaさんです。引き続きお楽しみください。&lt;/p&gt;
&lt;h2&gt;7. Refs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/tidb-resource-control-ru-groups/&quot;&gt;Use Resource Control to Achieve Resource Group Limitation and Flow Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/sql-statement-create-resource-group/&quot;&gt;CREATE RESOURCE GROUP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pingcap.com/tidb/stable/tidb-resource-control-runaway-queries/&quot;&gt;Manage Queries That Consume More Resources Than Expected (Runaway Queries)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pingcap.com/blog/tidb-resource-control-workload-consolidation-transactional-apps/&quot;&gt;TiDB Resource Control: Stable Workload Consolidation of Transactional Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pingcap.com/blog/managing-resource-isolation-optimizing-performance-stability-tidb/&quot;&gt;Managing Resource Isolation: Optimizing Performance Stability in TiDB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>メルカリAdsが広告を届けるまでの話</title><link>https://engineering.mercari.com/blog/entry/20251204-563130cd63/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251204-563130cd63/</guid><description>&lt;p&gt;はじめに こんにちは、Ads Servingチームでバックエンドエンジニアをしている@yanapです。 この記事は、Mercari Advent Calendar 2025の14日目の記事です。 メルカリは 2025年1 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sun, 14 Dec 2025 11:00:26 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;はじめに&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;こんにちは、Ads Servingチームでバックエンドエンジニアをしている@yanapです。&lt;/p&gt;
&lt;p&gt;この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;&lt;strong&gt;Mercari Advent Calendar 2025&lt;/strong&gt;&lt;/a&gt;の14日目の記事です。&lt;/p&gt;
&lt;p&gt;メルカリは 2025年10月時点で 月間 2,305 万人 のお客さまに利用されており、検索や閲覧などの操作に合わせて広告が表示される「メルカリAds」にも、毎日非常に多くの広告リクエストが届きます。&lt;br /&gt;
広告候補は用途ごとに多数存在し、その中から最適な広告を選び出す必要があります。&lt;br /&gt;
しかし、どれだけ処理が複雑であっても、広告は 数百ミリ秒以内 に返さなければ検索体験を損ねてしまいます。&lt;br /&gt;
大規模なトラフィックの中で高速に広告を選定する仕組みを成立させるのは、簡単ではありません。&lt;br /&gt;
お客さまがメルカリアプリで「トマト」と検索したその瞬間、裏側では広告配信のための小さな通信が静かに動き始めています。&lt;br /&gt;
その通信を受け取り、複数の内部サービスと連携しながら最適な広告を短時間で選定して返す仕組みを、Ads Serving チームが担当しています。&lt;br /&gt;
本記事では、この広告配信フローの全体像と、低レイテンシを維持するための工夫について紹介します。&lt;/p&gt;
&lt;h2&gt;広告がどこに表示されるか&lt;/h2&gt;
&lt;p&gt;まずは、メルカリAdsが実際にお客さまの目にどのように触れるのかを見てみましょう。この記事では、広告配信の仕組みそのものにスポットを当てていますが、こうした広告がメルカリアプリ内のどこに表示されるのかを具体的に知ることで、その仕組みをより身近に感じてもらえるかと思います。&lt;br /&gt;
メルカリAdsは、以下のような場所に表示される仕組みになっています。&lt;/p&gt;
&lt;h3&gt;検索結果画面&lt;/h3&gt;
&lt;p&gt;お客さまが検索したキーワードに関連する広告が、検索結果リストの上部や一定間隔ごとに表示されます。&lt;br /&gt;
検索体験を邪魔しないよう、通常の商品リストと自然に馴染むようにデザインされています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/c15e54e7-ad-request-page-5-274x300.png&quot; alt=&quot;MercariAdsSerp&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;商品詳細ページ&lt;/h3&gt;
&lt;p&gt;「この商品を見ている人におすすめ」ブロック内に、関連性の高い広告が表示されます。&lt;br /&gt;
閲覧中の商品や検索履歴などをもとに、興味を持ちそうな商品を提示しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/b59303d2-ads3-135x300.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリアプリでは、このようにお客さまの操作や閲覧内容に合わせて、自然に広告が挿入されるように設計されています。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;メルカリAdsの広告配信の仕組み&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;ここからは、メルカリAdsで広告がどのように選ばれ、短時間で返却されているのかを紹介します。&lt;br /&gt;
まずは中核となる AdServer を軸に「広告が返ってくるまでの流れ」を見ていきます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/6de2d37f-ad-request-page-4-2-300x134.png&quot; alt=&quot;Ads Flow&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;広告リクエストの開始&lt;br /&gt;
検索操作やページ遷移などをきっかけに、広告を表示するための通信が AdServer に送信されます。&lt;br /&gt;
広告枠の位置、検索キーワード、閲覧中の商品情報など、表示に必要な情報がここに含まれます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AdServer の役割：最適な広告を選ぶ司令塔&lt;br /&gt;
AdServer は、広告配信フローにおける 中心的な役割を担うサーバー です。&lt;br /&gt;
操作に応じて送られた広告リクエストを受け取り、「このお客さまには今どの広告を出すべきか？」を判断します。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;そのために、AdServer は裏側にある複数のサービスに問い合わせて「広告候補」を収集します。&lt;br /&gt;
収集された候補は、ターゲティング条件・配信設定・予算状況などにもとづいてフィルタリングされ、最終的にユーザーに適した少数の広告が選ばれて返却されます。&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;表示位置に合わせた広告の返却&lt;br /&gt;
検索結果画面では、広告の位置や頻度は画面や文脈に応じて細かく制御されています。&lt;br /&gt;
選ばれた広告は、その時点の仕様に基づき、適切な位置としてクライアントに返されます。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;高速な広告配信を支える仕組み&lt;/h2&gt;
&lt;p&gt;ここからは、メルカリAdsの広告配信が「なぜこれだけ複雑なのに高速なのか」を紹介します。&lt;/p&gt;
&lt;p&gt;広告リクエストが AdServer に届くと、最適な広告を選ぶために複数の内部サービスと連携して処理が進みます。&lt;br /&gt;
このように重い処理ですが、実際には 多くの工夫によって数百ミリ秒で完了するように最適化されています。&lt;br /&gt;
数百ミリ秒、つまり0.数秒です。まばたき1回分の時間ですね。&lt;br /&gt;
この短い時間で、多数のサービスが連携し、広告が選ばれて返却されます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;多数の広告候補生成ロジックを&amp;quot;並列&amp;quot;で動かす&lt;br /&gt;
AdServer の裏側では、用途ごとに分かれた「広告候補生成サービス」が&lt;br /&gt;
複数存在しています（この仕組みを社内では「Demand」と呼んでいます）。&lt;br /&gt;
なぜ複数のサービスに分かれているのでしょうか？&lt;br /&gt;
それは、広告を探す「手がかり」が状況によって異なるからです。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;たとえば、以下のようにそれぞれの広告製品ごとに専用の候補生成ロジックが動いています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Product Ads（商品広告）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;広告主が商品データフィードを連携することで、多品目を効率的に広告配信できる製品&lt;/li&gt;
&lt;li&gt;システム側では、メルカリの商品データベースと連携して広告候補を探索&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infeed Ads（インフィード広告）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;広告主が画像やテキストクリエイティブを入稿して配信する製品&lt;/li&gt;
&lt;li&gt;ユーザーのデモグラフィック情報を用いたターゲティング配信が可能&lt;/li&gt;
&lt;li&gt;システム側では、MLモデルを使って、ユーザーの興味に合った広告を推薦&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shops Ads（メルカリShops広告）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;メルカリShopsの店舗向けに特化した広告製品&lt;/li&gt;
&lt;li&gt;ショップ商品を効果的にプロモーション&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C2C Ads（メルカリC2C広告）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;メルカリの個人間取引（フリマ出品）を対象とした広告&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらのサービスは、AdServer のリクエストを受けて すべて並列に処理を開始し、広告候補を生成します。&lt;/p&gt;
&lt;p&gt;そして異なる広告抽出ロジックを持った各Demandを複数のmicroserviceに分割し、AdServerから並列で呼び出すことで、短時間で大量の広告候補を抽出することを実現しています。&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;配信設定や予算チェックの最適化&lt;br /&gt;
広告配信では広告を出稿している広告主が、配信の上限予算や期間を設定できます。AdServerで広告を返却する前に、これらが有効かどうか確認する必要があります。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ここで重要なのが、データの性質による使い分けです。&lt;br /&gt;
&lt;strong&gt;配信設定（ON/OFF）&lt;/strong&gt;&lt;br /&gt;
→ 頻繁には変わらないデータ&lt;br /&gt;
→ キャッシュ（一度取得した情報を保存）を使って高速化&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;予算残高&lt;/strong&gt;&lt;br /&gt;
→ 刻一刻と変化するデータ&lt;br /&gt;
→ 毎回リアルタイムで確認（ただし、タイムアウト時は柔軟に対応）&lt;/p&gt;
&lt;p&gt;つまり、「変化の速度」に応じて最適な取得方法を選んでいるわけです。&lt;/p&gt;
&lt;p&gt;重い判断処理であっても、データの性質に応じて最適な取得方法を使い分けることで、レイテンシを最小限に抑えています。&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;
&lt;p&gt;AIによる最終的な並び替え&lt;br /&gt;
レスポンスを返す直前には、AIによるスコアリングによって広告を「どの順序で表示するか」を最適化します。&lt;br /&gt;
ここでも軽量なモデルやキャッシュが利用され、処理の高速化が図られています。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;表示に必要な情報の組み立て&lt;br /&gt;
最終的なレスポンスを返すために、広告として表示するタイトル・画像などの詳細情報を取得し、クライアントがそのまま描画できる形に整えます。&lt;br /&gt;
ここでもキャッシュの活用によって、追加の遅延を抑えています。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;本記事では、メルカリAdsにおける広告配信の仕組みを紹介しました。&lt;br /&gt;
メルカリAdsが高速に動作しているのは、次のような設計思想のもと、複数のサービスが連携しているためです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用途別に分かれた複数の広告候補生成ロジックを並列で呼び出せる構造&lt;/li&gt;
&lt;li&gt;重い処理を複数のサービスに分散し、AdServer が統合して返すアーキテクチャ&lt;/li&gt;
&lt;li&gt;配信設定・商品情報など、頻繁に参照されるデータはキャッシュ前提で高速化&lt;/li&gt;
&lt;li&gt;予算情報はリアルタイム取得で正確性を保ちつつ、タイムアウト時は柔軟に対応&lt;/li&gt;
&lt;li&gt;AIスコアリングも軽量化・キャッシュにより遅延を抑制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの工夫により、検索結果が表示されるころには最適な広告が数百ミリ秒以内に返却されているという体験が実現されています。&lt;/p&gt;
&lt;p&gt;日々の開発を通してこの仕組みを理解していく中で、システムの設計思想や高速化の工夫に触れられたのは貴重な経験でした。&lt;/p&gt;
&lt;p&gt;今後もメルカリAdsは進化を続けていくと思います。&lt;br /&gt;
この記事が、メルカリAdsの仕組みに興味を持つきっかけになればうれしいです。&lt;/p&gt;
&lt;p&gt;最後までお読みいただき、ありがとうございました。&lt;/p&gt;
&lt;p&gt;明日の記事は @ogataka50さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>n8n &amp;#8211; 乱立するAIツール中でPoCを成功させるために考えたこと・実際に取り組んだこと</title><link>https://engineering.mercari.com/blog/entry/20251213-862c05648c/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251213-862c05648c/</guid><description>&lt;p&gt;こんにちは。メルペイ Manager of Managers の @abcdefuji です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の14日目の記事です。 アド [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sun, 14 Dec 2025 10:00:05 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイ Manager of Managers の &lt;a href=&quot;https://x.com/\_abcdefuji&quot;&gt;@abcdefuji&lt;/a&gt; です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の14日目の記事です。&lt;/p&gt;
&lt;p&gt;アドベントカレンダー内のn8nの連載企画の最終日です。&lt;/p&gt;
&lt;p&gt;本記事では、メルカリにおける &lt;strong&gt;n8n Enterprise 導入の PoC（Proof of Concept）をどのように成功させたか&lt;/strong&gt; を紹介します。&lt;/p&gt;
&lt;p&gt;近年、生成 AI の爆発的な普及により、企業内には日々膨大な AI ツールが流れ込んでいます。コード生成、文章要約、データ連携、AI エージェント、自動化、その他&lt;strong&gt;毎日どこかで新しいツールが誕生し利用されている状態&lt;/strong&gt; です。&lt;/p&gt;
&lt;p&gt;この状況が生む課題はシンプルであり、深刻です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PoC ツールが乱立する&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;情報アップデートに追いつけず、すべてを触り切る余裕がない&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PoC を始めても Adoption（定着）が進まない&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;評価するチームも、本業と並行では十分にリソースを割けない&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;こうした課題は、多くの企業で共通しているはずです。&lt;/p&gt;
&lt;p&gt;メルカリでもn8n の PoC は、このような“AIツールが溢れすぎる時代”の中で始まりました。&lt;br /&gt;
その中で、どのように導入まで至ったのか。&lt;/p&gt;
&lt;p&gt;最初に結論を言うと&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;n8n PoC の成功に必要だったのは、技術そのものではなく、“組織として AI をどう扱い、どう広げていくかを PoC で学んでいく姿勢” でした。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;これは、ツールの良し悪しを評価する PoC ではなく、 &lt;strong&gt;“AIツールを組織に根づかせる方法を見つけるための PoC”&lt;/strong&gt; だったのだと思います。&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;1. PoC を始める前に見えていた背景&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;メルカリでは、社内から100名を越えるメンバーが選出された横断組織「AI Task Force」 によって &lt;strong&gt;約4,000の業務プロセスが可視化&lt;/strong&gt;されています。&lt;br /&gt;
その多くは AI や自動化によって効率化できる余地がありました。&lt;/p&gt;
&lt;p&gt;AI Task Forceの詳細は下記の記事をご確認ください。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://careers.mercari.com/mercan/articles/53708/&quot;&gt;メルカリが本気で始めた「AI-Native」化。100名規模のタスクフォースが立ち上がるまで&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://careers.mercari.com/mercan/articles/56200/&quot;&gt;「AI Task Force」で変化を加速する。CTO @kimurasが描くメルカリの成長戦略&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;しかし現場には次のような課題があり、どの組織でも PoC が途中で止まってしまう理由にもなっています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;部署によって使うツールがまったく違う  &lt;/li&gt;
&lt;li&gt;非エンジニアが参加しづらい  &lt;/li&gt;
&lt;li&gt;セキュリティ観点の懸念が大きい  &lt;/li&gt;
&lt;li&gt;PoC の担当者には本業があり、専念できない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このままでは自動化が前に進みません。&lt;/p&gt;
&lt;p&gt;そこで私たちは、組織全体の“共通基盤”となる自動化プラットフォームとして、n8n の PoC を開始しました。&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;2. PoC が始まってすぐに見えてきた“勢い”&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;n8n を試した初期段階から、複数チームで自然にワークフローが作られはじめました。&lt;br /&gt;
気づくと次のような利用データが観測されていました&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/e546ad74--2025-12-13-22.57.14-1024x443.png&quot; alt=&quot;worlfow volume&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;metrics&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;週あたりの実行数&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;約 13,000 回&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;月あたりの実行数&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;約 40,000 回&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;PoC の段階にも関わらず、すでに “実務の中に入り始めている” という手応えがありました。&lt;/p&gt;
&lt;p&gt;ただし、数字だけで PoC がうまくいったわけではありません。&lt;br /&gt;
ここからは、&lt;strong&gt;どのように PoC を設計し、実行したか&lt;/strong&gt; の話になります。&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;3. PoC の最初の一歩は「とにかく触ること」&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;PoC の企画書を書くよりも先にやったことは、ただひたすら &lt;strong&gt;n8n を触る&lt;/strong&gt; ことでした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UI の特徴  &lt;/li&gt;
&lt;li&gt;JSON 構造がどう見えるか  &lt;/li&gt;
&lt;li&gt;どこまで柔軟に作れるか  &lt;/li&gt;
&lt;li&gt;LLM との相性  &lt;/li&gt;
&lt;li&gt;非エンジニアがつまずきそうなポイント&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらは、仕様を読むだけでは分かりません。&lt;br /&gt;
自分で触ってみて初めて、ツールが“どう現場にハマるか”が見えてきます。&lt;/p&gt;
&lt;p&gt;PoC 担当者が誰よりも詳しくなることで、現場と同じ目線で話せるようになります。&lt;br /&gt;
これが後の推進力につながりました。&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;4. n8n PoC は“やる気のある仲間探し”でもあった&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;実際に PoC を動かし始めて感じたのは、&lt;strong&gt;PoC の成否はツールよりも人で決まる&lt;/strong&gt; ということでした。&lt;/p&gt;
&lt;p&gt;今回のコアメンバーは以下の通りです：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;abcdefuji（Backend Manager）&lt;/strong&gt;：PoC 推進、ユースケース伴走  （当ブログ）  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hi120ki（AI Security）&lt;/strong&gt;：脅威モデル、ガードレール設計  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shuuk（AI Task Force）&lt;/strong&gt;：全社プロセスとAdoptionの橋渡し  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISSA（Director）&lt;/strong&gt;：DevEx の視点での全体整理
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-cf3b67a5a7/&quot;&gt;理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない | メルカリエンジニアリング&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mewuto（Backend Eng）&lt;/strong&gt;：静的解析 CLI（DAG / AST）
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-580dc508a7/&quot;&gt;n8nの静的解析CLIツールをOSS化 – JSON解析とDAGで実現するセキュリティチェックの自動化 | メルカリエンジニアリング&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T（SRE）&lt;/strong&gt;：Enterprise 構成、Enginerringの全て
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-6fe0bc0838/&quot;&gt;Making n8n Enterprise-Ready: 企業向けn8nの導入と運用の取り組み | メルカリエンジニアリング&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全員、&lt;strong&gt;本業がありながら隙間時間で参加していました&lt;/strong&gt;。 にもかかわらず PoC が成立したのは、全員に共通して&lt;strong&gt;「これはメルカリ全体にとって価値がある」&lt;/strong&gt;という強い思いがあったからです。&lt;/p&gt;
&lt;p&gt;PoC は仲間が多いほど強くなります。 そして熱量は伝播します。 n8n の PoC はまさにその象徴でした。&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;5. 小さな成功を一緒につくる：ユースケース伴走&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;PoC を成功に導くためには、&lt;strong&gt;早い段階で小さな成功を複数つくること&lt;/strong&gt; が重要です。&lt;/p&gt;
&lt;p&gt;n8n の PoC では、Marketing・QA・SRE・監査・HR などのチームに伴走し、次のような改善が生まれました：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;部署&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;ユースケース&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;効果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Marketing&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;KPI チェックの自動化&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;月 500 分削減&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;QA&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;リリース作業の自動化&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;90 分/週 → 0 分&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Eng&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;AI Agent 開発工数削減&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;2 週間 → 2〜3 日&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Audit&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;情報集約の効率化&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;属人性を大幅に改善&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ここで重要なのは、PoC チームが「代わりに作ってあげる」のではなく、 &lt;strong&gt;一緒にワークフローを作り、一緒に成功する&lt;/strong&gt; という姿勢でした。利用者がわからない時にサポートを行う。これは Adoption を加速する最も強力な手法です。&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;6. セキュリティとガバナンス：PoC で課題が出るほど良い&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;n8n は自由度が高い分、いくつかのリスクもあります。&lt;br /&gt;
PoC のなかで実際に以下のような問題が見つかりました：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;credential の誤設定  &lt;/li&gt;
&lt;li&gt;HTTP Node での不正なアクセス  &lt;/li&gt;
&lt;li&gt;権限混同によるリスク  &lt;/li&gt;
&lt;li&gt;Code Node 内の危険な処理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;しかし、これはむしろ好材料です。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PoC は問題を見つけるための場所&lt;/strong&gt; です。ここで検知できれば、本番導入では安全に運用できます。&lt;/p&gt;
&lt;p&gt;PoC 中に次の仕組みを整備しました：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;External Hook による保存前チェック  &lt;/li&gt;
&lt;li&gt;静的解析 CLI（JSON / DAG / AST）  &lt;/li&gt;
&lt;li&gt;SSO（Okta）  &lt;/li&gt;
&lt;li&gt;Task Runner によるコード実行の隔離  &lt;/li&gt;
&lt;li&gt;ガイドライン・テンプレート&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;こうして、導入時に必要な安全性が PoC のなかで自然と整っていきました。&lt;/p&gt;
&lt;p&gt;詳細はこちら&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-6fe0bc0838/&quot;&gt;Making n8n Enterprise-Ready: 企業向けn8nの導入と運用の取り組み | メルカリエンジニアリング&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-580dc508a7/&quot;&gt;&lt;strong&gt;n8nの静的解析CLIツールをOSS化 – JSON解析とDAGで実現するセキュリティチェックの自動化 | メルカリエンジニアリング&lt;/strong&gt;&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-cf3b67a5a7/&quot;&gt;理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない | メルカリエンジニアリング&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;strong&gt;7. 「ROI は罠」 ── 短期回収で PoC を評価してはいけない&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;PoC ではよく ROI が議論されますが、短期回収だけで判断すると誤った評価になりがちです。&lt;/p&gt;
&lt;p&gt;AI や自動化は &lt;strong&gt;「導入しない」 という選択肢のほうが損をする&lt;/strong&gt; 世界に入っています。&lt;/p&gt;
&lt;p&gt;参考: &lt;a href=&quot;https://careers.mercari.com/mercan/articles/57409/&quot;&gt;“BoldなAI活用”から1年。人間の限界を超えていく、メルカリの「AI-Native」な現在地&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;そのため私たちは、次のような前提で PoC を進めました：&lt;strong&gt;導入するかどうか、ではなく、どう導入すれば成功するかだけを議論する。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数字の比較は必要ですが、まず見るべきは “成果ベースの数字” です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;再現性のある成功事例がどれだけあるか  &lt;/li&gt;
&lt;li&gt;非エンジニアでも価値を出せているか  &lt;/li&gt;
&lt;li&gt;エラー削減、工数削減の実績  &lt;/li&gt;
&lt;li&gt;全社展開できる安全性があるか&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらが確認できて初めて、年間コストや ROI 試算の意味が出てきます。&lt;/p&gt;
&lt;p&gt;また、n8nにはEstimated time saved機能があります。&lt;/p&gt;
&lt;p&gt;1 workflow毎にどの程度時間を削減できたかの見積もりを設定する機能です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/64ce725d-screenshot-2025-12-12-21.28.28-1024x510.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;設定後、以下のようにDashboardから各Workflowで設定された削減時間を確認することができます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/4f58f658--2025-12-12-13.39.56-1024x370.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;中長期的にはこのようにn8nの効果測定をすることも可能です。&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;8. Adoption戦略：広げるには“雰囲気”も必要&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;PoC の後半では、次のような活動をして Adoption を広げていきました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All Hands や社内勉強会で紹介  &lt;/li&gt;
&lt;li&gt;Slack チャンネルで成功事例を共有  &lt;/li&gt;
&lt;li&gt;非エンジニア向けテンプレートの提供  &lt;/li&gt;
&lt;li&gt;質問にはすぐ返す文化づくり&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;気づけば、周囲の人たちが &lt;strong&gt;「ちょっと n8n 触ってみるね」&lt;/strong&gt; と自然に言うようになっていました。&lt;/p&gt;
&lt;p&gt;ツールが広がるには“雰囲気づくり”もとても大事です。&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;9. PoC を成功に導く“再現可能な型”&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;最後に、今回の PoC から得られた成功パターンをまとめます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;まず自分が一番触る  &lt;/li&gt;
&lt;li&gt;効率より熱量のある仲間を見つける  &lt;/li&gt;
&lt;li&gt;小さな成功を早くつくる  &lt;/li&gt;
&lt;li&gt;PoC 中に課題を見つけ、仕組みとして解決する  &lt;/li&gt;
&lt;li&gt;ROI よりも成果ベースの数字を見る  &lt;/li&gt;
&lt;li&gt;テンプレート・ガードレールで安全に広げる  &lt;/li&gt;
&lt;li&gt;Adoption を広げる雰囲気をつくる  &lt;/li&gt;
&lt;li&gt;PoC担当者の覚悟&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これらは n8n に限らず、AI ツール全般の導入で応用できる考え方です。&lt;/p&gt;
&lt;p&gt;PoC担当者には”覚悟”が必要です。達成するべきVisionを信じきる覚悟です。自身がなぜ必要・有効なのかのロジックを持ち、そしてそれを信じて突き進んでください。&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;おわりに&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;n8n PoC を振り返ると、成功を分けたのは技術だけではありませんでした。&lt;/p&gt;
&lt;p&gt;本業の合間に動きながら、皆でアイデアを出し合い、問題を潰し、成功を積み重ねていく。&lt;br /&gt;
その“姿勢”そのものが PoC の最大の価値であり、組織が AI を受け入れる力につながりました。&lt;/p&gt;
&lt;p&gt;PoC はツールのテストではなく、&lt;strong&gt;組織が AI をどう扱うかを学ぶフェーズ&lt;/strong&gt; です。&lt;br /&gt;
これからも改善を続けながら、AI-Native な組織を目指して進んでいきます。&lt;/p&gt;
&lt;p&gt;またどこかでメルカリがどのようなworkflowを構築し活用しているのか紹介できたらと思います。ここまで読んでいただきありがとうございました。&lt;/p&gt;
&lt;p&gt;明日の記事は Timoさんです。引き続きお楽しみください。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;n8n.io logo source: &lt;a href=&quot;https://n8n.io/brandguidelines/&quot;&gt;https://n8n.io/brandguidelines&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>メルカリが、AI時代にナレッジマネジメントに投資したわけ</title><link>https://engineering.mercari.com/blog/entry/20251213-96e00d1d91/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251213-96e00d1d91/</guid><description>&lt;p&gt;こんにちは、メルカリEngineering Officeチームの@thiroiです この記事は、Mercari Advent Calendar 2025 の13日目の記事です。 はじめに Engineering Offi [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sat, 13 Dec 2025 11:02:37 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、メルカリEngineering Officeチームの@thiroiです&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の13日目の記事です。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20231206-4e4f1e2323/&quot;&gt;Engineering Office&lt;/a&gt;は、”Establish a Resilient Engineering Organization.”というミッションを元に、エンジニア組織を横断的に支える役割を担っています。&lt;br /&gt;
今日は組織を裏側で支える仕組みの一つとして、「AI時代のナレッジマネジメント」をテーマに書いていきます&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;組織に情報、ノウハウを蓄積する仕組み、手法をナレッジマネジメントと言います&lt;/li&gt;
&lt;li&gt;ナレッジマネジメントはAIが存在する前、重要ではあるものの、コストパフォーマンスのバランスを取るのが難しいもので、メルカリでも課題を多く抱えていました&lt;/li&gt;
&lt;li&gt;AIの台頭により、ナレッジマネジメントの効率化が進んだことに加え、社内ナレッジAIのコンテキスト利用という新しいユースケースが生まれ、コストパフォーマンスが劇的に向上しました&lt;/li&gt;
&lt;li&gt;メルカリでは、この環境変化の元、AI-Nativeの推進にあたって、ナレッジマネジメントに投資を決定し、いくつかの施策を推進しています&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ナレッジマネジメントって何？&lt;/h2&gt;
&lt;p&gt;ナレッジマネジメントとは、個人の持つ情報、ノウハウを、組織に蓄積、共有する仕組み、手法のことです&lt;/p&gt;
&lt;p&gt;例えば、以下のようなことを感じたことはありませんか&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;過去に似たような障害対応をしたのに、どうやって対応したかがわからない&lt;/li&gt;
&lt;li&gt;現在のAPIの仕様がコードを読まないとわからない&lt;/li&gt;
&lt;li&gt;そもそもなんでこんな設計になったのかわからない&lt;/li&gt;
&lt;li&gt;ちょっと前に可視化に使ったSQLがわからない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの課題に対する解決策は一つではありませんが、ナレッジとして蓄積するということは一つの解決策になります&lt;/p&gt;
&lt;p&gt;過去の学びや、情報を蓄積し、引き出せる状態にすることにより、学び直しや、情報の再構築を防ぐことができます。いわゆる「車輪の再発明を防ぐ」、「巨人の肩の上に立つ」をしよう、ということです。ナレッジを会社の資産として正しく蓄積し、後続するメンバーであったり、未来の自分自身がそれを利用できる状態にすることで、生産性を向上できます。これがナレッジマネジメントの目的です&lt;/p&gt;
&lt;h2&gt;ナレッジマネジメントの課題&lt;/h2&gt;
&lt;p&gt;現場において、ナレッジマネジメントは非常に難しい課題です。程度の差はあれ、社会人として「この情報が見つからない」といった課題にぶつかったことがない人はいないのではないでしょうか。ナレッジマネジメントの課題の分け方はいくつかありますが、ここでは最もよく見られる4分類を利用します&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;記録されない（Create）&lt;/li&gt;
&lt;li&gt;発見できない（Find）&lt;/li&gt;
&lt;li&gt;活用できない（Use）&lt;/li&gt;
&lt;li&gt;更新されない（Maintain）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上から順に、詳しく見ていきましょう&lt;/p&gt;
&lt;h3&gt;1. 「記録されない」&lt;/h3&gt;
&lt;p&gt;もっともわかりやすく、頻繁に見る課題です&lt;br /&gt;
この発生原因は、そもそもナレッジの蓄積をするという習慣がなかったり、記録に対する時間を今取れない、記録のコストが高く感じる、といったものがあります。「後で書く」は私の感覚だと、「コードを後で書き直す」とほぼ同じく、90%以上行われません。書かないとほぼ同義です。特定の時間を取る仕組み（ドキュメントにチームで集中する日をとるなど）がない限りは非常に難しいです&lt;/p&gt;
&lt;h3&gt;2. 「発見できない」&lt;/h3&gt;
&lt;p&gt;問い合わせをしたら、「このページを参考にしてね」と言われた経験はありませんか。これは、該当するナレッジ自体は蓄積されているものの、発見ができないという状態です。&lt;br /&gt;
ドキュメントの検索性が低い、もしくは情報を調べるという文化、環境がないことが主な原因です。ツール自体の検索性能が低かったり、複数のツールにドキュメントが散らばっている場合によく発生します。&lt;/p&gt;
&lt;h3&gt;3. 「活用できない」&lt;/h3&gt;
&lt;p&gt;見つけたものの、それが役に立たない状態です。例えば、書いてある内容がハイコンテキストすぎたり、欲しい情報に対して情報量が多すぎて、読むコストを支払う気になれなかったり、何かしらのクオリティの問題で、読んでもわからないといったことはよく発生します。結果、活用に至らず、担当者に話を聞く、結局ソースコードを見て確認することになり、情報を探したこと自体が徒労に終わります&lt;/p&gt;
&lt;h3&gt;4. 「更新されない」&lt;/h3&gt;
&lt;p&gt;ドキュメントは実体に則してアップデートされる必要があります。例えば、プロダクト開発で画面仕様書やAPI仕様書がある場合、最新機能のアップデートに応じて、これらは更新する必要性があるでしょう。&lt;/p&gt;
&lt;p&gt;明確なプロセスがあったり、APIを外部に公開しているといった事情がない限り、こういったドキュメントのアップデートを必要な時に行うという習慣をもっている人は非常に少ないです。特にドキュメントのオーナーが会社を去った場合などは、多くの場合、良いドキュメントですら管理されず、廃れていきます。&lt;/p&gt;
&lt;p&gt;これはそもそも「ドキュメントやナレッジを資産として、管理対象とする」ということが組織として合意されて、チームが管理する仕事の一部になっていないためです。「更新されない」という問題が発生する際のコストは高くつくことがあります。読み手が問題に気づき、担当者に問い合わせるならまだいい方で、最悪の場合、適説でない情報を元に業務が行われる可能性があるからです。&lt;/p&gt;
&lt;h2&gt;AI時代以前のナレッジマネジメント&lt;/h2&gt;
&lt;p&gt;これらの課題があることを踏まえ、ナレッジマネジメントではどのようなことが発生していたのか考えてみます&lt;/p&gt;
&lt;p&gt;AI時代以前は、情報が増えるほど整理や検索のコストが膨らみ、「一部の人しか知らない」状態が常態化しやすく、特に会社の規模の拡大や歴史の積み重ねによって情報やナレッジが増えていくと、検索難易度や管理難易度があがりやすい環境でした。&lt;br /&gt;
しかしこれらの課題が浮き彫りになっていても、なぜか解決に至らないケースが多く見受けられます。それはコストパフォーマンスの問題が大きかったのだろうと思います。&lt;/p&gt;
&lt;p&gt;ナレッジを適切に管理、保管するのは非常にコストがかかりますし、標準的なプロセスを作るためには、現場の自由度を下げる可能性があるというトレードオフも存在します。多大なコストをかけて標準化を進め、コストを一定し払いそれらの課題を解決しても、ツールとしての検索性能がボトルネックになるかもしれません。&lt;/p&gt;
&lt;p&gt;これらの環境を踏まえると、ナレッジマネジメントに対してどのくらいの投資が適切であるかという判断は難しく、どこまでコストをかけて、どこまでの標準化やメンテナンスを行うのかは、慎重に意思決定をする必要がありました&lt;/p&gt;
&lt;p&gt;メルカリでも、情報が複数のツールに分散し、管理されていないナレッジも多く存在しています。結果、最初にあげた4つの問題がかなり発生している状態で、社内のEngineer向けのSurveyでも、常にナレッジマネジメントは課題のTop3に入っています&lt;/p&gt;
&lt;p&gt;さらに、メルカリグループの大きな特徴として、メルペイ、メルコインをはじめとする金融関連のプロダクトを保有しているという点があげられます。これらは厳格なプロセス、ドキュメンテーションが求められるドメインです。一方マーケットプレイスであるメルカリアプリ自体は、そこまで細かい管理は必要とされません。また、新規事業立ち上げもあるため、これら全てを横断した細かいプロセスで縛る場合、最も厳しいところに合わせる必要があり、それにはデメリットが少なからず伴う上、メルカリの自由度が高い風土とはマッチしづらく、課題は認識しつつも、課題解決には慎重になっていたという現状がありました&lt;/p&gt;
&lt;p&gt;AIはこの環境においてまさにゲームチェンジャーとして現れました&lt;/p&gt;
&lt;h2&gt;AIがナレッジマネジメントにもたらしたもの&lt;/h2&gt;
&lt;p&gt;AIがまずもたらした変革は、ドキュメンテーションの既存業務の圧倒的な生産性向上です。例えば以下のようなものは明確に、様々な課題を低減してくれました。既に多くのエンジニアが体験しているのではないでしょうか&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;議事録を自動で作成してくれる（「記録されない」課題を低減）&lt;/li&gt;
&lt;li&gt;概要を知りたい場合、全文を読まずとも、長い文章の要約をしてくれる（「活用できない」課題を低減）&lt;/li&gt;
&lt;li&gt;検索性が圧倒的に向上し、必要な情報にリーチしやすく（「発見できない」課題を低減）&lt;/li&gt;
&lt;li&gt;既存のコードから仕様の言語化を実行してくれる（「記録されない」課題を低減）&lt;/li&gt;
&lt;li&gt;最新の議論を元に、Product Requirement Documentation (PRD)のアップデートが必要な箇所を見つける（「更新されない」課題を低減）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの利便性の向上はまだしばらく続くでしょう。ナレッジマネジメントツールのAI特化の機能開発により、AIの恩恵をより幅広いユースケースで受けられるようになるはずです&lt;/p&gt;
&lt;p&gt;二つめの変革は、ナレッジ自体の価値があがったということです。ナレッジが人が読むものだけでなく、AIがコンテキストとして使うものになったからです。新しい、非常に大きなナレッジ活用のユースケースが生まれたと言い換えても良いでしょう&lt;/p&gt;
&lt;p&gt;AIは学習元となっている情報、コンテキストに依存します。そのため、仮に会社特有の開発のお作法や、ドメインナレッジがある場合、それをコンテキストとして正しく注入しない限り、それらは考慮されません。「会社のやり方や、ドメイン特有の内容を考慮したいい感じ」のアウトプットが欲しいわけで、そのためにはコンテキストとして、社内のナレッジをきちんと管理し、それがAIに届く状態にする必要があります&lt;/p&gt;
&lt;p&gt;総合すると、AIは&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ナレッジマネジメントを正しく行うコストを劇的に下げて&lt;/li&gt;
&lt;li&gt;ナレッジマネジメントのアウトプットの価値をあげた&lt;br /&gt;
といえます&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大事なことなので、別の表現で言います&lt;br /&gt;
非常にコストがかかって、まぁまぁな価値を出していたナレッジマネジメントは、AI時代において、コストがそこまでかからず、めちゃくちゃ大きな価値を生む業務になったのです&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;つまり、ナレッジマネジメントは、AI時代において、コスとパフォーマンスが爆発的にあがったのです&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;メルカリにおけるナレッジマネジメント戦略&lt;/h2&gt;
&lt;p&gt;メルカリでは、これらの背景を元に、ナレッジマネジメントをAI-Native時代において、重点的に投資すべき領域として定めています。&lt;br /&gt;
その中で推進していることがいくつかありますが、そのうち大きなものを3つほど紹介します&lt;/p&gt;
&lt;h3&gt;1. 全社員共通のAI ReadyなCentral Knowledge Baseの構築&lt;/h3&gt;
&lt;p&gt;まず一つめは、AIとの相性が良く、AIに特化した機能開発が盛んなKnowledge Baseを一つ選定し、そこにナレッジを集約することです。ここで言うKnowledge Baseとは、いわゆる社内Wikiで、社内における情報を集約するためのツールを指します&lt;br /&gt;
メルカリでは、複数のツールを必要に応じて許容しつつも、特段の理由がなければ、全てCentral Knowledge Baseに集める、という方向性を現在進めています。なぜ一つのツール、Central Knowledge Baseに舵を切ったかというと、以下のようなメリットがあるためです&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AIの接続のためのコストを減らせる（ツールが増えるとセキュリティ、運用構築、それらの動作テストなどかなりコストがかかります）&lt;/li&gt;
&lt;li&gt;AI機能含め、ナレッジベースツールに対する習熟度を会社全体としてあげやすい&lt;/li&gt;
&lt;li&gt;メタデータを統一できる（メタデータが異なる＝検索ロジックの複雑性や、管理の標準化の難易度があがる。メトリクスも統一しづらい。）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この方向性を元に、現在、メルカリはNotionをCentral Knowledge Baseとして位置付け、ナレッジの中央管理型への移行を進めています。本記事の主旨と離れるので、細かくは記載しませんが、ツール選定に関しては、フロー情報（議事録など、メンテしない情報）とストック情報の両方に強いという点や、AIとの親和性の高さが大きなポイントでした&lt;/p&gt;
&lt;h3&gt;2. ドキュメントをオープンにする文化の推進&lt;/h3&gt;
&lt;p&gt;せっかく情報が蓄積され、検索性があがっても、それらがAIや、社員がリーチできる状態でないと意味がありません。メルカリでは、良くも悪くも最低限のアクセス権限を付与する文化が多かれ少なかれあり、そもそも知りたい情報へのアクセス権限がないということがあったのです。例として、重要な意思決定に関するミーティングは基本的に参加者以外は見れないという状態でした&lt;/p&gt;
&lt;p&gt;そのため、現在はドキュメントを出来るだけオープンな場所におくための文化作りや仕組み作りをしており、重要な意思決定に関しても、公開範囲の明確化、拡大を推進しています&lt;/p&gt;
&lt;p&gt;もちろんこれは、同時に秘匿性の高い情報の適切な管理が大事になってくるため、 Personally Identifiable Information (PII、いわゆる個人情報)を含む情報などに関しては、管理方法の厳格化、プロセス化を併せて推進しています&lt;/p&gt;
&lt;h3&gt;3. ナレッジ蓄積文化の推進&lt;/h3&gt;
&lt;p&gt;そもそも、ナレッジを蓄積する文化というのは一朝一夕でできるものではありません。なぜそれが今重要なのか、そしてどのように蓄積すべきか、ということを繰り返し発信、推進しています&lt;/p&gt;
&lt;p&gt;メルカリ内でも、ナレッジ蓄積する文化が元々あるという部署もあれば、ナレッジ蓄積自体がそもそもほとんど行われていない部署もあり、温度感はかなりまちまちです&lt;br /&gt;
そのため各組織から一緒に推進してもらうメンバーをアサインしてもらい、現場の温度感に合わせた組織ごとにカスタマイズされたオンボーディングの実行をしたり、新入社員向けのオンボーディングコンテンツにナレッジマネジメントを追加したりといったことをしています&lt;/p&gt;
&lt;h2&gt;今後の展望&lt;/h2&gt;
&lt;p&gt;現状では、AIによる検索性の向上、ドキュメンテーション作成の補助などの恩恵は既に得られているものの、メルカリにおいて、AI-Nativeなナレッジマネジメント環境の整備は、まだまだ推進初期の段階です。課題は盛りだくさん。しかも移行によって、新たに解決すべき課題も発生しており、まだまだ最善の状態までは行き着いていません&lt;/p&gt;
&lt;p&gt;とはいえ、AI-Nativeな環境を作るにおいて、ナレッジマネジメントへの投資は必須だと考えています。このナレッジマネジメントの推進は、単なるナレッジ関連業務の効率化に留まらず、AIを最大限に活かすための土台作りだからです。今回は我々が今取り組んでいるもののベースとなっている考え方、目指している方向について記事にしましたが、来年の今頃には、もう少し結果や、そこからの学びなどを記事にできればと思います。ここまで読んでくださってありがとうございます。&lt;/p&gt;
&lt;p&gt;明日の記事は@yanapさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>n8nの静的解析CLIツールをOSS化 – JSON解析とDAGで実現するセキュリティチェックの自動化</title><link>https://engineering.mercari.com/blog/entry/20251211-580dc508a7/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251211-580dc508a7/</guid><description>&lt;p&gt;こんにちは。メルペイのPayment &amp;amp; Customer PlatformのAccountingチームでBackend Engineerをしている @mewuto（みゅーと）です。 この記事は、Merpay &amp;#038; [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sat, 13 Dec 2025 10:00:41 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイのPayment &amp;amp; Customer PlatformのAccountingチームでBackend Engineerをしている &lt;a href=&quot;https://x.com/mewuto&quot;&gt;@mewuto&lt;/a&gt;（みゅーと）です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の13日目の記事です。&lt;/p&gt;
&lt;h2&gt;要旨&lt;/h2&gt;
&lt;p&gt;メルカリでは、業務自動化プラットフォームとして&lt;a href=&quot;https://n8n.io/&quot;&gt;n8n&lt;/a&gt;を導入していますが、ワークフローの自由度が高い反面、セキュリティリスクも懸念されます。特に「権限混同（Confused Deputy Problem）」は重大な脅威であり、個人の権限を不適切に共有することで、意図しないデータアクセスや操作が可能になってしまいます。&lt;/p&gt;
&lt;p&gt;本記事では、n8nワークフローのセキュリティレビューを自動化するCLIツールの開発と、GitHub Actionsを活用した実際の運用例について解説します。このツールは&lt;a href=&quot;https://github.com/mewuto/n8ncheck&quot;&gt;GitHub&lt;/a&gt;でOSS公開しており、&lt;a href=&quot;https://www.npmjs.com/package/@mewuto/n8ncheck&quot;&gt;npm&lt;/a&gt;からインストール可能です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/e3dc703b-screenshot-2025-12-08-at-4.08.35-1024x314.png&quot; alt=&quot;npm-@mewuto/n8ncheck&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;対象読者&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ワークフロー自動化ツール(n8n、Zapier等)を使用している開発者・セキュリティ担当者&lt;/li&gt;
&lt;li&gt;JSONベースの静的解析やDAGを用いたフロー解析に興味のある方&lt;/li&gt;
&lt;li&gt;業務自動化基盤のセキュリティ強化に取り組んでいる方&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;目次&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;背景：n8n導入とセキュリティ課題&lt;/li&gt;
&lt;li&gt;セキュリティポリシーの定義&lt;/li&gt;
&lt;li&gt;アーキテクチャ概要&lt;/li&gt;
&lt;li&gt;ノードレベル検出の実装例：BigQuery本番環境アクセス検知&lt;/li&gt;
&lt;li&gt;シナリオレベル検出の実装例&lt;/li&gt;
&lt;li&gt;検出の例および社内での活用事例&lt;/li&gt;
&lt;li&gt;成果と今後の展望&lt;/li&gt;
&lt;li&gt;まとめ&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. 背景：n8n導入とセキュリティ課題&lt;/h2&gt;
&lt;h3&gt;n8nとは&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://n8n.io/&quot;&gt;n8n&lt;/a&gt;は、ノーコード/ローコードで業務自動化ワークフローを構築できるオープンソースのプラットフォームです。ZapierやMakeと同様に、異なるサービス間のデータ連携や処理の自動化が可能ですが、n8nはより自由度が高く、柔軟なカスタマイズや複雑なフローの構築ができる点が特徴です。&lt;/p&gt;
&lt;p&gt;メルカリでは、開発者やビジネスチームの業務効率化のため、n8n Enterpriseを導入しました（参考：&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-cf3b67a5a7/&quot;&gt;理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない&lt;/a&gt;）。しかし、その自由度の高さゆえに、以下のセキュリティ課題が浮上しました。&lt;/p&gt;
&lt;h3&gt;Confused Deputy Problem（権限混同）の脅威&lt;/h3&gt;
&lt;p&gt;「Confused Deputy Problem」とはあるシステムにおいて、ユーザーが本来持つ権限よりも大きな権限が設定されているときに、アクセスできないはずのリソースにアクセスできたり変更できてしまう問題です。n8nワークフローでは、権限を持つメンバーのcredentialが組み込まれているため、以下のようなリスクが発生します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;データベースへの意図しないアクセス&lt;/strong&gt;: 本来DBアクセス権を持たないメンバーが、Slackボットを実行することで、権限を持つメンバーのcredentialを通じて本番データベースにアクセスできてしまう&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;機密情報の意図しない漏洩&lt;/strong&gt;: ワークフローが出力した機密情報（スプレッドシート、Slack Publicチャンネルへの投稿等）を、本来アクセス権を持たないメンバーが閲覧できてしまう&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;チーム間での権限境界の破綻&lt;/strong&gt;: 別チームのメンバーが作成したワークフローを実行することで、本来アクセスできない他チームのリソース（社内ドキュメント、ストレージなど）にアクセスできてしまう&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの問題を防ぐため、ワークフロー有効化前の手動レビューを実施していましたが、以下の課題がありました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;レビュー工数の増大と品質の不安定性&lt;/strong&gt;: 1ワークフローあたり、修正依頼や修正後の再レビューを含めて30分〜1時間のレビュー時間が必要。また、セキュリティガイドラインは整備されているものの、詳細な技術ドキュメントを参照しながらワークフローを作成することは容易ではなく、ユーザー自身での自己改善が難しい。加えて、人間によるレビューである以上、見落としのリスクも存在する&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;継続的監視の欠如&lt;/strong&gt;: 承認後もワークフローは自由に変更可能であり、変更後の安全性を保証できない&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これらの課題を解決するため、静的セキュリティ解析ツールの開発に至りました。&lt;/p&gt;
&lt;h2&gt;2. セキュリティポリシーの定義&lt;/h2&gt;
&lt;p&gt;ツール開発にあたり、セキュリティガイドラインを策定しました。主要な要件は以下の通りです。&lt;/p&gt;
&lt;h3&gt;2.1 権限混同の防止&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;入力ソースの制限&lt;/strong&gt;: ワークフローをトリガーできるユーザーを明示的に制限&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;出力先の制限&lt;/strong&gt;: 結果の出力先を閲覧権限を持つユーザー内に限定&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 Slack Bot特有の要件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;実行可能メンバーのホワイトリスト化&lt;/strong&gt;: Slackトリガーには必ずユーザー検証を実装&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;エフェメラルメッセージの使用&lt;/strong&gt;: 機密情報は一時的なメッセージで返す&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Private Channel推奨&lt;/strong&gt;: Public Channelへの出力を制限&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 データの入出力制御&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;本番環境アクセスの明示&lt;/strong&gt;: 本番データへのアクセスは明示的に警告&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;動的クエリの検証&lt;/strong&gt;: 外部入力による動的クエリを検出。動的にSQLクエリを構築すると、本来意図していないテーブルやデータセットにアクセスできてしまうリスクがあるため、最小権限の原則に基づき使用範囲を制限&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;入力値のフィルタリング&lt;/strong&gt;: 外部入力を使用する箇所での検証実装&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;出力先のアクセス権限制御&lt;/strong&gt;: Google Sheetsなどへの出力時は、適切なアクセス権限スコープ（閲覧可能ユーザーの制限）が設定されていることを確認&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらのポリシーを自動検証することで、手動レビューの負担を大幅に軽減できます。&lt;/p&gt;
&lt;h2&gt;3. アーキテクチャ概要&lt;/h2&gt;
&lt;h3&gt;3.1 全体構成と処理の流れ&lt;/h3&gt;
&lt;p&gt;ツールは以下の処理フローで動作します。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;n8nワークフローJSON読み込み&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;静的解析による検証&lt;/strong&gt;:&lt;br /&gt;
2.1. &lt;strong&gt;ノードレベルチェック&lt;/strong&gt;: 個別ノード（BigQuery、HTTP Request、JavaScript Code、Google Sheets/Drive等）の設定を検証&lt;br /&gt;
2.2 &lt;strong&gt;シナリオレベルチェック&lt;/strong&gt;: 複数ノード間の関係性（Slackトリガー後の呼び出しユーザーのバリデーション実装、スプレッドシート出力先の権限範囲等）をDAG（有向非巡環グラフ）を用いて解析。&lt;br /&gt;
※ &lt;strong&gt;なぜDAGが必要か&lt;/strong&gt;: LLMによるチェックは再現性が保証されず、論理的なグラフ解析もまだ未発達です。DAGによる静的解析は、ノード間の実行順序や依存関係を確実に追跡でき、「バリデーション → 外部アクセス」といったセキュリティ上重要な順序関係を100%の精度で検証可能です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;検出結果の出力&lt;/strong&gt;: Console、JSON、PR Comment形式で結果を出力&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.2 ワークフローJSONの構造&lt;/h3&gt;
&lt;p&gt;n8nのワークフローは、以下のようなJSON形式で保存されます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;My workflow&amp;quot;,
  &amp;quot;nodes&amp;quot;: [
    {
      &amp;quot;id&amp;quot;: &amp;quot;node-id-1&amp;quot;,
      &amp;quot;type&amp;quot;: &amp;quot;n8n-nodes-base.googleBigQuery&amp;quot;,
      &amp;quot;parameters&amp;quot;: {
        &amp;quot;projectId&amp;quot;: &amp;quot;merpay-prod&amp;quot;,
        &amp;quot;sqlQuery&amp;quot;: &amp;quot;{{ $json.query }}&amp;quot;,
        &amp;quot;operation&amp;quot;: &amp;quot;executeQuery&amp;quot;
      }
    },
    {
      &amp;quot;id&amp;quot;: &amp;quot;node-id-2&amp;quot;,
      &amp;quot;type&amp;quot;: &amp;quot;n8n-nodes-base.code&amp;quot;,
      &amp;quot;parameters&amp;quot;: {
        &amp;quot;jsCode&amp;quot;: &amp;quot;const data = $input.item.json;\nreturn { result: data.rows.length };&amp;quot;
      }
    },
    {
      &amp;quot;id&amp;quot;: &amp;quot;node-id-3&amp;quot;,
      &amp;quot;type&amp;quot;: &amp;quot;n8n-nodes-base.slack&amp;quot;,
      &amp;quot;parameters&amp;quot;: {
        &amp;quot;resource&amp;quot;: &amp;quot;message&amp;quot;,
        &amp;quot;operation&amp;quot;: &amp;quot;post&amp;quot;,
        &amp;quot;channel&amp;quot;: &amp;quot;#general&amp;quot;,
        &amp;quot;text&amp;quot;: &amp;quot;{{ $json.result }}&amp;quot;
      }
    }
  ],
  &amp;quot;connections&amp;quot;: {
    &amp;quot;node-id-1&amp;quot;: {
      &amp;quot;main&amp;quot;: [[{&amp;quot;node&amp;quot;: &amp;quot;node-id-2&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;main&amp;quot;, &amp;quot;index&amp;quot;: 0}]]
    },
    &amp;quot;node-id-2&amp;quot;: {
      &amp;quot;main&amp;quot;: [[{&amp;quot;node&amp;quot;: &amp;quot;node-id-3&amp;quot;, &amp;quot;type&amp;quot;: &amp;quot;main&amp;quot;, &amp;quot;index&amp;quot;: 0}]]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このJSONから以下の情報を抽出できます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ノード設定&lt;/strong&gt;: 各ノードのtype、parameters、credentials&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;フロー構造&lt;/strong&gt;: connectionsによるノード間の依存関係&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;各ノードのparametersには、ノードタイプに応じた詳細情報が含まれます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JavaScript Codeノードの実装内容&lt;/li&gt;
&lt;li&gt;BigQueryのデータセット名やSQLクエリ&lt;/li&gt;
&lt;li&gt;HTTPリクエストのエンドポイント、メソッド、リクエストボディ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この構造化された豊富な情報により、実行前の詳細な静的解析が可能になります。&lt;/p&gt;
&lt;h3&gt;3.3 2層検出アーキテクチャ&lt;/h3&gt;
&lt;p&gt;セキュリティリスクは、個別ノードの設定だけでなく、ノード間の関係性によっても発生します。そのため、本ツールでは以下の2層アーキテクチャを採用しています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ノードレベル検出&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;個別ノードの設定を検証&lt;/li&gt;
&lt;li&gt;例：BigQueryの本番プロジェクトアクセス、Slackの投稿先チャンネル&lt;/li&gt;
&lt;li&gt;各ノードが独立して安全な設定になっているかを確認&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;シナリオレベル検出&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;複数ノード間の関係性を検証&lt;/li&gt;
&lt;li&gt;DAGを用いたフロー解析&lt;/li&gt;
&lt;li&gt;例:&lt;br /&gt;
(i) Slack Trigger → ユーザーバリデーション → 外部アクセスの順序検証&lt;br /&gt;
(ii) Google Sheets Create → Google Drive Share/HTTP Request Permissionsのスコープ設定検証&lt;/li&gt;
&lt;li&gt;ワークフロー全体としてセキュリティ要件を満たしているかを確認&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この2層アプローチにより、個別の設定ミスだけでなく、ワークフロー全体の設計上の問題も検出できます。&lt;/p&gt;
&lt;h2&gt;4. ノードレベル検出の実装例：BigQuery本番環境アクセス検知&lt;/h2&gt;
&lt;p&gt;ノードレベル検出の具体例として、BigQueryの本番環境アクセス検知を解説します。&lt;/p&gt;
&lt;h3&gt;4.1 検出対象&lt;/h3&gt;
&lt;p&gt;以下のようなワークフローを検出します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;parameters&amp;quot;: {
    &amp;quot;projectId&amp;quot;: &amp;quot;merpay-prod&amp;quot;,
    &amp;quot;operation&amp;quot;: &amp;quot;executeQuery&amp;quot;
  },
  &amp;quot;type&amp;quot;: &amp;quot;n8n-nodes-base.googleBigQuery&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この設定では、&lt;code&gt;projectId&lt;/code&gt;に&lt;code&gt;prod&lt;/code&gt;文字列を含む本番環境プロジェクトへのアクセスを検出します。&lt;/p&gt;
&lt;h3&gt;4.2 実装&lt;/h3&gt;
&lt;p&gt;検出ロジックは &lt;a href=&quot;https://github.com/mewuto/n8ncheck/blob/main/src/nodes/bigquery/checker.ts&quot;&gt;BigQueryChecker&lt;/a&gt; に実装されています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Typescript
// パラメータからプロジェクトIDを取得
const projectId = node.parameters.projectId;

// 本番環境へのアクセスを検出
if (projectId.includes(&amp;#039;prod&amp;#039;)) {
  // 警告を追加
  addWarning(&amp;#039;本番環境へのアクセスが検出されました&amp;#039;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;詳細な実装は&lt;a href=&quot;https://github.com/mewuto/n8ncheck&quot;&gt;GitHubリポジトリ&lt;/a&gt;を参照してください。&lt;/p&gt;
&lt;h3&gt;4.3 検出結果&lt;/h3&gt;
&lt;p&gt;検出された問題は以下のような形式で報告されます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;### ⚠️ 警告 (Warnings) (1)

- **[Execute a SQL query]** (n8n-nodes-base.googleBigQuery) 本番環境プロジェクト `merpay-prod` へのアクセスが検出されました。本当にアクセスして良いか確認してください。&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. シナリオレベル検出の実装例&lt;/h2&gt;
&lt;p&gt;シナリオレベル検出では、複数ノード間の関係性を解析します。代表的な実装例として、Slack User Validationシナリオを解説します。&lt;/p&gt;
&lt;h3&gt;5.1 検出対象のリスク&lt;/h3&gt;
&lt;p&gt;Slack Triggerを使用したワークフローでは、以下のリスクがあります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;想定されるリスクシナリオ&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本来アクセス権を持たないメンバーがSlackボットにコマンドを送信し、アクセス権を持つメンバーのcredentialを使用して本番データベース等の機密リソースにアクセス&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;必要な対策&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Slackトリガーの直後にユーザーバリデーションを実装&lt;/li&gt;
&lt;li&gt;外部アクセス（BigQuery、HTTP Request等）の前にバリデーションを完了&lt;/li&gt;
&lt;li&gt;バリデーションに失敗した場合はワークフローを停止&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.2 検出の流れ&lt;/h3&gt;
&lt;p&gt;Slack User Validationの検出は、以下の3ステップで実施されます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;DAG解析でバリデーションノードを探索&lt;/strong&gt; (5.3節): Slack Triggerから下流のノードを辿り、Code/Ifノードを探索&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;バリデーション実装を検証&lt;/strong&gt; (5.4節): 見つかったノードの内容を解析し、適切なユーザー検証が実装されているか確認&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;フロー整合性をチェック&lt;/strong&gt;: バリデーション前に外部アクセス(BigQuery等)が存在しないか検証&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;5.3 DAGによるフロー解析&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;n8nのループ対応&lt;/strong&gt;:&lt;br /&gt;
n8nではワークフロー内でループ（循環参照）を作成できます。しかし、フロー解析にはDAG（有向非巡環グラフ）が必要です。そのため、SCC（強連結成分分解）を用いてループを検出し、各SCCを1つのノードとして扱うことでDAGに変換します。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;実装&lt;/strong&gt; (&lt;a href=&quot;https://github.com/mewuto/n8ncheck/blob/main/src/graph/workflow-graph.ts&quot;&gt;workflow-graph.ts&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Typescript
// 1. 強連結成分（SCC）を検出
const sccs = stronglyConnectedComponents(this.graph);

// 2. 各SCCを1つのノードとして扱い、Condensed DAGを構築
const condensedDAG = this.createCondensedDAG(sccs);

// 3. トポロジカルソートで実行順序を決定
const executionOrder = topologicalSort(condensedDAG);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この変換により、ループを含むワークフローでも確実にフロー解析が可能になります。&lt;/p&gt;
&lt;p&gt;e.g. &lt;strong&gt;Slack Triggerからのパス解析&lt;/strong&gt; (&lt;a href=&quot;https://github.com/mewuto/n8ncheck/blob/main/src/scenarios/slack-user-validation/checker.ts&quot;&gt;slack-user-validation/checker.ts&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Typescript
// 1. Slack Triggerからの全ての下流ノードを取得
const allDownstreamNodes = this.getAllDownstreamNodes(slackTriggerId);

// 2. バリデーションノード候補を抽出（Code, If nodes）
const codeNodes = allDownstreamNodes.filter(
  node =&amp;gt; node.type === NODE_TYPES.CODE
);
const ifNodes = allDownstreamNodes.filter(
  node =&amp;gt; node.type === NODE_TYPES.IF
);

// 3. Slack Triggerからバリデーションノードまでのパスを取得
const pathToNode = this.graph.getPathFromNodeToNode(
  slackTriggerId,
  codeNode.id
);

// 4. パス上に外部アクセスノードがないか検証
const pathValid = this.isPathValid(pathToNode);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.4 バリデーションノードの検証&lt;/h3&gt;
&lt;p&gt;バリデーションノード候補が見つかったら、ノードタイプに応じて内容を解析します。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;Codeノードによるバリデーション&lt;/strong&gt; (&lt;a href=&quot;https://github.com/mewuto/n8ncheck/blob/main/src/scenarios/slack-user-validation/validators/jscode-validator.ts&quot;&gt;jscode-validator.ts&lt;/a&gt;)&lt;/h4&gt;
&lt;p&gt;BabelのAST解析で以下の4要素を検出:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;User ID抽出&lt;/strong&gt;: &lt;code&gt;$input.item.json.user&lt;/code&gt;または&lt;code&gt;$json.user&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;許可ユーザーのホワイトリスト定義&lt;/strong&gt;: &lt;code&gt;const users = { &amp;#039;U123&amp;#039;: &amp;#039;user1&amp;#039;, &amp;#039;U456&amp;#039;: &amp;#039;user2&amp;#039; }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;検証ロジック&lt;/strong&gt;: &lt;code&gt;!users.hasOwnProperty(userId)&lt;/code&gt;等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;エラーハンドリング&lt;/strong&gt;: 検証失敗時の&lt;code&gt;return&lt;/code&gt;または&lt;code&gt;throw&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;4要素すべて揃っている場合は完全な実装と判定します。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;Ifノードによるバリデーション&lt;/strong&gt; (&lt;a href=&quot;https://github.com/mewuto/n8ncheck/blob/main/src/scenarios/slack-user-validation/validators/if-node-validator.ts&quot;&gt;if-node-validator.ts&lt;/a&gt;)&lt;/h4&gt;
&lt;p&gt;Ifノードの条件式で以下をチェック:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;メールアドレスパターン&lt;/strong&gt;: 右辺値が会社ドメイン（例: &lt;code&gt;@example.com&lt;/code&gt;）のパターンと一致&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ユーザーコンテキスト抽出&lt;/strong&gt;: 左辺値にユーザー情報の抽出が含まれる&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;等価演算子&lt;/strong&gt;: &lt;code&gt;equals&lt;/code&gt;演算子による厳密なチェック&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If nodeで「&lt;code&gt;{{ $json.user }}&lt;/code&gt; が &lt;code&gt;@example.com&lt;/code&gt; メールと一致するか」をチェックするパターンを検出可能です。&lt;/p&gt;
&lt;h3&gt;5.5 検出結果の例&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;Codeノード（javascript）&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;完全なユーザーバリデーション実装&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Javascript
// Slack Triggerの直後のCode node
const userId = $input.item.json.user;
const users = {
  &amp;#039;U0123ABCD&amp;#039;: &amp;#039;authorized_user1&amp;#039;,
  &amp;#039;U4567EFGH&amp;#039;: &amp;#039;authorized_user2&amp;#039;
};

if (!users.hasOwnProperty(userId)) {
  return; // バリデーション失敗時に停止
}

// 以降の処理&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;検出結果:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;### ✅ OK (1)

Slack TriggerのCodeノードで適切なユーザー検証が実装されています。承認済みユーザー数: 2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;不完全なユーザーバリデーション実装&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Javascript
// User ID抽出のみ実装（ホワイトリストなし）
const userId = $input.item.json.user;
// 検証ロジックなし&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;検出結果:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;### 🚨 重大な問題 (Critical Issues) (1)

- **[Slack Trigger]** (n8n-nodes-base.slackTrigger) Slack Triggerに対するユーザー検証が不完全です。以下の「不足している要素と修正方法」を参考にCodeノード &amp;quot;User Validation&amp;quot; を設定してください：
  - 不足している要素と修正方法:
  - 1. 認証リスト: オブジェクトとして定義（変数名は &amp;quot;users&amp;quot; である必要があります）: `const users = { &amp;quot;userId1&amp;quot;: &amp;quot;userName1&amp;quot;, &amp;quot;userId2&amp;quot;: &amp;quot;userName2&amp;quot; }`
  - 2. 検証ロジック: `if (!users.hasOwnProperty(userId))`, `if (!(userId in users))` などのチェックを追加
  - 3. エラーハンドリング: 検証のif文内に `return` または `throw` ステートメントを追加&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;Ifノード&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Ifノードで以下のような条件を設定:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;左辺値: &lt;code&gt;{{ $json.user }}&lt;/code&gt; （Slackユーザー情報の抽出）&lt;/li&gt;
&lt;li&gt;演算子: &lt;code&gt;equals&lt;/code&gt; （等価演算子）&lt;/li&gt;
&lt;li&gt;右辺値: &lt;code&gt;@example.com&lt;/code&gt; （会社ドメインパターン）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;検出結果:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;### ✅ OK (1)

Slack TriggerのIfノードで適切なユーザー検証が実装されています。&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. 検出の例および社内での活用事例&lt;/h2&gt;
&lt;p&gt;メルカリでは、このツールをGitHub Actionsに組み込み、ワークフロー追加時のPRで自動的にセキュリティチェックを実行しています。&lt;/p&gt;
&lt;h3&gt;6.1 問題のあるワークフローの検出例&lt;/h3&gt;
&lt;p&gt;以下のワークフローには複数のセキュリティ上の問題および懸念があります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/2538f821-error-workflow.png&quot; alt=&quot;error-pattern-workflow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;検出された問題&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Slack Trigger直後のユーザーバリデーションが未実装&lt;/li&gt;
&lt;li&gt;BigQueryで本番環境（prod）プロジェクトへのアクセスを実行&lt;/li&gt;
&lt;li&gt;Google Sheets作成後のアクセス権限スコープ設定が未実装&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの問題は、PRコメントとして以下のように報告されます：&lt;br /&gt;
Slack TriggerとGoogle Sheetsでは重大な問題として、BigQueryの本番環境アクセスについては警告として検出されます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/45c2b175-error-pattern.png&quot; alt=&quot;error-pattern-pr-sample&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;6.2 修正後のワークフローの検証例&lt;/h3&gt;
&lt;p&gt;上記の問題を修正したワークフローでは、以下の対策が実装されています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/de03b561-success-workflow.png&quot; alt=&quot;success-workflow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;実装された対策&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ifノードによるユーザーバリデーション（メールドメインチェック）&lt;/li&gt;
&lt;li&gt;Google Driveノードによる適切なアクセス権限スコープ制御&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;そして、修正後のPRコメントでは、問題が解消されたことが確認できます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/a2c20638-success-pattern.png&quot; alt=&quot;success-pattern-pr-sample&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ユーザーバリデーションが正しく実装されていることを検出し、スコープ制御の設定内容も詳細に表示されます。これにより、レビュアーはセキュリティ観点を即座に確認できるようになっています。&lt;/p&gt;
&lt;h2&gt;7. 成果と今後の展望&lt;/h2&gt;
&lt;p&gt;ツール導入によって得られた定量的な成果に加え、開発を通じて得られた技術的な知見、そして今後の展望について整理します。&lt;/p&gt;
&lt;h3&gt;7.1 定量的成果&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;レビュー工数の削減&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;手動レビュー時間: 30-60分/ワークフロー → 5-10分/ワークフロー&lt;/li&gt;
&lt;li&gt;削減率: &lt;strong&gt;約80-85%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;検出精度&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重大セキュリティリスク（🚨 Error）の検出率: &lt;strong&gt;100%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;偽陽性率: 約15%（⚠️ Warningレベル）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7.2 技術的知見&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;JSONからの情報抽出&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;n8nのワークフローJSONには、ノード設定、接続情報、コード内容など、豊富な情報が含まれる&lt;/li&gt;
&lt;li&gt;この構造化データにより、他のワークフローツール（Zapier、Make等）よりも詳細な静的解析が可能&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;DAGによるフロー解析&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://graphology.github.io/&quot;&gt;graphology&lt;/a&gt;をベースとしたグラフ構造で、ノード間の依存関係をDAGとしてモデル化&lt;/li&gt;
&lt;li&gt;&lt;code&gt;graphology-components&lt;/code&gt;でSCC（強連結成分分解）を実行し、ループをDAGに変換&lt;/li&gt;
&lt;li&gt;&lt;code&gt;graphology-dag&lt;/code&gt;のトポロジカルソートで実行順序を決定し、複雑なワークフローパターンを検出可能&lt;/li&gt;
&lt;li&gt;特に「ユーザーバリデーション → 外部アクセス」の順序検証など、セキュリティ上重要な関係性を自動検証できる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;AST解析の有用性&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正規表現では検出が難しい複雑なJavaScriptパターンを、ASTによる構造的な解析で実現&lt;/li&gt;
&lt;li&gt;Babelのtraverseを使うことで、コードの意味を理解した検証が可能&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;n8n公式との型定義同期&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;n8nは個別ノードの型定義を公式に提供していないため、このツールでは独自に型定義を作成&lt;/li&gt;
&lt;li&gt;公式パッケージには&lt;strong&gt;TypeScript型定義&lt;/strong&gt;ではなく、&lt;strong&gt;ランタイムスキーマ定義&lt;/strong&gt;(&lt;code&gt;versionDescription.properties&lt;/code&gt;)が含まれており、各パラメータの名前、型、必須/任意などの情報を持つ&lt;/li&gt;
&lt;li&gt;このスキーマ定義を活用し、Schema Validator (&lt;a href=&quot;https://github.com/mewuto/n8ncheck/blob/main/src/nodes/bigquery/schema-validator.ts&quot;&gt;bigquery/schema-validator.ts&lt;/a&gt;) で自作型定義のキーと公式スキーマのプロパティ名を照合&lt;/li&gt;
&lt;li&gt;不一致が検出された場合は警告を出力することで、公式の破壊的変更（パラメータ名変更、削除等）にも迅速に対応可能&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7.3 今後の展望&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;検出ノードの拡充&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;現在対応しているノードタイプの拡大&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;LLMとの連携&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;検出結果に対する修正提案の自動生成&lt;/li&gt;
&lt;li&gt;ワークフローの意図を理解した高度なセキュリティ分析&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;動的情報を活用した検出の強化&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Slack APIを用いたチャンネル属性の検証：チャンネルIDからPrivate/Publicを判定し、Public Channelへの投稿を警告&lt;/li&gt;
&lt;li&gt;Credential情報から取得可能な権限スコープの検証：設定されたCredentialの実際の権限を確認し、過剰な権限付与を検出&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;8. まとめ&lt;/h2&gt;
&lt;p&gt;本記事では、n8nワークフローの静的セキュリティ解析ツールの開発について解説しました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技術的ポイント&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ワークフローJSONの豊富な情報活用&lt;/strong&gt;: ノード設定、接続情報、コード内容を完全に抽出可能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DAGによるフロー解析&lt;/strong&gt;: 複数ノード間の関係性を解析し、セキュリティ上重要なパターンを検出&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AST解析&lt;/strong&gt;: JavaScriptコードを構造的に理解し、ユーザーバリデーションロジックの完全性を検証&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;ビジネスインパクト&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;手動レビュー工数を80%削減&lt;/li&gt;
&lt;li&gt;継続的な自動監視により、変更後のワークフローも安全性を保証&lt;/li&gt;
&lt;li&gt;セキュリティポリシーの標準化と一貫性のある適用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ワークフロー自動化ツールの導入が進む中、本ツールのような静的解析アプローチは、他のプラットフォームにも応用可能です。業務効率化とセキュリティ確保の両立を目指す組織の参考になれば幸いです。&lt;/p&gt;
&lt;p&gt;明日の記事は abcdefujiさんです。引き続きお楽しみください。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;n8n.io logo source: &lt;a href=&quot;https://n8n.io/brandguidelines/&quot;&gt;https://n8n.io/brandguidelines/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Adsシステムの急成長を支える技術：信頼性と収益性を取り戻した「PJ-MARP」の全貌</title><link>https://engineering.mercari.com/blog/entry/20251212-7fe4c31bf4/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251212-7fe4c31bf4/</guid><description>&lt;p&gt;こんにちは。メルカリでエンジニアリングマネージャーとして働いています@tokkuuです。 この記事は、Mercari Advent Calendar 2025 の12日目の記事です。 今回は、私たちのAds事業が急成長す [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 12 Dec 2025 17:06:31 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリでエンジニアリングマネージャーとして働いています&lt;a href=&quot;https://x.com/tokkuu&quot;&gt;@tokkuu&lt;/a&gt;です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の12日目の記事です。&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;今回は、私たちのAds事業が急成長する過程で直面したシステム課題と、それを乗り越えるために立ち上げたプロジェクト「PJ-MARP」について紹介します。&lt;/p&gt;
&lt;p&gt;「PJ-MARP」は “Make Ads Robust and Profitable” の略称で、Adsシステムを「堅牢（Robust）」かつ「収益性の高い（Profitable）」状態へと再構築することを目的とした社内プロジェクトです。&lt;/p&gt;
&lt;p&gt;急成長する事業の足元を支えるため、技術的負債の解消からインフラ最適化、チーム運営の改善までを横断的に推進しました。&lt;/p&gt;
&lt;p&gt;事業の急拡大に伴うシステム負荷の増大、それに伴うコストの増加やインシデントの多発は、多くのプロダクトが通る「成長痛」ではないでしょうか。&lt;br /&gt;
本記事では、私たちがどのようにして&lt;strong&gt;システムの信頼性&lt;/strong&gt;と&lt;strong&gt;収益性&lt;/strong&gt;を取り戻したのか、技術的なアプローチと組織的な取り組みの両面からご紹介します。&lt;/p&gt;
&lt;h2&gt;急成長の裏で直面した「壁」&lt;/h2&gt;
&lt;p&gt;Ads事業はリリース当初から飛躍的に成長していきました。&lt;br /&gt;
具体的な数字で言うと、インプレッション数はローンチ前の4倍、コンバージョン数（CV）に至っては5〜10倍という規模にまで拡大しています。&lt;/p&gt;
&lt;p&gt;初期フェーズでは、市場探索と仮説検証を最優先し、広告主や代理店が利用したいと思うようなプロダクトへと早く成長させる必要がありました。&lt;br /&gt;
このような&lt;strong&gt;スピードと成長&lt;/strong&gt;を重視する戦略をとった結果、広告在庫が十分に溜まり、アカウント数が増加していき、ビジネスとして順調にスケールしていきました。&lt;br /&gt;
しかし、その急成長の裏でシステムでは課題が徐々に露呈していきました。&lt;/p&gt;
&lt;h2&gt;直面していた3つの課題&lt;/h2&gt;
&lt;p&gt;特に2024年の年末から2025年の年始にかけて、私たちは以下のような深刻な課題に直面していました。&lt;/p&gt;
&lt;h3&gt;頻発するインシデント&lt;/h3&gt;
&lt;p&gt;我々のビジネスの成長が加速したことと相まって、広告業界として盛り上がりを見せる年末にかけてインシデントが多発しました。&lt;br /&gt;
インシデントの突発的な対応が増え、対処療法的なスケールアップなどの対応を繰り返し、結果としてチームとして徐々に疲弊していきました。&lt;/p&gt;
&lt;h3&gt;インフラコストの増大&lt;/h3&gt;
&lt;p&gt;売上は伸びていましたが、その分それと同じ勢いでクラウド（GCP）の利用費も増大していました。スピードを優先したため非効率にコストがかかっている部分も多く、&lt;strong&gt;増えた売上がそのままGCP費用に消えていく&lt;/strong&gt;ような状況になりつつありました。&lt;/p&gt;
&lt;h3&gt;広告表示機会の損失（Fill-rateの低下）&lt;/h3&gt;
&lt;p&gt;Fill-rateとは広告リクエストに対して実際に広告を返せた割合のことです。&lt;br /&gt;
システム遅延により、タイムアウトが発生し、本来表示できるはずの広告が表示できないケースが増えていました。&lt;br /&gt;
具体的には改善前の1月の時点で、Worst caseでは iOSで51%、Androidで26% という危機的な状況でした。&lt;/p&gt;
&lt;p&gt;急成長したことによりこのような問題が多発したため、このままでは事業の成長を阻害してしまいます。&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/ee3004eb-pjmarpfigure.png&quot;&gt;
&lt;/div&gt;
&lt;p&gt;そこで私たちは、収益性と堅牢性を確立することを目的としたプロジェクト、PJ-MARPを立ち上げました。&lt;/p&gt;
&lt;h2&gt;技術的アプローチ：アーキテクチャレベルでの見直し&lt;/h2&gt;
&lt;p&gt;PJ-MARPでは、単なる対症療法的なバグ修正ではなく、根本的なアーキテクチャの見直しを行いました。リアーキテクチャ前のシステムでは非効率な処理やデータストアのアクセスが多く、処理速度の向上がそのままコスト最適化に結びつくケースが多々ありました。&lt;br /&gt;
また、広告においてログはとても重要な役割を持つため、ログを記録するパイプラインやデータストアについてはセンシティブに扱う必要があります。&lt;br /&gt;
私たちはこれらの理由から、&lt;strong&gt;パフォーマンスとコストの最適化&lt;/strong&gt;と&lt;strong&gt;データストアとインフラの改善&lt;/strong&gt;の2つの観点を重点的に対応することにしました。&lt;br /&gt;
詳細はこの記事では紹介しませんが、主に効果が高かった施策について簡単に紹介します。&lt;/p&gt;
&lt;h3&gt;パフォーマンスとコストの最適化&lt;/h3&gt;
&lt;p&gt;まず着手したのは、リクエスト処理の効率化です。&lt;/p&gt;
&lt;h4&gt;Cache-chainingの導入&lt;/h4&gt;
&lt;p&gt;頻繁にアクセスされるデータに対して、多層的なキャッシュ戦略（Cache-chaining）を導入しました。これにより、DBへの直接的なアクセス数を大幅に減らし、レイテンシを短縮しました。&lt;br /&gt;
DBへのアクセス数を減らすことで、DB側のコストも削減することができました。&lt;/p&gt;
&lt;h4&gt;Redisコマンドの最適化&lt;/h4&gt;
&lt;p&gt;Adsでは検索結果のページング処理の過程で、Redisへ検索セッションのキャッシュを行っています。&lt;br /&gt;
このときのアクセスパターンを見直し、非効率なRedis操作コマンド実行を削減しました。&lt;/p&gt;
&lt;h4&gt;gRPCコールの削減&lt;/h4&gt;
&lt;p&gt;Adsのシステムは広告を絞り込む過程で様々なマイクロサービス間の通信が発生します。&lt;br /&gt;
スピード重視の開発によって整理されていなかったマイクロサービス間の通信においても、Profilerを用いて処理を解析し、不要なgRPCコールを洗い出すことによって、通信オーバーヘッドを削減しました。&lt;/p&gt;
&lt;h3&gt;データストアとインフラの改善&lt;/h3&gt;
&lt;p&gt;インシデントの問題となりやすい、データストア周りについても改善を行いました。&lt;/p&gt;
&lt;h4&gt;BigTableのホットスポット解消&lt;/h4&gt;
&lt;p&gt;特定のノードに負荷が集中していたBigTableのキー設計を見直し、ホットスポットを解消することでスループットを安定させました。&lt;/p&gt;
&lt;h4&gt;Elasticsearchのインデックス再構築&lt;/h4&gt;
&lt;p&gt;Adsではユーザーの検索語句に基づいて広告をマッチングするためにElasticsearchを使用しています。このマッチングパフォーマンスを向上と、オーバーヘッドを減らす目的でインデックスのマッピングやシャード設定を見直し、再構築を行いました。&lt;/p&gt;
&lt;h4&gt;データパイプラインのリファクタリング&lt;/h4&gt;
&lt;p&gt;ログ収集やその後の集計処理を行うデータパイプラインを最適化し、遅延・欠損なくデータを処理できる基盤を整えました。&lt;br /&gt;
具体的にはDataflow上で動く各ジョブのコードレベル、フローレベルのリファクタリングや、失敗時の対処、モニタリングの強化を行いました。&lt;/p&gt;
&lt;h2&gt;プロセスと組織：チームをどう動かしたか&lt;/h2&gt;
&lt;p&gt;これらの多数の取り組みを素早く完了させるために、技術的な修正と同じくらい重要だったのが、徹底的な可視化でした。&lt;/p&gt;
&lt;h3&gt;システム構造の可視化で現状を正しく捉える&lt;/h3&gt;
&lt;p&gt;複雑化したシステムを改善するには、まず現状を正しく理解することが欠かせません。&lt;br /&gt;
今回のプロジェクトは、Adsチームだけでなく他チームからの協力も多く得ながら進めたため、システム全体の理解を深め、共通認識を持つことが重要でした。&lt;br /&gt;
そこで私たちは、時間をかけてアーキテクチャ図や処理フロー図を詳細化することにしました。&lt;br /&gt;
あえて非同期で進めず、長い時間を確保してドキュメントを繰り返し更新しながら議論を重ねることで、ステークホルダー全員の認識を丁寧に揃えることを狙いました。&lt;br /&gt;
その結果、「現在のシステムがどうなっているのか」「どこがボトルネックなのか」という点について、徹底的に共通理解を築くことができました。&lt;/p&gt;
&lt;h3&gt;KPIの可視化で改善効果をリアルタイムに把握&lt;/h3&gt;
&lt;p&gt;また、DataDogやLooker Studioを活用し、主要KPIを継続的にトラッキングできるダッシュボードを構築しました。&lt;br /&gt;
これにより、改善施策の効果をリアルタイムに可視化し、意思決定のスピードと精度を高めることができました。&lt;/p&gt;
&lt;p&gt;たとえば、「この施策を導入した結果、コストがどれだけ削減されたのか」「Fill-rateがどの程度改善したのか」といった成果を即座に確認できます。&lt;br /&gt;
数値として成果が見えることで、チーム全体の達成感や次の改善への意欲が自然と高まり、モチベーション維持にも大きく貢献したと感じています。&lt;/p&gt;
&lt;h2&gt;成果&lt;/h2&gt;
&lt;p&gt;PJ-MARPの取り組みの結果、劇的な改善が見られました。&lt;br /&gt;
改善前は危機的状況だったFill-rateは、施策適用後の直近1ヶ月では、プラットフォームを問わず 一貫して95%以上の水準 を維持できるようになりました。&lt;/p&gt;
&lt;p&gt;またコスト面でも大きな成果が出ました。1月と比べて、3月は約28%のコスト削減を実現しました。&lt;/p&gt;
&lt;p&gt;ビジネスの成長を止めずに、利益率を大きく改善することに成功しました。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;今回のプロジェクトを通じて、私たちは多くの学びを得ました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;コストダッシュボードは初期に作る&lt;/strong&gt;: 何かが起きてから見るのではなく、常にモニタリングできる状態にしておくことで、異常検知やトリアージが容易になります。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;キャッシュは最初から考える&lt;/strong&gt;: パフォーマンス向上のため、特に広告プロダクトのような高トラフィック低レイテンシが求められるシステムでは、キャッシュ機構はサービス開発の初期段階で組み込んでおくのがよいと感じました。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定期的な見直しと外部の視点&lt;/strong&gt;: アーキテクチャは一度作って終わりではなく、効率化のために定期的に見直す必要があります。その際、チーム外のエキスパートの視点を入れることが非常に有効です。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本番投入後の再評価&lt;/strong&gt;: 本番環境での挙動を確認し、設定値を再評価・チューニングするプロセスは考慮しておくべきだと感じました。この再評価とチューニングを継続的に行うことがシステムを堅牢にするうえで重要だと思います。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PJ-MARPを通じて、システムが堅牢になっただけでなく、Adsチームの結束力が強まり、特定の個人のスキルに依存しない体制が整いました。&lt;br /&gt;
今後も、システムの信頼性と収益性のバランスを取りながら、Ads事業のさらなる成長を支えていきたいと思います。&lt;/p&gt;
&lt;p&gt;明日の記事は &lt;a href=&quot;https://twitter.com/afroscript10&quot;&gt;@t-hiroi&lt;/a&gt;さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>Making n8n Enterprise-Ready: 企業向けn8nの導入と運用の取り組み</title><link>https://engineering.mercari.com/blog/entry/20251211-6fe0bc0838/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251211-6fe0bc0838/</guid><description>&lt;p&gt;こんにちは。JB SREの @T です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の12日目の記事です。 弊社ではAI Workflow Platformとしてn8 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 12 Dec 2025 10:00:11 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。JB SREの @T です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の12日目の記事です。&lt;/p&gt;
&lt;p&gt;弊社ではAI Workflow Platformとして&lt;a href=&quot;https://n8n.io/&quot;&gt;n8n&lt;/a&gt;を導入しました。&lt;br /&gt;
概要や背景についての詳細は、前日のISSAさんによる「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-cf3b67a5a7/&quot; title=&quot;理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない&quot;&gt;理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない&lt;/a&gt;」の記事で紹介されていますので、そちらもご覧ください。&lt;br /&gt;
本記事では、n8n Enterprise Edition を社内導入するにあたり、システム面で対応した内容について、いくつかご紹介します。&lt;/p&gt;
&lt;h2&gt;n8nを弊社基準のSecurity、ガバナンスに適合させるための挑戦&lt;/h2&gt;
&lt;p&gt;チームで、あるいは全社規模で自動化ツールを展開しようとするとき、ScalabilityやSecurity、ガバナンスといった壁に突き当たった経験はありませんか？&lt;br /&gt;
n8nは非常に柔軟で強力なAI Workflow Engineですが、それを数百人規模の組織で安全かつ安定して運用するためには、デフォルトの状態から一歩進んだ設計と工夫が必要です。&lt;br /&gt;
本記事では、私たちがn8nを単なる便利ツールから、信頼性の高い「AI Workflow Platform」へと進化させるために実施した具体的な取り組みをご紹介します。 もしかすると、「n8nはここまで拡張・カスタマイズできるのか」という、Enterprise利用における柔軟性の高さを再発見していただけるかもしれません。&lt;/p&gt;
&lt;h2&gt;なぜEnterprise Editionだったのか？&lt;/h2&gt;
&lt;p&gt;まず、私たちが直面した最初の選択は「&lt;a href=&quot;https://docs.n8n.io/hosting/community-edition-features/&quot;&gt;Community Edition（CE）&lt;/a&gt;か、&lt;a href=&quot;https://n8n.io/enterprise/&quot;&gt;Enterprise Edition&lt;/a&gt;か」でした。&lt;br /&gt;
「Workflow Engine自体は同じコードベースなのだから、無償のCEで十分ではないか？」 そう考える方も多いでしょう。実際、私たちも最初はそう考えました。しかし、組織で利用するPlatformとしてn8nを捉え直したとき、Enterprise Editionを選択する必然性が見えてきました。 CEとEnterprise Editionの決定的な違いは、Workflow機能そのものではなく、&lt;strong&gt;Team collaboration&lt;/strong&gt;、&lt;strong&gt;Security&lt;/strong&gt;、&lt;strong&gt;Scalability&lt;/strong&gt;といった「周辺機能」にあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Team collaboration&lt;/strong&gt;: CEではWorkflowはOwner1人が属人的に運用し、個々のユーザーが限られた権限の中、パーソナルな空間で使用する設計思想が感じ取れます。一方、Enterpriseでは&lt;a href=&quot;https://docs.n8n.io/user-management/rbac/projects/&quot;&gt;Projects&lt;/a&gt;機能により、チーム単位でのWorkflow共有や、&lt;a href=&quot;https://docs.n8n.io/user-management/rbac/role-types/&quot;&gt;RBAC (Role-Based Access Control)&lt;/a&gt; によるきめ細かな権限管理が可能になります  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: 企業導入の必須要件となりがちな&lt;a href=&quot;https://docs.n8n.io/hosting/securing/set-up-sso/&quot;&gt;SSO&lt;/a&gt; (SAML/OIDC)や、HashiCorp Vaultなどの外部Secret store連携はEnterpriseのみの機能です  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: 大規模なトラフィックを捌くための&lt;a href=&quot;https://docs.n8n.io/hosting/scaling/queue-mode/#configuring-multi-main-setup&quot;&gt;Multi-main mode&lt;/a&gt;や、Databaseを肥大化させないための&lt;a href=&quot;https://docs.n8n.io/data/binary-data/&quot;&gt;Binary data&lt;/a&gt;の外部ストレージ保存（S3等）もEnterpriseがサポートしています&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;単にWorkflowを動かすだけならCEで十分です。しかし、私たちが目指したのは、属人化を防ぎ、監査可能性を担保し、全社規模で安心して利用できる「基盤」でした。その実現には、Enterprise Editionが提供する、特にSecurityとガバナンスに関わる機能群が不可欠だったのです&lt;/p&gt;
&lt;h2&gt;Enterprise導入を支えるn8nのシステム構成&lt;/h2&gt;
&lt;p&gt;私たちはn8nをGoogle Cloud上にSelf-hostingしています。 Enterprise水準のScalabilityとSecurityを確保するために、以下のような構成を採用しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/c9b5160a-image4.png&quot; alt=&quot;Google Cloud architecture diagram for n8n enterprise&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;ComputeとNetwork&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Compute&lt;/strong&gt;: &lt;strong&gt;Cloud Run&lt;/strong&gt;を採用し、Serverlessならではの運用負荷の低さとScalabilityを享受しています  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network&lt;/strong&gt;: &lt;a href=&quot;https://docs.cloud.google.com/run/docs/configuring/vpc-direct-vpc&quot;&gt;&lt;strong&gt;Direct VPC Egress&lt;/strong&gt;&lt;/a&gt;と&lt;a href=&quot;https://docs.cloud.google.com/load-balancing/docs/https/setting-up-https-serverless&quot;&gt;&lt;strong&gt;Serverless NEG&lt;/strong&gt;&lt;/a&gt;を利用し、private IPのみを持つセキュアな構成を実現しています&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;データ永続化とGCSの活用&lt;/h3&gt;
&lt;p&gt;Cloud Runの一時ディスクはインスタンス再起動で消えてしまうため、GCS (Google Cloud Storage) をマウントし、以下のデータを永続化しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.n8n.io/data/binary-data/&quot;&gt;&lt;strong&gt;Binary data&lt;/strong&gt;&lt;/a&gt;: n8nの&lt;a href=&quot;https://docs.n8n.io/hosting/scaling/external-storage/&quot;&gt;&lt;code&gt;external-storage&lt;/code&gt;&lt;/a&gt;機能を使い、Workflowで扱うファイルの実体をDBではなくGCSに保存しています&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.n8n.io/integrations/community-nodes/installation/manual-install/&quot;&gt;&lt;strong&gt;Community node&lt;/strong&gt;&lt;/a&gt;: 通常、n8nのCommunity nodeはGUIから簡単にインストールできますが、Cloud Run環境では永続化のためにマウントしたGCSへインストールする必要があります。 しかし、GUI上で直接インストールを試みると、GCSのマウントに使用しているgcsfuseの制約により、下記のエラーが発生し、正常に完了しませんでした。
&lt;pre&gt;&lt;code&gt;writes will fall back to staged writes due to err: cant allocate any block as global max blocks limit is reached&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本来であれば &lt;a href=&quot;https://docs.cloud.google.com/storage/docs/cloud-storage-fuse/cli-options#write-global-max-blocks&quot;&gt;&lt;code&gt;--write-global-max-blocks&lt;/code&gt;&lt;/a&gt; optionでブロック制限を緩和すべきところですが、&lt;a href=&quot;https://docs.cloud.google.com/run/docs/configuring/services/cloud-storage-volume-mounts&quot;&gt;Cloud Runのvolume mount&lt;/a&gt;ではこのoptionがサポートされていませんでした。 そこで回避策として、GitHub Actions上で&lt;code&gt;npm install&lt;/code&gt;を実行し、生成されたfile群をGCSにsyncさせ、n8n起動時にそれを読み込ませるという運用フローを採用しています&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/223888df-image3.png&quot; alt=&quot;How to install Community node to n8n enterprise&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.n8n.io/embed/configuration/#backend-hooks&quot;&gt;&lt;strong&gt;External Hook&lt;/strong&gt;&lt;/a&gt;: 後述するガバナンス用のスクリプトもGCSに配置しています&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;a href=&quot;https://github.com/GoogleCloudPlatform/cloud-run-external-metrics-autoscaling&quot;&gt;Cloud Run External Metrics Autoscaling (CREMA)&lt;/a&gt; によるWorkerのAutoscale&lt;/h3&gt;
&lt;p&gt;特筆すべき工夫として、&lt;a href=&quot;https://docs.cloud.google.com/run/docs/deploy-worker-pools&quot;&gt;Worker pools&lt;/a&gt;のScalability確保があります。&lt;br /&gt;
「Cloud RunのAutoscale」といえば、CPU使用率やリクエスト数を基準にするのが一般的でしょう。しかし、n8nのWorkerのような非同期jobを処理するworkloadにおいては、それらの指標だけでは負荷（jobの滞留状況）を正確に反映できない場合があります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/1a8cc820-image1.png&quot; alt=&quot;Cloud Run External Metrics Autoscaling(CREMA)&quot; /&gt;&lt;/p&gt;
&lt;p&gt;そこで私たちは、&lt;a href=&quot;https://github.com/GoogleCloudPlatform/cloud-run-external-metrics-autoscaling&quot;&gt;&lt;strong&gt;Cloud Run External Metrics Autoscaling (CREMA)&lt;/strong&gt;&lt;/a&gt; の導入を検討しています。これは&lt;a href=&quot;https://keda.sh/&quot;&gt;KEDA&lt;/a&gt;を利用してDataDogなどの外部metricsを元にCloud Runをスケールさせる仕組みです。 これにより、n8nのQueueの深さなど、アプリケーション固有のmetricsに基づいた最適なAutoscalingを実現しようとしています。&lt;/p&gt;
&lt;h2&gt;n8nを統制のとれたPlatformへ昇華させる機能群&lt;/h2&gt;
&lt;p&gt;システム構成に加え、n8nの機能をフル活用して、ガバナンスと利便性を両立させています。&lt;/p&gt;
&lt;h3&gt;1. TerraformによるProject機能のコード管理&lt;/h3&gt;
&lt;p&gt;みなさんの組織では、誰がどのWorkflowをメンテナンスしているか、一目で追えているでしょうか？ 複数人での利用において、Personal spaceの共有機能はSecurity的な懸念（誰にでも全権限で共有されてしまう等）がありました。 解決策として&lt;strong&gt;Projects機能&lt;/strong&gt;を採用しましたが、GUI操作での管理は属人化の温床です。 そこで私たちは、&lt;strong&gt;n8n Terraform provider&lt;/strong&gt;を内製化し、Projectの作成からメンバーのアサインまでをすべてTerraformでコード管理しています。Terraform providerを内製化した理由は、公式でのTerraform providerの提供がなかったのと、OSSで開発されているTerraform providerがProjects機能に対応していなかったためです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource &amp;quot;n8n_project&amp;quot; &amp;quot;sre_team&amp;quot; {
  name = &amp;quot;SRE Team&amp;quot;
}

resource &amp;quot;n8n_project_member&amp;quot; &amp;quot;sato&amp;quot; {
  project_id = n8n_project.sre_team.id
  user_id    = data.n8n_user.sato.id
  role       = &amp;quot;project:editor&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;e.g.: ProjectのTerraform resource設定&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;これにより、権限設定の変更履歴がGitに残り、レビュープロセスを通すことが可能になりました。&lt;/p&gt;
&lt;h3&gt;2. Oktaを活用したSSOとUser-Provisioning&lt;/h3&gt;
&lt;p&gt;認証は全社標準のOktaとSAML連携し、SSOを実現しています。 さらに、v1.122.2から導入された&lt;a href=&quot;https://docs.n8n.io/release-notes/#n8n11220&quot;&gt;SAML経由でのrole-provisioning&lt;/a&gt;を活用予定です。これは、Okta側のuser属性（&lt;code&gt;n8n_instance_role&lt;/code&gt;, &lt;code&gt;n8n_projects&lt;/code&gt;）に応じて、n8nログイン時に自動的にロールや所属Projectを割り当てる機能です。 これにより、入退社や異動に伴う権限変更をOkta側で一元管理でき、n8n側の運用負荷を大幅に削減できます。&lt;/p&gt;
&lt;h3&gt;3. Audit Logの監査とプロアクティブな自動化&lt;/h3&gt;
&lt;p&gt;監査ログの取得は必須要件ですが、私たちは&lt;a href=&quot;https://docs.n8n.io/log-streaming/&quot;&gt;Log Streaming&lt;/a&gt;機能を使い、ログを単なる記録以上のものとして活用しています。 以前は「監査ログは何かあった時に後から見るもの」という受け身の運用でしたが、現在ではログを起点に次のアクションを自動化する運用へと変化しています。&lt;br /&gt;
n8nから出力されるeventログ(user登録、Workflow保存など)をトリガーに、Cloud RunのSidecarを利用し、以下のような自動化を行っています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;初期設定の自動化&lt;/strong&gt;: ユーザーが新規登録(&lt;code&gt;User signed up&lt;/code&gt;)されたら、自動的に共通のprojectへ招待し、Onboardingなどの手間を省いています&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/f7aa16c1-image2.png&quot; alt=&quot;Sequence Diagram for Initial Setup Automation&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;セキュリティ通知&lt;/strong&gt;: WorkflowやCredentialが不適切に共有(&lt;code&gt;User credentials shared&lt;/code&gt;等)されたら、Slackで通知し、Projects機能の利用を促します&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/ae70bacd-image5.png&quot; alt=&quot;Security Notification Sequence Diagram&quot; /&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/298d2cb5-image10.png&quot; alt=&quot;Slack Notification Example&quot; /&gt;&lt;em&gt;e.g.: Slackでの通知メッセージ&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このように、ログを単なる記録として扱うだけでなく、次のアクションを自動実行するトリガーとして活用することで、プロアクティブなガバナンスを実現しています。&lt;/p&gt;
&lt;h3&gt;4. External Hookによる徹底したガバナンス&lt;/h3&gt;
&lt;p&gt;n8nの自由度は魅力ですが、統制の観点からはリスクにもなり得ます。 私たちは&lt;a href=&quot;https://docs.n8n.io/embed/configuration/#backend-hooks&quot;&gt;External Hook&lt;/a&gt;機能を活用し、Workflowが保存(&lt;code&gt;workflow.update&lt;/code&gt;, &lt;code&gt;workflow.create&lt;/code&gt;)される直前に独自のバリデーション処理を挟み込んでいます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/c64aa6c2-image7.png&quot; alt=&quot;External Hook Sequence Diagram&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;機密情報のチェック&lt;/strong&gt;: Workflow内にAPI keyやパスワードなどが直書きされていないか確認し、検出された場合はWorkflowの保存をブロックして修正を促します&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通信先の制限&lt;/strong&gt;: n8nのHTTP Request nodeで、localhostや社内ネットワークの特定セグメントなど、アクセスしてほしくない宛先が設定されていないか検証します&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;承認フローの強制&lt;/strong&gt;: WorkflowをActiveにする際、所定の承認プロセスを経ていない場合は有効化できないように制御します。これにより、管理されていない野良Workflowの乱立や、意図しないデータ漏洩を防いでいます&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらのExternal Hook処理により、n8nは「自由なツール」から「ガードレールの効いたWorkflow Platform」へと変化しました。&lt;/p&gt;
&lt;h3&gt;5. Code node実行環境の分離と堅牢化&lt;/h3&gt;
&lt;p&gt;n8nのCode nodeは、ユーザーがJavaScriptコードを実行できる非常に強力な機能ですが、複数人が利用する環境においては、その実行環境の管理が課題となります。 そこで私たちは、コード実行を安全に行うための&lt;a href=&quot;https://docs.n8n.io/hosting/configuration/task-runners/&quot;&gt;Task runners&lt;/a&gt;という仕組みを使用しました。&lt;br /&gt;
Task runnersには2つの動作モードがあります。デフォルトの&lt;code&gt;Internal&lt;/code&gt;モードはn8nのメインプロセス内でコードを実行しますが、私たちはより堅牢性を高めるため、&lt;code&gt;External&lt;/code&gt;モードを採用しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/539d2ac3-image9.png&quot; alt=&quot;Task runners external mode Diagram&quot; /&gt;&lt;br /&gt;
&lt;em&gt;Source: &lt;a href=&quot;https://docs.n8n.io/hosting/configuration/task-runners/#external-mode&quot;&gt;n8n Docs &amp;#8211; Task runners External mode&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;これは、コードの実行環境をn8n本体から切り離された独立したプロセス（CloudRun Sidecarコンテナ）で動作させるモードです。この構成により、万が一Code node内で意図しない挙動のスクリプトが実行されたとしても、その影響範囲をコンテナ内に封じ込め、n8n本体の安定性を維持することができます。 さらに、私たちは以下の環境変数を設定し、セキュリティの強化を図っています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pythonの無効化&lt;/strong&gt;: &lt;a href=&quot;https://docs.n8n.io/hosting/configuration/environment-variables/nodes/&quot;&gt;N8N_PYTHON_ENABLED&lt;/a&gt;環境変数をfalseに設定し、Pythonの実行を無効化しました。これは、PythonのTask runnerがまだBeta版である点を考慮したためです&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一部Nodeの無効化&lt;/strong&gt;: &lt;a href=&quot;https://docs.n8n.io/embed/configuration/#environment-variables&quot;&gt;NODES_EXCLUDE&lt;/a&gt;環境変数を利用し、&lt;a href=&quot;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.executecommand&quot;&gt;Execute Command Node&lt;/a&gt;と&lt;a href=&quot;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.readwritefile&quot;&gt;Read/Write Files from Disk Node&lt;/a&gt;を無効化しました。これらのNodeは、envコマンドによる環境変数の閲覧や、サーバー上のファイルへの意図しないアクセスを許容してしまう可能性があるため、リスクを未然に防ぐ目的で利用を制限しています&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの設定を通じて、Code nodeの自由度を活かしつつも、より安心して利用できる環境を目指しました。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;n8n Enterprise Editionが提供するProjects、SSO、Log Streaming、External Hookといった機能群は、私たちが求めるセキュリティとガバナンス要件を満たす上で非常に大きな助けとなりました。 単にツールを導入するだけでなく、これらの機能を組み合わせ、自社の運用に合わせてカスタマイズすることで、n8nは真に「Enterprise-Ready」な基盤となり得ます。 私たちがここまでSecurityやガバナンスの強化にこだわった理由は、&lt;strong&gt;エンジニアの皆さんもエンジニア以外の皆さんも、誰もが安心して使える環境を作りたかったから&lt;/strong&gt;です。&lt;br /&gt;
エンジニア以外の皆さんは、&lt;a href=&quot;https://docs.n8n.io/advanced-ai/ai-workflow-builder/&quot;&gt;AI Workflow Builder&lt;/a&gt;のようなChat-baseの機能を活用することで、Non-Programmingで容易にAI-Agentを作成し、業務を効率化でき、「プログラミングができないと自動化は難しい」というこれまでの考えは、AIの力によって覆されつつあります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/1b34a0d6-image8.png&quot; alt=&quot;AI workflow Builder for n8n&quot; /&gt;&lt;br /&gt;
&lt;em&gt;Source: &lt;a href=&quot;https://docs.n8n.io/advanced-ai/ai-workflow-builder/&quot;&gt;n8n Docs &amp;#8211; AI Workflow Builder&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;一方でエンジニアの皆さんは、n8nを使用してプロトタイプを爆速で作成・検証し、高速なPDCAサイクルを回すことが可能になるでしょう。 更には人間が作成したWorkflowをAIが深く理解し、より堅牢でスケーラブルなアプリケーションとして自動で実装してくれる日がくるかもしれません。&lt;br /&gt;
n8nはほぼ週次でマイナーリリースが行われるなど開発スピードが非常に速く、日々新しい機能が追加され、直近だと&lt;a href=&quot;https://docs.n8n.io/2-0-breaking-changes/&quot;&gt;v2.0へのメジャーアップデート&lt;/a&gt;も控えています。 n8nの進化と、私たちが取り組んできたSecurityやガバナンスの強化が組み合わさることで、社内の「AI-Native」な活動を少しでも後押しできるような環境になれれば幸いです。&lt;/p&gt;
&lt;p&gt;明日の記事は mewutoさんによる、「n8nの静的解析CLIツールをOSS化 – JSON解析とDAGで実現するセキュリティチェックの自動化」についてです。n8nの承認フローの詳細が伺えるかと思います。引き続きお楽しみください。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;n8n.io logo source: &lt;a href=&quot;https://n8n.io/brandguidelines/&quot;&gt;https://n8n.io/brandguidelines/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>OpenID Connect Core 1.0 の Claims パラメーターの利用</title><link>https://engineering.mercari.com/blog/entry/20251211-c73c2b1747/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251211-c73c2b1747/</guid><description>&lt;p&gt;はじめに こんにちは。メルカリのバックエンドエンジニアの@kg0r0です。 この記事は、Mercari Advent Calendar 2025 の11日目の記事です。 本記事では OpenID Connect Core [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 11 Dec 2025 13:00:43 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。メルカリのバックエンドエンジニアの&lt;a href=&quot;https://x.com/kg0r0&quot;&gt;@kg0r0&lt;/a&gt;です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の11日目の記事です。&lt;br /&gt;
本記事では OpenID Connect Core 1.0 で定義されている Claims パラメーターについて説明し、最後にメルカリでの利用例について紹介します。&lt;br /&gt;
認証認可の領域を担当されているエンジニアの方であれば OpenID Connect Core 1.0 仕様に目を通したことがあるかもしれません。一方で、Claims パラメーターは比較的マイナーなパラメーターであり、利用を実際に検討したケースは多くないのではないかと想定しています。もしここで紹介する内容が OpenID Connect (OIDC) で属性情報を扱ううえでの何かしらの気づきになれば幸いです。&lt;/p&gt;
&lt;h2&gt;Claims パラメーターについて&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter&quot;&gt;Claims パラメーター&lt;/a&gt;は OpenID Connect Core 1.0 に定義されており、Authentication Request で指定可能なパラメーターの一つです。なお、&lt;a href=&quot;https://openid.net/specs/openid-connect-4-identity-assurance-1_0.html#section-5.3&quot;&gt;OpenID Connect for Identity Assurance 1.0&lt;/a&gt; などの一部の拡張仕様内でも利用されています。&lt;br /&gt;
Claims パラメーターは JSON オブジェクトとして表現され、以下の例のように &lt;code&gt;userinfo&lt;/code&gt; や &lt;code&gt;id_token&lt;/code&gt; といったメンバーによって UserInfo Endpoint や ID Token に含めて返却して欲しい属性情報を指定することができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; {
   &amp;quot;userinfo&amp;quot;:
    {
     &amp;quot;given_name&amp;quot;: {&amp;quot;essential&amp;quot;: true},
     &amp;quot;nickname&amp;quot;: null,
     &amp;quot;email&amp;quot;: {&amp;quot;essential&amp;quot;: true},
     &amp;quot;email_verified&amp;quot;: {&amp;quot;essential&amp;quot;: true},
     &amp;quot;picture&amp;quot;: null,
     &amp;quot;http://example.info/claims/groups&amp;quot;: null
    },
   &amp;quot;id_token&amp;quot;:
    {
     &amp;quot;auth_time&amp;quot;: {&amp;quot;essential&amp;quot;: true},
     &amp;quot;acr&amp;quot;: {&amp;quot;values&amp;quot;: [&amp;quot;urn:mace:incommon:iap:silver&amp;quot;] }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claims パラメーターは OPTIONAL であり、通常、Relying Party が取得したい属性情報を指定する場合は &lt;code&gt;scope&lt;/code&gt; パラメーターが使われます。一方で、&lt;code&gt;claims&lt;/code&gt; パラメーターでは &lt;code&gt;scope&lt;/code&gt; パラメーターでは指定することができない特定の組み合わせを要求することができます。また、&lt;code&gt;essential&lt;/code&gt; や &lt;code&gt;value&lt;/code&gt;、&lt;code&gt;values&lt;/code&gt; などのメンバーによってより詳細な条件を追加することもできます。&lt;br /&gt;
例えば、&lt;a href=&quot;https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html#IndividualClaimsRequests&quot;&gt;5.5.1. Individual Claims Requests&lt;/a&gt; に記載されている以下の例では &lt;code&gt;essential&lt;/code&gt; を true にすることで auth_time が必要であることを示すことができます。 &lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &amp;quot;auth_time&amp;quot;: {&amp;quot;essential&amp;quot;: true}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、&lt;code&gt;value&lt;/code&gt; を以下のように利用し sub として特定の値を指定することによって、 login_hint よりも強制力のある処理をしたいユースケースなどに利用することもできそうです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &amp;quot;sub&amp;quot;: {&amp;quot;value&amp;quot;: &amp;quot;248289761001&amp;quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一部の &lt;a href=&quot;https://www.keycloak.org/docs/latest/server_admin/index.html#_mapping-acr-to-loa-realm&quot;&gt;Identity Provider (IdP) 実装&lt;/a&gt;では Authentication Context Class Reference (ACR) を指定する方法として &lt;code&gt;acr_values&lt;/code&gt; だけでなく &lt;code&gt;claims&lt;/code&gt; がサポートされており、Level of Assurance (LoA) に基づいた処理に利用することも検討できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &amp;quot;acr&amp;quot;: {&amp;quot;essential&amp;quot;: true,
          &amp;quot;values&amp;quot;: [&amp;quot;urn:mace:incommon:iap:silver&amp;quot;,
                     &amp;quot;urn:mace:incommon:iap:bronze&amp;quot;]}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前述のように Claims パラメーターは、要求する属性情報に追加の条件を付与することを可能とし、Relying Party などが属性情報に基づいた特殊なユースケースを実現する際に利用できる可能性があります。&lt;br /&gt;
一方で、OpenID Connect Core 1.0 中では、Claims パラメーターの扱いに関する要件が複数記載されており、IdP 側の実装には少し複雑な箇所があります。&lt;br /&gt;
例えば、Claims パラメーターの定義において JSON オブジェクトの他に明示的に null を指定することができます。この場合、given_name はデフォルトの形式で要求されていることを示します。このため、IdP 側の実装によっては、Claims パラメーターをパースする際に明示的に null が指定されたのか値が存在しないため null になっているのか判断が必要な場合が考えられます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &amp;quot;given_name&amp;quot;: null&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、各メンバーのデフォルトの動作を正しく適用する必要があり、例えば、&lt;code&gt;essential&lt;/code&gt; は OPTIONAL であるため明示的に true または false が指定される以外にも、デフォルトの動作として false が指定された通りに Voluntary Claim として扱う必要があります。&lt;/p&gt;
&lt;p&gt;上記の通り、 Claims パラメーターは JSON オブジェクトで表現され、良くも悪くも自由度が高い構造になっています。また、OP 側の各メンバーの処理に関する要件が仕様中に複数記載されています。このため、実装が複雑になることを避けるためには、一度 Claims パラメーター以外のシンプルな方法がないか検討した方が良いかもしれません。&lt;/p&gt;
&lt;h2&gt;メルカリでの利用例&lt;/h2&gt;
&lt;p&gt;現在、メルカリでは &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9470.html&quot;&gt;RFC 9470 OAuth 2.0 Step Up Authentication Challenge Protocol&lt;/a&gt; で定義されているような LoA に基づく Step Up Authentication のメカニズムが導入されています。&lt;br /&gt;
例えば、Client が Protected Resource にリクエストする際に Protected Resource が要求する LoA を満たしていなければ、Client はその  LoA を満たすことができるように acr_values を指定して Authorization Request をおこなうといった流れになります。&lt;br /&gt;
ここで、Protected Resource が要求する ACR が mf (multi factor) だとします。このとき、Password のみで認証されていた場合は、MFA を実施するように誘導され、必要に応じて電話番号などの登録をおこないます。なお、Password + SMS などで認証済みであれば要求を満たすことができ、FIDO といった phishing resistant な MFA で認証済みであっても同様に要求が満たすことができます。&lt;br /&gt;
しかし、もし Protected Resource が電話番号を必要とするサービスであった場合、FIDO によって認証されたユーザーは要求された LoA は満たしますが必要な属性情報は登録済みでない可能性があります。ここで、Claims パラメーターを利用して以下のように検証済みの電話番号が必要であることを伝えます。これにより、IdP に対して一度の Authorization Request の中で電話番号の登録が必要であることも伝達して誘導することができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &amp;quot;phone_number_verified&amp;quot;: {&amp;quot;value&amp;quot;: true}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;本記事では OpenID Connect Core 1.0 で定義されている Claims パラメーターについて説明し、メルカリでの利用例についても紹介しました。Claims パラメーターは OpenID Connect Core 中に記載されている仕様ですが、あまり触れられていることが少ないと感じたためこの度記事にしてみました。実現したい処理をまずは標準仕様の中で行うことができないか検討することは、独自仕様の追加を避けて処理の透明性などを担保することに繋がると考えています。また、OpenID Connect Core など何度も読んだ仕様だとしても改めて読むと新しい気づきがあったりするのでオススメです。&lt;br /&gt;
明日の記事は @tokkuさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない</title><link>https://engineering.mercari.com/blog/entry/20251211-cf3b67a5a7/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251211-cf3b67a5a7/</guid><description>&lt;p&gt;こんにちわ。AI Task Force の @ISSA です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の11日目の記事です。 概要 ワークフローや業務自動化のツー [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 11 Dec 2025 10:00:51 GMT</pubDate><content:encoded>&lt;p&gt;こんにちわ。AI Task Force の @ISSA です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の11日目の記事です。&lt;/p&gt;
&lt;h2&gt;概要&lt;/h2&gt;
&lt;p&gt;ワークフローや業務自動化のツールは以前から数多く存在していますが、 Zapier や Make のようなノーコード・ローコードツール、Workato のようにエンタープライズ寄りの iPaaS、そして MuleSoft、Talend、Informatica といった本格的な統合基盤まで、選択肢は幅広く揃っています。&lt;br /&gt;
しかし、ノーコード・ローコードツールは構築スピードが速い一方で複雑なロジックや AI 活用には限界があり、エンタープライズ向け ETL は強力である反面、開発・運用コストが大きくスピードを求める現場では扱いづらい側面があります。&lt;br /&gt;
さらに AWS / GCP / Azure が提供する workflow engine は強力である一方で、業務部門とエンジニアが同じ環境で協働するにはハードルが高いという課題もあります。&lt;br /&gt;
コーポレート IT ではノーコード・ローコードツールを活用して業務を最適化し、プロダクト開発ではフルコードで機能を磨き込む——そんな二つの世界を行き来する中で私は気づいたことがあります。&lt;br /&gt;
それは、「スピード」「柔軟性」「協働しやすさ」を同時に満たす基盤なしに、モダンな業務自動化は成り立たないということです。&lt;br /&gt;
こうした要件から、今回私たちは&lt;a href=&quot;https://n8n.io/&quot;&gt;n8n&lt;/a&gt;を業務自動化基盤として導入しました。&lt;br /&gt;
n8nとは、ノーコード・ローコードで多様なアプリやサービスを連携させ、業務プロセスを自動化できるオープンソースのワークフロー自動化ツールです。&lt;br /&gt;
GUI とコードのバランス、イベント駆動とバッチ処理の両立、LLM との自然な統合など、現代の Developer Experience に求められる要素が揃っていたからです。本記事では、n8n を導入する上で Developer Experience がどのような観点で重要だったのか、その点を中心にお話しします。&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;私はこれまで、常に “業務と開発のあいだ” に身を置いてきました。&lt;br /&gt;
B2B SaaS のソフトウェアエンジニアとしてキャリアをスタートした後、事業会社の SE として業務システム導入とサービス開発の双方に携わりました。&lt;br /&gt;
Salesforce を中心とする PaaS 上でのアプリ構築や ETL / iPaaS ツールの導入を経験し、ノーコード・ローコードツールによる業務システム開発の世界を本格的に知ったのもこの頃です。&lt;br /&gt;
前職 では、Salesforce Platform（PaaS）上での 数十万行規模の SaaS プロダクト開発 を担当しました。その中で Salesforce DX（現 Salesforce CLI）に出会い、手作業による設定移行（いわゆるチェンジセット中心の開発）から脱却し、ソース主導の開発・バージョン管理・CI/CD といった “あるべき開発者体験” を強烈に実感しました。PaaS でありながらフルコードのように扱える世界が存在する——そんな手応えを得た転機でした。&lt;br /&gt;
現在はメルカリの IT 部門で、業務プロセス、SaaS、プロダクト開発が交差する環境の中、さまざまなシステムの開発・導入を担当しています。ノーコードとフルコード、業務と開発の双方を理解する立場として、「組織としてどのような Developer Experience を設計すべきか」を考え続けています。こうした背景から、長年追い求めてきたワークフロー基盤の“聖杯”とも言える理想像に最も近い存在として、n8n にたどり着きました。なぜ n8n が“聖杯に近い”と感じたのか&lt;br /&gt;
多くのツールを触ってきましたが、それぞれ強みと弱みがあり、業務と開発の双方が求める要件を“ちょうどよく”満たすものにはなかなか出会えませんでした。&lt;br /&gt;
n8n に触れたときに感じたのは、ノーコード・ローコードツールでありながら、ソフトウェア開発の当たり前をそのまま再現できるという稀有さです。これは単なる便利ツールではなく、業務オペレーションとプロダクト開発が協働できる “基盤” になり得るという感覚に近いものでした。ここからは、私が特に評価した n8n の Developer Experience の 3 つの特徴を紹介します。&lt;/p&gt;
&lt;h3&gt;1. 安全な開発とデプロイを支える環境分離&lt;/h3&gt;
&lt;p&gt;n8n の一つ目の特徴は、ノーコード・ローコードツールでありながら、開発（dev）と本番（prod）の環境分離を前提としている 点です。多くのツールでは、ワークフローの編集と本番実行が同じ環境で行われるため、「本番が壊れるのでは？」という不安が常につきまといます。n8n では、dev・stg・prod のように 明確に独立した環境を持てる ため、業務に影響を与えずに新しいワークフローを試し、改善することができます。環境ごとに設定や変数も独立しており、たとえば開発用の API キーやテストデータを安心して利用できます。このように、prod と dev を安全に分離して扱えること自体が、業務自動化基盤として非常に大きな価値 です。&lt;/p&gt;
&lt;h3&gt;2. 変更管理と協働を支える Source Control（GitOps）&lt;/h3&gt;
&lt;p&gt;環境分離が「どこで安全に実行するか」を決める仕組みだとすれば、&lt;br /&gt;
Source Control は “何をどの状態で管理し、どう協働するか” を担う仕組みです。&lt;br /&gt;
n8n の Source Control は単なる Git 連携ではなく、&lt;br /&gt;
ワークフローを JSON として push/pull し、その時点の状態を正確に再現できる GitOps 基盤になっています。これにより、変更履歴の可視化、ロールバック、環境間の同期など、ソフトウェア開発で一般的なプロセスを低コード環境にも適用できます。PR レビュー自体は GitHub/GitLab 側で行いますが、ワークフローの更新を Git に集約することで、ノーコード・ローコードツールでは珍しいレベルの協働性と透明性を実現します。n8n が提供するこの GitOps 体験こそ、複数人で安全にワークフローを進化させるための大きな強みです。&lt;/p&gt;
&lt;h3&gt;3. Infra as Code を可能にする JSON ベースの構造&lt;/h3&gt;
&lt;p&gt;n8n のワークフローはすべて JSON で定義されています。これにより、CLI や API を使った自動デプロイ、CI/CD への統合が容易になり、ワークフローをコードと同じプロセスで管理できる 点が大きな特徴です。一般的なノーコードツールは UI 操作に依存するため、変更の再現性やテスト、自動化が難しいという課題があります。一方 n8n はワークフローそのものが構造化データとして扱えるため、&lt;br /&gt;
IaC と同様にバージョン管理・レビュー・自動デプロイを実現できます。&lt;br /&gt;
これにより、ワークフローは“便利なツール”の域を超え、組織全体の運用基盤として耐えうる堅牢さと拡張性を備えるようになります。&lt;/p&gt;
&lt;h2&gt;ノンエンジニアにとっての課題と、組織としての解決策&lt;/h2&gt;
&lt;p&gt;ここまで主に Developer Experience の観点から n8n を紹介してきましたが、一方で「ノーコード・ローコードだから誰でも簡単に使える」というわかりやすいストーリーだけでは語れない側面も存在します。特に、業務部門のメンバーを含む ノンエンジニアが n8nを扱う際の難易度は、正直に向き合うべきテーマだと考えています。&lt;/p&gt;
&lt;h2&gt;なぜノンエンジニアにとって難しいのか&lt;/h2&gt;
&lt;p&gt;n8n は柔軟な分、ワークフロー構築時に求められる選択肢が多く、設定項目も細かいのが特徴です。たとえば以下のようなポイントは、非エンジニアにとって大きなハードルになります。&lt;/p&gt;
&lt;h3&gt;HTTP ノードの設定&lt;/h3&gt;
&lt;p&gt;どのエンドポイントに、どの HTTP メソッドで、どの認証方式を使うのか。API の仕様理解そのものが前提となります。&lt;/p&gt;
&lt;h3&gt;認証情報・スコープ管理&lt;/h3&gt;
&lt;p&gt;API キーをどこに保持するか、どの権限が必要か、最小権限設計が適切かなど、IAM の基礎知識が求められます。&lt;/p&gt;
&lt;h3&gt;MCP（Model Context Protocol）の拡張&lt;/h3&gt;
&lt;p&gt;MCP に独自の機能を追加する際、どのように設定し、どの認証情報を与えるか。これはもはやアプリケーションアーキテクチャの理解が必要です。&lt;/p&gt;
&lt;h3&gt;コードノード（Python / JavaScript）の存在&lt;/h3&gt;
&lt;p&gt;柔軟性が高い一方で、自由度を活かそうとするとコーディング能力そのものが要求されます。&lt;br /&gt;
このように、n8n の“できることの広さ”は魅力である一方、ノンエンジニアにとっては 情報リテラシーの要求水準が決して低くない、という現実があります。&lt;/p&gt;
&lt;h2&gt;メルカリのスケールで展開するために実施した3つのソリューション&lt;/h2&gt;
&lt;p&gt;こうした課題に対して、私たちは「個々のスキル差に依存しない、安全で再現性のある運用」を実現するために、3 つの仕組みを導入しました。&lt;/p&gt;
&lt;h3&gt;1.セキュリティガードレールの整備&lt;/h3&gt;
&lt;p&gt;詳細は別の記事で紹介されますが、n8n の柔軟性ゆえに発生し得るリスク（権限の混同、過剰な API 権限、危険な分岐など）を自動検出するため、静的解析・DAG 解析を組み合わせた独自ツールを構築しました。これにより、セキュリティレビュー工数を大幅に削減し、安全性を担保しています。&lt;/p&gt;
&lt;h3&gt;2.MCP による自然言語開発体験の導入&lt;/h3&gt;
&lt;p&gt;メルカリでは、MCP は「会社として認可されたもののみ使用可能」というルールがありますが、n8n においては一般公開されている OSS の MCPを利用可能にすることで、個々人が自然言語を使ってワークフロー構築をサポートできるようにしました。これにより、ノンエンジニアでも“どこから手を付ければよいか分からない”という状況を減らし、最初の一歩を踏み出しやすい環境が整備されています。&lt;/p&gt;
&lt;h3&gt;3.社内レビュー体制と Slackbot による補助&lt;/h3&gt;
&lt;p&gt;n8n ワークフローは、社内のレビュー体制によって品質・セキュリティが担保されています。さらに、Slackbot がレビューや申請のフローを補助することで、 レビュー依頼 → 自動チェック → 承認までをスムーズに進められるようになっています。&lt;br /&gt;
この仕組みによって、ノンエンジニアが構築したワークフローでも安心して本番運用できる環境が整いました。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;n8n がもたらす価値は、ワークフロー自動化を“業務ツール”ではなく“技術基盤”として扱えるようにする点にあります。環境の分離、変更管理の明確さ、そしてコードと同等の扱いやすさ。これらが組み合わさることで、業務自動化が持続的に改善できるプロセスへと変わります。&lt;br /&gt;
一方で、導入には学習コストがかかったり、PR レビュー機能が組み込まれていないなど、改善の余地も存在します。それでも、ワークフローをエンジニアリングの文脈で扱えるツールは多くなく、n8n はその中でも際立った選択肢だと感じています。&lt;br /&gt;
今回の記事では Developer Experience に焦点を当てましたが、n8n には他にもセキュリティ、拡張性、運用性など、多くの語るべきポイントがあります。&lt;br /&gt;
今後、シリーズとしてさまざまな視点から n8n の実像を紹介していく予定です。&lt;/p&gt;
&lt;p&gt;明日の記事は Tさんによる、「Making n8n Enterprise-Ready: 企業向けn8nの導入と運用の取り組み」です。お楽しみください。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;n8n.io logo source: &lt;a href=&quot;https://n8n.io/brandguidelines/&quot;&gt;https://n8n.io/brandguidelines/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>LiveContactToolにおける機微情報の取り扱い~Cloud DLPを使ったマスキング</title><link>https://engineering.mercari.com/blog/entry/20251208-b7adaa9b98/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251208-b7adaa9b98/</guid><description>&lt;p&gt;こんにちは。メルカリでソフトウェアエンジニアをやっている@stersです。 この記事は、Mercari Advent Calendar 2025 の10日目の記事です。 LiveContactTool LiveConta [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 10 Dec 2025 11:00:32 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリでソフトウェアエンジニアをやっている&lt;a href=&quot;https://twitter.com/sters9&quot;&gt;@sters&lt;/a&gt;です。&lt;/p&gt;
&lt;p&gt;この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の10日目の記事です。&lt;/p&gt;
&lt;h1&gt;LiveContactTool&lt;/h1&gt;
&lt;p&gt;LiveContactToolとは、カスタマーサービスのオペレーターがチャット応対に使用するシステムです。 “Effortless Customer Experience Project” というプロジェクトで、“世界中のメルカリのお客さまのお困りごとを 5 分で解決する” というゴールへ向かうために開発しているシステムです。&lt;/p&gt;
&lt;p&gt;先日行われた、Mercari Gears 2025 でも関連するセッションがありました。こちらもぜひご覧ください！&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://speakerdeck.com/mercari/mercari-gears-2025-transforming-customer-engagement-with-google-customer-engagement-suite&quot;&gt;https://speakerdeck.com/mercari/mercari-gears-2025-transforming-customer-engagement-with-google-customer-engagement-suite&lt;/a&gt; &lt;/p&gt;
&lt;h1&gt;機微情報&lt;/h1&gt;
&lt;p&gt;機微情報とは「個人情報のうち、特に取り扱いに注意すべき情報」として取り扱いが定められている情報です。「&lt;a href=&quot;https://www.ppc.go.jp/personalinfo/legal/kinyubunya_GL/#a5&quot;&gt;金融分野における個人情報保護に関するガイドライン&lt;/a&gt;」では次のように書かれています。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;法第２条第３項に定める要配慮個人情報並びに労働組合への加盟、門地、本籍地、保健医療及び性生活（これらのうち要配慮個人情報に該当するものを除く。）に関する情報（本人、国の機関、地方公共団体、学術研究機関等、法第57条第１項各号に掲げる者若しくは施行規則第６条各号に掲げる者により公開されているもの、又は、本人を目視し、若しくは撮影することにより取得するその外形上明らかなものを除く。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;LiveContactTool以前のお問い合わせ応対の中でも、そういった情報が添付された場合には、該当の情報を削除できるような仕組み・フローが整備されています。ただしオペレーターによる手動での対応がメインのため、オペレーターの作業工数もかかっていました。&lt;/p&gt;
&lt;h1&gt;Cloud DLP&lt;/h1&gt;
&lt;p&gt;Cloud DLPとは、GCPのプロダクトのひとつで、Data Loss Prevention (DLP)を行うためのソリューションです。&lt;/p&gt;
&lt;p&gt;DLP とは 機密データを監視、保護をして、流出や悪用されることから守りましょう、というツール、プロセス、あるいは総称のソリューションです。保護の方法として、マスキング、難読化、匿名化をしてリスクの軽減を行うことができます。&lt;/p&gt;
&lt;p&gt;Cloud DLPはGCPの様々なプロダクトと連携して、自動的にDLPの処理を行うことができます。&lt;/p&gt;
&lt;p&gt;詳しくは公式のドキュメントを読んでいただくとよいでしょう。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cloud.google.com/security/products/dlp?hl=ja&quot;&gt;https://cloud.google.com/security/products/dlp?hl=ja&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;LiveContactTool上での機微情報の取り扱いにおいて、このCloud DLPを含めて様々な方法を検討しました。&lt;/p&gt;
&lt;p&gt;前提として、手動での機微情報削除はオペレータの工数を取ってしまうため、リアルタイムなオペレーションが必要なチャット応対ではあまりやりたくありませんでした。また、手動での機微情報削除のためのリッチなシステムを構築する必要があることも採用しにくい理由でした。&lt;/p&gt;
&lt;p&gt;結果として、Cloud DLPを自動化された機微情報削除のシステムとして採用することにしました。この理由はいくつかあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LiveContactTool上だけでなく、チャット応対として利用するCCaaSやConversational AgentsともCloud DLPと連携できる&lt;/li&gt;
&lt;li&gt;様々な情報がデフォルトでサポートされている
&lt;ul&gt;
&lt;li&gt;国ごとに固有の情報もサポートされている&lt;/li&gt;
&lt;li&gt;辞書や正規表現でカスタマイズ可能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;“マイナンバー” が前後10文字以内にあること、のようなホットワードを設定できる&lt;/li&gt;
&lt;li&gt;画像ファイルのDLPを行うことができる&lt;/li&gt;
&lt;li&gt;APIでの即時実行と、ジョブによる非同期実行がサポートされる
&lt;ul&gt;
&lt;li&gt;ジョブトリガーを設定して永続的に実行しつづけることも可能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;マネージドサービスのため、コンピューティングに関する管理が不要&lt;/li&gt;
&lt;li&gt;PoCしたところ、マスキングの精度が期待を満たしていた&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;LiveContactToolでCloud DLPをどのように使うか&lt;/h1&gt;
&lt;p&gt;簡略化したものですが、Cloud DLPまわりの全体像がこちらです。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/54125b1c-image.png&quot;&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/54125b1c-image-1024x698.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;現状では、チャットが終わり次第DLPを実施するようにしています。&lt;/p&gt;
&lt;h2&gt;テキストのマスキング&lt;/h2&gt;
&lt;p&gt;テキストデータを送るとDLP処理をしたテキストデータを返すAPIがあります。これをつかってチャットメッセージをマスキングしています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.cloud.google.com/sensitive-data-protection/docs/reference/rest/v2/projects.locations.content/deidentify&quot;&gt;https://docs.cloud.google.com/sensitive-data-protection/docs/reference/rest/v2/projects.locations.content/deidentify&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;1つのチャットメッセージは1つのSpanner上のレコードで管理しています。1つのセッションで複数のチャットメッセージが入ります。これをそのままCloud DLPのAPIで1つメッセージずつ処理しようとした場合、Cloud DLPのAPI呼び出しクオータの制限に引っかかってしまいます。&lt;/p&gt;
&lt;p&gt;そこでチャットメッセージを結合し、一定の長さの文字列にまとめてCloud DLPのAPIを呼び出すようにしています。実際にはctxや、API呼び出しのエラーハンドリング等諸々がありますが、やっていることはこんな感じです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;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) &amp;gt; 0 {
            size += len(separator)
        }
        if currentSize+size &amp;gt; maxBatchSizeBytes &amp;amp;&amp;amp; len(currentBatch) &amp;gt; 0 {
            batches = append(batches, currentBatch)
            currentBatch = nil
            currentSize = 0
        }
        currentBatch = append(currentBatch, deidentifyTarget{index: i, content: content})
        currentSize += size
    }
    if len(currentBatch) &amp;gt; 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
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;画像のマスキング&lt;/h2&gt;
&lt;p&gt;画像のバイナリを送るとDLP処理をしたバイナリを返すAPIがあります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.cloud.google.com/sensitive-data-protection/docs/reference/rest/v2/projects.locations.image/redact&quot;&gt;https://docs.cloud.google.com/sensitive-data-protection/docs/reference/rest/v2/projects.locations.image/redact&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;しかし、アプリケーションのメモリやネットワーク帯域を使うことになります。またCloud DLP側のファイルサイズの制限もあり、4MBとなっています（&lt;a href=&quot;https://docs.cloud.google.com/sensitive-data-protection/limits&quot;&gt;参考&lt;/a&gt;）。この都合のためにジョブを使うようにしています。実装としては、アプリケーションはCloud DLPのジョブを作成するAPIを呼び出しています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.cloud.google.com/sensitive-data-protection/docs/reference/rest/v2/projects.locations.dlpJobs/create&quot;&gt;https://docs.cloud.google.com/sensitive-data-protection/docs/reference/rest/v2/projects.locations.dlpJobs/create&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;ジョブを使うことで非同期にCloud DLP内で処理を行います。ファイルサイズや画像の量にも依存すると思いますが、今のところ、数秒〜30秒くらいで処理が終わっているようで、そこまで大きな遅延や問題は起きていません。&lt;/p&gt;
&lt;p&gt;Cloud DLP終了時のイベントをアプリケーション側で受け取れるので、それを利用してSpanner上のデータを更新しています。&lt;/p&gt;
&lt;h2&gt;Cloud DLPの設定管理&lt;/h2&gt;
&lt;p&gt;DLP処理を実施するAPIでは、どのように検査やマスキングを行うかを直接指定することができます。しかし、これでは設定がアプリケーションに閉じてしまい、他のアプリケーションやシステム全体から利用することが困難になります。&lt;/p&gt;
&lt;p&gt;そこでTerraformを使い、CustomInfoType、InspectTemplate、DeidentifyTemplateを定義するようにしました。このようなterraformを書いてリソースを作成しています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tf&quot;&gt;resource &amp;quot;google_data_loss_prevention_stored_info_type&amp;quot; &amp;quot;custom_regexp_infotype&amp;quot; {
  parent = &amp;quot;projects/gcp-project-id-production/locations/us&amp;quot;

  stored_info_type_id = &amp;quot;CUSTOM_REGEXP_INFOTYPE&amp;quot;
  display_name        = &amp;quot;CUSTOM_REGEXP_INFOTYPE&amp;quot;
  description         = &amp;quot;Custom InfoType using regexp&amp;quot;

  regex {
    pattern = &amp;quot;(?:${join(&amp;quot;|&amp;quot;, local.dlp_phrases.phrases)})&amp;quot;
  }
}

resource &amp;quot;google_data_loss_prevention_inspect_template&amp;quot; &amp;quot;dlp_inspect_template_for_text_content&amp;quot; {
  parent = &amp;quot;projects/gcp-project-id-production/locations/us&amp;quot;

  template_id  = &amp;quot;inspect_template_for_text&amp;quot;
  display_name = &amp;quot;Inspect template for text&amp;quot;
  description  = &amp;quot;DLP Inspect Template for text content with built-in and custom InfoTypes&amp;quot;

  depends_on = [
    google_data_loss_prevention_stored_info_type.custom_regexp_infotype,
  ]

  inspect_config {
    min_likelihood = &amp;quot;UNLIKELY&amp;quot;
    info_types {
      name = &amp;quot;LOCATION&amp;quot;
    }
    info_types {
      name = &amp;quot;CUSTOM_REGXP_INFOTYPE&amp;quot;
    }
  }
}

resource &amp;quot;google_data_loss_prevention_deidentify_template&amp;quot; &amp;quot;replace_with_detected_infotype_for_text&amp;quot; {
  parent = &amp;quot;projects/gcp-project-id-production/locations/us&amp;quot;

  template_id  = &amp;quot;replace_with_detected_infotype_for_text&amp;quot;
  display_name = &amp;quot;Mercari Contact Replace with detected InfoType for text&amp;quot;
  description  = &amp;quot;Deidentify template that mask data with detected InfoType for text&amp;quot;

  deidentify_config {
    info_type_transformations {
      transformations {
        primitive_transformation {
          replace_with_info_type_config = true
        }
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;terraformで作ったリソースを、APIの呼び出し時のパラメータに指定することができます。これで、terraformで管理するDLP設定を使った、検査・マスキングを行います。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (c *client) DeidentifyStringWithTemplate(
    ctx context.Context,
    input string,
    inspectTemplateName string,
    deidentifyTemplateName string,
) (string, error) {
    req := &amp;amp;dlppb.DeidentifyContentRequest{
        Parent:                 c.parentResource,
        InspectTemplateName:    inspectTemplateName,
        DeidentifyTemplateName: deidentifyTemplateName,
        Item: &amp;amp;dlppb.ContentItem{
            DataItem: &amp;amp;dlppb.ContentItem_Value{
                Value: input,
            },
        },
    }

    res, err := c.dlpClient.DeidentifyContent(traceCtx, req)
    if err != nil {
        return &amp;quot;&amp;quot;, err
    }

    return res.GetItem().GetValue(), nil
}&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;精度をあげるために&lt;/h1&gt;
&lt;p&gt;組み込みのInfoTypeではうまく判定されないものがあったり、正規表現や辞書での定義をすり抜けたり、あるいは間違ってマスキングされたり、といったことが起こります。そういったものをチャット応対のオペレーション中に見つけた場合、即時に対応する方針はありますが、それだけでは不十分です。&lt;/p&gt;
&lt;p&gt;そこで定期的に内容をサンプリングし、おかしなものが無いかをチェックすることにしました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ランダムにチャットメッセージを抽出し、マスキング漏れがないかを確認&lt;/li&gt;
&lt;li&gt;ランダムにマスキングされたチャットメッセージを抽出し、過剰なマスキングがないかを確認&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先日のサンプリング調査では、配送番号がクレジットカードやマイナンバーとして判定されてしまい、マスキングされるケースが見つかりました。配送番号がわからなくなってしまうとオペレーションに不都合が出てしまうため、このマスキングはされないように調整することが必要です。これについては、Cloud DLPのテンプレート、InfoTypeの辞書や正規表現、ホットワードを見直すことで改善をしました。&lt;/p&gt;
&lt;p&gt;また、検討レベルの段階ですが、システム面でモニタリングの実現可能性も探っています。Cloud DLPでは様々なメトリクスを取ることができ、何をどのくらいの一致の可能性で検出したのか、結局マスキングをしたのかしてないのか、といった情報がわかります。これらを使ったとしても、マスキング漏れや過剰なマスキングがなされていないか？を正確に知ることは難しいと思いますが、全体の傾向はわかるはずです。機微情報のモニタリングをするという点で、有用な情報になりそうだと思っています。&lt;/p&gt;
&lt;h1&gt;おわりに&lt;/h1&gt;
&lt;p&gt;LiveContactToolではCloud DLPを使って機微情報をマスキングしています、という説明をしました。&lt;/p&gt;
&lt;p&gt;Cloud DLPを使うことは初めてだったのですが、技術的な設計・実装以上に「機微情報をシステムとしてどう定義し、運用していくか」に難しさを感じました。これにはエンジニアメンバーだけではなく、関連するチームも巻き込んで、モニタリングしていく体制もセットで整備が必要でしょう。&lt;/p&gt;
&lt;p&gt;このように書くと大変そうではありますが、Cloud DLPはAPIやデータ構造の仕組みが使いやすく、様々なGCPプロダクトと連携できるのが魅力的です。フルマネージドサービスのため、細かいメンテナンスが不要なのも大きなメリットだと思います。このようにシステム面から、機微情報の取り扱いを簡単にできるようにしてくれるプロダクトだと思います。機会があればぜひ使ってみてください。&lt;/p&gt;
&lt;p&gt;そして明日12/11の記事は @kgoro です。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>AI Task Forceで学んだ「不確実性との向き合い方」</title><link>https://engineering.mercari.com/blog/entry/20251210-ai-tf-facing-uncertainty/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251210-ai-tf-facing-uncertainty/</guid><description>&lt;p&gt;はじめに こんにちは。@panorama です。 この記事は Merpay &amp;amp; Mercoin Advent Calendar 2025 の 10 日目の記事です。 私は現在、7月に新設されたAI Task Fo [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 10 Dec 2025 10:00:48 GMT</pubDate><content:encoded>&lt;h1&gt;はじめに&lt;/h1&gt;
&lt;p&gt;こんにちは。&lt;a href=&quot;https://x.com/panorama32_&quot; title=&quot;@panorama&quot;&gt;@panorama&lt;/a&gt; です。&lt;br /&gt;
この記事は &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot; title=&quot;Merpay &amp;amp; Mercoin Advent Calendar 2025&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の 10 日目の記事です。&lt;/p&gt;
&lt;p&gt;私は現在、7月に新設されたAI Task ForceというチームでEnablerを務めています。&lt;br /&gt;
AI Task Forceは、メルカリをAI Nativeな組織へと変革するために立ち上がった100名規模のチームで、Enablerは「各領域でAI Nativeな業務変革を主導する役割」とされています。&lt;br /&gt;
私の担当領域はFT Engineering(メルペイ・メルコインのエンジニアリング組織)です。&lt;/p&gt;
&lt;p&gt;AI Task Forceについては以下の記事が詳しいです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://careers.mercari.com/mercan/articles/53708/&quot;&gt;メルカリが本気で始めた「AI-Native」化。100名規模のタスクフォースが立ち上がるまで&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://careers.mercari.com/mercan/articles/56200/&quot;&gt;「AI Task Force」で変化を加速する。CTO @kimurasが描くメルカリの成長戦略&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;また、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251201-pj-double-towards-ai-native-development/&quot;&gt;2025/12/02のAdvent Calendar&lt;/a&gt;で@nnaakkaaiiさんから発表されたpj-doubleというプロジェクトでは、QA領域のTech Leadを担当しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/6e5dae32-asdd-1024x571-1.png&quot; alt=&quot;overview-of-asdd-20251210&quot; /&gt;&lt;br /&gt;
▲上記図の「Double QA」のTech Lead&lt;/p&gt;
&lt;p&gt;AIを前提とした、仕様解析、テスト観点やテストケースの設計、自動テスト、そしてQA全体のワークフロー再設計に取り組みながら、「人間が何を行い、どのような業務体験を目指し、品質をどう担保するか」を日々議論しています。そして複数のパイロットチームとともに手法やツールの改善ループを回しています。&lt;/p&gt;
&lt;p&gt;最初は「AI Task Forceとpj-doubleの話」(※1) や「なぜQA領域をやることになったか」、「どのような手法を試しているのか」を記事にしようと思っていました。しかしここ数ヶ月を振り返って、一番私に取って重大だったのは&lt;strong&gt;マインドセットの変化&lt;/strong&gt;だと気付きました。&lt;br /&gt;
そこで本記事では、AI Task Forceとpj-doubleで組織と業務のAI Native化を進める中で得られた、「不確実なものへの向き合い方」にフォーカスして書いていきます。&lt;/p&gt;
&lt;p&gt;※1 簡潔に補足しておくとAI Task Forceとpj-doubleは現在は統合状態に近く、双方の文脈で自然に登場します。実は紆余曲折あってそのようになっているのですが、今回は本題ではないので割愛します。技術書典19にメルカリとして出版した「&lt;a href=&quot;https://techbookfest.org/product/cXmRrrRCAVn3tTYfN7QPFJ?productVariantID=aFqdCda8MRkL5M0mbibGW5&quot;&gt;Unleash Mercari Tech! vol.7&lt;/a&gt;」の第6章に「AI Task Forceに異動してから何をしてきたのか、pj-doubleがどのように拡大してAI Task Forceと統合状態になったか」などを書いていますので、もし気になる方はお手に取っていただけると。&lt;/p&gt;
&lt;h1&gt;不確実なものへの向き合い方&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/d4f0ea05--1024x1024.png&quot; alt=&quot;不確実なものに向き合う&quot; /&gt;&lt;/p&gt;
&lt;p&gt;AI Task Forceで最初に直面したのは、答えのない領域でどう判断し、どう動くかという問いでした。&lt;/p&gt;
&lt;p&gt;AI時代における組織や業務のあり方も、AI技術の未来も、誰も明確な答えを持っていません。&lt;br /&gt;
そのため「失敗したらどうしよう」「確証がないままこれだけの人を巻き込んで進めて良いのか」と不安を抱く日々が続きました。&lt;/p&gt;
&lt;p&gt;しかし私は次第に&lt;br /&gt;
​&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不確実な中でも挑戦する意義&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不確実な中で物事を決める、進める価値&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という2つの考え方に行き着き、迷いを生じずに動けるようになりました。&lt;/p&gt;
&lt;h2&gt;不確実な中でも挑戦する意義&lt;/h2&gt;
&lt;p&gt;私は当初、AI Task Forceの目指す壮大な目標と答えのない挑戦にどう向き合えば良いかわからずいました。&lt;br /&gt;
一時は「社内固有の最適化だけ自分たちで行う。しかし一般的な大部分はソリューションプロバイダに任せてしまう方が、自分たちで挑戦するより合理的ではないか」という消極的な考え方になりました。&lt;/p&gt;
&lt;p&gt;しかしその考え方はAI Task Forceの挑戦を根本的に否定するものでした。&lt;/p&gt;
&lt;p&gt;たとえば、pj-doubleの取り組みではSDD (spec-driven development)に近い方法論が含まれており、こうした手法を検証することも活動の一部でした。&lt;br /&gt;
一方で、KiroのようにSDDの実現を支援するための外部ソリューションも存在しており、そうしたツールの進化に期待すれば、自分たちで手法を磨いたり検証したりする挑戦は不要なのでは、と感じる瞬間もありました。&lt;/p&gt;
&lt;p&gt;しかし最終的には&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;「誰も正解がわからない。でも、手探りで失敗を繰り返したとしても進める価値がある」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;のがAI Native化だと考え直しました。&lt;/p&gt;
&lt;p&gt;自分たちで解を見つけられるかもしれないし、他社の成功例を参考にすることになるかもしれない。結局ソリューションプロバイダのサービスを採用することになるかもしれない。&lt;br /&gt;
AI Task Forceでは最初に自分たちが真にAI Nativeな組織に辿り着くことを目指していますが、たとえそうでなかったとしても、築き上げた下地や知見があればすぐに最適解に追従することができます。&lt;br /&gt;
答えが出るまで待っていてはいつまで経っても変化が起きませんし、答えが出た頃にはまた新しい問いが生まれます。だから取り残されないために、挑戦し続けることが大切だと気付いたのです。&lt;/p&gt;
&lt;p&gt;実はこの考え方は、メルカリが10年以上かけて育ててきたカルチャー、Valueの一つ”&lt;strong&gt;Go Bold&lt;/strong&gt;”にも含まれていました。&lt;/p&gt;
&lt;p&gt;Go Bold&lt;br /&gt;
大胆にやろう&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大きな成功のためには、思考のリミッターを外して、試行回数を増やすことが必要です。誰かと同じことをすれば普通の成果しか得られません。失敗や変化を恐れず、普通ではない大胆なチャレンジをし続けます。&lt;/li&gt;
&lt;li&gt;失敗自体を責めることはしません。ナイストライを賞賛します。&lt;/li&gt;
&lt;li&gt;ミッション達成のために一人一人がありたい姿を描き、周囲に示すことを重要視します。&lt;/li&gt;
&lt;li&gt;成功・失敗に関わらず振り返りを言語化して共有することで、組織全体で素早く学び、次のチャレンジの糧にします。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;私はこのバリューのことはとっくに理解したつもりになっていました。&lt;br /&gt;
ですが今回の経験でバリューを再解釈し、メルカリはずっとこのスタンスだったのだと気付いたとき、とても背中を押されたような気持ちになりました。&lt;/p&gt;
&lt;h2&gt;不確実な中で物事を決める・進める価値&lt;/h2&gt;
&lt;p&gt;AI Task Forceは活動を開始して間もないチームです。&lt;br /&gt;
そのため&lt;br /&gt;
​&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;何を対象にAI Native化を進めるか&lt;/li&gt;
&lt;li&gt;誰を巻き込み、どの規模でやるか&lt;/li&gt;
&lt;li&gt;どの手法を採用するか&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;など、無数に決めることがあります。&lt;/p&gt;
&lt;p&gt;私はAI Task Forceへ異動する前、メルカードを作るバックエンドチームに所属していました。&lt;br /&gt;
そのチームではすでにある程度業務フローが確立しており、決定に関してはTech LeadやPM、 EM、 Directorと議論して決めていました。&lt;br /&gt;
そのためAI Task Forceに来てしばらくはどんな選択肢に対しても「これを私が決めて良いのだろうか」「とりあえず自分の意見をまとめて”偉い人”(※2)に持っていこう」みたいな考え方をしていました。&lt;/p&gt;
&lt;p&gt;※2 メルカリではTech LeadやEMも1つのRoleとしてフラットに考えるカルチャーがあるため、”偉い”という概念は本来ありません。この”偉い”という言葉は私が当時持っていた考え方を言語化しただけです。&lt;/p&gt;
&lt;p&gt;しかしAI Task Forceで物事を決めようと思ったとき、&lt;strong&gt;それを決める人は自分&lt;/strong&gt;でした。&lt;br /&gt;
というより本当は以前からずっとそうだったのです。&lt;br /&gt;
「権限がないから決められない」と思い込み、判断を他者に委ねていただけでした。&lt;br /&gt;
そこで初めて&lt;strong&gt;「決める」こと自体に大きな価値がある&lt;/strong&gt;ことに気付きました。&lt;br /&gt;
特にそれが誰にもわからないこと、決められないこと、自分で決めたくないことであればあるほどです。&lt;/p&gt;
&lt;p&gt;AI Task Forceが向き合う不確実性もまさにそうです。&lt;br /&gt;
答えがないからこそ、決断して実行に移せることに大きな価値があります。&lt;br /&gt;
もちろんその決断には自分なりの根拠があり、関係者から納得が得られる必要があります。&lt;br /&gt;
ですがそういった細かいところは差し置いて、&lt;strong&gt;「決める」「進める」ことが不確実な領域に向き合う上でとても大切&lt;/strong&gt;だとわかりました。&lt;br /&gt;
(言葉の上でも”不確実”と言っているのだから、論理的に考えても当然ですよね。ですがそれをきちんと仕事上で実感したのはAI Task Forceに入ってからでした。)&lt;br /&gt;
また可能であればその専門性をもって「決める」前に「主張する」ができるとなお良いことにも気付きました。&lt;br /&gt;
決めることも重要ですが、決めるためには自分なりの意見が必要で、「主張する」「主張を持つ」という前段階があることも重要だと感じました。&lt;/p&gt;
&lt;p&gt;たとえばpj-doubleのBackend QAでは、Scenarigo(※3)というテストツールを使うべきかという議論がありました。&lt;br /&gt;
使わない理由としては、Scenarigoがyamlベースで独自文法を含むため、AIがその構造と意味を十分に理解できず、AIの効果を最大限発揮できない懸念があったことです。&lt;br /&gt;
一方で使う理由としては、社内に既存の利用実績と知見があることと、仮に将来別ツールへ移行する場合でも、Backend QA全体フローから見れば置き換えの影響は限定的だと見積もれた点がありました。&lt;/p&gt;
&lt;p&gt;これらを踏まえ、最終的には Scenarigo を採用することに決めました。&lt;br /&gt;
どちらが最適かは現時点でも確定していません。&lt;br /&gt;
しかし、自分なりの主張を持ち、決断し、言語化したことで、停滞していた状況が一気に動き始めたのを感じました。&lt;/p&gt;
&lt;p&gt;これが「決める」「進める」ことが大切だと感じた場面の一例です。&lt;/p&gt;
&lt;p&gt;※3 Go製のテストツール。&lt;a href=&quot;https://github.com/scenarigo/scenarigo&quot;&gt;https://github.com/scenarigo/scenarigo&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;AI Nativeな未来での人間の価値&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/2f15fe0b--1024x683.png&quot; alt=&quot;人間が決める&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先ほどの「決める力」や「進める力」ですが、おそらくAIに関わらずTech LeadやEMといった方向性を示す必要がある役割を担う方に取っては、従来から求められてきた力だったと思います。&lt;br /&gt;
しかし私は最近、これらは単なる役職上のスキルを超えて、&lt;strong&gt;AI Nativeな未来において人間が持つ本質的な価値になる&lt;/strong&gt;のではないかと感じています。&lt;br /&gt;
というのも、先ほどの私の例を思い出すと、「いくつもの選択肢を検討し、それぞれのパターンで起こることを予測し、意見を言う (ただし決めない)」というのはもはやAIができていることだからです。&lt;br /&gt;
一方で責任と自信をもってして「決める」というのは以前の私と同じく、AIにもまだできていないことです。&lt;br /&gt;
今年の春頃「AIは意思や欲望を持たない。だからそれを持つことが人間の価値だ。」という言葉をよく耳にしましたが、今ならその意味がよくわかります。&lt;/p&gt;
&lt;h1&gt;終わりに&lt;/h1&gt;
&lt;p&gt;私は上記のようなマインドセットの変遷を経て、不確実への不安(「失敗したらどうしよう。確証がないまま進めて良いのか」という気持ち)を払拭し、この挑戦をすること自体に大きな価値を見出せるようになりました。&lt;br /&gt;
またAIというもの自体が不確実性をはらんでおり、時代も含めて不確実が大きいからこそ、決める行為そのものが価値になるということに気が付きました。&lt;br /&gt;
決める力こそが人間に残る最後のクリエイティビティなのかもしれません。&lt;/p&gt;
&lt;p&gt;この記事では、ここ数ヶ月で私の中で起こった考え方の変化を赤裸々に説明してみました。&lt;br /&gt;
この気付きが、誰かの背中を押せていたら嬉しいと思います。&lt;/p&gt;
&lt;p&gt;明日の記事はISSAさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>Search Results Quality Monitoring with LLMs</title><link>https://engineering.mercari.com/blog/entry/20251208-search-results-quality-monitoring-with-llms/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251208-search-results-quality-monitoring-with-llms/</guid><description>&lt;p&gt;こんにちは。メルカリの検索領域で Software Engineer をしている @otter です。 この記事は、Mercari Advent Calendar 2025 の９日目の記事です。 メルカリの商品検索とその [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 09 Dec 2025 11:00:03 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリの検索領域で Software Engineer をしている &lt;a href=&quot;https://x.com/omohayui&quot;&gt;@otter&lt;/a&gt; です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の９日目の記事です。&lt;/p&gt;
&lt;h2&gt;メルカリの商品検索とその品質管理&lt;/h2&gt;
&lt;p&gt;メルカリの商品検索は、膨大な商品のなかからお客さまの意図を的確に汲み取り、本当に探している商品を検索結果に表示することが重要です。そのため、検索キーワードと検索結果との関連性や妥当性を日々チェックし、品質を維持・向上させることは不可欠と言えます。&lt;/p&gt;
&lt;p&gt;この記事では、検索結果の品質チェックフローをどのようにLLM（大規模言語モデル）を活用して改善してきたかをご紹介します。&lt;/p&gt;
&lt;h2&gt;検索結果の品質レビューにおける課題と要件&lt;/h2&gt;
&lt;p&gt;これまで、プロダクトマネージャーやエンジニアがサンプリングした検索キーワードごとに、検索結果アイテムを一つ一つ目視で確認し、無関係なアイテムの表示率を計算してきました。ただ、この作業は非常に時間がかかる上、複数人で行うと評価基準にばらつきが生じ、評価結果が安定しないという課題がありました。&lt;/p&gt;
&lt;p&gt;こうした課題を受け、品質レビューには、日次や週次で自動化されダッシュボードで監視できることに加えて、十分なレビュー数を安定して確保できること、明確な評価基準があること、そして検索者のコンテキストや意図を正確に汲み取れることが求められるようになりました。&lt;/p&gt;
&lt;h2&gt;LLMと評価基準による客観的かつ安定したモニタリングの実現&lt;/h2&gt;
&lt;p&gt;上記の要件を満たすため、私たちはLLMを用いた検索結果品質レビューに取り組みました。&lt;/p&gt;
&lt;p&gt;いくつかモデルを比較した結果、Gemini 2.5 Proがユーザーの意図を最も正確に把握できていたため、採用しています。&lt;/p&gt;
&lt;p&gt;当初は、検索者の目線に近い形で検索結果画面のスクリーンショットのみをLLMに入力して評価を行っていました。しかし、この方法では複雑な商品情報まで踏み込んだ判断が難しく、例えば商品の仕様やカテゴリの違いによる誤判定が生じるなど、十分な精度が得られないケースがありました。そこで、評価精度を高めるために、各商品の詳細な情報、商品名、商品種別、価格、カテゴリ、サムネイル画像などもあわせてLLMへ入力するよう改良しました。&lt;/p&gt;
&lt;h3&gt;評価基準&lt;/h3&gt;
&lt;p&gt;LLMには各商品について「Relevance Score (0.0–1.0)」と、その理由を返答するように指示しています。スコアはAmazonの &lt;a href=&quot;https://github.com/amazon-science/esci-data&quot;&gt;ESCI&lt;/a&gt; relevance judgements (Exact, Substitute, Complement, Irrelevant) に基づく関連性判定を用い、分類ごとにスコアを設定しています。&lt;br /&gt;
　&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Exact (1.0)：&lt;/strong&gt; 指定クエリと完全に一致する商品（例：「iPhone 14 Pro Max 256GB」→完全に同じモデルと仕様）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Substitute (0.75)：&lt;/strong&gt; 機能的に代替可能な商品（例：「iPhone 14」→iPhone 13など、世代違いだが似た仕様）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complement (0.5)：&lt;/strong&gt; 補完的な商品やアクセサリー（例：「iPhone」→iPhoneケース、充電器）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Irrelevant (0.0)：&lt;/strong&gt; 全く無関係、または条件を満たさない商品（例：「望遠鏡」→靴下）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;従来の目視評価では判断基準が属人的になりやすく、評価結果にばらつきが生じがちでしたが、このような明確なスコア定義とLLMを組み合わせることで、評価結果の安定性や客観性が大きく向上しました。&lt;/p&gt;
&lt;h2&gt;品質モニタリングツールの仕組み&lt;/h2&gt;
&lt;p&gt;検索チームにとってSearch Relevancyの品質チェックには、現在大きく２つのユースケースがあります。&lt;/p&gt;
&lt;h3&gt;Online Monitoring&lt;/h3&gt;
&lt;p&gt;本番環境の検索クエリログからランダムに抽出したキーワードで検索結果の関連性を評価します。週に1回、約1,000件の検索キーワードについて、それぞれの検索結果上位120件の商品が対象です。&lt;/p&gt;
&lt;p&gt;レビュー結果はBigQueryテーブルに出力され、モニタリングダッシュボード等から継続的に確認できます。また、検索品質改善のためのA/Bテストや新機能リリース時に、Average Relevance ScoreやIrrelevant Items Rateへの影響を監視できます。 &lt;/p&gt;
&lt;h3&gt;Offline Evaluation&lt;/h3&gt;
&lt;p&gt;新機能をA/Bテストする前のオフライン評価や、改善検証などに使われています。検証したいキーワードをエンジニアやプロダクトマネージャーが入力することで、その検索結果・カテゴリ・ブランド・価格帯の分布、さらにLLMによる関連性評価を即時にツール上で確認できます。また、あらかじめ用意したキーワードセットによる大量一括レビューも可能です。&lt;/p&gt;
&lt;p&gt;これらの2ユースケースは異なるシステム上で稼働していますが、LLMのプロンプトを共通化することで、評価基準と結果の一貫性を保っています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/7d63f813-serp_monitor_diagram-scaled.jpg&quot; alt=&quot;SERP Monitor&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;今後の拡張の可能性&lt;/h2&gt;
&lt;p&gt;画像データとテキストデータを組み合わせることで評価精度が向上しましたが、まだ人の目による判断が必要な難しいケースも残っています。とはいえ、モデルの精度は年々大きく向上しており、今後さらに自動化できると期待しています。&lt;/p&gt;
&lt;p&gt;また、評価・監視用途だけでなく、LLMによる評価データそのものを学習用データとして活用し、検索機能のモデル精度を高めていく、といった応用も視野に入れています。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;メルカリの検索品質向上のために、これまで人手のみで行ってきた検索結果の関連性評価を、LLMを活用して自動化・安定化する取り組みを紹介しました。&lt;br /&gt;
LLMの導入によって、レビュー作業の効率化だけでなく、より客観的な評価軸をもとに継続的な品質モニタリングが実現できました。&lt;br /&gt;
今後は、評価データを活用したさらなる検索機能の改善や、より難易度の高いケースへの対応にもチャレンジしていく予定です。&lt;/p&gt;
&lt;p&gt;検索や推薦システムの品質評価に悩んでいる方、またLLMの活用に興味がある方の参考になれば幸いです。&lt;/p&gt;
&lt;p&gt;明日の記事は @task さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>Agent Specで小さく素早く回すメルカリモバイル開発現場</title><link>https://engineering.mercari.com/blog/entry/20251209-d0de07214d/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251209-d0de07214d/</guid><description>&lt;p&gt;こんにちは。メルカリモバイル Tech Leadの@_seitauです。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の9日目の記事です。 先日公開された記事『pj-d [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 09 Dec 2025 10:00:34 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリモバイル Tech Leadの&lt;a href=&quot;https://x.com/_seitau&quot;&gt;@_seitau&lt;/a&gt;です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の9日目の記事です。&lt;/p&gt;
&lt;p&gt;先日公開された記事『&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251201-pj-double-towards-ai-native-development/&quot;&gt;pj-double: メルカリの開発生産性向上に向けた挑戦&lt;/a&gt;』では、メルカリグループ全体として取り組んでいる ASDD (Agent Spec Driven Development) という新たな開発手法の概念や、AIネイティブな開発への展望について解説されました。&lt;br /&gt;
この記事では、その実践編として、より現場の実務に焦点を当てます。 2025年3月にリリースしてからさまざまな機能追加をしてきたメルカリモバイルの開発現場において、私がどのように ASDD を運用・実践してきたか、実際のプロジェクト事例を交えて紹介します。&lt;/p&gt;
&lt;h2&gt;実践フロー：PRDから実装まで&lt;/h2&gt;
&lt;p&gt;Coding Agentの登場により、開発者は複数のタスクを並列で進めることが可能になりました。このメリットを最大化するための具体的なワークフローについて、最近リリースした メルカリモバイルの&lt;a href=&quot;https://jp-news.mercari.com/articles/2025/12/05/mobile_multiplelines/&quot;&gt;複数回線対応プロジェクト&lt;/a&gt;を例に解説します。図示しているタスク名は仮のものを使用しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/52b2ae9d-mermaid-diagram-2025-12-04-212223-1024x862.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Step 1: PRDをPdMが作成し、リポジトリにコミットする&lt;/h3&gt;
&lt;p&gt;まず、情報の管理方法から見直します。 PdM（プロダクトマネージャー）は、PRD（プロダクト要件定義書）をドキュメントツールではなく、Markdown形式でGitHubリポジトリに直接コミットします。&lt;br /&gt;
リポジトリ内に仕様が存在することで、後続のCoding Agent（Claude CodeやCursorなど）にコンテキストとして読み込ませやすくなり、エンジニアが素早く開発に着手しやすくなります。&lt;/p&gt;
&lt;h3&gt;Step 2: Coding Agentを活用してAgent Specを生成する&lt;/h3&gt;
&lt;p&gt;次に、エンジニアはCoding Agentを使用して Agent Spec（AIへの実装指示書） を生成します。&lt;br /&gt;
この際、単にPRDを読ませるだけでなく、Slackでの議論ログやNotion上のメモなど、散らばっている関連コンテキストにも接続して読み込ませます。これにより、人間がゼロから仕様を書き起こすことなく、文脈を考慮した精度の高いSpecドラフトをAgenticに生成することができます（この仕組みの詳細は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251201-pj-double-towards-ai-native-development/&quot;&gt;先述の記事&lt;/a&gt;を参照）。&lt;/p&gt;
&lt;h3&gt;Step 3: タスクリストを作成し、依存関係を可視化する&lt;/h3&gt;
&lt;p&gt;生成されたSpecを元に、具体的な実装タスクを洗い出し、「どのタスクが独立しており、どのタスクが依存関係にあるか」 を整理します。 私は実装に着手する前に、以下のようなタスクリストを作成し、依存関係を可視化しています。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;タスクID&lt;/th&gt;
&lt;th&gt;機能項目&lt;/th&gt;
&lt;th&gt;タスク名&lt;/th&gt;
&lt;th&gt;説明&lt;/th&gt;
&lt;th&gt;規模&lt;/th&gt;
&lt;th&gt;依存関係&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T001&lt;/td&gt;
&lt;td&gt;申し込み&lt;/td&gt;
&lt;td&gt;回線申し込み情報取得API修正&lt;/td&gt;
&lt;td&gt;フィールド追加&lt;/td&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;&amp;#8211;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T002&lt;/td&gt;
&lt;td&gt;申し込み&lt;/td&gt;
&lt;td&gt;回線申し込みAPI修正&lt;/td&gt;
&lt;td&gt;バリデーション追加&lt;/td&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;T001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T003&lt;/td&gt;
&lt;td&gt;申し込み&lt;/td&gt;
&lt;td&gt;回線数制限バリデーション&lt;/td&gt;
&lt;td&gt;上限追加&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;T001, T002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T006&lt;/td&gt;
&lt;td&gt;Top画面&lt;/td&gt;
&lt;td&gt;回線一覧API実装&lt;/td&gt;
&lt;td&gt;回線一覧取得&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;&amp;#8211;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T009&lt;/td&gt;
&lt;td&gt;Default回線&lt;/td&gt;
&lt;td&gt;インデックス追加&lt;/td&gt;
&lt;td&gt;インデックス追加&lt;/td&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;&amp;#8211;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T008&lt;/td&gt;
&lt;td&gt;Default回線&lt;/td&gt;
&lt;td&gt;回線切り替えロジック実装&lt;/td&gt;
&lt;td&gt;申込・開通&lt;/td&gt;
&lt;td&gt;L&lt;/td&gt;
&lt;td&gt;T009&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;この表において最も重要なのが 依存関係 の項目です。これを図解すると、以下のように並列実行可能なラインが見えてきます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/bff2809a-mermaid-diagram-2025-12-04-212846-1024x992.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これにより、「Stream A, B, Cは依存関係がないため、3つのAgentを同時に稼働させて並列実装が可能である」といった判断を的確に行えるようになります。&lt;/p&gt;
&lt;h3&gt;Step 4: チームレビューとSpecの磨き込み&lt;/h3&gt;
&lt;p&gt;整理されたタスクとAgent Specは、実装を開始する前にチームメンバーによるレビューを受けます。ここで仕様の抜け漏れや認識のズレを解消しておきます。&lt;/p&gt;
&lt;h2&gt;現場で機能させる原則&lt;/h2&gt;
&lt;p&gt;このフローを運用する上で、私が重視している原則が3点あります。&lt;/p&gt;
&lt;h3&gt;粒度を小さく保つ&lt;/h3&gt;
&lt;p&gt;1タスク = 1PR を基本とします。 AIに一度に大量の変更を指示すると、意図しない挙動やバグが発生するリスクが高まります。「バリデーション追加のみ」「DBカラム追加のみ」といったレベルまで粒度を細分化することで、AIの実装精度が安定し、人間側のレビューコストも低減されます。&lt;/p&gt;
&lt;h3&gt;依存関係を明示する&lt;/h3&gt;
&lt;p&gt;前述の通り、依存関係の定義は並列化における要です。 自分が設計（Spec作成）を行っている間に、バックグラウンドで複数のエージェントが独立したタスクの実装を進めているという状態を作り出すことが重要です。&lt;/p&gt;
&lt;h3&gt;レビューの対話をコンテキストとして活用する&lt;/h3&gt;
&lt;p&gt;Agent SpecのレビューMTGは、録画または文字起こしをしておくことを強くおすすめします。&lt;br /&gt;
最初のSpecだけでは表現しきれていなかった背景やニュアンスが、レビュー時の対話には多く含まれています。&lt;strong&gt;この文字起こしログをCoding Agentに読ませてSpecを改善させる&lt;/strong&gt;ことで、当初の記述では表現できていなかった文脈や設計の抜け漏れを補足し、より精度の高いSpecへと昇華させることができます。&lt;/p&gt;
&lt;p&gt;例えば、チームメンバーに設計を説明する中で、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同じ時期に走っている別PJで追加される機能との整合性は？&lt;/li&gt;
&lt;li&gt;なぜこのカラムにIndexを追加する必要があるのか？&lt;/li&gt;
&lt;li&gt;どのデータが回線に紐付き、どれがアカウントに紐づくのか？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;など、チームメンバーから設計をみた時の疑問点が浮き上がります。レビューMTG内でこれらの質問に対して説明し、そのログをSpecに反映することで、設計者だけでなくチーム全体で納得感のあるSpecになります。設計の意図を適切にSpecに反映するのは、Coding Agentの脱線を防ぐうえでも重要です。&lt;/p&gt;
&lt;h2&gt;導入に向けたステップ&lt;/h2&gt;
&lt;p&gt;もし、この手法を個人のタスクから試してみたい場合は、以下のステップから始めることをおすすめします。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;PRDをMarkdownで記述する: AIがコンテキストを理解しやすくなります。&lt;/li&gt;
&lt;li&gt;Coding Agentにドラフトを書かせる: 自分でゼロから書かず、PRDを読ませたAgentにSpecの叩き台を作らせてください。&lt;/li&gt;
&lt;li&gt;依存関係リストを作成する: 実装に着手する前に、タスク一覧と依存関係を整理してください。これが並列化の指針となります。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;あらかじめSpecを書いてから実装に着手する手法は目新しいものではありませんが、この記事に書いた原則やワークフローを意識することで、Coding Agentの力をさらに引き出し、並列に素早く実装を進めることができます。&lt;/p&gt;
&lt;p&gt;実際にAgent Specを用いて並列で開発するワークフローを実践するようになってから、1ヶ月かかる想定だったプロジェクトを1週間で終えることができた事例もあり、確実に開発速度が向上しています。&lt;/p&gt;
&lt;p&gt;これらの定量的な開発速度の変化や、AI Native化へ向けたメルカリの開発組織の取り組みについては先日開催したmercari GEARS 2025で発表しています。ぜひ併せてご覧ください。&lt;/p&gt;
&lt;p&gt;&lt;iframe loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/YrZQZvJxhOY?si=DbKUlIWMRIedPahM&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;メルカリでは、ASDD (Agent Spec Driven Development) を含め、日々よりよい開発手法を試行錯誤しています。この記事がAIネイティブな開発スタイルについて考えるきっかけとなれば幸いです。&lt;/p&gt;
&lt;p&gt;明日の記事はpanoramaさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>QAエンジニアがAIで日々の課題を解決した話</title><link>https://engineering.mercari.com/blog/entry/20251203-46bf6511f3/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251203-46bf6511f3/</guid><description>&lt;p&gt;こんにちは。メルカリ ハロでQAエンジニアをしている@Yu-gaです。 この記事は、Mercari Advent Calendar 2025 の7日目の記事です。 概要 「この作業、AIで効率化できないかな？」そんな模索 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sun, 07 Dec 2025 11:00:29 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリ ハロでQAエンジニアをしている&lt;a href=&quot;https://x.com/monsterggg?t=-N08JojGYjJ5db3V4nGExA&amp;amp;s=09&quot;&gt;@Yu-ga&lt;/a&gt;です。&lt;/p&gt;
&lt;p&gt;この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の7日目の記事です。&lt;/p&gt;
&lt;h2&gt;概要&lt;/h2&gt;
&lt;p&gt;「この作業、AIで効率化できないかな？」そんな模索をしていた時、人手不足という現実的な課題に直面しました。本記事では、QAエンジニアとして1〜2日で作れる小さなプロトタイプから始め、チームのフィードバックを受けながら実用的なツールに育ててきた実体験を紹介します。&lt;/p&gt;
&lt;h2&gt;きっかけと課題&lt;/h2&gt;
&lt;p&gt;メルカリが「AI Native」への転換を発表する中、QAチームでも「私たちの業務にもAIを取り入れられないか？」という議論が活発になっていました。&lt;/p&gt;
&lt;p&gt;限られたリソースの中で品質を維持するには、これまでのやり方を見直す必要があります。そこでチーム全体で集まり、&lt;strong&gt;「今後のQAプロセスをどう進化させるか」「AIでどの部分を効率化できるか」&lt;/strong&gt; を議論しました。&lt;/p&gt;
&lt;p&gt;話し合いを重ねる中で、「まずは課題解決ツールのプロトタイプを作ってみよう」という共通の方向性が生まれ、限られた時間とリソースの中でQA業務全体の効率化を目指し、AIを活用した新しいQAプロセスの検証が始まりました。&lt;/p&gt;
&lt;h2&gt;QAエンジニア以外でもACを書けるようにしたい&lt;/h2&gt;
&lt;p&gt;メルカリ ハロではQAエンジニアがAcceptance Criteria（以下AC）をJiraのストーリーチケットで作成していましたが、これをQAエンジニア以外でも簡単かつQAエンジニアと同程度の品質で作成できるようにする必要があり「AC Generator」というWebツールを開発しました。&lt;/p&gt;
&lt;p&gt;ここでいうAcceptance Criteria（受け入れ基準）とは、&lt;strong&gt;その機能が「完成した」と判断するための具体的な条件&lt;/strong&gt;のことです。&lt;br /&gt;
※以前のAC活用に関する取り組みについては&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241207-mercari-hallo-2024/&quot;&gt;こちらの記事&lt;/a&gt;もご覧ください。&lt;/p&gt;
&lt;p&gt;「AC Generator」の開発にあたっては、「PMやエンジニア全員が使えること」と「人間によるレビュー・修正を必須とすること」を設計の核としました。&lt;br /&gt;
誰もが使えるようにしたのは、単なるQAのリソース不足解消だけが目的ではありません。QAエンジニア以外でもACを作成できるようになれば、開発の初期段階から全員が品質について同じ解像度で議論しやすくなると考えたためです。&lt;/p&gt;
&lt;p&gt;また、レビューを必須としたのは、AIに「ある程度の土台」を作ってもらうことで作成のハードルを下げつつ、AI特有の誤り（ハルシネーション）を人間の目で排除するためです。これにより、誰でも効率的に、かつ一定品質以上のACを作成できるようになると考えました。&lt;/p&gt;
&lt;p&gt;また、「過去にQAエンジニアが作成したACから厳選した学習データ」をAIに分析させ、AC作成ルールを生成しました。このルールには、AC作成の際の書き方や構造、仕様書からACを抽出する際の観点などが含まれています。&lt;/p&gt;
&lt;p&gt;最初のプロトタイプでは、ユーザーがConfluenceの仕様書URLを入力すると、AC作成ルールを元にAIが自動でACを生成し、Web画面で結果を表示、それを人間がレビュー・修正してJiraチケットに起票するという一連のフローを構築しました。このプロトタイプは、AIを用いて1日程度で作成することができました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/795f12cd-pr-rule.png&quot; alt=&quot;AC Generator システムフロー&quot; /&gt;&lt;/p&gt;
&lt;p&gt;実際にPMやエンジニア、QAエンジニアにツールを使用してもらい、SlackやJiraでフィードバックを収集し、それらに一件一件対応しました。例えば「成果物の精度がばらつく」という意見に対してはAIレビュー機能を追加し、「AIに再度修正して欲しい」という要望にはAIフィードバック機能を追加するなど、20件を超えるフィードバックに対応して機能を拡張しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/6c823f0d-092a-4a8e-ab7b-b8182158f6e4-1024x886.png&quot; alt=&quot;AC Generator 実際の画面&quot; /&gt;&lt;/p&gt;
&lt;p&gt;その結果、PMやエンジニアがACを作成できるようになり、個人差はあるものの&lt;strong&gt;最低限のACが誰でも作成可能な状態&lt;/strong&gt; となりました。AC Generatorが一時的に使用できなくなった際に、エンジニアから「AC GeneratorがないとAC作れないよ」という声をいただき、実用的なツールに成長できたことを実感しました。&lt;/p&gt;
&lt;p&gt;現在は仕様書とルールベースでの生成に限定されているため、仕様書に記載されていない影響範囲の分析ができませんが、将来的には仕様書全体の取り込みやコードベースを考慮した生成にも発展させていきたいと考えています。&lt;/p&gt;
&lt;h2&gt;リグレッションテストケース作成を効率化したい&lt;/h2&gt;
&lt;p&gt;AC Generatorと同様のアプローチで、リグレッションテストケース作成の効率化にも取り組みました。「ACから重要なものをリグレッションテストに追加する」というプロセスがありましたが、その作業は後回しにされることが多く、&lt;strong&gt;リグレッションテストが増えない&lt;/strong&gt; という課題がありました。&lt;/p&gt;
&lt;p&gt;そこで、テスト管理ツールの&lt;a href=&quot;https://www.testrail.com/&quot;&gt;TestRail&lt;/a&gt;に登録されている既存のリグレッションテストとその階層構造をAIに分析させました。これにより、どのようなテストがどのフォルダに分類されるべきかという分類ルールや粒度を学習し、現状の運用に即したリグレッションテスト作成ルールを策定しました。&lt;/p&gt;
&lt;p&gt;先ほど紹介したAC Generatorのアーキテクチャをベースに、誰でも簡単に使えることを重視し、ACとルールからリグレッションテストを生成、Web画面で人間がレビュー・修正してTestRailに起票するというフローをAIを用いて1〜2日で構築しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/eab9ac77-regression-test-generator.png&quot; alt=&quot;リグレッションテスト Generator システムフロー&quot; /&gt;&lt;/p&gt;
&lt;p&gt;このツールにより生成されたテストケースの精度が高く、そのまま使えるレベルのものも多かったため&lt;strong&gt;リグレッションテスト作成の工数は体感で8割程度削減&lt;/strong&gt; されました。しかし、作成コストは削減できたものの、リグレッションテスト作成のきっかけ作りには至らず、テスト数の増加効果は限定的でした。&lt;/p&gt;
&lt;p&gt;根本的な解決には、作業のトリガーとタイミングの自動化が必要でした。そこで、ACのJiraチケット完了時に自動でリグレッションテスト生成・PR作成を行うワークフローの開発にも着手しましたが、現状まだ実用段階にはなっておりません。&lt;/p&gt;
&lt;h2&gt;E2Eテスト作成を効率化したい&lt;/h2&gt;
&lt;p&gt;E2Eテスト作成においては、テストコードの書き方やTestRailの構造に合わせたファイル設計、Page Object Model、コーディング規約など習得すべき知識が多く、&lt;strong&gt;特定のメンバーに依存してスケールしにくい&lt;/strong&gt; という課題がありました。&lt;/p&gt;
&lt;p&gt;この課題に対し、既存のE2EテストをAIに分析させてコーディング規約を自動生成し、さらに&lt;a href=&quot;https://playwright.dev/docs/best-practices&quot;&gt;Playwrightのベストプラクティス&lt;/a&gt;も組み込んだルールを作成しました。&lt;br /&gt;
開発用エディタでの動作を前提としたE2E作成ルールと、TestRailのケースIDからテスト手順などの情報を取得するスクリプトを組み合わせることで、&lt;strong&gt;AIがTestRail情報を基にE2Eテストコードを自動生成できる&lt;/strong&gt; ようにしました。このプロトタイプもAIを用いて2日程度で作成することができました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/4c27b74b-create-e2e-rule.png&quot; alt=&quot;E2Eテスト作成効率化フロー&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Page Objectの書き方統一やAPI利用ルールの追加、AIレビュールールの追加など、フィードバックを受けて改善を行った結果、AIによるE2Eテスト作成の精度が向上し、以前よりも効率化されました。&lt;br /&gt;
E2E作成ルールの整備により、チームメンバー誰もが一定品質のE2Eテストを作成できるようになりました。&lt;/p&gt;
&lt;p&gt;ただし、生成されたコードには人間によるレビューと修正が必要で、チームメンバーにも基礎知識が求められるため、完全な自動化には至らず、人的コストは残存しています。&lt;/p&gt;
&lt;h2&gt;テスト前にコードベースからIssueを検出したい&lt;/h2&gt;
&lt;p&gt;コードレビューによる問題特定は従来から行っていましたが、得意・不得意な技術領域があり、レビューの質に偏りがあるという課題がありました。&lt;/p&gt;
&lt;p&gt;そこで、AIを活用してPRの修正内容（差分）を分析し、仕様書やACと照合してコードベースからバグを検出する取り組みを行いました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/331db3b5-ac-generator.png&quot; alt=&quot;コードベースIssue検出フロー&quot; /&gt;&lt;/p&gt;
&lt;p&gt;QAエンジニアがテストを開始する前にこのチェックを行い、問題点だけでなく解決策もセットでエンジニアにフィードバックするようにしました。その結果、手動テストでは発見困難な問題（動作には影響しない型定義のミスなど）や仕様との齟齬をテスト前に検出できるようになりました。&lt;/p&gt;
&lt;p&gt;この取り組みにより、&lt;strong&gt;手動テスト前のIssue発見とコード理解&lt;/strong&gt; の両面で大きな効果を得られました。一方で、AIの回答には誤りも多く含まれており、AIの提案を鵜呑みにせず、人間による適切な判断が不可欠であることを学びました。&lt;/p&gt;
&lt;h2&gt;学び&lt;/h2&gt;
&lt;p&gt;今回の取り組みを通じて、AIは単なる作業自動化の手段にとどまらず、QAエンジニアが抱える課題を自らの手で解決するための強力な武器になることを実感しました。一連のツール開発とチームでの運用を通じて得られた、AI活用における学びを以下にまとめます。&lt;/p&gt;
&lt;h3&gt;まずはやってみる&lt;/h3&gt;
&lt;p&gt;技術的な難易度を事前に考えすぎるより、まず手を動かして動くものを作ることの重要性を実感しました。どの機能も最初のプロトタイプは1〜2日で作成でき、AIを活用すれば想像以上に多くのことが実現可能であることがわかりました。&lt;/p&gt;
&lt;h3&gt;フィードバックループを早く回す&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;最小プロトタイプを1〜2日で作成&lt;/li&gt;
&lt;li&gt;みんなに使ってもらう&lt;/li&gt;
&lt;li&gt;フィードバックを収集&lt;/li&gt;
&lt;li&gt;改善する&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;このサイクルを早く回すことで、実用的なツールに育ちました。&lt;/p&gt;
&lt;p&gt;また、&lt;strong&gt;小さく始めることで失敗のリスクを下げる&lt;/strong&gt; ことができました。1〜2日で作れるプロトタイプなら、仮に使われなかったり方向性が間違っていても、投資した時間やコストは最小限で済みます。大規模な開発を始める前に、本当にニーズがあるのか、アプローチが正しいのかを低コストで検証できたことは大きなメリットでした。&lt;/p&gt;
&lt;p&gt;多くの人に使ってもらいフィードバックを得ることは簡単ではありませんが、最初のツール紹介の反応をきっかけに、発信の場や伝え方を工夫するようになりました。その結果、より多くのメンバーの目に留まり、さまざまなフィードバックを得ることができました。&lt;/p&gt;
&lt;p&gt;プロトタイプは使われなければ成長しないため、開発前から「どうやって使ってもらうか」「どうフィードバックを集めるか」を戦略的に考えることの重要性を学びました。&lt;/p&gt;
&lt;h3&gt;半分の効率化でも十分価値がある&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;AIが頭出し → 人間がレビュー・修正&lt;/li&gt;
&lt;li&gt;AIが下書き → 人間が仕上げ&lt;/li&gt;
&lt;li&gt;AIが指摘 → 人間が判断&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この協働パターンが最も効果的でした。AIが最初に生成するものには品質のばらつきがあるため、人間によるレビューと修正は必須のプロセスだと思います。&lt;/p&gt;
&lt;p&gt;100%の効率化を目指すと、完璧を求めるあまり開発に時間がかかったり、そもそも実現できなかったりします。しかし、50%程度の効率化であれば、短期間で実現でき、すぐに効果を実感できます。&lt;/p&gt;
&lt;p&gt;そして重要なのは、削減できた時間で新たな改善に取り組めることです。50%削減で生まれた時間を使って次のツールを作り、さらに効率化を進める。この積み重ねによって、結果的に大きな効率化を実現できました。&lt;/p&gt;
&lt;h3&gt;テストを事前に書いておく&lt;/h3&gt;
&lt;p&gt;AIは想定外の動きをします。フィードバックを元に機能を追加する際に、既存機能を壊してしまうということが散見されました。それを防ぐために、コア機能についてテストを事前に実装し、AIが修正を行う際には必ずテストをクリアすることを必須としました。&lt;/p&gt;
&lt;p&gt;例えば、AC Generatorでは以下のようなテストを実装しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基本表示テスト&lt;/strong&gt;: フォーム要素が正しく表示されるか&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;バリデーションテスト&lt;/strong&gt;: 必須フィールドやURL形式のチェック&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完了フローテスト&lt;/strong&gt;: フォーム入力からチケット作成完了まで&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらのテストは品質保証だけでなく、&lt;strong&gt;AIに対する仕様の明確化&lt;/strong&gt;という重要な役割も果たしました。テストコードが「期待する動作」を具体的に定義することで、AIが修正を行う際の指針となり、意図しない変更を防ぐことができました。&lt;/p&gt;
&lt;p&gt;しかし、AIにコード修正を依頼すると、&lt;strong&gt;「テストが通らないから」という理由で、AIが勝手にテストコード側を修正してしまう&lt;/strong&gt;という問題も発生しました。これを防ぐために、「テストコードは修正禁止」というルールを明確にAIに与えることで解決しました。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;今回の取り組みを通じて、&lt;strong&gt;「経験がないからできない」という時代は終わった&lt;/strong&gt;と強く感じました。&lt;/p&gt;
&lt;p&gt;AIの登場により、未経験の領域でも成果を出せる可能性が劇的に広がりました。実際、今回のツール開発では、これまで使ったことのない技術やアーキテクチャを容易に取り入れることができました。「やったことがないから無理だ」と諦めていたアイデアも、AIというパートナーがいれば、わずか1〜2日で形にできる時代です。&lt;/p&gt;
&lt;p&gt;今回の取り組みで最も強く感じたのは、AI活用において重要なのはプログラミングスキルそのものよりも、「この課題を解決したい」という強い当事者意識だということです。&lt;br /&gt;
「誰かが解決してくれるのを待つ」のではなく、「AIを活用して自分で解決する」。&lt;/p&gt;
&lt;p&gt;このマインドセットの変化こそが、業務効率化以上の価値を私自身にもたらしてくれました。AIですべてを100%解決できるとは思いませんが、自らの手で課題を解決し、より本質的な品質保証活動に注力するための強力な武器になると思います。&lt;/p&gt;
&lt;p&gt;今後もAIの力を借りながら、品質保証の新しい形を模索し続けていきたいと思います。&lt;/p&gt;
&lt;p&gt;明日の記事は @Antony Chane-Hive さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>本番環境でしか起こらない Goroutines 急増問題と解決までの道のり</title><link>https://engineering.mercari.com/blog/entry/20251207-61e24d343e/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251207-61e24d343e/</guid><description>&lt;p&gt;こんにちは。Backend Enablement Team のエンジニアの @goccy です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の7日目の記事です。 時は [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sun, 07 Dec 2025 10:00:51 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。Backend Enablement Team のエンジニアの &lt;a href=&quot;https://x.com/goccy54&quot;&gt;@goccy&lt;/a&gt; です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の7日目の記事です。&lt;/p&gt;
&lt;p&gt;時は今から4ヶ月ほど前の8月下旬、とても暑い日でした。珍しくチームの On Caller の PagerDuty がアラートを上げました。内容は Gateway からあるマイクロサービスへの呼び出しが突然 Timeout するようになり、gRPC の Deadline Exceeded が急増しているというものでした。対象のマイクロサービスは &lt;a href=&quot;https://github.com/mercari/grpc-federation&quot;&gt;gRPC Federation&lt;/a&gt; を利用した BFF ( Backend for Frontend ) で、基本的にはさらに後ろのマイクロサービスにプロキシするだけの単純なサービスです。少し調査すると、該当の gRPC メソッドには Timeout が設定されていなかったため、Gateway 側で設定した Timeout ( 30秒 ) を超過した際に、Gateway 側でリクエストをキャンセルするタイミングで出ているエラーだとわかりました。この間、BFF 側でエラーが発生していないにもかかわらず Goroutines は増え続けていることから、リクエストが処理できずにサーバー上で滞留しているような状況だと推測できました。しかし、それ以上の調査が困難なことと、問題が起きているのが一部の Kubernetes Pod だけだったため、該当の Pod を再起動することを試したところ、無事解決に向かいました。その日は営業時間が終了間際だったこともあり、もやもやしたものを抱えつつ、いったん解散して帰途につくことになりました。&lt;/p&gt;
&lt;p&gt;このときはまだ、これから長い闘いが待っていることをチームの誰も知る由もありませんでした。&lt;/p&gt;
&lt;p&gt;サービス間の関係図&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/c52c28b5-day7】merpay-mercoin-advent-calendar-2025-engineering-blog-draft-template-e1764916208261.png&quot;/&gt;&lt;/p&gt;
&lt;p&gt;それから二日後、先日の問題の原因究明もろくにできていないような状況で、再びアラートが上がりました。しかし今回は前回とは別のサービスで、かつ gRPC Federation を利用していました。現象は前回同様、突然該当のサービスの特定の Pod だけ Goroutines が急増し応答しなくなるという問題で、Pod を作り直すと改善するという現象も同様でした。&lt;/p&gt;
&lt;p&gt;立て続けに gRPC Federation を利用する複数のサービスで同様の問題が起こったため、原因が gRPC Federation 側にありそうだという線が濃厚になってきました。これはとてもまずい状況です。gRPC Federation を利用するサービスは 20 を超えます。&lt;/p&gt;
&lt;p&gt;突如として暗雲が垂れ込めます。この日から、チーム総出でこの問題に対処するようになりました。&lt;/p&gt;
&lt;h2&gt;Lock 範囲の最小化&lt;/h2&gt;
&lt;p&gt;まず、該当のサービスの直近のリリースに含まれる gRPC Federation の変更点などを調査しましたが、こちらは特に怪しいものはないという結論に至りました。次に、リクエストが滞留し続けることから、gRPC Federation 側で取得している Lock の待ち時間が長すぎたり、Dead Lock しているケースがないかを疑いました。該当のサービスでは Cloud Profiler を導入しており、その結果から Lock 待ちをしているスレッドが大量にあることがわかりました。Lock 箇所として複数の候補がありましたが、それらを Claude Code を使ってプロファイリング結果を参照しながら議論したところ、いずれも Dead Lock でないこともわかりました。次の可能性として、Lock を解放するまでの時間が長く、Lock を解放するよりも Lock の待ち行列に加わるスレッドの方が多くなり、結果 Goroutines が増え続けている可能性を考えました。そこで、まずは Cloud Profiler が挙げた Lock 箇所のうち、Lock 範囲を最小化することで不必要に長く Lock を取得している箇所をなくしました。( &lt;a href=&quot;https://github.com/mercari/grpc-federation/pull/323&quot;&gt;該当のPR&lt;/a&gt; )&lt;/p&gt;
&lt;p&gt;しかし数日後、また Goroutines が急増しアラートが上がりました。この時点から、一日に3〜 5回程度不定期にアラートが鳴り続けるようになってしまい、Pod を再起動するだけとはいえ、On Caller の負担が顕著に増えました。ただ、Lock 範囲を狭めたことで、Lock 待ちをしている箇所のほとんどが gRPC Federation のプラグインを利用している箇所であるとわかりました。&lt;/p&gt;
&lt;h2&gt;gRPC Federation のプラグインアーキテクチャ&lt;/h2&gt;
&lt;p&gt;ここで、以降の説明を理解しやすくするために gRPC Federation のプラグインアーキテクチャについて解説します。gRPC Federation 自体の詳しい説明は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20240401-4f426bd460/&quot;&gt;私が以前書いた別記事&lt;/a&gt;をご参照ください。&lt;/p&gt;
&lt;p&gt;簡単に説明すると、gRPC Federation は Protocol Buffers 上で記述できる型推論付き静的型付け言語であり、式の評価には &lt;a href=&quot;https://github.com/google/cel-spec/blob/master/doc/langdef.md#language-definition&quot;&gt;CEL( Common Expression Language )&lt;/a&gt; を利用しています。 OSS として開発しているため、Protocol Buffers 上で記述する CEL 式の中でドメイン固有のロジックを利用することは通常できません。例えば認証処理を例にとると、gRPC の metadata から 認証用の JWT を取り出してパースし、得られた値をバリデーションしたり、さらに別の値を取得するような処理は CEL の表現力の範囲を超えています。ですが、こういった処理は gRPC Federation を利用するすべてのサービスで必要になる共通処理でもあります。&lt;br /&gt;
gRPC Federation ではこのような要件のために、CEL 式の中で利用できる API を独自に定義し、その実装を Go で書いて WebAssembly に変換して利用する&lt;a href=&quot;https://github.com/mercari/grpc-federation/blob/main/docs/cel_plugin.md&quot;&gt;プラグインシステムを用意しています&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Go でプラグインシステムを提供する場合、WebAssembly を利用するのが最善であることは、先日 Go Conference 2025 で発表してきた (  &lt;a href=&quot;https://speakerdeck.com/goccy/go-de-webassembly-woli-yong-sita-shi-yong-de-napuraguinsisutemunogou-zhu-fang-fa&quot;&gt;Go で WebAssembly を利用した実用的なプラグインシステムの構築方法&lt;/a&gt; ) ので、詳しくはそちらを参照して欲しいのですが、重要なこととして、WebAssembly を利用する場合に次のような制約があることを知っておく必要があります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WASI P1 という仕様に沿って動作するが、P1 の段階ではスレッドの仕様は存在しない&lt;/li&gt;
&lt;li&gt;スレッドの仕様がないため、必ずシングルスレッドで動作する&lt;/li&gt;
&lt;li&gt;WebAssembly を動作させるために作るインスタンスは、起動するだけで数十MBのメモリを消費するため、インスタンスを無制限に作るようなアーキテクチャは採用できない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの制約のため、gRPC Federation ではプラグインを評価する際、gRPC Service ごとにプラグインに対する WebAssembly インスタンスを一つだけ作成し、評価の前後で Lock を取得して直列に処理します。これはつまり、サーバーに対してどんなに並行にアクセスしても、プラグインの処理の段階では直列実行になることを意味しています。&lt;/p&gt;
&lt;p&gt;Cloud Profiler の結果、このプラグイン評価時に取得する Lock 待ちが大量に発生していることが原因だとわかりました。&lt;/p&gt;
&lt;p&gt;また、以降ではプラグインの評価に際して、WebAssembly ランタイムを動かすGo 側をホスト、プラグイン側をゲストと呼称して説明します。&lt;/p&gt;
&lt;h2&gt;問題の再現に挑戦&lt;/h2&gt;
&lt;p&gt;具体的な原因がわかってきたところで、プラグインの評価を高頻度かつ大量に行うことで問題が再現するかを試し始めました。ローカルで立ち上げたサーバーに対して高負荷な状況を作ったり、開発環境にデプロイしているサーバーに対してリクエストするなどさまざまなアプローチをチーム総出で試しましたが、問題が起こった gRPC メソッドに対して 1000rps を超える負荷を 30分近くかけ続けても再現には至りませんでした。&lt;/p&gt;
&lt;p&gt;ここで、問題を再現することが難しいことが分かったため、問題発生時に本番環境で取れる情報を増やす方向も考え始めました。具体的には対象のサービスに pprof による計測用のエンドポイントを仕込んでおき、問題が起きたときに対象の Pod のサーバーに対してプロファイリングを実行することで、 Cloud Profiler ではわからない情報をとれるようにしました。&lt;/p&gt;
&lt;h2&gt;pprof による調査&lt;/h2&gt;
&lt;p&gt;pprof を仕込んだ翌日、問題が再発しました。今回は対象の Pod を再起動するのではなく、Pod を Kubernetes Service から切り離し、切り離したサーバーの pprof のエンドポイントにリクエストを送ることで各種データを取得しました。&lt;br /&gt;
取得した結果を対象に、例えば &lt;code&gt;go tool pprof -traces&lt;/code&gt; を実行すると、計測時に動いていたすべての Goroutines をスタックトレース付きで把握することができるのですが、プラグイン関連のスタックトレースを見ると、プラグイン評価に際してホストとゲストそれぞれが相手からのリクエストを待っている状態になっていました。これはありえない状況です。ホストは正しくゲストに要求を伝えたと思っているが伝わっていない、もしくはその逆の現象が発生している可能性があります。&lt;br /&gt;
それぞれが同時に相手からの要求を待ち受けているということは、永久にその要求は満たされません。そのため、デッドロックのような状況になってプラグイン評価が永遠に行われず、プラグイン評価は直列に動作するため、サーバーの処理が停止してしまう事態になっていることがわかりました。&lt;/p&gt;
&lt;p&gt;しかしここから調査が難航します。そもそもなぜこのような事態になっているかがわからなかったのと、問題が起こるたびにプロファイリングをしても結果が毎回同じ内容だったため、新しい情報が得られませんでした。ローカルでも引き続き再現しません。&lt;/p&gt;
&lt;p&gt;そこで、プラグインインスタンスに何らかの不調が起きて、突然ホストとゲスト間の疎通ができなくなると仮定し、ホストとゲスト間のメッセージのやりとりに利用するメモリの読み書き処理でエラーになった場合は、あらかじめ作っておいたバックアップインスタンスにフォールバックするような仕組みを作成しました ( &lt;a href=&quot;https://github.com/mercari/grpc-federation/pull/327&quot;&gt;https://github.com/mercari/grpc-federation/pull/327&lt;/a&gt;  )。もしどこかでエラーが返るようであれば、インスタンスをリフレッシュすることで復帰できるはずです。&lt;/p&gt;
&lt;p&gt;しかし結果は期待とは異なり、エラーが発生することもなく、予兆なしに突然問題が発生してしまいました。&lt;/p&gt;
&lt;h2&gt;GC を疑い始める&lt;/h2&gt;
&lt;p&gt;プラグインを評価する際、ホストからゲストへのリクエスト方法は大きく2通り存在します。&lt;br /&gt;
一つは認証処理の例で説明したような通常の API リクエストです。もう一つはGCの実行でした。&lt;br /&gt;
WebAssembly ランタイムではアロケーション戦略としてリニアメモリを採用しており、メモリが足りなくなったら倍のメモリを確保して対処します。ですが、メモリが十分でも確保した範囲を縮小することはしません。つまり、GC が走るタイミングが遅れるほど、メモリが不必要に多く確保されてしまうことになります。これを防ぐため、プラグイン側で細かく GC を実行する必要があり、gRPC Federation では定期的にホストからゲストに対して、強制的に &lt;code&gt;runtime.GC()&lt;/code&gt; を実行するように要求する戦略をリリース初期から1年以上行ってきました。&lt;/p&gt;
&lt;p&gt;このアーキテクチャをふまえ、本番環境でしばらく運用した際にエラーなく突然問題が起こることから、GC をトリガーした際に問題が起こるのではないかという仮説を立て、ホストからの GC 呼び出し処理をなくすことで、事態が好転するのではないかと考え始めました。仮に GC が関係なかったとしても、ホストからゲストにリクエストを投げる回数自体が減るため、問題が発生する頻度が下がるのではないかという期待もありました。&lt;br /&gt;
トレードオフとして、プラグイン側で GC が走る頻度が減るため OOM ( Out Of Memory ) になるリスクは上がりますが、まずはホストからゲストへの GC 要求をやめて様子を見てみることにしました。&lt;/p&gt;
&lt;h2&gt;事態が好転し始める&lt;/h2&gt;
&lt;p&gt;GC の経路を削除した後、アラートが上がる頻度が目立って減りました。しかし、予想通り OOM が発生してしまったのと、頻度がゼロにはなっていないため、根本対策にもなっていません。&lt;br /&gt;
プロファイリングしても相変わらず同じ結果でした。&lt;/p&gt;
&lt;p&gt;しかし、事態が今までと変わったことから、問題の切り分けのためにプラグイン側で自動で走る GC を疑ってみることにしました。プラグイン側で走る自動GCを &lt;code&gt;GOGC=off&lt;/code&gt; 環境変数を指定することで止め、強制 GC のみで運用する方法です。このとき、より実装をシンプルにするため、今までホスト側で GC の終了を待っていなかったのを同期的に待つようにしました。補足として、今まで GC の終了を待っていなくて大丈夫なのかと疑問に思った方がいるかもしれませんが、ゲスト側で強制GCの場合を特別扱いして処理しているので非同期にすること自体は問題ありません。&lt;/p&gt;
&lt;p&gt;この状態でしばらく運用したところ、問題が発生する時は、スタックトレースから必ず強制GCの経路になっていることがわかりました。つまり、ホストから強制GCを実行したところ、その結果が返らずに停止しているということになります。&lt;/p&gt;
&lt;h2&gt;ホスト・ゲスト間の通信方法の改善&lt;/h2&gt;
&lt;p&gt;突然 &lt;code&gt;runtime.GC()&lt;/code&gt; から処理が返ってこなくなるとは考えにくかったので、GC が走った影響で、ホストとゲスト間の通信に利用する何かが壊れたと推測しました。そこで、両者の通信方式をよりシンプルなものに変更することで GC の影響を減らすことができるのではないかと考え、今まで利用していた STDIO を使った通信方法をやめ、ホスト関数と Go のチャネルを利用した通信方式に変更し、すべてをホスト上で行う標準的な Go のチャネルを介した処理に変更しました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mercari/grpc-federation/pull/331&quot;&gt;https://github.com/mercari/grpc-federation/pull/331&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://github.com/mercari/grpc-federation/pull/333&quot;&gt;https://github.com/mercari/grpc-federation/pull/333&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;この処理は Go Conference 2025 で紹介した際のホスト・ゲスト間の通信処理を改善したものなので、興味のある方は見てみていただければと思います。&lt;/p&gt;
&lt;p&gt;別件で、プラグイン上でネットワークソケットを扱うために導入した &lt;a href=&quot;https://github.com/goccy/wasi-go&quot;&gt;wasi-go&lt;/a&gt; というライブラリも疑い、ネットワークソケット関連の syscall 以外を &lt;a href=&quot;https://github.com/wazero/wazero&quot;&gt;wazero&lt;/a&gt; が提供する枯れた実装に置き換えようともしましたが、wazero がファイルシステムまわりの API を private にしているために、 wasi-go 側で作った ソケットディスクリプタ を wazero 側に伝える方法がなく断念しました。&lt;/p&gt;
&lt;p&gt;さて、この改善を行ったことで事態がより進展すると思われましたが、問題は収束せず、プロファイリングすると、ホストとゲストそれぞれでチャネルの受信処理でブロックしているという結果になりました。ただし、いずれも強制GCの結果待ちでした。&lt;/p&gt;
&lt;h2&gt;ログを仕込む&lt;/h2&gt;
&lt;p&gt;今回の問題は本番環境で長時間運用しなければ発生しなかったため、コストの面からデバッグログを仕込むことを躊躇っていました。しかし、これ以上原因をしぼりこむことができなかったため、遂にログを追加することにしました。&lt;a href=&quot;https://github.com/mercari/grpc-federation/pull/335&quot;&gt;あわせて Trace も追加しました&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;気をつけたこととして、問題が発生したらできるだけすぐにデバッグログを無効にしたかったため、環境変数を有効にしたときだけログを有効にするような Feature Flag 方式を採用し、環境変数の変更を巻き戻せばすぐに元に戻せるようにして運用しました。&lt;/p&gt;
&lt;p&gt;ログを有効にして計測した結果、ゲストがGC 実行の要求を受け取ったというログは見つかりましたが、結果を送信するというログがなかったため、ないだろうと考えていたGC実行中にハングしている可能性が濃厚になりました。&lt;/p&gt;
&lt;p&gt;また、ログを仕込んだ結果、ホストとゲストでどちらも受信待ちになっていたと思っていた不可思議な現象は、実は別のプラグインインスタンスだったことがわかりました。つまり、ホスト側とゲスト側のスタックトレースは、それぞれ別のインスタンスのものを表示していたのです。先に説明した通り、gRPC Federation は gRPC Service ごとに作るプラグインインスタンスは一つですが、 &lt;strong&gt;同一プロセス上で動く gRPC Service が複数ある場合はインスタンスも複数になります&lt;/strong&gt;。このことを考慮できていなかったのが悔やまれます。しかしこれで、問題がデッドロックではなくGC実行中のハングに収束したためとてもシンプルになりました。&lt;/p&gt;
&lt;h2&gt;インスタンスの自動復旧対策&lt;/h2&gt;
&lt;p&gt;気づくと10月も半ばに差し掛かっていました。振り返ると8月下旬から数えて2ヶ月近く格闘していたことになります。&lt;/p&gt;
&lt;p&gt;ですが問題が明確になったことで、取れる策も具体的になりました。GC実行中に処理が返らなくなることから、まだその原因はわからないにせよ、ワークアラウンドを実装することはできます。強制GCにタイムアウトを設け、タイムアウトした場合はインスタンスが壊れたと判断し、前に実装していたインスタンスをバックアップに切り替える仕組みを使って自動的に復旧することができるはずです。&lt;br /&gt;
ついでに、インスタンスを切り替える際にログも出力してみます。無事復旧しているならばログが出るはずです。&lt;/p&gt;
&lt;p&gt;この対応を行ってから、数日間様子を見ましたが、仕込んだログは定期的に出力されつつ、Goroutines が急増することもなくなりました。&lt;br /&gt;
ひとまず、ワークアラウンドはうまく動いていそうで、障害対応が落ち着いたといえる状態になりました。&lt;/p&gt;
&lt;p&gt;このとき行った実装は&lt;a href=&quot;https://github.com/mercari/grpc-federation/pull/340&quot;&gt;こちらです&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;根本原因が判明&lt;/h2&gt;
&lt;p&gt;障害対応が落ち着いたので、今まで得られた情報からより再現しやすい環境で調査を続けたところ、長時間動作させる中でローカルで再現できるようになりました。依然として再現することは難しかったですが、それでも再現できたことは大きく、詳細なデバッグログを仕込んで複数回実行する中で、 ネットワークソケットを利用するために導入した wasi-go というライブラリの &lt;code&gt;PollOneOff&lt;/code&gt; syscall 内で呼び出している &lt;code&gt;unix.Poll&lt;/code&gt; から処理が返ってこなくなることがわかりました。デバッグログの結果から、どうやら通常は 0 になるはずの &lt;code&gt;unix.Poll&lt;/code&gt; に渡している timeout 値が異常値(負数)になっており、永久にくることのないイベントを待ってブロッキングする挙動になっていることがわかりました。また、&lt;code&gt;PollOneOff&lt;/code&gt; と GC に因果関係があるかを調べるために Go 本体のソースコードを追ってみると、GC のシーケンスで &lt;code&gt;PollOneOff&lt;/code&gt; が呼び出される経路があることもわかりました。&lt;/p&gt;
&lt;p&gt;この問題を修正したときのPRは&lt;a href=&quot;https://github.com/goccy/wasi-go/pull/6&quot;&gt;こちらです&lt;/a&gt;。&lt;br /&gt;
※ ライブラリは私が作ったもののように見えますが、実際には fork したものを使っています。&lt;/p&gt;
&lt;p&gt;この修正を適用したあと、仕込んだインスタンス切り替え時のログは出力されなくなり、無事問題が解決しました。問題発生から2ヶ月ほどのことで、あの暑かった夏から少し肌寒くなってきていました。&lt;/p&gt;
&lt;h2&gt;反省点&lt;/h2&gt;
&lt;p&gt;あらためてプロファイリング結果を思い返すと、実は &lt;code&gt;PollOneOff&lt;/code&gt; のスタックトレースは一番最初に計測したプロファイリング結果に含まれていました。勘がよければ、最初の時点で &lt;code&gt;PollOneOff&lt;/code&gt; が原因だと気づくこともできたのです。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PollOneOff&lt;/code&gt; は epoll のようにファイルディスクリプタに対するイベントを監視する WASI 用の syscall で、スタックトレースは最初から目に入っていましたが、初期の実装ではホストとゲスト間の通信に STDIO 方式を採用していたため、通信時に利用する標準入出力のイベント待ちで呼び出されているのかと勘違いしていました。また、wazero ( WebAssembly ランタイム ) 側で特別に別スレッドでイベント待ちをしているのかとも予想しており、あまり気にしていなかったのが悔やまれます。&lt;/p&gt;
&lt;p&gt;冷静に考えると、あとで Go のチャネルを利用した通信方式に切り替えてもスタックトレースに現れていたのはおかしいし、シングルスレッドアーキテクチャなのに別スレッドでイベント監視しているのもおかしいので、先入観で判断してはいけないと改めて気付かされました。&lt;/p&gt;
&lt;p&gt;逆に、最初からプロファイリング結果に答えは書かれていたので、pprof が大事だとも言えます。&lt;/p&gt;
&lt;h2&gt;振り返り&lt;/h2&gt;
&lt;p&gt;本番環境でしか問題が起こらない場合、問題のある Pod を単に再起動するのではなく、サービスから切り離してプロファイリングするのは非常に重要なフローでした。&lt;br /&gt;
pprof は実行時にしか負荷がかからないので、本番用のビルドでも関係なく pprof の計測エンドポイントを持っておいた方がいいという気づきも得ました。&lt;/p&gt;
&lt;p&gt;また、ちょっとしたことですが、デバッグログを無効化するために実装を巻き戻して image を作り直し、 hotfix を deploy する方法だと時間がかかってしまうので、環境変数によってデバッグログの出し分けを制御できたのは良かったです。&lt;/p&gt;
&lt;p&gt;今回の対応では、問題のあるPodをサービスから切り離した後、pprof によるプロファイリングをメインに対応してきましたが、実はもうひとつ試したかったこととして、 Delve でのサーバープロセスへのアタッチによるデバッグがありました。しかし、いくつかの制約により実施には至らず、もし実行できていれば &lt;code&gt;unix.Poll&lt;/code&gt; 時に timeout 値が変な値になっていることに気付いたかもしれないため、今後の課題です。&lt;/p&gt;
&lt;p&gt;メルカリグループ全体の課題として、プロファイリング結果や Core ファイルなどを永続化する手段を標準化していないため、仮にそれらの情報をディスクに書き出したとしても、Pod が消えると情報が消えてしまう問題があります。Go 1.25 から入った &lt;a href=&quot;https://go.dev/blog/flight-recorder&quot;&gt;Flight Recorder&lt;/a&gt; などを効果的に利用するためにも、解析情報を Pod の外に退避させ、デバッグ時に参照できるような仕組みを構築することが重要だと考えています。&lt;/p&gt;
&lt;p&gt;今回修正を加えた &lt;a href=&quot;https://github.com/goccy/wasi-go&quot;&gt;https://github.com/goccy/wasi-go&lt;/a&gt; は、&lt;a href=&quot;https://github.com/dispatchrun/wasi-go&quot;&gt;https://github.com/dispatchrun/wasi-go&lt;/a&gt; を fork したものなのですが、fork 元のメンテナンスが停止していること、自分の方のライブラリでは今回の修正対応以外にも TLS 対応や exec.Command 対応など、実際のユースケースに応じたさまざまな改善が入っているので、これから Go が WASI P2 に対応するまでの間で WebAssembly を使いたいと思っている方には強い味方になってくれるはずです。ぜひご利用ください。&lt;/p&gt;
&lt;p&gt;最後に、今回 Claude Code にプロファイリング結果の分析などの壁打ち相手になってもらいましたが、AI は答えを出そうとするあまり誤った答えを平気で出してくるので、嘘を嘘だと見抜く力が必要だと強く感じました。&lt;/p&gt;
&lt;p&gt;明日の記事は fenomasさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>YAPC::Fukuoka 2025に参加しました！！ #yapcjapan</title><link>https://engineering.mercari.com/blog/entry/20251206-yapc-fukuoka-2025/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251206-yapc-fukuoka-2025/</guid><description>&lt;p&gt;Merpay KYC チームの Sakabe です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の6日目の記事です。 2025年11月14日〜15日に開催された「YA [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Sat, 06 Dec 2025 10:00:02 GMT</pubDate><content:encoded>&lt;p&gt;Merpay KYC チームの Sakabe です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot; title=&quot;Merpay &amp;amp; Mercoin Advent Calendar 2025&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の6日目の記事です。&lt;/p&gt;
&lt;p&gt;2025年11月14日〜15日に開催された「&lt;a href=&quot;https://yapcjapan.org/2025fukuoka/&quot; title=&quot;YAPC::Fukuoka 2025&quot;&gt;YAPC::Fukuoka 2025&lt;/a&gt;」へメルカリグループはスポンサーとして参加していました。今回はその参加レポートをお届けします！&lt;/p&gt;
&lt;h3&gt;YAPC::Fukuoka 2025 について&lt;/h3&gt;
&lt;p&gt;YAPC は Yet Another Perl Conference の略で、Perl を軸にしながらも、今では Web・インフラ・AI・SRE・フロントエンドなど幅広い技術者が集まる技術カンファレンスです。&lt;br /&gt;
今回のテーマは「きゅう」。探”求” や ”急”激な成長体験など、”九”州開催の YAPC らしいキーワードでした。&lt;/p&gt;
&lt;p&gt;開催概要&lt;br /&gt;
日程: 2日開催&lt;br /&gt;
2025年11月14日（金）&lt;br /&gt;
2025年11月15日（土）&lt;br /&gt;
会場: 福岡工業大学&lt;/p&gt;
&lt;p&gt;会場入口には YAPC の装飾が並び、到着した瞬間からイベントの熱気に包まれました。&lt;br /&gt;
福岡工業大学は駅を降りるとすぐに広いグラウンドが目に入り、学生の活気も合わさって “技術のお祭り” らしい雰囲気が一気に高まります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/f91e8ba5-img_5699-scaled.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Mercari メンバーの登壇&lt;/h3&gt;
&lt;p&gt;今回は Mercari から 「&lt;a href=&quot;https://fortee.jp/yapc-fukuoka-2025/proposal/d4d17d8d-e69b-43e0-8d0b-51d3f9570627&quot; title=&quot;iPhone のマイナンバーカードを使った本人確認の実装&quot;&gt;iPhone のマイナンバーカードを使った本人確認の実装&lt;/a&gt;」と題しeKYC に関するトークが採択されました。&lt;br /&gt;
iPhone のマイナンバーカードを使った本人確認の機能について、Verifier における技術的な観点や開発経緯などをkgoroさんに話していただきました。&lt;br /&gt;
最新の eKYC 手法ということもあり質問が非常に多く、Q&amp;amp;A 後も聴講者が集まって議論が続くほどの盛り上がりでした。&lt;br /&gt;
現地ならではの熱量を強く感じたセッションでした。&lt;/p&gt;
&lt;p&gt;発表資料はこちらをご確認ください。&lt;/p&gt;
&lt;p&gt;&lt;iframe class=&quot;speakerdeck-iframe&quot; frameborder=&quot;0&quot; src=&quot;https://speakerdeck.com/player/27dbd43fed5a497d9bdf691e2a932f12&quot; title=&quot;YAPC Fukuoka 2025 - iPhone のマイナンバーカードによる本人確認&quot; allowfullscreen=&quot;true&quot; style=&quot;border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;&quot; data-ratio=&quot;1.7777777777777777&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/1efb62bc-img_5738-scaled.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;セッションの見どころ&lt;/h3&gt;
&lt;p&gt;DBからエンジニアリング、AI まで幅広い発表があり、個人的に特に印象に残ったのはこちらです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fortee.jp/yapc-fukuoka-2025/proposal/1ee39cf1-a581-450d-8692-a9a9f7f39b7f&quot; title=&quot;今、MySQLのバックアップを作り直すとしたら何がどう良いのかを考える旅&quot;&gt;今、MySQLのバックアップを作り直すとしたら何がどう良いのかを考える旅&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fortee.jp/yapc-fukuoka-2025/proposal/4dd02c14-92fc-4fd2-b93f-5b38463ec32a&quot; title=&quot;競馬で学ぶ機械学習の基本と実践&quot;&gt;競馬で学ぶ機械学習の基本と実践&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fortee.jp/yapc-fukuoka-2025/proposal/f73df5fa-179c-4fb3-80a8-5ba78f5a6832&quot; title=&quot;旧から新へ: 大規模ウェブクローラのPerlからGoへの置き換え&quot;&gt;旧から新へ: 大規模ウェブクローラのPerlからGoへの置き換え&lt;/a&gt;&lt;br /&gt;
YAPC の特徴として、特定の技術に閉じすぎず、技術と向き合う姿勢を語るセッションが多いのも魅力でした。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参加メンバーの感想&lt;/h3&gt;
&lt;p&gt;(kgoro) 今回は「iPhone のマイナンバーカードを使った本人確認」というテーマで採択いただき発表させていただきました。発表について多くのご質問をいただき、個人的にも iPhone のマイナンバーカードについて改めて深く考える貴重な機会になりました。&lt;br /&gt;
聴講した発表に関しては、「&lt;a href=&quot;https://fortee.jp/yapc-fukuoka-2025/proposal/055b33be-0474-4c30-91ca-99985adbc080&quot; title=&quot;クレジットカードの不正を防止する技術&quot;&gt;クレジットカードの不正を防止する技術&lt;/a&gt;」という発表が特に印象に残りました。こちらは普段扱う領域に少なからず関連する技術ですが、直接的に扱う機会が少なかったため、非常に学びになる内容でした。また、今後これらの技術を考えるうえでの後押しにもなったと感じています。&lt;br /&gt;
全体を通して、この度のカンファレンスは遠方かつトークの内容もさまざまなトピックがあったことから自分にとって新鮮な経験となりました。&lt;/p&gt;
&lt;p&gt;(myuto) 普段は機械学習まわりの開発をしていますが、分野関係なくいろんなバックグラウンドの人が集まるイベントなので、終始ゆるく楽しく過ごせました。技術系のカンファレンスって専門領域に寄りがちな印象があったけど、YAPCは良い意味で雑多で、それがすごく心地よかったです。&lt;br /&gt;
最新の生成AIの動向に対しても、いかにAIを使った上で開発や制作をうまく回すかといった見解や価値観もいろいろ発表や懇親会を通して伺うことができたのが楽しかったです。&lt;br /&gt;
イベント全体の雰囲気が明るくて、参加者同士で気軽に話せるのが良かったです。普段あまり接点のない人たちとも技術の話で盛り上がれるのは、やっぱり現地イベントならではですね。スポンサーエリアも盛り上がっていて、各社の工夫が感じられて見て回るだけで楽しかったです。 &lt;/p&gt;
&lt;h3&gt;まとめ&lt;/h3&gt;
&lt;p&gt;YAPC は コミュニティ × 技術 × 熱量 が心地よく交わる、独特の魅力を持つイベントです。&lt;br /&gt;
福岡開催ということもあり、主催者の気合いの入り方も違いましたね。&lt;br /&gt;
運営・スポンサー・スピーカー・参加者のみなさん、本当にありがとうございました！&lt;br /&gt;
次回の YAPC も楽しみにしています！&lt;/p&gt;
&lt;p&gt;明日の記事はgoccyさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>メルカリ ハロ Web フロントエンドの1年間の改善と学び</title><link>https://engineering.mercari.com/blog/entry/20251205-mercari-hallo-frontend-improvements/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251205-mercari-hallo-frontend-improvements/</guid><description>&lt;p&gt;こんにちは。メルカリのフロントエンドエンジニアの @mattsuu です。 この記事は、Mercari Advent Calendar 2025 の5日目の記事です。 はじめに メルカリ ハロ Webフロントエンドの開発 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 05 Dec 2025 11:00:41 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリのフロントエンドエンジニアの &lt;a href=&quot;https://x.com/ryo_manba&quot;&gt;@mattsuu&lt;/a&gt; です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の5日目の記事です。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20240613-mercari-hallo-web-frontend/&quot;&gt;メルカリ ハロ Webフロントエンドの開発スピードと品質両立の取り組み&lt;/a&gt; という記事を公開してから約1年が経過しました。 前回の記事では、Next.js、Apollo Client、Tailwind CSS、そして monorepo 構成を採用し、MSW を使用したモック駆動開発などの技術スタックと開発手法について紹介しています。&lt;/p&gt;
&lt;p&gt;この1年間、私たちは初回の技術選定を基盤としながら、パッケージ管理、テスト戦略、開発体験の3つの領域に焦点を当てて継続的な改善を進めてきました。 本記事ではそれらの取り組みと効果について紹介します。&lt;/p&gt;
&lt;h2&gt;パッケージ管理の改善&lt;/h2&gt;
&lt;p&gt;monorepo 環境での依存関係管理とセキュリティ対策として、pnpm catalog による一元管理、minimumReleaseAge によるサプライチェーン攻撃対策、Knip による未使用パッケージの検出を導入しました。これらにより、依存関係の更新作業の効率化とセキュリティリスクの低減を実現しています。&lt;/p&gt;
&lt;h3&gt;pnpm catalog による一元管理&lt;/h3&gt;
&lt;p&gt;monorepo 環境では、同じライブラリの異なるバージョンを管理する場合があります。バージョンによって I/F が異なったり、セキュリティ update の対応が複数箇所で必要になるなど、対応コストが発生します。これを防ぐために各ライブラリで単一のバージョンを利用する方針を採用しました。&lt;/p&gt;
&lt;p&gt;pnpm の catalog 機能を使うことで、依存関係のバージョンを一箇所で管理することができます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# pnpm-workspace.yaml
catalog:
  &amp;#039;@apollo/client&amp;#039;: 3.13.4
  &amp;#039;next&amp;#039;: 15.3.2
  &amp;#039;react&amp;#039;: 19.0.0
  # ... その他の依存関係&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; にカタログを定義し、各パッケージの &lt;code&gt;package.json&lt;/code&gt; で &lt;code&gt;catalog:&lt;/code&gt; を参照する運用により、同一ライブラリのバージョン分散を防いでいます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;@apollo/client&amp;quot;: &amp;quot;catalog:&amp;quot;,
    &amp;quot;next&amp;quot;: &amp;quot;catalog:&amp;quot;,
    &amp;quot;react&amp;quot;: &amp;quot;catalog:&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この仕組みにより、依存関係のバージョン更新は catalog 側を更新するだけで monorepo 全体へ反映されるため、個別のパッケージに対して複数 PR が発生する問題を解消できました。&lt;/p&gt;
&lt;p&gt;導入手順については次の記事を参考にしました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tech.newmo.me/entry/one-version-rule-built-on-pnpm-catalog&quot;&gt;monorepo内でのパッケージのバージョンを1つだけに統一するOne Version Ruleをpnpm catalogで実装する&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Dependabot の活用&lt;/h3&gt;
&lt;p&gt;Dependabot を catalog と組み合わせることで、依存関係更新のワークフローが改善しました。Dependabot が catalog のバージョンを更新する PR を作成し、それをマージするだけで monorepo の依存関係を一括更新できます。&lt;/p&gt;
&lt;p&gt;運用面では導入して間もなかったこともあり、ナレッジ共有も踏まえて週1で担当をローテーションしていました。CI で自動テストが通り、PR に対して E2E テスト手動でトリガーして通過したらマージするようにしています。major update など破壊的変更があるものは別途チケットを切って対応していました。&lt;/p&gt;
&lt;p&gt;また、Dependabot の &lt;a href=&quot;https://github.blog/changelog/2025-07-01-dependabot-supports-configuration-of-a-minimum-package-age/&quot;&gt;cooldown 機能&lt;/a&gt;を利用して、既存ライブラリの更新に一定の待機期間を設けていました。これにより、公開直後の不具合やセキュリティリスクを回避しやすくなりました。&lt;/p&gt;
&lt;h3&gt;minimumReleaseAge によるセキュリティ対策&lt;/h3&gt;
&lt;p&gt;前述の Dependabot の cooldown 機能は既存のライブラリアップデートに対するセキュリティ対策ですが、手元で新規ライブラリのインストールやアップデートを行う際には pnpm の &lt;a href=&quot;https://pnpm.io/settings#minimumreleaseage&quot;&gt;&lt;code&gt;minimumReleaseAge&lt;/code&gt;&lt;/a&gt; が役立ちます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# pnpm-workspace.yaml
minimumReleaseAge: 10080 # 1 week in minutes
minimumReleaseAgeExclude:
  - &amp;#039;@example/*&amp;#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この設定により、公開されてから一定期間経過していないパッケージはインストールされません。 社内パッケージは &lt;code&gt;minimumReleaseAgeExclude&lt;/code&gt; で除外し、開発の妨げにならないようにしています。また未導入ではありますが、pnpm の &lt;a href=&quot;https://pnpm.io/settings#trustpolicy&quot;&gt;trustPolicy&lt;/a&gt; オプションを有効にすると、&lt;a href=&quot;https://pnpm.io/supply-chain-security#enforce-trust-with-trustpolicy&quot;&gt;サプライチェーン攻撃への対策&lt;/a&gt;をさらに強化できます。&lt;/p&gt;
&lt;h3&gt;Knip による未使用パッケージの検出&lt;/h3&gt;
&lt;p&gt;プロジェクトが成長する中で、使われなくなった依存関係が残り続ける問題がありました。未使用のパッケージはバンドルサイズの増加やセキュリティリスクの原因になります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://knip.dev/&quot;&gt;Knip&lt;/a&gt; を導入することで、未使用のパッケージを自動検出できるようになりました。 CI では &lt;a href=&quot;https://github.com/marketplace/actions/knip-reporter&quot;&gt;knip-reporter&lt;/a&gt; を利用していますが、現在は &lt;a href=&quot;https://github.com/webpro-nl/knip/pull/1231&quot;&gt;builtin の reporter が実装された&lt;/a&gt;ため、そちらを使うと良いかもしれません。また、Knip は未使用のファイルや、export しているがどこからも import されていない型や関数も検出できるため、コードベースを健全に保つ上で効果的です。 &lt;/p&gt;
&lt;p&gt;詳しくは次の記事が参考になります。&lt;br /&gt;
&lt;a href=&quot;https://tech.basemachina.jp/entry/introduction-knip&quot;&gt;TypeScript/JavaScriptの不要なコードを削除するツール「Knip」の紹介 &amp;#8211; ベースマキナ エンジニアブログ&lt;/a&gt; &lt;/p&gt;
&lt;h2&gt;テスト基盤の整備&lt;/h2&gt;
&lt;p&gt;テスト戦略として、全画面への Playwright による Integration Test の導入、Component Test の最小化、VRT の活用を進めました。Code Coverage よりも Use Case Coverage を重視し、テストの種類を絞ることでメンテナンスコストを削減しながら、安定したテスト基盤を整えることができました。&lt;/p&gt;
&lt;h3&gt;Playwright を用いた Integration Test と VRT の導入&lt;/h3&gt;
&lt;p&gt;1年前の時点では Integration Test の基盤が十分に整っておらず、軽微な機能改修でも QA エンジニアに詳細な確認を依頼する必要がありました。また、機能自体に問題がなくとも UI のリグレッションが発生した際に気づきにくい体制でした。&lt;/p&gt;
&lt;p&gt;この課題を解消するため、画面全体を対象とした Playwright による Integration Test を導入し、UI の差分検出には Playwright の VRT を活用しました。&lt;/p&gt;
&lt;p&gt;Integration Test の方針は、メルペイでのテスト自動化方針を参考に次の考えをベースとしています。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;基本的に、仕様書をベースにアプリケーションの振る舞いが期待通りかをテストします。実装の詳細は考慮しません。インテグレーションテストが仕様書と対応し、テストコードを見るとアプリケーション挙動がわかるように管理されると理想的です。&lt;br /&gt;
参考: &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20211208-test-automation-policy-in-merpay-frontend/#:~:text=%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AB%E3%80%81%E4%BB%95%E6%A7%98%E6%9B%B8%E3%82%92%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E6%8C%AF%E3%82%8B%E8%88%9E%E3%81%84%E3%81%8C%E6%9C%9F%E5%BE%85%E9%80%9A%E3%82%8A%E3%81%8B%E3%82%92%E3%83%86%E3%82%B9%E3%83%88%E3%81%97%E3%81%BE%E3%81%99%E3%80%82%E5%AE%9F%E8%A3%85%E3%81%AE%E8%A9%B3%E7%B4%B0%E3%81%AF%E8%80%83%E6%85%AE%E3%81%97%E3%81%BE%E3%81%9B%E3%82%93%E3%80%82%E3%82%A4%E3%83%B3%E3%83%86%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%86%E3%82%B9%E3%83%88%E3%81%8C%E4%BB%95%E6%A7%98%E6%9B%B8%E3%81%A8%E5%AF%BE%E5%BF%9C%E3%81%97%E3%80%81%E3%83%86%E3%82%B9%E3%83%88%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E8%A6%8B%E3%82%8B%E3%81%A8%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E6%8C%99%E5%8B%95%E3%81%8C%E3%82%8F%E3%81%8B%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E7%AE%A1%E7%90%86%E3%81%95%E3%82%8C%E3%82%8B%E3%81%A8%E7%90%86%E6%83%B3%E7%9A%84%E3%81%A7%E3%81%99%E3%80%82&quot;&gt;メルペイフロントエンドのテスト自動化方針&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;仕様書とテストコードを完全に一致させることは難しいものの、基本方針として Code Coverage よりも Use Case Coverage を重視する姿勢で運用していました。&lt;/p&gt;
&lt;h4&gt;テスト構成&lt;/h4&gt;
&lt;p&gt;テストコードの大枠とトップレベルの &lt;code&gt;describe&lt;/code&gt; について次のような構成にしています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;/**
 * Specs:
 * https://example.com/specs/feature-abc  // ①
 */
test.beforeEach(async ({ page }) =&amp;gt; {
  await mockLogin(page);
  await mockAllGraphQL(page);     // ②
});

// 画面のパス
const url = &amp;#039;/sample-page&amp;#039;;

test.describe(&amp;#039;Rendering&amp;#039;, () =&amp;gt; {
  test(&amp;#039;renders correctly&amp;#039;, async ({ page }) =&amp;gt; {
    await mockGraphQL( // ③
      page,
      SampleQueryDocument,
      produce(mockSampleQueryResponse(), (draft) =&amp;gt; {
        // ... 必要に応じてレスポンスを調整
      })
    );
    await page.goto(url); // ④
    // ... レンダリング確認
  });
});
test.describe(&amp;#039;Validation&amp;#039;, () =&amp;gt; {});
test.describe(&amp;#039;Actions&amp;#039;, () =&amp;gt; {});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;① 仕様へのリンクを記述する&lt;br /&gt;
② &lt;code&gt;mockAllGraphQL&lt;/code&gt; を利用してすべての API リクエストをモックする&lt;br /&gt;
③ 個別ケースは &lt;code&gt;mockGraphQL&lt;/code&gt; でレスポンスを上書きする&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;トップレベルの &lt;code&gt;beforeEach&lt;/code&gt; 内で &lt;code&gt;mockGraphQL&lt;/code&gt; を利用することは避け、対象のテストと近い場所で呼び出す&lt;/li&gt;
&lt;li&gt;多少冗長でもモック内容とテスト内容の関連が明確になることを優先する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;④ モック設定完了後に &lt;code&gt;page.goto&lt;/code&gt; を実行し、テスト対象の画面に遷移する&lt;/p&gt;
&lt;p&gt;トップレベルの &lt;code&gt;describe&lt;/code&gt; については次の3つに分類しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rendering&lt;/strong&gt;: レスポンスに基づいて情報を正しくレンダリングできるか&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation&lt;/strong&gt;: 無効なリクエストが送信されるのを防げるか&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actions&lt;/strong&gt;: 利用者の操作に応じて、正しいリクエストを生成し、レスポンスを適切に処理できるか（更新、エラー、ページ遷移を含む）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;記述ルールを明確にしたことで一貫した実装が可能になり、monorepo で複数メンバーがそれぞれのアプリケーションや機能を担当する場合でも、テストを書く負荷を減らすことができました。また、これらのルールを Claude.md にまとめておくことで、AI によるセルフレビューやテスト生成の精度も向上し、開発体験の改善につながりました。&lt;/p&gt;
&lt;h3&gt;Component Test 最小化戦略&lt;/h3&gt;
&lt;p&gt;テストの種類とバランスを考えたとき、私たちは次の方針を採用しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Integration Test (Playwright)&lt;/strong&gt;: ブラウザ上で実際のユースケースを検証し、全画面をカバーする&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Component Test (React Testing Library)&lt;/strong&gt;: DatePicker など複雑な UI コンポーネントのみに限定する&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unit Test (Jest)&lt;/strong&gt;: ロジック部分を対象にカバレッジ 80% 程度を維持する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;テストの種類を絞ることで、チームメンバーが学ぶべき範囲を限定し、テストの重複排除などメンテナンスを削減することができました。&lt;/p&gt;
&lt;p&gt;Integration Test に寄せた構成では CI のパフォーマンスが気になるところですが、&lt;a href=&quot;https://playwright.dev/docs/test-sharding&quot;&gt;Playwright の  sharding&lt;/a&gt; で4並列にテストを実行させることで実行時間が10分程度に抑えられるようにしています。&lt;/p&gt;
&lt;p&gt;また、Playwright にはテスト計画生成・テスト生成・自動修復を行う &lt;a href=&quot;https://playwright.dev/docs/test-agents&quot;&gt;Test Agents&lt;/a&gt; が用意されており、将来的にテスト運用をより効率化できる可能性があります。QA チームが管理する E2E テストでも Playwright を採用していたため、チーム間でツールが統一されている点もメリットでした。&lt;/p&gt;
&lt;h3&gt;VRT (Visual Regression Testing) の活用&lt;/h3&gt;
&lt;p&gt;モバイル向け Web UI は事業者向け管理画面と比べて画面あたりの情報量が少なく、UI の変化を見つけやすいという特徴があります。また、UI 検知に必要なモックも少なくて済むため、実装やメンテナンスの負担を抑えられます。そのため、モバイル向け UI では Rendering のテストを VRT 中心にし、事業者向け管理画面では text や element の assertion を用いる構成にしていました。VRT には Playwright の &lt;a href=&quot;https://playwright.dev/docs/screenshots&quot;&gt;screenshot 機能&lt;/a&gt;を利用して全画面実装しました。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;test(&amp;#039;should render page correctly&amp;#039;, { tag: &amp;#039;@vrt&amp;#039; }, async ({ page }) =&amp;gt; {
  await mockGraphQL(
    page,
    ExampleDocument,
    produce(mockExampleResponse(), (draft) =&amp;gt; {
      // UI の確認に使うデータは immer で上書きする
      draft.data.item.name = &amp;#039;テストアイテム1&amp;#039;;
    })
  );

  await page.goto(&amp;#039;/example&amp;#039;);
  await expect(page).toHaveScreenshot({ fullPage: true });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これまでは開発環境でデザイナーに UI を直接確認してもらう必要がありましたが、VRT の導入により特定条件の UI を Snapshot で比較・確認できるようになり、確認作業の工数を削減できました。&lt;/p&gt;
&lt;h2&gt;開発体験の向上&lt;/h2&gt;
&lt;p&gt;開発効率と品質を向上させるため、ESLint カスタムルールによる品質担保、Mock の自動生成、Claude Code Action によるセルフレビュー、Apollo MCP によるテストデータ作成の効率化といった取り組みを行いました。これらにより、レビュー負荷の軽減や開発スピードの向上につながりました。&lt;/p&gt;
&lt;h3&gt;ESLint カスタムルールによる品質担保&lt;/h3&gt;
&lt;p&gt;実装漏れが原因で QA 段階で不具合が見つかった場合には、修正と合わせて ESLint のカスタムルールを追加していました。チーム固有のルールをドキュメントや Claude.md / Agents.md にまとめる方法もありますが、静的解析によってルールを強制するほうがヒューマンエラーを防ぎやすく、品質担保に効果的です。&lt;/p&gt;
&lt;p&gt;カスタムルールの追加には、次の記事が参考になります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zenn.dev/nissy_dev/scraps/ca7137375da3aa&quot;&gt;no-restricted-syntax でお手軽にカスタムルールを追加する &amp;#8211; Zenn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zenn.dev/ubie_dev/articles/a5c8fb2d219258&quot;&gt;Ubie における ESLint 活用 &amp;#8211; Zenn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Mock 自動生成の仕組み&lt;/h3&gt;
&lt;p&gt;GraphQL スキーマが更新されるたびに手動で Mock データを更新するのは負担が大きいため、&lt;a href=&quot;https://the-guild.dev/graphql/tools/docs/mocking&quot;&gt;@graphql-tools/mock&lt;/a&gt; を利用した Mock データの自動生成を導入しました。スキーマが更新されたら、pnpm でスクリプトを実行するだけで Mock データを更新できます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# GraphQL スキーマから型と Mock データを生成
pnpm generate:graphql&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GraphQL Code Generator のカスタムプラグインを作成し、&lt;code&gt;@graphql-tools/mock&lt;/code&gt; の &lt;code&gt;addMocksToSchema&lt;/code&gt; を使って Mock データを生成しています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { addMocksToSchema } from &amp;#039;@graphql-tools/mock&amp;#039;;

const mockedSchema = addMocksToSchema({
  schema: graphqlSchema,
  mocks: {
    // 型に応じて初期値を設定
    ID: () =&amp;gt; &amp;#039;mock-id-0001&amp;#039;,
    String: () =&amp;gt; &amp;#039;Hello World&amp;#039;,
    Int: () =&amp;gt; 10,
    Boolean: () =&amp;gt; true,
    Time: () =&amp;gt; &amp;#039;2021-01-01T00:00:00.000Z&amp;#039;,
  },
});

// スキーマに対してクエリを実行し、Mock データを生成
const result = executeSync({
  schema: mockedSchema,
  document: queryDocument,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この &lt;code&gt;result&lt;/code&gt; を使って、次のように mock 関数を自動生成します:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const mockFn = `
export function mockExampleResponse(): ExecutionResult&amp;lt;ExampleQuery&amp;gt; {
  return ${JSON.stringify(result, null, 2)};
}
`;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この仕組みにより、GraphQL スキーマから適切な Mock データが自動生成されるため、手動で値の整合性を確認する手間が大きく削減されました。&lt;/p&gt;
&lt;p&gt;また、特定のケースを再現したい場合は、immer を使って必要な部分のみ上書きできます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { produce } from &amp;#039;immer&amp;#039;;
import { mockExampleResponse } from &amp;#039;example-mockdata/base/gen/query&amp;#039;;

const customMock = produce(mockExampleResponse(), (draft) =&amp;gt; {
  // 特定の条件を再現するためにデータを上書き
  draft.data.items[0].status = &amp;#039;active&amp;#039;;
  draft.data.items[0].count = 10;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このように、意図的に上書きした箇所がそのまま Storybook やテストで確認したい条件として明確に残るため、Mock の管理や UI・テスト確認がシンプルになりました。&lt;/p&gt;
&lt;h3&gt;Claude Code Action によるセルフレビュー&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code-action&quot;&gt;claude-code-action&lt;/a&gt; を導入することで、PR レビューを依頼する前の draft の段階で実装者自身が抜け漏れに気づけるようになりました。レビュワーが指摘する前に基本的なミスを修正できるため、レビュワーの負担削減や品質の向上につながりました。&lt;/p&gt;
&lt;h3&gt;Apollo MCP によるテストデータ作成の効率化&lt;/h3&gt;
&lt;p&gt;ローカルで開発する際は、API が未実装の場合を除き、基本的にバックエンドと実際に疎通しながら進めていました。モックで開発する手もありますが、実際の API と通信するほうが挙動の再現性や信頼性が高いためです。一方で、特定の条件を再現するためのテストデータ作成には課題がありました。開発環境の管理画面を操作したり、場合によっては DB を直接触る必要があり、準備に手間がかかりがちでした。&lt;/p&gt;
&lt;p&gt;そこで &lt;a href=&quot;https://www.apollographql.com/docs/apollo-mcp-server&quot;&gt;Apollo MCP&lt;/a&gt; を導入し、テストデータを口語ベースで簡単に作成できるようにしました。Apollo MCP は GraphQL のスキーマやクエリを操作して実行できるため、次のようなことが可能になります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「今日の18:00 ~ 20:00 で時給1200円の募集を北海道で作って」のような指示をすると、該当する mutation を実行しテストデータを作成&lt;/li&gt;
&lt;li&gt;GraphQL スキーマを自動で読み取り、どんなクエリや mutation が使えるかをで一覧・検索&lt;/li&gt;
&lt;li&gt;特定のクエリに必要な引数を口語形式で検索&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これにより、ローカルやテスト環境でのデータ準備が大幅に効率化され、手動テストの工数削減や開発スピードの向上につながりました。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;この1年間、日々の開発の中で出てきた課題に向き合いながら、少しずつ仕組みを整えてきました。All for One のカルチャーのもと、メンバーがそれぞれの専門性を活かしつつ、知見を共有しながら開発できたことが改善を進める上で良い循環になっていたと思います。本記事で紹介した取り組みが参考になれば幸いです。&lt;/p&gt;
&lt;p&gt;明日の記事は sathiya さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>メルコインとメルカリNFTのフロントエンド開発のインターンシップで取り組んだこと</title><link>https://engineering.mercari.com/blog/entry/20251202-5f1fdbecce/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251202-5f1fdbecce/</guid><description>&lt;p&gt;はじめに こんにちは。 @Sakamoto です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の4日目の記事です。 2025年9月からメルコインでフロントエンドのイン [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 04 Dec 2025 10:00:02 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。 @Sakamoto です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の4日目の記事です。&lt;/p&gt;
&lt;p&gt;2025年9月からメルコインでフロントエンドのインターンを始め、12月初めでちょうど3か月になりました。期間中はメルコインに加え、メルカリNFTの開発にも参加しました。&lt;br /&gt;
本記事では、インターン期間中に取り組んだタスクを振り返り、そこで得た学びや気づきをまとめます。&lt;/p&gt;
&lt;h2&gt;チームについて&lt;/h2&gt;
&lt;p&gt;今回のインターンではメルコインとメルカリNFTでフロントエンドエンジニアとして参加しました。それぞれ扱うプロダクトの特性が大きく異なるため、求められる視点や進め方にも違いがありました。&lt;/p&gt;
&lt;h3&gt;メルコイン&lt;/h3&gt;
&lt;p&gt;暗号資産交換業としてガバナンスやリーガルと関わりが深く、単に機能を実装するだけでなく、背景となる法律や制約を理解したうえで開発を進める必要があります。&lt;br /&gt;
メルコインのフロントエンドチームはお客さま対応のための社内ツールのフロントエンド開発、LP（ランディングページ） の作成などを担当しています。&lt;/p&gt;
&lt;h3&gt;メルカリNFT&lt;/h3&gt;
&lt;p&gt;NFTの売買を可能にするプロダクトを持つチームで、2025年1月に提供を開始し、現在も毎週のように新機能が追加されています。&lt;br /&gt;
そのため、よりスピード感のある開発が求められる環境でした。&lt;br /&gt;
メルカリNFTのフロントエンドチームは主にメルカリNFTのフロントエンド開発を担当しています。&lt;/p&gt;
&lt;h2&gt;取り組み概要&lt;/h2&gt;
&lt;h3&gt;メルコイン&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;社内ツールのフロントエンド改修&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;メルカリNFT&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;買取機能の実装・改善&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;メルカリNFTのくじをメルカリアプリ内から購入できるようにするための対応&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;リリース後のメモリ監視&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;その他&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Web3領域のドメイン知識の習得&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;社内の多職種の方とのコミュニケーション&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;エンジニア業務でのAI活用&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ここでは太字の6つについてまとめようと思います。&lt;/p&gt;
&lt;h2&gt;社内ツールのフロントエンドの改修&lt;/h2&gt;
&lt;h3&gt;背景&lt;/h3&gt;
&lt;p&gt;メルコインで進行しているプロジェクトの一部として、口座の状況、暗号通貨の配信価格など、お客さまからのお問い合わせに必要な情報を確認できる社内ツールの改修を担当し、フロントエンドの仕様整理から実装、QAチームとのテストケースの調整まで、一連の開発プロセスに関わりました。&lt;/p&gt;
&lt;h3&gt;やったこと&lt;/h3&gt;
&lt;p&gt;社内ツールの改修にあたって、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表記揺れがないか&lt;/li&gt;
&lt;li&gt;バリデーションに過不足はないか&lt;/li&gt;
&lt;li&gt;エラーハンドリングが適切か&lt;/li&gt;
&lt;li&gt;UIの整合性があるか&lt;/li&gt;
&lt;li&gt;API連携が正しいか&lt;/li&gt;
&lt;li&gt;変更に柔軟に対応できる設計か&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といったポイントを確認し、後工程での手戻りが起きにくい状態まで仕様書のレビューを行いました。&lt;br /&gt;
その上で自分の実装スピードや改修範囲を考慮し、適切なバッファを含めて工数見積もりを行いました。&lt;/p&gt;
&lt;p&gt;実装では、各マイクロサービス間の通信にはgRPCが使われているため、Protocol Buffersの定義からモックを作り、UIとの接続を進めると同時にCypressでのE2Eテストも作成・改修に取り組むことができました。&lt;br /&gt;
またQAチームとはテストケースや指摘内容を確認しながら、仕様と実装の齟齬をなくすことに努めました。&lt;/p&gt;
&lt;h3&gt;学び&lt;/h3&gt;
&lt;p&gt;gRPCやProtocol Buffers、Cypressといったこれまで触れる機会の少なかった技術に取り組んだことで、フロントエンド以外の領域にも一部理解が広がりました。&lt;br /&gt;
あわせて、仕様書の精度をどう高めるか、どの程度のバッファを見込んで工数を組み立てるか、レビューされやすいコードにするにはどんな書き方がよいかなど、プロダクト開発全体を俯瞰する視点も身についたと感じています。&lt;/p&gt;
&lt;h2&gt;買取機能の実装・改善&lt;/h2&gt;
&lt;h3&gt;背景&lt;/h3&gt;
&lt;p&gt;メルカリNFT内のGMV（流通取引総額）を伸ばすための施策として、NFTくじを購入したお客さまが買取依頼を出せる機能の追加をしました。&lt;br /&gt;
それに伴い、商品詳細ページに新しいUIや状態管理が必要となり、この部分の改修を担当しました。&lt;br /&gt;
また、このタスクがメルカリNFTのコードを触る最初の機能開発だったため、既存コードや周辺仕様のキャッチアップを素早く進める必要もありました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/64217c39--2025-11-26-17.54.02.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;やったこと&lt;/h3&gt;
&lt;p&gt;仕様書を確認し、商品詳細ページのどのタイミングで買取ボタンを表示するか、どの状態をどう見せるかといったUIの出し分けを整理しました。&lt;br /&gt;
その上で、実装した内容の品質を担保するためにVRT（Visual Regression Test）を活用し、Playwrightを用いたインテグレーションテストも追加・修正しました。&lt;/p&gt;
&lt;h3&gt;学び&lt;/h3&gt;
&lt;p&gt;Playwrightを使ったインテグレーションテストやVRTの活用など、これまで触れる機会のなかったテスト手法を実際に使いながら理解を深められました。&lt;br /&gt;
また、買取前後でUIが複雑に変化する仕様だったこともあり、コンポーネントの責務をどう分けるか、保守性と可読性をどこまで両立できるか、といった設計面の学びも多かったです。&lt;br /&gt;
スピードが求められる環境の中でも、既存実装の調査や理解を並行して進めることの重要性を実感しました。&lt;/p&gt;
&lt;h2&gt;リリース後のメモリ監視&lt;/h2&gt;
&lt;h3&gt;背景&lt;/h3&gt;
&lt;p&gt;メルカリNFTでは、サービスを安定して提供し続けるために、リリース後のリソース状況の監視や挙動の確認を継続的に行っています。&lt;br /&gt;
特にメモリ使用量は、トラフィックの増減や特定機能の利用状況によって変動しやすいため、定期的に観測し、必要に応じて挙動を調査する運用を行っています。&lt;/p&gt;
&lt;p&gt;今回の取り組みでは、本番環境のメモリ使用状況をより細かく把握し、どのタイミングでどう増減しているのかを整理しながら、今後の安定した運用につなげるための調査を進めています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/6d4bb4c7--2025-11-28-17.56.01.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;やったこと&lt;/h3&gt;
&lt;p&gt;まずは「いつ・どんな状況でメモリが増えるのか」を把握するために、各Podのメトリクスを確認し、傾向を調査しました。&lt;br /&gt;
その上で、なぜそのような増加が起こるのか仮説を立て、順に検証を進めました。&lt;/p&gt;
&lt;p&gt;Datadogからログやメトリクスを細かく確認しつつ、他チームにも相談し、Node.jsやNext.jsの内部挙動、キャッシュやSSR周りの可能性など、さまざまな観点から情報を集めることで検証の方向性を調整しています。&lt;/p&gt;
&lt;h3&gt;学び&lt;/h3&gt;
&lt;p&gt;日々の調査の中で得られる学びは非常に多いと感じています。&lt;br /&gt;
Node.jsやNext.jsが内部でどのようにメモリを使うのか、CPU・GCの仕組み、キャッシュの扱いなど、技術的な理解が大きく深まりました。&lt;/p&gt;
&lt;p&gt;同時に、進行中の問題を扱う際のコミュニケーションの取り方や調査内容のまとめ方、仮説の立て方と検証の進め方、進捗が見えにくいタスクをどうやって周囲と共有するかなど、機能開発とは異なるスキルも求められることを実感しています。&lt;/p&gt;
&lt;h2&gt;Web3領域のドメイン知識の習得&lt;/h2&gt;
&lt;p&gt;メルコインとメルカリNFTの両方に関わる中で、Web3に関する基礎知識を継続的に身につけることを意識してきました。&lt;br /&gt;
暗号資産やNFTの基本概念をはじめ、法規制やコンプライアンス、チェーンの仕組み、トランザクションの流れ、ウォレットの挙動など、業務に必要な知識は社内ドキュメントとして多く整理されています。&lt;/p&gt;
&lt;p&gt;日々の開発と並行して、それらの資料を自分から探して読み、背景となるドメイン理解を深めるようにしてきました。&lt;br /&gt;
Web3は学ぶ範囲も広く、継続して取り組みたい領域です。&lt;/p&gt;
&lt;h2&gt;社内の多職種の方とのコミュニケーション&lt;/h2&gt;
&lt;p&gt;もう一つ意識的に取り組んでいたのが、社内の多職種の方とのコミュニケーションです。&lt;br /&gt;
プロダクト開発では、さまざまな職種と連携しながら仕様の確定やリリースに向けた調整を進める必要があります。&lt;/p&gt;
&lt;p&gt;そのため、日々のやり取りだけでなく、会社の施策として実施されているチービルやメンターランチの機会を活用しつつ、自分からも声をかけて1on1やランチの機会をつくるようにしていました。&lt;br /&gt;
チーム全体が話しかけやすい雰囲気を持っていたこともあり、積極的にコミュニケーションを取ることができました。&lt;/p&gt;
&lt;p&gt;また、普段どのように技術をキャッチアップしているのか、どんな観点で仕様を見ているのか、なぜ今のキャリアを選んだのかといった話を聞くことで、自分の知らなかった考え方や知識に触れる機会も多くありました。&lt;br /&gt;
開発スキルだけでは得られない学びが多く、視野を広げるきっかけになったと感じています。&lt;/p&gt;
&lt;h2&gt;エンジニア業務でのAI活用&lt;/h2&gt;
&lt;p&gt;CursorなどのAIツールも積極的に活用し、コード全体の構造を素早く把握したり、関連部分の洗い出しを効率よく進めるようにしていました。&lt;br /&gt;
開発・実装では、メンターの方からのアドバイスも参考にしながら、どの情報を渡すと意図が正確に伝わるかといった点を意識するようにしました。&lt;/p&gt;
&lt;p&gt;仕様を明確にまとめてコンテキストとしてAIに渡すことで、実装の方向性がぶれにくくなり、提案されるコードの品質も安定するようになりました。&lt;br /&gt;
また、AIから返ってくるコードはそのまま使うのではなく、自分の考えとの違いを確認したり、なぜその書き方になるのかを読み解くことで理解を深めるきっかけにもなりました。&lt;/p&gt;
&lt;p&gt;結果として、実装を効率化するだけでなく、コードの設計方針を比較しながら学べる良い教材のような存在にもなっていたと思います。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;3か月を通して、メルコインとメルカリNFTのそれぞれ異なる特徴を持つプロダクトの開発に携わり、多くの学びを得ることができました。&lt;br /&gt;
機能実装だけでなく、仕様の詰め方、テストの考え方、多職種とのコミュニケーションなど、エンジニアとして必要なスキルを幅広く経験できた期間だったと感じています。&lt;/p&gt;
&lt;p&gt;残りの1か月も、これまでの学びを活かしながら、より深い理解と確かなアウトプットを積み重ねていきたいと思います。&lt;/p&gt;
&lt;p&gt;明日の記事は @Fabさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>Kubeflow PipelinesとPydantic Settingsを活用してMLパイプラインを型安全かつシンプルに実装する</title><link>https://engineering.mercari.com/blog/entry/20251128-c46fa95168/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251128-c46fa95168/</guid><description>&lt;p&gt;はじめに こんにちは。メルペイのGrowth PlatformでML Engineerをしている @hokao です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の3 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 03 Dec 2025 10:00:47 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。メルペイのGrowth PlatformでML Engineerをしている &lt;a href=&quot;https://x.com/khokawo&quot;&gt;@hokao&lt;/a&gt; です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の3日目の記事です。&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;Growth Platformでは、新たにGrowth MLというMLチームを立ち上げました。私は主にMLOpsを担当しつつ、チームの生産性向上に向けた基盤づくりにも取り組んでいます。&lt;/p&gt;
&lt;p&gt;Growth MLチームでは、&lt;a href=&quot;https://www.kubeflow.org/docs/components/pipelines/&quot;&gt;Kubeflow Pipelines（KFP）&lt;/a&gt;でMLバッチ処理を実装し、&lt;a href=&quot;https://docs.cloud.google.com/vertex-ai/docs/pipelines/introduction&quot;&gt;Vertex AI Pipelines&lt;/a&gt;上で運用しています。KFPは、Kubernetes上でコンテナを使ってMLワークフローを構築・実行するためのプラットフォームであり、Vertex AI PipelinesはそれらをGoogle Cloud上でサーバーレスに運用・管理できるサービスです。KFPには&lt;a href=&quot;https://kubeflow-pipelines.readthedocs.io/en/stable/&quot;&gt;Python SDK&lt;/a&gt;が用意されており、パイプラインをPythonコードとして記述できます。&lt;/p&gt;
&lt;p&gt;KFPはPythonでMLパイプラインを実装できる一方で、コンポーネントの設計・実装の自由度が高く、適切に使いこなすのは容易ではありません。そこで、メンテナンス性と可読性を高め、チームの開発生産性を向上させるために、KFPを用いた実装パターンを整備しました。本稿では、その概要とポイントを紹介します。&lt;/p&gt;
&lt;h2&gt;KFPのコンポーネント実装パターン&lt;/h2&gt;
&lt;p&gt;コンポーネントはパイプラインを構築する基本的な構成要素です。執筆時点では、コンポーネントの実装方法として次の3パターンが提供されています。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kubeflow.org/docs/components/pipelines/user-guides/components/lightweight-python-components/&quot;&gt;Lightweight Python Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kubeflow.org/docs/components/pipelines/user-guides/components/containerized-python-components/&quot;&gt;Containerized Python Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kubeflow.org/docs/components/pipelines/user-guides/components/container-components/&quot;&gt;Container Components&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Lightweight Python Components&lt;/h3&gt;
&lt;p&gt;Lightweight Python Componentsは最も手早く実装できますが、関数スコープに完全に閉じた実装が求められるという制約があります。実装例は次のとおりです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from kfp import dsl

@dsl.component(
    base_image=&amp;#039;python:3.14&amp;#039;,
    packages_to_install=[&amp;#039;numpy==2.3.0&amp;#039;],
)
def sin(val: float = 3.14) -&amp;gt; float:
    import numpy as np

    return np.sin(val).item()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;packages_to_install&lt;/code&gt;で指定したライブラリはパイプライン実行時にインストールされ、その後に関数のコードが実行されます。依存ライブラリは関数ごとに管理する必要があり、MLバッチ全体で用いる特定ライブラリのバージョンを統一することが困難です。また、ライブラリのインポートや使用するシンボルの定義を関数内に閉じ込める必要があるため、関数の責務が容易に肥大化し、単体テストの記述も難しくなります。さらに、KFPが担うコンポーネント定義などのパイプラインのオーケストレーションと、ドメイン固有のビジネスロジックが密結合になりやすく、コードの見通しも損なわれます。これらの特性から、プロダクション用途には向きません。&lt;/p&gt;
&lt;h3&gt;Containerized Python Components&lt;/h3&gt;
&lt;p&gt;Containerized Python Componentsは前述のLightweight Python Componentsに近い書き方ですが、関数スコープに閉じるという制約が一部緩和されます。コードと依存関係を含むコンテナイメージを事前にビルドすることで、関数外で定義したシンボルも参照できます。&lt;/p&gt;
&lt;p&gt;しかし、関数ごとに依存ライブラリを定義する必要がある点は同じです。さらに、KFPのCLIが特定のディレクトリ構造に依存して自動生成する&lt;code&gt;runtime-requirements.txt&lt;/code&gt;と&lt;code&gt;Dockerfile&lt;/code&gt;を前提とするため、柔軟性に欠けます。したがって、これらの制約を踏まえた上で利用する必要があります。&lt;/p&gt;
&lt;h3&gt;Container Components&lt;/h3&gt;
&lt;p&gt;3つ目のContainer Componentsは、&lt;code&gt;docker run&lt;/code&gt;のようにイメージ、コマンド、引数を指定してコンポーネントを定義する方式です。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from kfp import dsl

@dsl.container_component
def say_hello(name: str) -&amp;gt; dsl.ContainerSpec:
    return dsl.ContainerSpec(
        image=&amp;#039;gcr.io/my-project/my-base-image:latest&amp;#039;,
        command=[&amp;#039;python&amp;#039;, &amp;#039;main.py&amp;#039;],
        args=[&amp;#039;--name&amp;#039;, name],
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;パイプラインで用いるコードとライブラリを含んだイメージを用意する必要がありますが、依存関係の管理がはるかに容易になります。実行時にライブラリをインストールする必要もありません。また、実行するPythonスクリプトはKFP特有の制約を受けない通常のスクリプトとして実装できるため、テストも書きやすくなります。&lt;/p&gt;
&lt;p&gt;こうした理由から、Growth MLチームでは原則としてContainer Componentsでコンポーネントを実装することとしました。&lt;/p&gt;
&lt;h2&gt;Container ComponentsでのCLI実装とその課題&lt;/h2&gt;
&lt;p&gt;Container Componentsで実行コマンドを指定するため、処理ロジックはPythonのCLIとして実装するのが自然です。CLIコマンドは&lt;code&gt;docker run&lt;/code&gt;のように実行されるため、引数としてリストや辞書などの複雑な引数を渡すときは注意が必要です。&lt;/p&gt;
&lt;p&gt;たとえば、次のようなパイプラインを用意したとします。実行するコンポーネントは1つだけで、CLI引数としてPythonのリストを渡します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from kfp import dsl, local

@dsl.container_component
def argparse_component(
    list_var: list,  # KFP does not support the `list[str]` type annotation.
) -&amp;gt; dsl.ContainerSpec:
    return dsl.ContainerSpec(
        image=&amp;#039;kfp-demo:latest&amp;#039;,
        command=[&amp;#039;python&amp;#039;, &amp;#039;-m&amp;#039;, &amp;#039;cli.argparse_cli&amp;#039;],
        args=[&amp;#039;--list-var&amp;#039;, list_var],
    )

@dsl.pipeline
def pipeline() -&amp;gt; None:
    argparse_component(list_var=[&amp;#039;a&amp;#039;, &amp;#039;b&amp;#039;, &amp;#039;c&amp;#039;])

def main() -&amp;gt; None:
    # Using local Docker runner for demonstration
    local.init(runner=local.DockerRunner())

    pipeline()

if __name__ == &amp;#039;__main__&amp;#039;:
    main()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実行するCLIは次のとおりです。標準ライブラリの&lt;code&gt;argparse&lt;/code&gt;で実装しており、リストを受け取り、&lt;code&gt;print&lt;/code&gt;関数でそのまま出力するだけのシンプルなものです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import argparse

def main() -&amp;gt; None:
    parser = argparse.ArgumentParser()
    parser.add_argument(&amp;#039;--list-var&amp;#039;, nargs=&amp;#039;+&amp;#039;, required=True)
    args = parser.parse_args()

    print(args.list_var)

if __name__ == &amp;#039;__main__&amp;#039;:
    main()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CLI引数として渡しているのは&lt;code&gt;[&amp;#039;a&amp;#039;, &amp;#039;b&amp;#039;, &amp;#039;c&amp;#039;]&lt;/code&gt;なので、&lt;code&gt;print&lt;/code&gt;の結果も&lt;code&gt;[&amp;#039;a&amp;#039;, &amp;#039;b&amp;#039;, &amp;#039;c&amp;#039;]&lt;/code&gt;となってほしいところです。しかし実際には、&lt;code&gt;[&amp;#039;[&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;]&amp;#039;]&lt;/code&gt;と出力されます。&lt;/p&gt;
&lt;p&gt;Container Componentsはほぼ&lt;code&gt;docker run&lt;/code&gt;と同じ仕組みで動作するため、引数を受け渡す際に変数の値がシリアライズ（文字列化）されます。そのため、実際の&lt;code&gt;docker run&lt;/code&gt;コマンドで渡される引数は文字列&lt;code&gt;&amp;#039;[&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;]&amp;#039;&lt;/code&gt;になります。また、先ほどの&lt;code&gt;argparse&lt;/code&gt;の実装では、スペース区切りで渡された複数の値をリストとして扱う仕様になっています。結果として、この文字列全体が1要素として扱われ、&lt;code&gt;[&amp;#039;[&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;]&amp;#039;]&lt;/code&gt;というリストになってしまいます。この挙動は、&lt;a href=&quot;https://click.palletsprojects.com/&quot;&gt;Click&lt;/a&gt;や&lt;a href=&quot;https://typer.tiangolo.com/&quot;&gt;Typer&lt;/a&gt;など他のCLIライブラリでも同様です。&lt;/p&gt;
&lt;p&gt;この問題を単純に解決しようとすると、ひとまず引数を文字列（&lt;code&gt;str&lt;/code&gt;）として受け取り、CLI側でデシリアライズするという実装が考えられます。ただ、それだと本質ではない処理が増えてコードの見通しが悪くなり、なにより型の安全性が担保されません。&lt;/p&gt;
&lt;p&gt;引数が&lt;code&gt;int&lt;/code&gt;や&lt;code&gt;bool&lt;/code&gt;だけで済むならシンプルで理想的ですが、Pythonだけでパイプラインを書く以上、&lt;code&gt;list&lt;/code&gt;や&lt;code&gt;dict&lt;/code&gt;といった構造化データを引数にしたいケースはどうしても発生します。&lt;/p&gt;
&lt;h2&gt;Pydantic Settingsで型安全かつシンプルにCLI引数を扱う&lt;/h2&gt;
&lt;p&gt;いくつかのアプローチを検討・試行した結果、&lt;a href=&quot;https://docs.pydantic.dev/latest/concepts/pydantic_settings/&quot;&gt;Pydantic Settings&lt;/a&gt;を使う方法が、これらの課題を最もシンプルに解決できることが分かりました。&lt;/p&gt;
&lt;p&gt;Pydantic Settingsは、&lt;a href=&quot;https://docs.pydantic.dev/latest/&quot;&gt;Pydantic&lt;/a&gt;の型定義を使って環境変数や&lt;code&gt;.env&lt;/code&gt;などから設定を型安全に読み込めるライブラリです。さらに、&lt;code&gt;SettingsConfigDict(cli_parse_args=True)&lt;/code&gt;を有効にすればCLI引数もPydanticモデルでパースできます。&lt;/p&gt;
&lt;p&gt;先ほど&lt;code&gt;argparse&lt;/code&gt;で実装したCLIをPydantic Settingsで書き直すと次のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;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=&amp;#039;list-var&amp;#039;)

def main() -&amp;gt; None:
    settings = Settings()
    print(settings.list_var)

if __name__ == &amp;#039;__main__&amp;#039;:
    main()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pydanticは入力値を指定した型にパースし、その出力がその型に一致することを保証します。Pydantic SettingsでCLI引数をパースする場合、&lt;code&gt;list&lt;/code&gt;や&lt;code&gt;dict&lt;/code&gt;などはJSON形式をサポートしているため、KFPのContainer ComponentsでシリアライズされたCLI引数（JSON文字列）も適切にパースしてくれます。その結果、わざわざデシリアライズ処理を書く必要がなく、型安全性も自然に確保されます。&lt;/p&gt;
&lt;h2&gt;複雑な構造体もPydantic Settingsで型安全に扱う&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;argparse&lt;/code&gt;など標準的なCLIライブラリでは&lt;code&gt;dict&lt;/code&gt;などをそのまま引数型として扱えませんが、Pydantic SettingsならJSON文字列をモデルにマッピングできるので、簡単かつ型安全に扱えます。&lt;/p&gt;
&lt;p&gt;ここでは少し応用的な例として、KFPの&lt;a href=&quot;https://kubeflow-pipelines.readthedocs.io/en/stable/source/dsl.html#kfp.dsl.PipelineTaskFinalStatus&quot;&gt;&lt;code&gt;dsl.PipelineTaskFinalStatus&lt;/code&gt;&lt;/a&gt;を使って実行タスクの最終状態を受け取る方法を紹介します。使い方はシンプルで、コンポーネントに&lt;code&gt;PipelineTaskFinalStatus&lt;/code&gt;の型アノテーションを付けた引数を追加するだけです。変数に自動的に値が代入されるようになっているため、コンポーネントの関数を呼び出す際にこの引数を指定する必要はありません。公式ドキュメントにはContainer Componentsでの具体例はありませんが、Pydantic Settingsを使えば問題なく扱えます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from kfp import dsl

@dsl.container_component
def pipeline_task_final_status_component(
    base_image: str,
    pipeline_task_final_status: dsl.PipelineTaskFinalStatus,
) -&amp;gt; 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=[
            &amp;#039;python&amp;#039;,
            &amp;#039;-m&amp;#039;,
            &amp;#039;cli.pydantic_settings_cli&amp;#039;,
        ],
        args=[
            &amp;#039;--pipeline-task-final-status&amp;#039;,
            pipeline_task_final_status,
        ],
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PipelineTaskFinalStatus&lt;/code&gt;の変数をCLIの引数に指定すると、他の値と同様にJSON文字列としてシリアライズされてコンテナに渡されます。シリアライズ後の実体は単なるJSON文字列なので、対応するPydanticのモデルを定義しておけば、CLI側でそのまま型付きオブジェクトとして安全に扱うことができます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;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[&amp;#039;SUCCEEDED&amp;#039;, &amp;#039;FAILED&amp;#039;, &amp;#039;CANCELLED&amp;#039;]

class Settings(BaseSettings):
    model_config = SettingsConfigDict(cli_parse_args=True)

    pipeline_task_final_status: PipelineTaskFinalStatus = Field(
        alias=&amp;#039;pipeline-task-final-status&amp;#039;,
        description=&amp;#039;Pipeline task execution status (JSON format)&amp;#039;,
    )

def main() -&amp;gt; None:
    settings = Settings()
    print(settings.pipeline_task_final_status)

if __name__ == &amp;#039;__main__&amp;#039;:
    main()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;想定と異なる形式のデータが渡された場合、Pydanticは入力時点で検証し即座にバリデーションエラーを返します。そのため、ネストした&lt;code&gt;dict&lt;/code&gt;や&lt;code&gt;list&lt;/code&gt;などのやや複雑な構造のデータでも、Pydantic Settingsを使えば型に沿って安全に扱えます。&lt;/p&gt;
&lt;p&gt;Pydantic Settingsを併用することで、KFPのContainer Componentsの開発体験が大きく向上します。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;本稿では、KFPのコンポーネント実装パターンを整理し、Container ComponentsとPydantic Settingsを組み合わせることで、開発者がより簡単かつ型安全にパイプラインを構築できるアプローチを紹介しました。&lt;/p&gt;
&lt;p&gt;他にも、パイプラインの実行スケジュールを宣言的に記述し、&lt;code&gt;terraform apply&lt;/code&gt;のように差分を確認・適用できる仕組みなど、運用を支えるさまざまな取り組みをしています。これらについても、また別の機会に紹介できればと思います。&lt;/p&gt;
&lt;p&gt;今回紹介した内容が、KFPを使った開発における設計で悩んでいる方の参考になれば幸いです。&lt;/p&gt;
&lt;p&gt;明日の記事は @Sakamoto さんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>LLM Key ServerによるLLM APIへの安全で便利なアクセス提供</title><link>https://engineering.mercari.com/blog/entry/20251202-llm-key-server/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251202-llm-key-server/</guid><description>&lt;p&gt;こんにちは。メルカリのAI Securityエンジニアの@hi120kiです。 この記事は、Mercari Advent Calendar 2025 の2日目の記事です。 メルカリでは社内でのAI/LLM活用の拡大を目指 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 02 Dec 2025 11:00:42 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリのAI Securityエンジニアの&lt;a href=&quot;https://twitter.com/hi120ki&quot;&gt;@hi120ki&lt;/a&gt;です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/&quot;&gt;Mercari Advent Calendar 2025&lt;/a&gt; の2日目の記事です。&lt;/p&gt;
&lt;p&gt;メルカリでは社内でのAI/LLM活用の拡大を目指し、様々な取り組みが行われています。これらの動きを後押しするために&lt;a href=&quot;https://careers.mercari.com/mercan/articles/55843/&quot;&gt;AI Securityチーム&lt;/a&gt;はLLM APIへのアクセスを安全でありながら便利に提供するためのサービスLLM Key Serverを開発しました。&lt;/p&gt;
&lt;p&gt;これによりLLM APIへのアクセス申請を受け担当者が手作業でユーザーを登録していたプロセスがLLM Key Serverによって置き換えられ、LLM APIユーザーは申請の手間なく自身の社内アカウント経由で有効期限付きのLLM APIキーを取得できるようになりました。&lt;/p&gt;
&lt;p&gt;また、GitHub ActionsやGoogle Apps Script上でLLM APIを利用するための共通テンプレートも提供し、ローカル環境のみならずCIやクラウドやノーコードツール等の複数のサービス上でのLLM利用促進にも貢献しました。&lt;/p&gt;
&lt;p&gt;本記事ではLLM APIにおけるセキュリティ課題・プロセスの改善・LLM Key Serverのアーキテクチャ・実装におけるポイントについて解説します。&lt;/p&gt;
&lt;h2&gt;LLM APIにおけるセキュリティ課題&lt;/h2&gt;
&lt;p&gt;現在様々なLLMが各社から展開されており、メルカリでは複数のLLMをタスクごとの要求や従業員の好みごとに使い分けています。しかし、LLMへのアクセスを提供するAPIには多くの場合APIキーが必要となっています。&lt;/p&gt;
&lt;p&gt;主要なLLMベンダーが提供するAPIへのアクセスに使われるAPIキーは、有効期限がなく、流出しそれが検知されなかった場合、長い期間にわたって情報漏洩・経済的損失を受ける恐れがあります。さらに現在のAI/LLM活用の流れの中で、多くのAPIキーが発行されており、管理があいまいになってしまうことも懸念されています。また、複数社のLLMを利用する中でユーザー・チーム・権限管理が複雑になることが想定されます。このような複雑な管理体制は定期的なアクセス権の監査を難しくします。&lt;/p&gt;
&lt;p&gt;もちろんAPIキーを用いずWorkload Identityおよびクラウド間連携を用いてGoogle CloudやAzure経由でLLM APIにアクセスする方法が最も安全であり、推奨される方法として社内で提示しています。しかし一方で、設定の複雑さや、様々な外部のAI/LLMプロダクトがそれらをサポートせずリリースされることもあり、多くのLLM関連ツールを評価している状況下では別の方法が求められていました。&lt;/p&gt;
&lt;p&gt;また追加の要件として、セキュリティポリシーによって煩雑なフローを強制することはかえってポリシーの逸脱を助長しかねないため、安全性を保ちながら利便性も追求することが必要でした。&lt;/p&gt;
&lt;h2&gt;安全かつ便利なLLM APIへのアクセス提供&lt;/h2&gt;
&lt;p&gt;安全かつ便利なLLM APIへのアクセス手段を提供するため、複数のモデルを一つのAPIから利用可能にするOSSの&lt;a href=&quot;https://www.litellm.ai/&quot;&gt;LiteLLM&lt;/a&gt;と、Google WorkspaceおよびGoogle Cloudの&lt;a href=&quot;https://cloud.google.com/docs/authentication/get-id-token&quot;&gt;OIDC IDトークン発行機能&lt;/a&gt;を用いることにしました。&lt;/p&gt;
&lt;p&gt;LiteLLMは様々なモデルプロバイダーから提供されるLLMを一つのAPIから利用可能にするOSSで、基本的なLLM API呼び出しの他にClaude CodeなどのCoding Agentツールにも対応しています。&lt;/p&gt;
&lt;p&gt;また、OIDC IDトークン発行機能はGoogleのOAuthやサービスアカウントの権限を利用することでGoogleによって署名されたIDトークンを取得できるというもので、これを利用することでユーザーの身元を確認することができます。&lt;/p&gt;
&lt;p&gt;メルカリでは&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241203-token-server-google-cloud/&quot;&gt;Google CloudからGitHubへのアクセスを有効期限が短い認証情報へ切り替えるためのToken Server&lt;/a&gt;を運用していますが、LLM Key ServerはこのアーキテクチャをベースにLLMへのアクセスへと発展させたものになります。&lt;/p&gt;
&lt;h3&gt;LLM Key Serverのアーキテクチャ&lt;/h3&gt;
&lt;p&gt;LLM Key Serverの認証フローは以下のようになっています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/287533cf-llmkeyserver.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;center&gt;LLM Key Serverの認証フロー&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;まず、Claude Codeを利用したいユーザーやLLMを利用したいアプリケーションは、GoogleのAPIから自身を証明するためのOIDC IDトークンを取得します。ここではGoogle Workspaceアカウントによる認証や、Compute metadataサーバーからのサービスアカウント認証などが利用されます。&lt;/p&gt;
&lt;p&gt;次に、取得したOIDC IDトークンをLLM Key Serverに送信すると、LLM Key Serverはトークンの署名を検証し、トークン内の情報をもとにLiteLLMへアクセスするための一時的なAPIキーを発行します。このAPIキーは有効期限が短く設定されており、ユーザーはこのキーを用いてLiteLLM経由で様々なLLMへアクセスすることができます。&lt;/p&gt;
&lt;p&gt;ローカル環境での利用においてGoogle Workspaceアカウントによる認証を利用する際は、社内公開のCLIツールを利用することで1つのコマンドでOAuth認可フローを開始し、OIDC IDトークンの取得からLLM APIキーの取得までを行うことができます。&lt;/p&gt;
&lt;p&gt;またAPIキーはサービスアカウントに対しては1時間という短い有効期限が設定されています。しかし、クラウド上で動作しLLMを利用するアプリケーションが長時間動作することを想定し、自動的にキーを更新する仕組みも提供しています。これはGo言語のライブラリとして整備されており、自動的にキーの更新を行い、継続してLLM APIを利用することができます。&lt;/p&gt;
&lt;p&gt;このようにしてGoogle Workspaceアカウントによる認証やGoogle Cloud上のサービスアカウント認証を利用することで、安全にLLM APIへのアクセスを提供しつつ、有効期限付きのキーを発行することで情報漏洩リスクを低減し、さらに自動更新ライブラリを提供することで利便性も確保しました。&lt;/p&gt;
&lt;h2&gt;LLM Key Serverの利用形態の拡張&lt;/h2&gt;
&lt;p&gt;LLM Key Serverはローカル環境やクラウド上で動作するアプリケーションの利用だけでなく、様々な社内ツールやサービス上での利用も想定しています。特に以下の2つの形態での利用をサポートしています。&lt;/p&gt;
&lt;h3&gt;GitHub Actions&lt;/h3&gt;
&lt;p&gt;GitHub Actions上でLLM APIを利用するための共通テンプレートを提供しています。GitHub Actions上では&lt;a href=&quot;https://docs.github.com/en/actions/concepts/security/openid-connect&quot;&gt;GitHubから提供されるOIDC IDトークン&lt;/a&gt;を用いてLLM Key ServerからLLM APIキーを取得し、LiteLLM経由で様々なLLMへアクセスすることができます。これにより、CI/CDパイプライン上でのLLM活用が促進されており、Claude Codeを用いたコードレビューが自動化されたりしています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- 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}&amp;amp;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(&amp;#039;Failed to retrieve OIDC token from GitHub Actions&amp;#039;);
      }

      const res = await fetch(&amp;#039;https://key-server.example.com/llm-key&amp;#039;, {
        method: &amp;#039;GET&amp;#039;,
        headers: {
          &amp;#039;Authorization&amp;#039;: `Bearer ${oidc_token}`,
          &amp;#039;Content-Type&amp;#039;: &amp;#039;application/json&amp;#039;,
        }
      });
      if (res.status !== 200) {
        core.setFailed(`LiteLLM API Error: HTTP ${res.status}`);
      }
      const body = await res.json();
      core.setSecret(body.key);
      core.setOutput(&amp;#039;token&amp;#039;, body.key);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このようなGitHub Actionsのactionを用意することで、開発者が直接LLM APIキーを管理することなく、CI/CDパイプライン上で安全にLLM APIを利用できるようになりました。&lt;/p&gt;
&lt;h3&gt;Google Apps Script&lt;/h3&gt;
&lt;p&gt;Google Apps Script上でもLLM APIを利用するための共通テンプレートを提供しています。Google Apps Script上では&lt;a href=&quot;https://developers.google.com/apps-script/concepts/scopes&quot;&gt;OAuth scope設定&lt;/a&gt;を用いてユーザーの認証を行い、OIDC IDトークンを取得することができます。&lt;/p&gt;
&lt;p&gt;Google Apps Scriptの設定ページから&lt;code&gt;appsscript.json&lt;/code&gt;ファイルを表示するようにした後、以下のようにOAuth scopeを追加します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  &amp;quot;oauthScopes&amp;quot;: [
    &amp;quot;openid&amp;quot;,
    &amp;quot;https://www.googleapis.com/auth/userinfo.email&amp;quot;,
    &amp;quot;https://www.googleapis.com/auth/script.external_request&amp;quot;
  ],&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;その後、Google Apps Script上で以下のようにOIDC IDトークンを取得し、LLM Key ServerからLLM APIキーを取得し、LiteLLM経由で様々なLLMへアクセスすることができます。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function getLLMToken() {
  try {
    const cache = CacheService.getUserCache();
    const cacheKey = &amp;quot;llm_token&amp;quot;;
    const cachedToken = cache.get(cacheKey);
    if (cachedToken) {
      return cachedToken;
    }
    console.log(&amp;quot;[+] Fetching new LLM token&amp;quot;);
    const token = ScriptApp.getIdentityToken();
    const options = {
      method: &amp;quot;GET&amp;quot;,
      headers: {
        Authorization: &amp;quot;Bearer &amp;quot; + token,
      },
    };
    const response = UrlFetchApp.fetch(
      &amp;quot;https://key-server.example.com/llm-key&amp;quot;,
      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(&amp;quot;Key not found in response&amp;quot;);
    }
    cache.put(cacheKey, responseData.key, 50 * 60); // Cache for 50 minutes
    return responseData.key;
  } catch (e) {
    console.error(&amp;quot;Error getting LLM token: &amp;quot; + e.toString());
    return null;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OIDCのIDトークンを検証する際には、ユーザーのメールアドレスだけでなく、Apps Scriptのバックエンドとして動作しているGoogle Cloudプロジェクトが、Google Cloud組織内の&lt;code&gt;system-gsuite/apps-script&lt;/code&gt;フォルダに所属しているかどうかも確認しています。&lt;br /&gt;
これにより、信頼できるスクリプトからのアクセスのみを許可するようにしています。&lt;/p&gt;
&lt;p&gt;これによりノーコードツール上での平文でのLLM APIキーの管理を避け、安全にLLM APIを利用することができます。&lt;/p&gt;
&lt;p&gt;この仕組みにより社内でのLLM活用が促進されており、社内ドキュメントの要約や翻訳などに利用されています。&lt;/p&gt;
&lt;h2&gt;さいごに&lt;/h2&gt;
&lt;p&gt;組織としてLLM APIキーを安全に扱うための仕組みとしてLLM Key Serverを開発しました。これにより安全性と利便性の両立を実現し、社内でのLLM活用をポジティブにSecurityチームから促進することができました。今後もAI Securityチームは安全で便利なAI/LLM活用のための取り組みを続けていきます。&lt;/p&gt;
&lt;p&gt;このようなメルカリでのAI/LLM活用やセキュリティに関する取り組みに興味がある方は、ぜひ&lt;a href=&quot;https://careers.mercari.com/&quot;&gt;メルカリの採用ページ&lt;/a&gt;をご覧ください。&lt;/p&gt;
&lt;p&gt;明日の記事は@Jazzさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌</title><link>https://engineering.mercari.com/blog/entry/20251201-pj-double-towards-ai-native-development/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251201-pj-double-towards-ai-native-development/</guid><description>&lt;p&gt;こんにちは。メルペイ VPoE室マネージャーの @nnaakkaaii です。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の2日目の記事です。 はじめに この記事では [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 02 Dec 2025 10:00:05 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイ VPoE室マネージャーの &lt;a href=&quot;https://www.linkedin.com/in/yu-nakai-swe&quot;&gt;@nnaakkaaii&lt;/a&gt; です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の2日目の記事です。&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;この記事では、メルカリで2025年7月からスタートした取り組みであるProject Double（以下、pj-double）についてご紹介します。&lt;br /&gt;
「AIと協業する開発体験」「開発プロセスの再設計」「AI‑Nativeな組織づくり」に関心のあるエンジニア・PM・マネージャーの方にとって何かしらの参考になればと思い、本記事を執筆しました。&lt;/p&gt;
&lt;p&gt;pj-doubleは、プロダクト開発における生産性を2倍に向上させることをミッションに掲げ、開発体制・プロセスをAI-Nativeに再設計する挑戦です。しかしその背景には、私たちがAIの導入を試みる中で経験した数々の試行錯誤と、そこから得た気づきがありました。&lt;/p&gt;
&lt;p&gt;まずは、その前段として社内におけるAI活用の始まりと、初期フェーズでの課題から振り返ってみたいと思います。&lt;/p&gt;
&lt;h2&gt;2025年初頭: Vibe Codingの台頭と課題&lt;/h2&gt;
&lt;p&gt;2025年初頭は、AIを活用したコーディング手法の黎明期でした。社内でもさまざまなAIツールの活用、MCPサーバーの整備などがボトムアップに推進されました。それぞれの開発者が多種多様な方法論を模索する、いわゆる “Divergence” のフェーズでした。&lt;/p&gt;
&lt;p&gt;このAI活用の初期段階において、私たちは大きな可能性を確認しました。設計・実装・テスト環境上の動作確認までを全てAIで行い、開発工数の見積もりに対して5分の1まで効率化を実現した事例も生まれました。&lt;/p&gt;
&lt;p&gt;このようにAIを活用した開発手法が、一部の開発者の生産性を格段に向上した一方で、AIを使いこなしている開発者とそうでない開発者の間の溝も顕在化し始めていました。当時のAIを活用した開発手法の模索は、個人ごとに異なる個別最適化の域を出ず、組織全体としての生産性を向上するための組織的ムーブメントには至っていないという課題がありました。&lt;/p&gt;
&lt;p&gt;実際に、こうした開発手法に再現性を与え、組織全体で共有知化するための試みもいくつか立ち上がったものの、局所的なプラクティスの共有・CLAUDE.mdやCursor Rulesの共有知化などに留まっていました。その理由として、当時主流であったVibe Codingに構造的な要因があったと振り返っています。Vibe Codingは、開発の状態に合わせてAIに都度指示を送って、その出力を都度確認しながら方向修正を行い、AIと並走しながら実装の完了まで導くような開発手法です。この同期的でインタラクティブなプロセスの性質上、開発者が直感的・経験的に行なっている状況判断の質が生産性を大きく左右します。このように、個々の状況判断はAIとの対話ログに閉じてしまって透明性が低く、また場面ごとの判断根拠を事後的に説明することも難しいため再利用性も低かったことが、再現可能な共有知に昇華する上での決定的な課題となっていました。&lt;/p&gt;
&lt;p&gt;またベイエリアの新興企業を中心として、AIを前提とした開発プロセスや開発体制によって、数十人でユニコーン級のサービスを作る事例が知られるようになりました。このようなAI-Nativeな組織では、開発のプロセスも、そこにおける1人1人の役割や職能も、既存のそれらとは異なる形を取ると思います。社内においても、一部の開発者は「開発体験そのものが根底から変わる実感」を覚えていたものの、ボトムアップなAI-Native化の推進体制では、既存の開発プロセスや体制の上での局所最適なAI活用に留まってしまうという限界が見えていました。AIがない時代に最適化された開発プロセスや開発体制を、AI-Nativeな開発プロセスや開発体制へと変革することが、プロダクトデリバリーにおける持続的な競争力を有するための至上命題でした。&lt;/p&gt;
&lt;h2&gt;2025年7月: pj-doubleの発足&lt;/h2&gt;
&lt;p&gt;このような背景を受けて、AI-Nativeな開発プロセス・開発体制へのシフトを本格的に推進させるため、2025年の7月頃にメルペイのVP of Engineering Officeにて “pj-double” が発足されました。これは、さまざまな手法を模索して個人の個別最適化を達成する “Divergence” のフェーズから、全社として高生産性を再現度高く実現可能な開発プロセスのスタンダードを確立する “Convergence” のフェーズへの移行のスタートでした。私たちはこのスタンダードの確立により、社内のあらゆる知見やフィードバックがそこに集積され、持続的に組織として共有知を育てていけるような体制を実現することを目指しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/12/e00569c7-project-double-1024x576.png&quot; alt=&quot;Pj Double&quot; /&gt;&lt;/p&gt;
&lt;p&gt;時期を同じくして、技術環境のトレンドも変化しました。Prompt EngineeringからContext Engineeringへ、そしてVibe CodingからAgentic Codingへの変化です。Prompt Engineeringのもとでは「いかに良いプロンプトを与えるか」が重要でしたが、Context Engineeringのもとでは「いかに多くのデータソースと繋いで、いかに必要な一次情報を直接インプットするか」が重要視されるようになりました。また、同期的かつインタラクティブにAIを導く必要があったVibe Codingに対して、Agentic Codingでは、「最初に目的・ステップ・完了条件さえ与えれば、Agentが自律的にタスクを完遂するため、開発者はその結果のみを評価すれば良い」という非同期的かつスケーラブルな開発体験を可能にしました。&lt;/p&gt;
&lt;p&gt;このようにAI活用は、個人のアートから、再現性のあるサイエンスへと変化し、pj-doubleが掲げる再現可能なAI-Native開発への挑戦を後押しする鍵となりました。&lt;/p&gt;
&lt;h2&gt;2025年7-9月: AIを活用した開発手法の効果の実証&lt;/h2&gt;
&lt;p&gt;pj-doubleではこの開発手法のスタンダードを確立することを、最初の3ヶ月の目標に置きました。メルペイやメルカリモバイルにおける30以上のバックエンド開発のプロジェクトと連携を開始し、社内で蓄積されたさまざまな開発手法に関する知見を集積することから始めました。&lt;/p&gt;
&lt;p&gt;この連携を通して、「どのようなプロジェクトのどのような開発フェーズではどのようなAI活用が行われているのか」、「それらの手法には定性的にどのような利点・欠点があるのか」、「それらの手法が定量的にどのくらいの開発生産性向上の効果を出しているのか」、「それらの手法は他のプロジェクトにおいても同程度の効果を再現可能なのか」、などの観点について、各プロジェクトとの週例のミーティングを通して解像度高くトラックしていきました。&lt;/p&gt;
&lt;p&gt;ここにおいて特に、開発生産性の測定のためにどのような指標を設定すべきかが議論に上がりました。&lt;a href=&quot;https://getdx.com/&quot;&gt;DX&lt;/a&gt;（メルカリで導入している開発者体験を可視化するツール）などで定量的に確認可能なメトリクスは既に複数あったものの、どれも一長一短で、また3ヶ月という期間の短さを踏まえて即効性も重視されました。結果として私たちは、プロジェクト開始時点における従来通りの開発工数の見積もりと、最終的な開発工数の実績値との間で、どれほどの開発速度向上の効果があったかを主観ベースで統計化することにしました。なるべくノイズや主観によるボラティリティを減らすため、週例のミーティングにおいて細かく開発フェーズごとに行なった作業と要した時間を報告してもらいました。これには、どの工程においてどのような課題があるのかを顕在化させる副次的な効果もありました。&lt;/p&gt;
&lt;p&gt;そしてこの連携を通した定量的な調査の中で、最も開発生産性向上の効果が高かった手法が、Specドリブン開発（Spec-Driven Development; SDD）でした。SDDを採用した複数のプロジェクトは、見積比で平均して150%以上の開発速度向上の効果を実現しました。またSDD以外のAIを活用した開発手法を採用したプロジェクト群と比較しても、80%以上の開発速度向上の効果を実現しました。&lt;/p&gt;
&lt;p&gt;また更に定性的な調査の中で、AIを活用した開発手法の効果を高く実感している開発者とそうでない開発者の間には、次のような観点で差があることがわかりました。&lt;/p&gt;
&lt;p&gt;(1) AIのコンテキストに適切な一次情報を与えられているか&lt;/p&gt;
&lt;p&gt;最初にAIに開発時の前提となる全ての一次情報を明示的に渡すことによって、AIの作業中に開発者がプロンプトから情報を補完する必要がなくなります。これにより、AIが要件を無視して実装したり、Hallucinationを起こすことを防止できます。&lt;/p&gt;
&lt;p&gt;(2) AIにタスクの明確な目的・ステップ・完了条件などを明示することができているか&lt;/p&gt;
&lt;p&gt;最初に開発のステップや完了条件などを明示することによって、AIの作業中に開発者が介入して方向修正する必要性が減ります。これにより、AIが全く意図しない方針で実装を進めてしまったり、タスクのゴールを都合良く解釈してしまうといったことを防止できます。また実装が終わるまでどのようなアウトプットになるのかわからないといった不確実性も解消されます。&lt;/p&gt;
&lt;p&gt;(3) AIが作業中にコンテキストに保持する情報を最適化できているか&lt;/p&gt;
&lt;p&gt;AIが単一のコンテキストで「資料を読み、技術調査を行い、コードベースを解析し、実装を生成し、テストを実行し、不具合を修正し、…」というステップを全て進めようとすると、不要なドキュメントやコードの情報、テスト時のログなどが全てコンテキストに含まれることになってしまい、すぐに会話がcompactされてしまいます。このcompactionによって、AI自身が何のタスクを行なっていたのか忘れてしまうこともあります。実装を複数のステップで異なる会話のコンテキストに分離したり、Claude CodeのSubagent機能のようなコンテキストを部分的に分離するような機能を用いることが、これに対する解決策となります。&lt;/p&gt;
&lt;h2&gt;2025年9月: スタンダードとしてのAgent-Spec Driven Development（ASDD）の提案&lt;/h2&gt;
&lt;p&gt;このような結果を踏まえて、SDDの高い開発生産性への効果と、上記3つの観点に代表されるAIを活用した開発手法の効果を左右する不確実性や属人性を低減するために、メルカリ流に最適化された手法が、Agent Specドリブン開発（Agent-Spec Driven Development; ASDD）でした。&lt;/p&gt;
&lt;p&gt;ASDDは、「資料の読解・技術調査・コードベースの解析を通して、実装計画（Agent Spec）を生成する」ステップと、「その策定されたAgent Specに基づいて実装を行う」ステップの、2つのステップから構成されます。このAgent Specには、タスクの一覧、タスクごとの詳細設計、タスクごとに「どの実装を参考に」・「どのファイルに」・「どのような変更を加えるか」などの詳細に記載されており、SDDのSpecにおいてAIにとっての明瞭性と人間にとっての可読性を両立したものになっています。ASDDは、SDDのSpecすらも自動生成することを目指した開発手法であるとも言えます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/de1adba0-content-of-asdd-1024x574.png&quot; alt=&quot;Content of Agent Spec&quot; /&gt;&lt;/p&gt;
&lt;p&gt;1つ目のAgent Spec生成のステップでは、メルカリのナレッジ基盤に最適化されたエージェントが自動的に一次情報にアクセスし、詳細な実装計画を生成します。また別のエージェントが、実装計画がサービスのコーディング規約に沿っているか、計画が所定のセキュリティ観点をクリアしているかなどのさまざまな調査を行います。この2つのエージェントが交互に修正と評価を繰り返し、最後に要件や仕様における不確実要素が残った場合には、開発者に追加の質問を行います。このように、「AIのコンテキストに適切な一次情報を与える」ことをプロセスによって保証しました。&lt;/p&gt;
&lt;p&gt;2つ目のAgent Specから実装を生成するステップでは、実装を行うエージェントと、テストや静的解析を行うエージェント、実装結果のAgent Specとの整合性を検証するエージェントが互いに協調しながら、タスクの完了まで自律的に実行されます。このように、「AIにタスクの明確な目的・ステップ・完了条件などを明示する」ことをプロセスによって保証しました。&lt;/p&gt;
&lt;p&gt;このASDDには、次のような利点がありました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AIは計画時・実装時のそれぞれで必要な情報のみをコンテキストに保持することができるため、コンテキスト中の情報密度が高い&lt;/li&gt;
&lt;li&gt;AIの実装計画をレビューできるため、実装結果の予測可能性が高い&lt;/li&gt;
&lt;li&gt;生成される実装は実装計画に基づく宣言的なアプローチであるため、再利用性や透明性が高い&lt;/li&gt;
&lt;li&gt;各ステップへのcontributionを通して、全社横断で知見の集約と持続的改善を行うことができる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;そして、ASDDによって実装全体がAIに移譲して非同期的に実行可能なプロセスになったため、高いスケーラビリティの実現が可能になりました。このスケーラビリティこそが、高い開発生産性の実現を可能にしたと考えられます。&lt;/p&gt;
&lt;h2&gt;2025年10月から現在: 開発プロセス全体のAI-Native化へ&lt;/h2&gt;
&lt;p&gt;2025年10月からは、1人から始まったpj-doubleも10人超のチームに拡大し、またその対象領域もメルペイの一部プロジェクトから、全社へとスケールしています。対象範囲も、これまでのバックエンドの詳細設計と実装のAI-Native化に加えて、要件定義や設計、iOS/Androidのクライアント開発やQAを含めた、デリバリーサイクル全体のプロセス再設計へと拡大しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/6e5dae32-asdd-1024x571.png&quot; alt=&quot;Overview of ASDD&quot; /&gt;&lt;/p&gt;
&lt;p&gt;要件定義・設計領域では、プロダクト仕様書と技術設計書を作成・合意するプロセスを再設計しています。そこでは、PMや開発者が簡単な要件や技術方針を伝えるだけで、AIが一次情報や過去のプロジェクトを参考にプロダクト仕様書と技術設計書を自動生成することを目指してきました。&lt;/p&gt;
&lt;p&gt;クライアント実装の領域では、バックエンドの実装と同様にASDD開発の有効性を調査してきました。既にiOS開発では効果が検証され始めており、バックエンドとクライアントの一貫した実装体験として、ASDDの整備をさらに続けていく予定です。&lt;/p&gt;
&lt;p&gt;QA領域では、バックエンドQAとクライアントQAのそれぞれにおいて、またテストケースの自動設計と自動実装のそれぞれについて、ASDDと同様にQAのワークフロー全体をAgenticに実行するための手法を模索しています。テストケース自動設計においては、Claude Code Commandによって、マイクロサービスの依存関係やテストケースのもとになる仕様書の解析・テストケースの設計などを自動化する試みが模索されており、現場のQAエンジニアからはポジティブなフィードバックをいただいています。またテストケース自動実装においては、ASDDと同様にテストの実装と評価のループをAgentが自律的に行うことによって、高い精度で正しいテストケースが実装されることを確認しています。&lt;/p&gt;
&lt;p&gt;これらの各領域における取り組みは現在進行形で、現場のプロジェクトとの連携の中で、地に足つけた持続的な検証と改善を行いつつも、トップダウンで「プロセスがどう変化するか」「体制がどう変化するか」についてビジョンを打ち立てて、着実に一歩ずつ前進しています。&lt;/p&gt;
&lt;h2&gt;pj-doubleを通して得た学び&lt;/h2&gt;
&lt;p&gt;2025年7月から現在に至るまでのpj-doubleの道のりは、決して順風満帆なものではありませんでした。技術的な課題、組織的な摩擦、そしていわゆる「ビルドトラップ」など、多くの壁に直面しました。&lt;/p&gt;
&lt;p&gt;しかし、それらを乗り越える過程で得られた学びこそが、pj-doubleの道筋や目指す開発プロセスの礎となっています。ここでは、特に重要だった4つの学びを共有します。&lt;/p&gt;
&lt;h3&gt;学び1: AI活用は品質・保守性とのトレードオフではない&lt;/h3&gt;
&lt;p&gt;プロジェクト発足当初、多くの開発者が懸念していたのが、「開発速度を上げることで、品質や保守性が犠牲になるのではないか」というトレードオフでした。しかし、検証はこの直感に反する結果を示しました。&lt;/p&gt;
&lt;p&gt;まず機能的な品質についてです。pj-doubleでは、QAワークフローの自動化などのGuardrail整備を進めることで、「デグレが発生していないか」「意図通りに動作するか」といった機能的な品質保証をプロセスとして組み込んでいます。&lt;/p&gt;
&lt;p&gt;重要な視点は、これが「AIと開発者の責任分界点の再定義」であるということです。AIに実装を任せる（非同期化する）ことで、開発者はそこで浮いたリソースを、より本質的な品質担保や、開発者がリソースを責任を持つべき高度な検証作業に充てることができます。実際に、メルカリモバイル開発における事例では、実装の高速化によって創出された時間を動作検証やQAに充てることで、設計段階でのインシデントの未然防止に寄与するという成果が確認されました。&lt;/p&gt;
&lt;p&gt;次に保守性の観点です。「AIが書いたコードが読みにくい」「拡張性が低い」といった懸念もよく聞かれます。しかし、これらの問題はAIの能力不足ではなく、コンテキストの欠如に起因することが多いです。「社内の共通ライブラリを使って欲しい」「特定のコーディング規約に従って欲しい」といった意図があるならば、そのドメイン知識や規約をコンテキストとしてAIに与えることが効果的です。実際にASDDでは、Agent Specの生成時に、社内フレームワークの参考実装などをコンテキストに与えることで、社内の実装における暗黙的なパターンを反映させており、開発者からも「自分で書くのと同じかそれ以上の品質の実装計画が生成される」というポジティブなフィードバックを受けています。&lt;/p&gt;
&lt;p&gt;さらに、こうしたASDDなどの共通基盤を整備することで、組織として推奨する実装パターンや規約を誰もがコンテキストに注入できるようになります。個人のスキルに依存してバラバラに行われていたAIコーディングに対して、組織全体で品質のベースラインを引き上げることが可能になると期待しています。&lt;/p&gt;
&lt;p&gt;もちろん、短期的な速度向上が長期的な技術的負債につながらないよう、私たちはDXツールを用いてRevert Rate（手戻り率）やMTTR（平均復旧時間）などの指標を常にモニタリングしています。定性的な感覚と定量的な計測の両軸で確認を続けていますが、現時点ではAI活用が品質を低下させるというシグナルは出ておらず、むしろ標準化の強力な武器になり得ると確信しています。&lt;/p&gt;
&lt;h3&gt;学び2: レビューのボトルネックはPull Requestの粒度で解消できる&lt;/h3&gt;
&lt;p&gt;「AI時代の開発は、人間によるコードレビューがボトルネックになる」。これはよく耳にする懸念であり、実際に社内のDX指標を見ても、レビューのリードタイムが開発生産性の向上を阻害している傾向が一部で見受けられました。しかし、現場への定性的なヒアリングと分析を通して、これは「AIコード特有の問題」ではなく、「プロセスの運用」によって解決可能な課題であることが見えてきました。&lt;/p&gt;
&lt;p&gt;レビューが辛くなる原因を調査したところ、単に「AIが書いたコードだから」ではなく、AIの圧倒的な生産速度によって「1つのPR（Pull Request）に含まれる差分が膨大になっていたから」であることが判明しました。人間はインクリメンタルな情報の処理には長けていますが、一気に大量の情報を受け取ることで、認知負荷が限界を超えてしまったとも言い換えられます。&lt;/p&gt;
&lt;p&gt;pj-doubleではこの課題に対して、仕組みによって解決することを目指しました。Agent Specを生成する段階でタスクを「合理的かつ動作可能な最小単位」に細かく区切り、そのタスクごとにPRを作成するという運用を徹底したのです。 実際にこのプロセスを採用した開発者からは、「PRが小さく保たれることで、AIが書いたコードであってもレビューのストレスを感じなくなった」などのポジティブなフィードバックが寄せられています。AIと人間のコードの間に、レビュー工数上の有意な差は生じないというのが、私たちの現在の結論です。&lt;/p&gt;
&lt;p&gt;さらに私たちは、AI-Nativeな開発プロセスの再設計において、コードレビューという行為そのものの再定義も必要だと考えています。 一口にコードレビューと言っても、品質や保守性を守る側面、踏襲的なプロセスとして習慣化して手段と目的が逆転した側面、監査上の要求を満たすための側面など、その役割は複数の意味を持ちます。これら全てを「人間が目視で保証する」という形で行う必要があるのでしょうか。 pj-doubleでは、単に全ての生成コードに人間が目を通すという既存の形を超えて、より意義のある、本質的な価値提供にフォーカスした新しいコードレビューの体験を模索し続けています。&lt;/p&gt;
&lt;h3&gt;学び3: 現場に地に足つけたアジャイルな検証と改善のサイクルの難しさ&lt;/h3&gt;
&lt;p&gt;私たちがpj-doubleを通して度々実感したのは、現場での検証と改善のサイクル、いわゆるフィードバックループを回すことの難しさです。特にpj-doubleのQA領域での取り組みにおいて、私たちは典型的な「ビルドトラップ」を経験しました。&lt;/p&gt;
&lt;p&gt;QA領域でのAgent活用において、私たちは当初、QAワークフローを自動化するためのリッチなUI付きのツールを開発・提供しました。プロンプトだけを配布するよりも、画面を提供したほうがデモがしやすく、配布も容易で、何より入力を制限することでAgentの挙動の再現性を担保できると考えたからです。 しかし、これは結果として検証の足枷となりました。本来、検証フェーズで最も重要なのは「プロンプトや手法の改善」です。しかし、アプリという堅牢な形にしてしまったことで、改善のためにはアプリ側のコード修正まで必要になり、コントリビューションのハードルを上げてしまいました。ASDDのように最小構成で始めていれば、アーリーアダプターたちが直接プロンプトを改善できたはずが、アプリのメンテナンスに時間を取られ、本来の目的である「課題解決の手法の確立」がおざなりになるという本末転倒な事態を招いたのです。&lt;/p&gt;
&lt;p&gt;また、アジャイルな改善ループの実現も、想像以上にハードルが高いものでした。 当初は週に2〜3回程度の頻度でフィードバックをもらい、高速に改善を回す想定でした。しかし、現場の開発者にとって、検証内容を言語化してフィードバックを送る行為自体が大きなコストです。実際に届くフィードバックは非常に質が高く丁寧なものでしたが、それを高頻度で要求することは現実的ではありませんでした。&lt;/p&gt;
&lt;p&gt;これらの経験から、私たちはアプローチを修正しています。 まずツールありきで考えるのではなく、最小限の構成で「手法の洗練」に集中すること。そして、週に1度などのミーティングにおけるカジュアルなフィードバック収集や、Slack上のフィードバックの自動収集、DXの機能を活用した集計値の自動算出などの仕組みを導入しています。&lt;/p&gt;
&lt;p&gt;この学びを踏まえたアプローチの修正によって、pj-doubleのQA領域では提供する手法の洗練と現場からのフィードバックの収集のアジャイルなサイクルが実現されるようになりました。この2025年10月から始まったQA領域のAgentic化の取り組みは、今や急速なスピードで各領域におけるQAエンジニアの体験を塗り替えていっています。&lt;/p&gt;
&lt;p&gt;この取り組みを通した最も重要な教訓は、現場に対して「良いツールがあるから使ってフィードバックして」という姿勢ではうまくいかないということです。 「開発プロセスを変える必要があり、そのために協力してほしい」というスタンスで巻き込み、ツールはそのプロセス変革を支援するための手段に過ぎないという合意形成を行うこと。ツールに依存しないプロセスを現場と共に見出し、泥臭く検証し続けることこそが、遠回りのようでいて最短の道であると痛感しています。&lt;/p&gt;
&lt;h3&gt;学び4: 混沌とした過渡期こそ、明確なビジョンが旗となる&lt;/h3&gt;
&lt;p&gt;上のビルドトラップの話とも重複しますが、pj-doubleにおいて私たちが最も警戒したのは、手段の目的化、すなわち「AIを使った便利ツールの開発」に陥ることでした。&lt;/p&gt;
&lt;p&gt;日々新しい生成AIモデルやツールが生まれては消えるカオスの渦中で、今の技術スタックに合わせてツールを作り込んでも、それは数ヶ月後には陳腐化してしまいます。pj-doubleの模索においても、目前の課題解決に最適化したソリューションが、数ヶ月後には時代遅れになっているリスクと常に隣り合わせでした。取り組み自体を陳腐化させないためには、特定のツールや局所的な最適化ではなく、ツールに依存しない「プロセスの変革」に向き合う必要があります。しかし、私たちが今向き合っているものが、本当にツールに依存しない普遍的なプロセスなのか、それとも現在のツールの限界を補うための過渡的な対処療法に過ぎないのか、その見極めは非常に困難です。&lt;/p&gt;
&lt;p&gt;このような「標準化と陳腐化のジレンマ」の中で、組織として価値あるアセットを積み上げ続けるために最も重要だった思考の転換こそが、現在の手元に見える課題から積み上げ式に考えるのではなく、来たる未来から逆算する「バックキャスティング」でした。&lt;/p&gt;
&lt;p&gt;今のツールの限界を度外視し、技術のポテンシャルを前提に置くことから始めます。「Agentが自律的にコンテキストを収集し、コーディングルールに従って実装を完遂し、QAもAgenticに自動で行われる」。これらが当たり前に実現されたとき、プロダクトデリバリーはどのような形態を取るべきか？ ボトルネックはどこに移るのか？ この理想的な未来の体験から逆算して初めて、今なすべき投資が見えてくるものかと思います。&lt;/p&gt;
&lt;p&gt;私たちが進めているナレッジフロー整備、非同期のエージェント実行基盤開発、リグレッションQAの自動化、テスト環境への自動デプロイを含むCI/CDの改善などは、単なるインフラ整備ではなく、この描いた世界観へシームレスに移行するための戦略的な布石です。&lt;/p&gt;
&lt;p&gt;メディア理論家のマーシャル・マクルーハンはかつて、「私たちはバックミラー越しに現在を見ながら、未来に向かって後ろ向きに進んでいる（&amp;quot;We look at the present through a rear-view mirror. We march backwards into the future.&amp;quot;）」と言いました（出典：The Medium is the Massage by Marshall McLuhan）。 これは、私たちの「常識」や「思考様式」そのものが過去の技術環境によって形作られているという、構造的な限界への指摘です。私たちは今の常識（バックミラー）を通してしか世界を見ることができないため、Agentが当たり前になった新しいパラダイムにおいて、私たちがどのような思考や行動の原理を持つべきかを、現在の延長線上で想像することはできません。ゆえに、過去の成功体験や現在の思考の常識だけで将来を設計しようとすれば、それは局所最適化に留まり、本当に目指すべき世界観には辿り着けません。明確なプロセスが見えない過渡期だからこそ、ともすれば「ビルドトラップ」に陥りがちです。&lt;/p&gt;
&lt;p&gt;だからこそ、pj-doubleでは既存の常識を意図的に崩し、統一された未来の体験を鮮明にイメージとして描き出し、それを旗として掲げ続ける能動的な態度が不可欠であると強く意識してきました。実際に、pj-doubleでは積極的に目指す世界観やその先の開発体験を社内で発信することで、先の見えない変革の中でも、組織全体が同じ方向を向いて進むことができてきたのだと振り返っています。&lt;/p&gt;
&lt;h2&gt;pj-doubleが上流プロセスで直面した課題&lt;/h2&gt;
&lt;p&gt;ASDDでは、Agent SpecというAIにとっての中間表現を統一することで、AIの実行計画への介入が可能になりました。すなわち、開発標準・ドメイン知識・職能の専門知識を強制的に注入することに成功しました。このようにASDDは、pj-doubleが当初目指していた集合知の共通基盤としての効果を発揮しています。&lt;/p&gt;
&lt;p&gt;ASDDはこのように下流の「仕様が決まった後の実装」において大きな効果を発揮しているのと同時に、上流の「仕様が決まるまでのプロセス」において深刻な課題に直面しています。私たちは、開発現場における継続的な検証とフィードバックの中で、「コードを書く」速度は劇的に向上したものの、その前段である「何を作るか（What）」と「どう設計するか（How）」の合意形成プロセスにおいて、深刻なボトルネックに直面しました。&lt;/p&gt;
&lt;p&gt;より具体的には、これまで開発者からはASDDについて次のようなフィードバックを受けました。&lt;/p&gt;
&lt;p&gt;(1) Agent Spec以前に必要な合意形成が無視されている&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「Agent Specは仕様や設計方針が確定していることを前提にしているが、そもそも関係者間の合意形成に最も多くの時間がかかっている」&lt;/li&gt;
&lt;li&gt;「Agent Specのように自動生成されたドキュメントは判断根拠の説明が伴わないため、合意形成に用いる媒体としても適切ではない」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(2) Agent Specの生成とレビューのコストが高く、開発サイクルの速度が落ちる&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「生成されたAgent Specや実装の意図が分からず、情報源を探すために技術設計書を遡り、プロダクト仕様書を遡り、Slackの議論を遡らないといけない」&lt;/li&gt;
&lt;li&gt;「毎回0から書き換わるAgent Specを一行一行レビューして修正するくらいなら、自分で書いたほうが早い」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1つ目について、今のASDDでは、たとえ合意のない確度の低い要求でも、そのまま実装の自動生成まで行うことができてしまいます。結果として、AIは曖昧な部分を勝手に補完し、「技術的には正しいが、誰も合意していない成果物」が自動生成されます。立ち返って考えてみると、従来の上流から下流までの開発フローにおいては、仕様・設計・方針に関する段階的な合意の積み重ねこそが、開発フローのチェックポイントとなって手戻りを防止する機能を果たしていたはずです。またこのプロセスの本当のボトルネックは、これらの合意形成を同じ時間・同じ場所に集まって同期的に行うステップです。pj-doubleは要件定義段階で、プロダクト仕様書や技術設計書を自動で生成することを目指していましたが、このような一次情報が創出される創造的な議論の場や、そこにおける合意形成などの本質的なプロセスがそのスコープから抜け落ちてしまっていました。つまり、上流工程において私たちがフォーカスすべきは、一次情報を組み合わせてプロダクト仕様書や技術設計書に変換するプロセスではなく、その一次情報そのものを生成するプロセスだったという反省です。&lt;/p&gt;
&lt;p&gt;また2つ目について、ASDDによって生成された長大なAgent Specや実装は開発者の認知負荷を遥かに凌駕し、真面目にレビューしようと思うと、膨大なレビューコストを強いることになってしまいます。さらにAgent Specには、一次情報と自動生成された真偽不明な情報とが入り混じることもしばしばありました。開発者は、生成された情報に心当たりがない場合、その情報の出自を遡って調査する必要があります。本質的に人間の認知モデルは、インクリメンタルな認識の更新を基本としていて、今の認識との差分として追加情報を期待するはずです。他方でpj-doubleで取ってきたドキュメント自動生成のアプローチは、膨大な情報源からかき集めた新情報を既知の情報と一緒くたにして提示するため、認知過負荷を引き起こしてしまっていました。&lt;/p&gt;
&lt;h2&gt;課題解決への系統的分析とアプローチ&lt;/h2&gt;
&lt;p&gt;このような課題に対して、私たちはその原因が、「『AIと考える・AIと決める』べきことを、『AIに任せる』体験として設計してしまったこと」であったと総括しました。&lt;/p&gt;
&lt;p&gt;AIと開発者の協業モデルは、大きく同期プロセス・非同期プロセスに分類できると考えています。（ここで暗黙知とは言語化されていない情報、形式知とは言語化・文書化された情報を指しています。）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;同期的協業 — AIと考える・決める&lt;/th&gt;
&lt;th&gt;非同期的協業 — AIに任せる&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;活動の様態&lt;/td&gt;
&lt;td&gt;AIが情報収集・開発者が意思決定・AIが生成・開発者が評価&lt;/td&gt;
&lt;td&gt;AIが自律的に情報収集・意思決定・生成・評価を行う&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;活動の役割&lt;/td&gt;
&lt;td&gt;暗黙知と形式知の交換 / 一次情報の生成を伴う&lt;/td&gt;
&lt;td&gt;形式知から形式知への転換 / 情報の変換に過ぎない&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;活動の意義&lt;/td&gt;
&lt;td&gt;選択・意思決定・合意&lt;/td&gt;
&lt;td&gt;アウトプットの機能的側面、実利的な要求の実現&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;機能やテストの実装は、動作するという機能的な要求を満足することが目標であるため、非同期的協業によって認知負荷を超えてスケールさせることが重要で、その点でAIへの移譲を促進することがAI-Native化の促進を意味します。&lt;/p&gt;
&lt;p&gt;一方で上流プロセスにおいては、作成される仕様や設計そのものの価値は小さく、むしろそこに至るまでの比較検討・意思決定・合意などの、人間による承認の記録としての側面が大きいです。そして承認を要するという性質上、誰かの認知負荷の範囲内で運用される必要があり、その意味で同期的協業を必要とします。すなわち、上流フェーズにおける最重要事項は、生成されるアウトプットではなく、仕様や設計が検討され、レビューされ、合意され、それらが新たな一次情報として管理されるようになることです。&lt;/p&gt;
&lt;p&gt;しかしpj-doubleでは、同期プロセスの意義を過小評価し、全てを非同期プロセスとして処理しようとしていました。一次情報は既知で既出の前提として、それらを用いた情報の変換（プロダクト仕様書・技術設計書・Agent Spec・実装生成）のAgentic化にフォーカスしてしまっていました。私たちは、「AIが最初から90%の完成度の設計を作る」ことと、「AIと一緒に考え、機微な方向性を決定しながら同じ設計に辿り着く」ことが、上流プロセスにおいては全く異なる意味を持つという観点を見落としていたということです。&lt;/p&gt;
&lt;p&gt;すなわち、上流プロセスを非同期的協業とは区別された同期的協業として再設計することがpj-doubleにおける次の命題となるわけですが、このAIとの同期的協業も更に、「AIと考える」体験と「AIと決める」体験の2つに区別することで、更に見通しが良くなると考えています。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;同期的協業 — AIと考える&lt;/th&gt;
&lt;th&gt;同期的協業 — AIと決める&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;活動の様態&lt;/td&gt;
&lt;td&gt;AIが情報を収集・提示し、開発者の思考を拡張する&lt;/td&gt;
&lt;td&gt;思考や意思決定を通して新たな一次情報を生み出す&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;活動の役割&lt;/td&gt;
&lt;td&gt;形式知の提示による暗黙知の拡大&lt;/td&gt;
&lt;td&gt;一次情報の形式知への登録・参照&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;活動の意義&lt;/td&gt;
&lt;td&gt;対話を通した認知の範囲・思考の深度・選択の質の向上&lt;/td&gt;
&lt;td&gt;合意が形成され、蓄積されること&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/3d43f10a-ai-collaboration-1024x548.jpeg&quot; alt=&quot;AI Collaboration&quot; /&gt;&lt;/p&gt;
&lt;p&gt;「AIと考える」プロセスは、開発者が1人では見つけられなかった情報にアクセスし、1人では実現し得なかった深度で検討することを可能にします。ここでは、AIとの自然な対話の中で、AIが設計・実装を前進させるためのガイドをしてくれることが理想的な体験です。例えば、ChatGPTやClaudeとの対話を通して、設計や実装の方針について議論するような体験です。ここでは、開発者自身への知の内面化（Internalization）の側面に価値があります。&lt;/p&gt;
&lt;p&gt;そのためには、人の認知モデルに基づいて、開発者の現状理解からの差分として新たな情報が補完的に提示されるようなインタラクションの設計であるべきです。これは例えば、エンジニアがテックリードとの会話を通して、「こういう設計もあったか」「この設計はこの考慮が漏れていた」などの発見を伴うプロセスに対応します。AIと開発者の関係性は、AIが生成した情報を開発者が受動的に消費するという関係性ではなく、開発者が次に必要とする情報をAIが補完的に提示するという関係性を目指すべきであると考えます。&lt;/p&gt;
&lt;p&gt;またこのような開発者とAIの思考の模索は、完全なフリーフォーマットで行うことも、完全なテンプレートに基づいて行うことも、どちらも適切ではないと考えています。過度な自由度は選択麻痺を引き起こし、過剰な制約は模索の余地を奪うためです。pj-doubleのプロセス再設計においても、プロセスを標準化することが何度か試みられましたが、これは思考の幅を制限してしまう過度な制約の典型例だったと言えそうです。AIが全体プロセスやそこにおけるステータスを見据えつつ、開発者を自然な対話の中で誘導できるような思考フローが理想的でしょう。&lt;/p&gt;
&lt;p&gt;「AIと決める」プロセスは、開発者によるさまざまな選択肢の採用・不採用の意思決定とその理由を、新たな一次情報として蓄積するためのプロセスです。ここでは、建設的なプロセスのチェックポイントとしての意思決定やその蓄積、一次情報の生成という、知の表出化的側面に価値があります。&lt;/p&gt;
&lt;p&gt;このように、AIと決めたことが蓄積し、それがAIと考えるために使われるという循環をデザインすることが、同期的協業の条件であると考えています。&lt;/p&gt;
&lt;p&gt;私たちpj-doubleは現在、このような考えに基づいた課題解決方針の模索とその検証を実施するフェーズにあります。もしその取り組みを通して更なる発見や学びがあれば、またいつか別のテックブログなどで共有させてください。&lt;/p&gt;
&lt;h2&gt;pj-doubleが描く世界観&lt;/h2&gt;
&lt;p&gt;そしてこの体験が実現した世界観は、次のようにイメージできるかもしれません。&lt;/p&gt;
&lt;p&gt;(1) AIと考える&lt;/p&gt;
&lt;p&gt;開発者はAIとの話し合いを通じて、仕様や設計を考え進めていきます。そこにおいてAIは既存のサービスやドメイン知識を駆使しながら、思索や意思決定をサポートします。このような能力の拡張により、上流工程において開発者1人が担える役割も拡大します。&lt;/p&gt;
&lt;p&gt;(2) AIと決める&lt;/p&gt;
&lt;p&gt;AIは対話の中で追加で発生する一次情報を蓄積します。PMの「なぜこの仕様は却下されたのか」という質問や、テックリードの「なぜこの技術選定を行なったのか」という質問に対して、蓄積された一次情報に基づいて回答します。&lt;br /&gt;
このような合意の形成と参照という側面は、これまで必要だった「開発者↔開発者」の同期的なコミュニケーションを、「開発者↔Agent」＋「Agent↔開発者」のコミュニケーションへと分解し、合意の形成・参照を非同期的に実現可能にします。すなわち、各職能の人が同じ時間・同じ場所に集まらなくても、合意の確認や意思決定をできるようになります。&lt;/p&gt;
&lt;p&gt;(3) AIに任せる&lt;/p&gt;
&lt;p&gt;このように合意形成が行われたら、あとはプロジェクトの機能要求を実現するための仕事をAIに移譲するだけです。例えばここで生成されるプロダクト仕様書や技術設計書は、全て過去の意思決定に基づくため、開発者がその生成意図を吟味したり、Hallucinationを精査する雑務が発生しません。またASDDの本質的な付加価値であるAgenticな実装生成も、高い確度で任せることができます。QAも自動で実行されるため、デグレの心配もありません。あとは、安心して実装の完了を待つだけです。&lt;/p&gt;
&lt;p&gt;この世界観のもとで、PMや開発者はAIによって拡張された思考や調査能力を存分に使いながらプロダクトのアイデアを考え進めていき、アイデアが煮詰まったと思ったら即座にそれが形になります。試行と学習のサイクルが圧倒的な速度で回り、より多くのアイデアを試し、より速く学ぶことができるようになるでしょう。&lt;/p&gt;
&lt;p&gt;また職能の拡張は一人一人の役割やプロジェクトの体制にも変化を与えるはずです。開発者がAIによって拡張されることで、専門知識が必要な調査や業務が自動化され、PM、アナリスト、Designer、エンジニア、QAといった個人間の職能の差異が薄れていき、結果として1人が担う役割も大きくなるはずです。またそのもとで、プロダクトデリバリーは少人数の自律的なチームによってEnd-to-Endに実施されるようになります。これまではOutputに目が向きがちだった開発者も、よりお客さまへの価値提供との距離が近づくことで、OutcomeやImpactに責任を持つようになっていくでしょう。&lt;/p&gt;
&lt;p&gt;さらに、薄れるのは職能の境界だけでなく、サービスの境界にも広がりそうです。ドメイン知識が形式知化されることで、プロダクト開発のために要求されるドメイン知識の総量も減少し、サービスの境界と認知負荷の境界が等価ではなくなるかもしれません。すなわち、逆コンウェイの法則に基づくような組織体制ではなく、よりビジネスを加速させるために最適な組織構造へと変化するかもしれません。例えば、1つの少人数開発チームが複数のサービスをまたいで1つの施策を実現するような体制はその候補の1つです。&lt;/p&gt;
&lt;p&gt;こうした要件定義からQAまでをAI-Nativeに再設計するための取り組みの効果は、開発生産性の向上だけに留まらず、本質的なビジネスの強度向上に貢献するとも考えています。これまでは各工程で担当者が異なるため知識の分断が、各工程でプラットフォームが違うためデータの分断が発生することがありました。プロジェクトを通したより多くの知識が、またより多くの職能ごとの専門知が集積されるようになることで、市場やお客さまのOutcome・Impactがフィードバックされて、それをもとにプロダクトを改善・拡張するというループが自己完結化するようになれば、施策の精度や戦略の練度の向上にも寄与できると信じています。&lt;/p&gt;
&lt;p&gt;pj-doubleの取り組みやそこで得た学びは、知的活動を中心とする他のあらゆる領域にも拡大できるかもしれません。「AIと考える」「AIと決める」「AIに任せる」の3つを使い分けながらプロセスを再設計することが、プロダクト開発に留まらない「AI-Nativeな働き方」の重要な要素になると考えています。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;以上、メルカリにおけるpj-doubleを通した、AI-Native化への挑戦とその学びについての紹介でした。本記事が、Human CentricからAgent Centricへと向かうこの過渡期において、AIとの新しい協業の形を模索する一助となれば幸いです。長文にお付き合いいただき、ありがとうございました。&lt;/p&gt;
&lt;p&gt;明日の記事は hokaoさんによる「Kubeflow PipelinesとPydantic Settingsを活用してMLパイプラインを型安全かつシンプルに実装する」です。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>2025年のメルペイの取り組みを振り返って</title><link>https://engineering.mercari.com/blog/entry/20251201-looking-back-merpay-initiatives-in-2025/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251201-looking-back-merpay-initiatives-in-2025/</guid><description>&lt;p&gt;はじめに こんにちは。メルペイVPoEの@keigowです。 この記事は、Merpay &amp;amp; Mercoin Advent Calendar 2025 の1日目の記事です。 2025年も色々なことがありました。年末 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 01 Dec 2025 10:00:49 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。メルペイVPoEの&lt;a href=&quot;https://x.com/keigow&quot;&gt;@keigow&lt;/a&gt;です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025&lt;/a&gt; の1日目の記事です。&lt;/p&gt;
&lt;p&gt;2025年も色々なことがありました。年末ということで、2025年にメルペイとして、或いはメルカリグループとして取り組んだことをこの機会に振り返られればと思います。&lt;/p&gt;
&lt;h2&gt;mercari GEARS 2025&lt;/h2&gt;
&lt;p&gt;11月13日に「メルカリエンジニアリングの今」をテーマにテックカンファレンスを開催しました。リアルな場でのイベントは実に7年ぶりの開催となりましたが、社内外含め沢山の方にご来場いただき、盛り上がることができました。キーノートを始めとして各発表の&lt;a href=&quot;https://speakerdeck.com/mercari&quot;&gt;スライド&lt;/a&gt;や&lt;a href=&quot;https://www.youtube.com/playlist?list=PLgznQFGqsAWD4Amf_scEA8XwIfg25aLu7&quot;&gt;動画&lt;/a&gt;も公開されておりますので、ぜひ御覧ください。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/d0cf5413-gears-scaled.jpg&quot; alt=&quot;mercari GEARS 2025&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;AIによって進化する開発&lt;/h2&gt;
&lt;p&gt;ちょうど半年前に同じくブログの連載企画で&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250602-merpay-ai-utilization/&quot;&gt;メルペイにおけるAI活用の取り組み&lt;/a&gt;についてご紹介しました。当時は&lt;a href=&quot;https://www.claude.com/product/claude-code&quot;&gt;Claude Code&lt;/a&gt;が流行り始めたぐらいだったことを考えると、凄い速度の変化が起きていると改めて感じます。当時はエンジニアのAI Coding Toolの利用率を追いかけていましたが、今ではほぼ全てのエンジニアが何らかのAI Coding Toolを利用するようになりました。&lt;/p&gt;
&lt;p&gt;グループ全体として大きな取り組みとなったのは、&lt;a href=&quot;https://careers.mercari.com/mercan/articles/53708/&quot;&gt;こちらの記事&lt;/a&gt;でも取り上げているAI Task Forceです。目標としてAI Nativeな会社を目指し、エンジニアの部署に限らず、カスタマーサービスやマーケティングなど全社33の部署を対象に、専任のエンジニアを含めた100名規模の組織を組成し、業務の棚卸しと、AI Nativeなワークフローとはそもそもどうあるべきかを考え、見直しを行っています。まだ道半ばではあるものの、各部署でロードマップを作成し、着実に成果が出てきています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/99f9724d-ai_task_force.png&quot; alt=&quot;AI Task Force&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Project Double&lt;/h2&gt;
&lt;p&gt;AI Task Forceの活動と並行して、メルペイにVPoE Officeという新しい箱を作り、AIによる生産性向上の取り組みとしてProject Doubleをスタートしました。その名の通り生産性を2倍にしようという取り組みで、当時一部のエンジニアたちが行っていたAgenticなCoding手法を、Agent Spec Driven Development（ASDD）として標準化することで誰もがそれを利用可能にしようとする試みです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/6e5dae32-asdd.png&quot; alt=&quot;Overview of ASDD&quot; /&gt;&lt;/p&gt;
&lt;p&gt;初期はBackendのみを対象としたProjectでしたが、7-9月の四半期で一定の成果を出すことができたため、その範囲をClientの開発、Backend/ClientそれぞれのQA領域、仕様書や設計などのPlanning領域へと広げることを決めました。また対象となるCompanyもFinTechの一部のPilot Projectから、グループ全体へと広がり、現在はEngineeringの中でも最優先の取り組みとして進行しています。&lt;/p&gt;
&lt;p&gt;内容の詳細や得られた学びについてはこのProjectをリードしているnakai-sanの記事をご確認ください。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251201-pj-double-towards-ai-native-development/&quot; title=&quot;pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌&quot;&gt;pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Project Doubleが実現した世界で&lt;/h2&gt;
&lt;p&gt;Agentic Codingの推進において、乗り越えるべき課題は精度やイテレーションにかかる時間などたくさんありますが、日々起きるモデルの進化やツール、環境の整備によっていずれ問題としては解決されていくと思っています。&lt;/p&gt;
&lt;p&gt;一方で、1人のエンジニアが企画から設計、開発、テスト、リリースまでを一貫して行える世界が来たときに備えて、いくつか解決しなければいけない部分もあります。オンコール体制の整備、問い合わせ対応などの運用業務の最適化など、Agentic Codingが中心となった時代に最適な組織設計です。こちらについては一定の仮説はあるものの、まだ明確な応えが見えていない領域であり、Project Doubleの推進と合わせてトライアルをしていきたいと思っています。&lt;/p&gt;
&lt;h2&gt;しゃべるおさいふ&lt;/h2&gt;
&lt;p&gt;AIの活用はEngineeringだけでなく、プロダクトへの活用もPoCという形でトライしています。メルペイで現在取り組んでいるのがこちらの「しゃべるおさいふ」という機能で一部のお客さまを対象に試験的に導入しています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/889504b5-osaifu.jpg&quot; alt=&quot;Osaifu&quot; /&gt;&lt;/p&gt;
&lt;p&gt;内容は非常にシンプルで、利用データを元にAIがコメントをしてくれるという機能です。ちょっとした日常の利用に対してコメントを貰えるというのは思ったよりも嬉しい体験で社内テストは想定していたよりも好評でした。そこまで複雑な機能では無いものの、コストの観点や安全性、倫理的な観点も含め、AIをプロダクトで活用するとはどういうことなのか、という学びを得ることができました。12/20のkobaryo-sanの記事ではこちらの機能のバックエンドの設計について紹介する予定なのでお楽しみに。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251219-f15f686553/&quot; title=&quot;LLMを用いたおしゃべり機能「しゃべるおさいふ」のバックエンド設計&quot;&gt;LLMを用いたおしゃべり機能「しゃべるおさいふ」のバックエンド設計&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;開発合宿&lt;/h2&gt;
&lt;p&gt;1月の事になりますが、数年ぶりにメルペイで一泊二日の開発合宿を行いました。普段業務に追われて取り組めないような新しい技術へのチャレンジ、OSSの開発、やりたいと思っていた新機能の開発などに取り組みました。この開発合宿をきっかけとして、&lt;a href=&quot;https://careers.mercari.com/mercan/articles/54652/&quot;&gt;有志による開発プロジェクト&lt;/a&gt;がスタートし、実際にリリースまでつながることもありました。コロナ禍で普段直接顔を合わせることが少なくなっていたエンジニア同士のコミュニケーションの機会にもなり、参加してくださった皆様の満足度もかなり高かったので、また来年もトライできればと思っています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/80898002-development_camp-scaled.jpg&quot; alt=&quot;Development Camp&quot; /&gt;&lt;/p&gt;
&lt;p&gt;開発合宿の様子はMercari GearsのYouTubeに動画も上がっているので、気になった方はぜひご覧いただければと思います。&lt;/p&gt;
&lt;p&gt;&lt;iframe loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Aa5-5s7wi_4?si=clSs_xJygwVOxWOg&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;今年は全社のテーマとしてAI Nativeを掲げ、Engineering組織としてもAI中心の一年となりました。半年前も同じようなことを書いた気がしますが、目まぐるしい変化の中で働けることを楽しんでいます。&lt;/p&gt;
&lt;p&gt;明日の記事はその中でも中心的な取り組みとなる、nakaiさんによる「pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌」です。引き続きMerpay &amp;amp; Mercoin Advent Calendarをお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>「Merpay &amp;#038; Mercoin Advent Calendar 2025」開催のお知らせ</title><link>https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025/</guid><description>&lt;p&gt;こんにちは。メルペイ Engineering Engagement チームの mikichin です。 Advent Calendarの季節がやってきます！今年も、メルカリグループは Advent Calendar を実 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 26 Nov 2025 11:00:26 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイ Engineering Engagement チームの &lt;a href=&quot;https://twitter.com/chida_miki&quot; title=&quot;mikichin&quot;&gt;mikichin&lt;/a&gt; です。&lt;br /&gt;
Advent Calendarの季節がやってきます！今年も、メルカリグループは Advent Calendar を実施します！&lt;br /&gt;
&lt;br /&gt;
▶&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251126-mercari-advent-calendar-2025&quot;&gt;Mercari Advent Calendar 2025 はこちら&lt;/a&gt;&lt;br /&gt;
&lt;/p&gt;
&lt;h1&gt;Merpay &amp;amp; Mercoin Advent Calendar とは？&lt;/h1&gt;
&lt;p&gt;Advent Calendar の習慣にもとづいて、メルペイ・メルコインのエンジニアがプロダクトや会社で利用している技術、興味のある技術分野やちょっとしたテクニックなど知見をアウトプットしていきます。このAdvent Calendarを通じてクリスマスまでの毎日を楽しく過ごしていただければと思っています。&lt;/p&gt;
&lt;h3&gt;2024年のMercari / Merpay &amp;amp; Mercoin Advent Calendar はこちら&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/&quot;&gt;Mercari Advent Calendar 2024&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/&quot;&gt;Merpay &amp;amp; Mercaoin Advent Calendar 2024&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;公開予定表 （こちらは、後日、各記事へのリンク集になります）&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Date&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Theme / Title&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Author&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/1&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251201-looking-back-merpay-initiatives-in-2025/&quot; title=&quot;2025年のメルペイの取り組みを振り返って&quot;&gt;2025年のメルペイの取り組みを振り返って&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@keigow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/2&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251201-pj-double-towards-ai-native-development/&quot; title=&quot;pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌&quot;&gt;pj-double: メルカリの開発生産性向上に向けた挑戦 — AI-Native化が辿り着いたASDDとプロセス変革の全貌&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@nakai&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/3&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251128-c46fa95168/&quot; title=&quot;Kubeflow PipelinesとPydantic Settingsを活用してMLパイプラインを型安全かつシンプルに実装する&quot;&gt;Kubeflow PipelinesとPydantic Settingsを活用してMLパイプラインを型安全かつシンプルに実装する&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@hokao&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/4&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251202-5f1fdbecce/&quot; title=&quot;メルコインとメルカリNFTのフロントエンド開発のインターンシップで取り組んだこと&quot;&gt;メルコインとメルカリNFTのフロントエンド開発のインターンシップで取り組んだこと&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Sakamoto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/5&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251205-a-pragmatic-approach-to-ai-powered-documentation-generation/&quot; title=&quot;A Pragmatic Approach to AI-Powered Documentation Generation&quot;&gt;A Pragmatic Approach to AI-Powered Documentation Generation&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Fab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/6&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251206-yapc-fukuoka-2025/&quot; title=&quot;YAPC::Fukuoka 2025に参加しました！！ #yapcjapan&quot;&gt;YAPC::Fukuoka 2025に参加しました！！ #yapcjapan&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Sakabe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/7&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251207-61e24d343e/&quot; title=&quot;本番環境でしか起こらない Goroutines 急増問題と解決までの道のり&quot;&gt;本番環境でしか起こらない Goroutines 急増問題と解決までの道のり&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@goccy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/8&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251205-finally-mercari-japan-in-english-our-road-to-cross-platform-i18n/&quot; title=&quot;Finally, Mercari Japan in English! Our Road to Cross-Platform i18n&quot;&gt;Finally, Mercari Japan in English! Our Road to Cross-Platform i18n&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@fenomas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/9&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251209-d0de07214d/&quot; title=&quot;Agent Specで小さく素早く回すメルカリモバイル開発現場&quot;&gt;Agent Specで小さく素早く回すメルカリモバイル開発現場&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@seitau&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/10&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251210-ai-tf-facing-uncertainty/&quot; title=&quot;AI Task Forceで学んだ「不確実性との向き合い方」&quot;&gt;AI Task Forceで学んだ「不確実性との向き合い方」&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@panorama&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/11&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-cf3b67a5a7/&quot; title=&quot;理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない&quot;&gt;理想の Workflow Platform という“聖杯”に、n8n でついに手が届くかもしれない&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@ISSA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/12&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-6fe0bc0838/&quot; title=&quot;Making n8n Enterprise-Ready: 企業向けn8nの導入と運用の取り組み&quot;&gt;Making n8n Enterprise-Ready: 企業向けn8nの導入と運用の取り組み&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/13&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-580dc508a7/&quot; title=&quot;n8nの静的解析CLIツールをOSS化 – JSON解析とDAGで実現するセキュリティチェックの自動化&quot;&gt;n8nの静的解析CLIツールをOSS化 – JSON解析とDAGで実現するセキュリティチェックの自動化&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@mewuto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/14&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251213-862c05648c/&quot; title=&quot;n8n – 乱立するAIツール中でPoCを成功させるために考えたこと・実際に取り組んだこと&quot;&gt;n8n – 乱立するAIツール中でPoCを成功させるために考えたこと・実際に取り組んだこと&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@abcdefuji&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/15&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251212-extending-the-balance-service-challenges-in-implementing-multi-currency/&quot; title=&quot;Extending the Balance Service: Challenges in Implementing Multi-Currency&quot;&gt;Extending the Balance Service: Challenges in Implementing Multi-Currency&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Timo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/16&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251214-supercharging-user-engagement-how-mercari-is-using-server-driven-ui-to-reduce-time-to-market/&quot; title=&quot;Supercharging User Engagement: How Mercari is Using Server-Driven UI to Reduce Time-to-Market&quot;&gt;Supercharging User Engagement: How Mercari is Using Server-Driven UI to Reduce Time-to-Market&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Stefan_droid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/17&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251216-mandates-for-recurring-payments/&quot; title=&quot;多様な支払い手段と継続課金を安全に扱う「Mandate」の設計&quot;&gt;多様な支払い手段と継続課金を安全に扱う「Mandate」の設計&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251216-mandates-for-recurring-payments/&quot; title=&quot;Designing “Mandates” for Safe and Flexible Recurring Payments&quot;&gt;Designing “Mandates” for Safe and Flexible Recurring Payments&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@tomo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/18&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251217-ce2057d0ad/&quot; title=&quot;メルペイでの3ヶ月間：非同期処理への移行とASDD開発の学び&quot;&gt;メルペイでの3ヶ月間：非同期処理への移行とASDD開発の学び&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Minato&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/19&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251219-payment-platform-2025/&quot; title=&quot;Payment Platform の 2025 年: メルカリグループを支える決済基盤のこれまでとこれから&quot;&gt;Payment Platform の 2025 年: メルカリグループを支える決済基盤のこれまでとこれから&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@komatsu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/20&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251219-f15f686553/&quot; title=&quot;LLMを用いたおしゃべり機能「しゃべるおさいふ」のバックエンド設計&quot;&gt;LLMを用いたおしゃべり機能「しゃべるおさいふ」のバックエンド設計&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@kobaryo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/21&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251219-nine-months-of-devex-improvement-at-mercari-group/&quot; title=&quot;メルカリグループのDevEx改善、9ヶ月間の実践&quot;&gt;メルカリグループのDevEx改善、9ヶ月間の実践&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251219-nine-months-of-devex-improvement-at-mercari-group/&quot; title=&quot;Nine Months of DevEx Improvement at Mercari Group&quot;&gt;Nine Months of DevEx Improvement at Mercari Group&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@ntk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/22&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251219-029c76e123/&quot; title=&quot;インターン生が挑んだ認証方式のマイグレーション──メルペイ加盟店管理画面へのOAuth 2.0導入&quot;&gt;インターン生が挑んだ認証方式のマイグレーション──メルペイ加盟店管理画面へのOAuth 2.0導入&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@taki&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/23&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251223-ai-driven-learning/&quot; title=&quot;Cursorでプログラミング言語を学び直す方法——AI駆動学習の4ステップ&quot;&gt;Cursorでプログラミング言語を学び直す方法——AI駆動学習の4ステップ&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@kubomi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/24&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251224-mercari-cast/&quot; title=&quot;なぜ再発防止は、思ったように機能しないのか。メルカリのプロダクト開発でCAST分析が必要だった理由&quot;&gt;なぜ再発防止は、思ったように機能しないのか。メルカリのプロダクト開発でCAST分析が必要だった理由&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@pooh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/25&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251225-building-egp-cards-at-merpay/&quot; title=&quot;メルペイ インターンでの挑戦と学び：EGP Cardsと向き合った3ヶ月間&quot;&gt;メルペイ インターンでの挑戦と学び：EGP Cardsと向き合った3ヶ月間&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251225-building-egp-cards-at-merpay/&quot; title=&quot;Building EGP Cards at Merpay: Lessons from a Frontend Internship&quot;&gt;Building EGP Cards at Merpay: Lessons from a Frontend Internship&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Yusaku&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/25&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251225-mercari-ai-native-company/&quot; title=&quot;AI-Nativeという選択 ー 正解のない時代に、メルカリが選んだ指針&quot;&gt;AI-Nativeという選択 ー 正解のない時代に、メルカリが選んだ指針&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@kimuras&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;初日の記事は keigowさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>「Mercari Advent Calendar 2025」開催のお知らせ</title><link>https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251126-mercari-advent-calendar-2025/</guid><description>&lt;p&gt;こんにちは。メルカリ Engineering Officeのyasu_shiwakuです。 今年もメルカリとメルペイ・メルコインで2本のAdvent Calendarを実施します！ ▶Merpay &amp;amp; Merco [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 26 Nov 2025 11:00:20 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリ Engineering Officeのyasu_shiwakuです。&lt;br /&gt;
今年もメルカリとメルペイ・メルコインで2本のAdvent Calendarを実施します！&lt;br /&gt;
&lt;br /&gt;
▶&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251126-merpay-mercoin-advent-calendar-2025&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2025 はこちら&lt;/a&gt;&lt;br /&gt;
&lt;/p&gt;
&lt;h1&gt;Mercari Advent Calendar とは？&lt;/h1&gt;
&lt;p&gt;メルカリグループのエンジニアがプロダクトや会社で利用している技術、興味のある技術分野やちょっとしたテクニックなど知見をアウトプットしていきます。このAdvent Calendarを通じてクリスマスまでの毎日を楽しく過ごしていただければと思っています。&lt;/p&gt;
&lt;h3&gt;2024年のMercari / Merpay Advent Calendar&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/&quot;&gt;Mercari Advent Calendar 2024&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/&quot;&gt;Merpay &amp;amp; Mercoin Advent Calendar 2024&lt;/a&gt;&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;公開予定表 （こちらは、後日、各記事へのリンク集になります）&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Date&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Theme / Title&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Author&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/1&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251127-websocket-xss-vulnerability-discovery-my-security-journey-at-mercari/&quot;&gt;Websocket XSS vulnerability discovery: My security journey at Mercari&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@philolo1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/2&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251202-llm-key-server/&quot;&gt;LLM Key Server: Providing Secure and Convenient Access to Internal LLM APIs&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Hiroki Akamatsu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/3&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251202-shops-monorepo-five-years-later-a-tale-of-bazel-and-cursor/&quot;&gt;Shops Monorepo Five Years Later: A Tale of Bazel and Cursor&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Jazz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/4&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251204-enhancing-developer-experience-through-mercaris-unified-platform-interface/&quot;&gt;Enhancing DX through Mercari&amp;#8217;s Unified Platform Interface&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@whygee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/5&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251205-mercari-hallo-frontend-improvements/&quot;&gt;メルカリ ハロ Web フロントエンドの1年間の改善と学び&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@mattsuu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/6&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251206-engineering-the-semantic-layer-principles-for-data-at-scale/&quot;&gt;Engineering The Semantic Layer: Principles for Data at Scale&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@sathiya&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/7&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251203-46bf6511f3/&quot;&gt;QAエンジニアがAIで日々の課題を解決した話&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@yuga&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/8&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251202-navigating-change-learning-to-reinvent-in-an-unstable-world/&quot;&gt;Navigating Change: Learning to Reinvent in an Unstable World&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Antony Chane-Hive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/9&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251208-search-results-quality-monitoring-with-llms/&quot;&gt;Search Results Quality Monitoring with LLMs&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@otter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/10&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251208-b7adaa9b98/&quot;&gt;LiveContactToolにおける機微情報の取り扱い~CloudDLPを使ったマスキング&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@sters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/11&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-c73c2b1747/&quot;&gt;OpenID Connect Core 1.0 の Claims パラメーターの利用&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@kgoro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/12&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251212-7fe4c31bf4/&quot;&gt;Adsシステムの急成長を支える技術：信頼性と収益性を取り戻した「PJ-MARP」の全貌&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@tokku&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/13&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251213-96e00d1d91/&quot;&gt;メルカリが、AI時代にナレッジマネジメントに投資したわけ&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@t-hiroi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/14&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251204-563130cd63/&quot;&gt;メルカリAdsが広告を届けるまでの話&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@yanap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/15&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-3846ed440d/&quot;&gt;TiDB Resource Groupでワークロードを制御する&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@ogataka50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/16&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251215-the-cost-of-speed-a-battle-against-cost-debt-and-diverging-systems/&quot;&gt;The Cost of Speed: A Battle against Cost, Debt, and Diverging Systems&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Sneha&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/17&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251216-building-a-learning-culture-with-devdojo/&quot;&gt;Building a Learning Culture with DevDojo&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@mariz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/18&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251218-capturing-network-packets-in-kubernetes/&quot;&gt;Kubernetes環境におけるパケットキャプチャ&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@mshibuya&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/19&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251211-4cfd1db1bf/&quot;&gt;AI-Native 開発を加速する AWS Kiro の導入と、Okta を活用したアカウント管理の自動化&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@amenbo &amp;amp; @siroken3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/19&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251218-26bcec59ba/&quot;&gt;メルカリ内部の Dynamic Client Registration 活用事例&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/20&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251220-jamf-terraform-gitops/&quot;&gt;PR駆動の変更、CI/CDでOS設定を自動反映 — Terraformで実現するJamf ProのIaC＋GitOps基盤&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@yu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/21&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251217-2204b3261b/&quot;&gt;Non-AI tasks in the AI task force：AIツール開発の現場でこそ必要な「AI以外の」技術選定&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@akkie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/22&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251221-tales-of-oidc-oauth-security-what-it-takes-to-trust-a-token/&quot;&gt;Tales of OIDC &amp;amp; OAuth Security: What It Takes to Trust a Token&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Kahla&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/23&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251223-when-speed-wasnt-about-coding-faster-our-journey-to-one-person-one-release/&quot;&gt;When Speed Wasn’t About Coding Faster: Our Journey to ‘One Person One Release’&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Sneha &amp;amp; @Yu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/24&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251224-mercariadventcalendar/&quot;&gt;「AIが学習しやすいナレッジ基盤」メルカリが全社で導入したNotion Architecture ver1.0&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@kiko &amp;amp; aisaka&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12/25&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251225-mercari-ai-native-company/&quot;&gt;AI-Nativeという選択 ー 正解のない時代に、メルカリが選んだ指針&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@kimuras&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最初の記事は、「&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251127-websocket-xss-vulnerability-discovery-my-security-journey-at-mercari/&quot;&gt;Websocket XSS vulnerability discovery: My security journey at Mercari&lt;/a&gt;」です。&lt;/p&gt;
&lt;p&gt;どうぞお楽しみに！&lt;/p&gt;
</content:encoded></item><item><title>【mercari GEARS 2025】セッション以外の楽しみ方をご紹介</title><link>https://engineering.mercari.com/blog/entry/20251105-mercarigears2025-enjoy-besides-sessions/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251105-mercarigears2025-enjoy-besides-sessions/</guid><description>&lt;p&gt;こんにちは！メルカリ Engineering Office の@mikichinです。 来る11月13日、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます！ 2018年に実施し [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 05 Nov 2025 10:00:10 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは！メルカリ Engineering Office の&lt;a href=&quot;https://x.com/chida_miki&quot; title=&quot;@mikichin&quot;&gt;@mikichin&lt;/a&gt;です。&lt;br /&gt;
来る11月13日、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます！&lt;/p&gt;
&lt;p&gt;&lt;iframe loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/TDXzEjwqbaw?si=QJTLP0JGhJtu2kIP&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;2018年に実施した「Mercari Tech Conf 2018」から7年の時を経て、久しぶりのオフラインでの開催となります。&lt;br /&gt;
テーマは「メルカリエンジニアリングの今」。&lt;br /&gt;
今年の全社的なテーマでもある「AI-Native」についてはもちろん、2018年以降メルカリグループのエンジニアリングがどのように変化してきたかを、技術・組織・カルチャーの観点からご紹介します。&lt;br /&gt;
オンライン配信はありませんので、ぜひ会場でご自身の目と耳で確かめてください！！&lt;/p&gt;
&lt;p&gt;セッションの紹介記事はこちらをご確認ください。&lt;br /&gt;
PASSION Stage のURL：&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251008-mercarigears2025-passion-stage/&quot;&gt;https://engineering.mercari.com/blog/entry/20251008-mercarigears2025-passion-stage/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GROW Stage のURL：&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251009-mercarigears2025-grow-stage/&quot;&gt;https://engineering.mercari.com/blog/entry/20251009-mercarigears2025-grow-stage/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;MECHANISM Stage のURL：&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251010-mercarigears2025-mechanism-stage/&quot;&gt;https://engineering.mercari.com/blog/entry/20251010-mercarigears2025-mechanism-stage/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本記事では、オフラインイベントならではのセッション以外の楽しみ方をご紹介します！&lt;/p&gt;
&lt;h2&gt;フロアマップ&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/2f7425a0--2025-11-04-20.50.35-1024x623.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;会場は、メルカリのエンジニアリング組織における信念や行動の基盤となる共通認識を明文化した「&lt;a href=&quot;https://engineering.mercari.com/culture/&quot; title=&quot;Mercari Engineering Principles&quot;&gt;Mercari Engineering Principles&lt;/a&gt;」をモチーフにした「PASSION Stage」「GROW Stage」「MECHANISM Stage」の3つのステージがあり、ここでプレゼンテーションを聞くことができます。&lt;br /&gt;
その他、Ask the SpeakerやTech Quizを実施している「COLLABORATION Lounge」、「Unconference」ルーム、「Break Area」があります。&lt;/p&gt;
&lt;h2&gt;スタンプラリー&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/416f9499-img_9799-1024x768.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;会場に到着したら、名札を受け取ります。会場で参加者同士が話しかけやすいように、お名前や技術領域、所属など記載してくださいね。&lt;/p&gt;
&lt;p&gt;受付時に、名札とあわせてスタンプラリーのカードをお渡しします。スタンプラリーの詳細はカードに説明文がありますが、セッションに参加したり、Tech Quizに回答したりするとステッカーをもらえるほか、参加者同士で交換しながら集めていきます。集めたステッカーの枚数でもらえるグッズが異なりますので、ぜひ全部集めてみてください！&lt;/p&gt;
&lt;h2&gt;ポスターセッション&lt;/h2&gt;
&lt;p&gt;本イベントではプレゼンテーションだけではなく、ポスターセッションを準備しています。今回はEngineering組織だけではなく、メルカリR4Dラボでの研究も含め14つの発表があります。&lt;br /&gt;
ポスターセッションの詳細は、下記ブログ記事をご確認ください。&lt;br /&gt;
&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251029-mercarigears2025-poster/&quot;&gt;https://engineering.mercari.com/blog/entry/20251029-mercarigears2025-poster/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ポスターセッションでは、発表者の方が掲示されているポスターの前に立っているので、質問をしたり、情報交換をしたりできます。ぜひ、お気軽にお立ち寄りください。&lt;br /&gt;
※常に、発表者がポスター前に立っているわけではありません。いない時間帯もありますこと、ご了承ください。&lt;/p&gt;
&lt;h2&gt;Ask the Speaker&lt;/h2&gt;
&lt;p&gt;セッションは聞いて終わりではありません。各セッション終了後には、登壇者と直接お話しいただける時間を設けております。セッション内では触れられなかった詳細な内容から、率直なご質問まで、疑問を解消し理解を深めていただける貴重な機会です。&lt;br /&gt;
「COLLABORATION Lounge」までお越しください。&lt;/p&gt;
&lt;h2&gt;Unconference Area&lt;/h2&gt;
&lt;p&gt;事前に用意されたテーマを待つ必要はありません。ご自身が話したいトピックを持ち込み、その場で議論を始めてみませんか。メルカリメンバーと意見を交わすのはもちろん、当日に提示されるテーマをきっかけに知見を交換することも可能です。知識や経験が交差する場を、ぜひご活用ください。&lt;br /&gt;
「Unconference」ルームまでお越しください。&lt;/p&gt;
&lt;h2&gt;Tech Quiz&lt;/h2&gt;
&lt;p&gt;メルカリのスペシャリストが用意したクイズに挑戦してみませんか。Backend、Clientなど各技術領域ごとにクイズを準備しています。普段あまり触れることのない分野でも大丈夫です。&lt;br /&gt;
Tech Quiz AreaにはQuiz作成者もいる予定なので、お気軽にお声がけください。また、参加者同士で意見を出し合って一緒に考えてみましょう！&lt;/p&gt;
&lt;h2&gt;オリジナルグッズやお菓子&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/89420f47-img_9949-1024x768.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;本イベント用に準備している「mercari GEARS 2025」オリジナルグッズです。&lt;br /&gt;
ぜひ、ステッカーを集めて「Stamp Rally Kiosk」でGETしてくださいね！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/3bb5e1df--2025-11-04-20.55.19-1024x395.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;参加者同士でお話をするときのお供に、Coffee Standにて、オリジナルのお菓子やコーヒーを受け取ってください。&lt;/p&gt;
&lt;p&gt;「mercari GEARS 2025」では、単なる情報伝達の場ではなく、オフラインイベントならではの経験を共有し、交流を通じて新たな機会が生み出されることを期待しています。&lt;br /&gt;
プレゼンテーション以外にもさまざまなコンテンツを準備していますので、ぜひ積極的にご参加ください。&lt;/p&gt;
&lt;p&gt;「mercari GEARS 2025」のお申し込みは&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちらから&quot;&gt;こちらから&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;イベント詳細&lt;/h2&gt;
&lt;p&gt;開催日時：&lt;br /&gt;
2025年11月13日（木） 11:00-18:00&lt;/p&gt;
&lt;p&gt;概要：&lt;br /&gt;
mercari GEARS 2025は、メルカリグループのエンジニアリング組織の技術への挑戦と、カルチャーを体感する技術イベントです。&lt;br /&gt;
本イベントは、単なる情報伝達の場ではなく、エンジニアたちが出会い、経験を共有し、交流を通じて新たな機会が生み出されることを目的としています。&lt;/p&gt;
&lt;p&gt;参加費：無料&lt;br /&gt;
会場：TODA HALL &amp;amp; CONFERENCE TOKYO&lt;br /&gt;
参加方法：&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちらのページ&quot;&gt;こちらのページ&lt;/a&gt;にてお申し込みください。&lt;br /&gt;
【&lt;a href=&quot;https://gears.mercari.com/&quot; title=&quot;公式サイト&quot;&gt;公式サイト&lt;/a&gt;】&lt;/p&gt;
&lt;p&gt;本イベントに関する追加情報があれば、随時 &lt;a href=&quot;https://x.com/MercariGears&quot; title=&quot;@MercariGears&quot;&gt;@MercariGears&lt;/a&gt; でお知らせしますので、気になる方はぜひフォローをお願いします。&lt;/p&gt;
</content:encoded></item><item><title>iOSDC Japan 2025に参加しました #iosdc #iwillblog</title><link>https://engineering.mercari.com/blog/entry/20251031-iosdc-2025-report/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251031-iosdc-2025-report/</guid><description>&lt;p&gt;こんにちは。株式会社メルカリ iOSエンジニアのkntkです。 9月19日から9月21日にかけて開催された「iOSDC Japan 2025」にメルカリはゴールドスポンサーとして参加しました。 本記事では、その参加レポー [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 04 Nov 2025 10:00:20 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。株式会社メルカリ iOSエンジニアの&lt;a href=&quot;https://x.com/kntkymt&quot;&gt;kntk&lt;/a&gt;です。&lt;br /&gt;
9月19日から9月21日にかけて開催された「&lt;a href=&quot;https://iosdc.jp/2025/&quot;&gt;iOSDC Japan 2025&lt;/a&gt;」にメルカリはゴールドスポンサーとして参加しました。&lt;br /&gt;
本記事では、その参加レポートをお届けします！&lt;/p&gt;
&lt;h1&gt;Swiftコードバトル&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;私kntkはiOSDC Japan 2025のday 0に開催された企画、&lt;a href=&quot;https://fortee.jp/iosdc-japan-2025/proposal/7a9ef46c-e16d-469e-bd90-028e3ca69f5b&quot;&gt;Swiftコードバトル2025&lt;/a&gt;で優勝しました。&lt;/strong&gt;&lt;br /&gt;
iOSDC Japan 2025の運営の皆さんには貴重な体験をさせていただき、本当にありがとうございました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/01af2c15-img_3384-768x1024.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;後日iOSDC Japan 2025運営から頂いた優勝記念品&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Swiftコードバトルとは&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Swiftコードバトルはお題で指示された動作をするSwiftコードをより短く書けた方が勝ち、という競技です。 お題は決して難しいものではなく、少し練習すればSwiftプログラマであればどなたでも参加できる難易度を目指しています。&lt;br /&gt;
お題例1: 入力された文字列を逆順にして出力するプログラムを書いてください。&lt;br /&gt;
お題例2: 与えられた整数リストの要素の合計を計算するプログラムを書いてください。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://iosdc.connpass.com/event/365136/&quot;&gt;Swiftコードバトル2025予選&lt;/a&gt;の説明文から引用&lt;/p&gt;
&lt;p&gt;一般的にはコードゴルフと呼ばれる競技になります。&lt;br /&gt;
昨年のiOSDC Japan 2024から開催されている企画で、今年が第2回目となります。&lt;br /&gt;
第1回目は私も参加し準優勝しました。&lt;br /&gt;
&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20240925-iosdc-2024-report/&quot;&gt;iOSDC Japan 2024に参加・登壇しました #iosdc #iwillblog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;昨年からの変化として、問題の複雑度が上がったように感じました。その結果&lt;strong&gt;回答が多様化し、戦略的な立ち回りが求められるようになった&lt;/strong&gt;と思います。&lt;br /&gt;
その影響もあってか、今年は問題文と一緒にサンプルコードが提供されるようになりました。&lt;/p&gt;
&lt;h2&gt;感想&lt;/h2&gt;
&lt;p&gt;去年は決勝で敗退して悔しかったのですが、今年は問題との相性などが巡って無事優勝することができて非常に嬉しかったです。予選から接戦が多く、Swiftコードバトルの環境が成熟して来ているのを感じました。&lt;/p&gt;
&lt;p&gt;また、&lt;strong&gt;参戦することで対戦中により良い解法を思いついた際の爽快感や、逆に思い付けなかった際の悔しさを味わうことができ、Swiftコードバトルをより深く楽しむことができます。&lt;/strong&gt;是非皆さんも来年（開催された場合は）参戦してみてはいかがでしょうか。&lt;/p&gt;
&lt;p&gt;Swiftコードバトルの詳しい解説や私の戦略を書いた記事もありますので、ぜひ興味があればご覧ください！&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251031-swift-code-battle-2025-report&quot;&gt;Swiftコードバトル2025で優勝しました  #iosdc #iwillblog&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;登壇&lt;/h1&gt;
&lt;p&gt;株式会社メルカリからは私がLTで登壇しました。&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://fortee.jp/iosdc-japan-2025/proposal/5d8aba2e-2123-4a63-bda8-74f68a40e64b&quot;&gt;“奇妙”なSwift&lt;/a&gt;（LT5分）: &lt;a href=&quot;https://x.com/kntkymt&quot;&gt;kntk&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Swiftにはさまざまな文法があり、それらは明示的・直感的で読みやすいことに定評がある一方で、Swiftの特定の文法を応用すると一見&amp;quot;奇妙&amp;quot;なプログラムも記述できます。&lt;strong&gt;”奇妙”なプログラムの背景にはさまざまなSwiftのテクニックが含まれており、解き明かすことで新たな発見を得ることができます。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;このLTでは、”奇妙”なSwiftの例とそれらの背景にあるテクニックを3つ紹介しました。特に2つ目の例で紹介した、Switch文の内部で利用されているパターンマッチング演算子は利用することで冗長な表現を減らせる実用的なテクニックだと考えています。&lt;a href=&quot;https://github.com/swiftlang/swift-syntax/blob/ef367f7370c51200ed95b384e03d545f59c7fc98/Sources/SwiftParser/TokenSpec.swift#L137-L143&quot;&gt;実際にSwiftSyntaxなどの1st party libraryでも利用されています&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;また、最後の例ではSwiftコードバトルで利用できる関数呼び出しの文字数短縮テクニックを紹介しました。この構成はプロポーザル提出時に考えたものなのですが、day0のSwiftコードバトルで優勝したことによって綺麗なオチになって良かったなと思います。&lt;/p&gt;
&lt;h1&gt;メルカリスポンサーブース&lt;/h1&gt;
&lt;p&gt;メルカリスポンサーブースではAIツールに関するポストイット企画を実施しました。1日1問、合計3問の質問を用意し実施しました。このセクションでは、ポストイットの回答結果を共有します！&lt;/p&gt;
&lt;p&gt;※目視で回答の集計を行っている関係上、集計結果はおおよその値となります。&lt;/p&gt;
&lt;h2&gt;Day 0: 普段iOS開発で使っているAI Coding Assistant Toolは何ですか？&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/11/c8f8984e-img_1931-1-754x1024.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;合計回答数: 約90件（1件のポストイットに対して複数回答あり）&lt;/p&gt;
&lt;p&gt;回答数上位のツールから順に列挙すると以下の様になります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude Code
&lt;ul&gt;
&lt;li&gt;月定額で利用でき、利用上限も多くコスパが良いと感じているため。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Copilot
&lt;ul&gt;
&lt;li&gt;企業が導入しているため。&lt;/li&gt;
&lt;li&gt;学生は無料で利用できるため。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Gemini
&lt;ul&gt;
&lt;li&gt;学生は無料で利用できるため。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cursor&lt;/li&gt;
&lt;li&gt;ChatGPT&lt;/li&gt;
&lt;li&gt;Codex&lt;/li&gt;
&lt;li&gt;Devin&lt;/li&gt;
&lt;li&gt;Amazon Q Developer&lt;/li&gt;
&lt;li&gt;Codeium&lt;/li&gt;
&lt;li&gt;Alex Sidebar
&lt;ul&gt;
&lt;li&gt;Xcode用のAI Coding Assistant。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Zed&lt;/li&gt;
&lt;li&gt;Grok&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;私が初耳のツールも含まれており、新しいツールを知る良い機会となりました。&lt;/p&gt;
&lt;h2&gt;Day 1: iOSアプリの開発で、生成AIが最も役立つと思う作業は何ですか？&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/abb98608-img_1907-680x1024.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;合計回答数: 約180件&lt;br /&gt;
次の回答が上位となりました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;テストコード生成&lt;/li&gt;
&lt;li&gt;プロトタイプ作成&lt;/li&gt;
&lt;li&gt;UI実装&lt;/li&gt;
&lt;li&gt;コードレビュー&lt;/li&gt;
&lt;li&gt;調査・コードリーディング&lt;/li&gt;
&lt;li&gt;リファクタリング&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;また、来場者の方から具体的なプラクティスも聞くことができました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;実装より先にテストコードを生成することで、AIが特定の実装に依存したテストコードを書くのを防いでいる。（テストコード生成）&lt;/li&gt;
&lt;li&gt;AIの生成したコードは品質に疑問があるので、主にコードを読ませる用途で利用している。（調査・コードリーディング）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全体的にAIの使い方を試行錯誤しているお話が多く、さまざまな取り組みや現状の感触を共有いただきました。今後AI Coding手法が発展していくのが楽しみですね！&lt;/p&gt;
&lt;h2&gt;Day 2: 生成AIがアプリ開発者の作業をどう効率化していると思いますか？&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/32e843f6-img_1926-801x1024.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;合計回答数: 約100件&lt;/p&gt;
&lt;p&gt;全体的に作業効率化に関する回答が多く、具体的には次の項目の効率化が大きいという声がありました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;アイデア出し・壁打ち&lt;/li&gt;
&lt;li&gt;調査&lt;/li&gt;
&lt;li&gt;プロトタイプ&lt;/li&gt;
&lt;li&gt;実装&lt;/li&gt;
&lt;li&gt;レビュー&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;効率化の結果、「設計に集中できる」「意思決定が早くなった」というお話もありました。&lt;br /&gt;
私も業務でAIを使い冗長な作業が減った結果、設計に集中できるようになったと感じており、共感できるお話が多くありました。&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;メルカリブースに来てくださった参加者の皆さん、AIツールに関するノウハウを共有していただきありがとうございました！私自身も参加者の方とAIツールのノウハウについて話す中で大変勉強になりました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最後に、iOSDC Japan 2025 の運営の皆様お疲れ様でした&amp;amp;ありがとうございました！また来年も参加したいなと思います！ #iosdc #iwillblog&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>Swiftコードバトル2025で優勝しました #iosdc #iwillblog</title><link>https://engineering.mercari.com/blog/entry/20251031-swift-code-battle-2025-report/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251031-swift-code-battle-2025-report/</guid><description>&lt;p&gt;こんにちは。株式会社メルカリ iOSエンジニアのkntkです。 私kntkは9月19日から9月21日にかけて開催された「iOSDC Japan 2025」のday 0に開催された企画、Swiftコードバトル2025で優勝 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 04 Nov 2025 10:00:13 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。株式会社メルカリ iOSエンジニアの&lt;a href=&quot;https://x.com/kntkymt&quot;&gt;kntk&lt;/a&gt;です。&lt;br /&gt;
&lt;strong&gt;私kntkは9月19日から9月21日にかけて開催された「&lt;a href=&quot;https://iosdc.jp/2025/&quot;&gt;iOSDC Japan 2025&lt;/a&gt;」のday 0に開催された企画、&lt;a href=&quot;https://fortee.jp/iosdc-japan-2025/proposal/7a9ef46c-e16d-469e-bd90-028e3ca69f5b&quot;&gt;Swiftコードバトル2025&lt;/a&gt;で優勝しました。&lt;/strong&gt;&lt;br /&gt;
iOSDC Japan 2025の運営の皆さんには貴重な体験をさせていただき、本当にありがとうございました。&lt;/p&gt;
&lt;p&gt;せっかくの機会なので、この記事では&lt;strong&gt;私がどのような戦略やプロセスで参戦したのかを言語化&lt;/strong&gt;しようと思います。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/01af2c15-img_3384-768x1024.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;後日iOSDC Japan 2025運営から頂いた優勝記念品&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;また、メルカリは「&lt;a href=&quot;https://iosdc.jp/2025/&quot;&gt;iOSDC Japan 2025&lt;/a&gt;」にゴールドスポンサーとして参加しており、その様子はこちらをご確認ください！&lt;br /&gt;
&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251031-iosdc-2025-report&quot;&gt;iOSDC Japan 2025に参加しました #iosdc #iwillblog&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Swiftコードバトルとは&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Swiftコードバトルはお題で指示された動作をするSwiftコードをより短く書けた方が勝ち、という競技です。 お題は決して難しいものではなく、少し練習すればSwiftプログラマであればどなたでも参加できる難易度を目指しています。&lt;br /&gt;
お題例1: 入力された文字列を逆順にして出力するプログラムを書いてください。&lt;br /&gt;
お題例2: 与えられた整数リストの要素の合計を計算するプログラムを書いてください。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://iosdc.connpass.com/event/365136/&quot;&gt;Swiftコードバトル2025予選&lt;/a&gt;の説明文から引用&lt;/p&gt;
&lt;p&gt;一般的にはコードゴルフと呼ばれる競技になります。&lt;br /&gt;
昨年のiOSDC Japan 2024から開催されている企画で、今年が第2回目となります。&lt;br /&gt;
第1回目は私も参加し準優勝しました。&lt;br /&gt;
&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20240925-iosdc-2024-report/&quot;&gt;iOSDC Japan 2024に参加・登壇しました #iosdc #iwillblog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;昨年からの変化として、問題の複雑度が上がったように感じました。その結果&lt;strong&gt;回答が多様化し、戦略的な立ち回りが求められるようになった&lt;/strong&gt;と思います。&lt;br /&gt;
その影響もあってか、今年は問題文と一緒にサンプルコードが提供されるようになりました。&lt;/p&gt;
&lt;h1&gt;何をする競技なのか&lt;/h1&gt;
&lt;p&gt;Swiftコードバトルの問題には文字数短縮余地のある&lt;strong&gt;「最適化ポイント」&lt;/strong&gt;が複数個存在し、我々選手は&lt;strong&gt;個々の最適化ポイントに対して文字数短縮方法を考え、文字数短縮を行っています。&lt;/strong&gt;&lt;br /&gt;
問題が複数個の小問で構成されており、小問の合計スコアで競っている、というイメージを私は持っています。（実際には小問が相互に関係しあう場合があり、単純に可分できるわけではない。）&lt;/p&gt;
&lt;p&gt;最適化ポイントについて、昨年の決勝問題を例に紹介します。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;標準入力の各行に、ちょうど5文字からなる英単語が一つずつ並んでいます。&lt;br /&gt;
与えられた単語と「iOSDC」のハミング距離を出力してください。大文字と小文字は区別します。&lt;br /&gt;
「iOSDC」なら「0」、「CDSOi」なら「4」、「iosos」なら「4」を出力します。&lt;br /&gt;
すべての行についてこの手順を繰り返してください。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// 回答a
while let a = readLine() {
    print(zip(&amp;quot;iOSDC&amp;quot;, a).filter { $0 != $1 }.count)
}

// 回答b
while let a = readLine() {
    print(zip(a, &amp;quot;iOSDC&amp;quot;).reduce(0) { $0 + ($1.0 != $1.1 ? 1 : 0) })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この問題には結果的に以下の最適化ポイントがありました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;“iOSDC”と入力にどのようにアクセスするか？&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;a. それぞれにインデックスアクセス&lt;/li&gt;
&lt;li&gt;b. &lt;code&gt;zip&lt;/code&gt;でまとめてからアクセス (最短解)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;異なる文字数をどの様にカウントするか？&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;a. カウンタを持ってfor文でインクリメント&lt;/li&gt;
&lt;li&gt;b. &lt;code&gt;reduce&lt;/code&gt;でカウンタをインクリメント&lt;/li&gt;
&lt;li&gt;c. &lt;code&gt;filter.count&lt;/code&gt; (最短解)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;これらの最適化ポイントは問題に明示されていないので、まず最適化ポイントを発見する必要があります。&lt;/strong&gt;「ここは複数の書き方ができるな」と、一目見ただけで発見できるわかりやすいポイントもあれば、問題を解く中で問題の理解度が上がることで発見できるポイントや、間違い探しのようにじっくり考え込んでやっと発見できるポイントもあります。&lt;/p&gt;
&lt;p&gt;最適化ポイントを発見した後は文字数短縮方法を考えて実装します。&lt;strong&gt;Swiftの知識と競プロ的な知識を総合的に活用して短縮方法を考える必要があります&lt;/strong&gt;（詳しくは後述）。&lt;/p&gt;
&lt;p&gt;実際の回答を見てみると、回答aだと1, 2両方のポイントで最短解を採用できている一方で、回答bだと2で非最短解であるreduceを採用しており、2.の短縮方法で勝敗が決まった結果になっています。&lt;/p&gt;
&lt;p&gt;このように、&lt;strong&gt;問題に存在する最適化ポイントを発見し、最適な短縮方法を考える&lt;/strong&gt;ことがこの競技の基本的な作業になると考えています。問題が複雑な場合は全ての最適化ポイントを発見・回答しきれず時間切れになることもあり、&lt;strong&gt;最適化ポイントを発見する速さと短縮方法を考える速さも重要になってきます。&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;サンプルコード vs 0から実装&lt;/h1&gt;
&lt;p&gt;問題文と一緒にサンプルコードが提供されるようになったことにより、&lt;strong&gt;大きな方針として二つの作戦&lt;/strong&gt;が存在していました。（コードバトル予選でも参加者間で話題になっていました。）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;サンプルコードを元に文字数短縮を行う作戦&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;初期実装コストがなく、個々の最適化ポイントに順に対処していけば安定してスコアを伸ばせる利点があります。その一方で、サンプルコードに存在する冗長な表現の修正に時間を要したり、既存のロジックや構造に縛られて大幅な変更をしにくいなどの欠点があります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;サンプルコードを用いず0からコードを実装する作戦&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;サンプルコードの構造やロジックに縛られないため、最初から一気に短いプログラムを記述して高いスコアを狙える利点があります。その一方で、初期実装コストが必要な上、テストケースが通らない間違ったコードを書いてしまい、時間を消費する危険性があります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;実際に取った作戦&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;私はサンプルコードを元に文字数短縮を行う作戦で進めました。理由としては後者に必要となる競プロ的な能力に自信がなかったというのと、前者の安定してスコアを取れる点が複数試合を行う上で有利だと考えたためです。&lt;/p&gt;
&lt;h1&gt;対戦前の準備&lt;/h1&gt;
&lt;p&gt;競プロ等でも一般的に有効な手法だと思いますが、&lt;strong&gt;傾向を把握し、その対策を行っていました。&lt;/strong&gt;事前に頻出パターンとその短縮方法（テクニック）調査して知識化し、対戦中はそれらの知識を適用できるようにしていました。これによって、問題に存在する複数の最適化ポイントのうち&lt;strong&gt;8-9割程度を事前知識で瞬時に発見して解くことができ、対戦時間を残りの1-2割（初見のパターンや一般化が難しい複雑なパターン）への対処に集中できていました。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;具体的にどのような短縮テクニックを調査・利用していたのか、実際の準決勝の問題を例に紹介します。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;標準入力の各行に、英字のみからなる文字列が与えられます。キャメルケースをスネークケースに変換し、改行区切りで出力してください。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;出題側から提供されるサンプル回答（読みやすさのため一部変更）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;while let line = readLine() {
    var result = &amp;quot;&amp;quot;
    for (index, character) in input.enumerated() {
        if character.isUppercase &amp;amp;&amp;amp; index &amp;gt; 0 {
            result.append(&amp;quot;_&amp;quot;)
            result.append(character.lowercased())
        } else {
            result.append(character.lowercased())
        }
    }

    print(result)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この問題では次のような文字数短縮テクニックが利用できます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;String.append(Character)&lt;/code&gt;は&lt;code&gt;+=&lt;/code&gt;で代替可能&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RangeReplaceableCollection&lt;/code&gt;の実装由来。&lt;code&gt;String&lt;/code&gt;は&lt;code&gt;RangeReplaceableCollection&lt;/code&gt;に準拠しているため。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;If aaa &amp;amp;&amp;amp; bbb&lt;/code&gt;は&lt;code&gt;if aaa, bbb&lt;/code&gt;に代替可能&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;変数名の一文字化&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共通項の抜き出し（&lt;code&gt; r += c.lowercased()&lt;/code&gt;）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;それらのテクニックを利用したプログラム&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;while let l = readLine() {
    var r = &amp;quot;&amp;quot;
    for (i, c) in l.enumerated() {
        if c.isUppercase, i &amp;gt; 0 {
            r += &amp;quot;_&amp;quot;
        }

        r += c.lowercased()
    }

    print(r)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;その他、多数のテクニックをドキュメントにまとめておき、対戦前に眺めていました。&lt;br /&gt;
また、&lt;strong&gt;「この手法では短縮できない」という知識も持っておくことで、対戦中の試行錯誤時におけるノイズを減らしていました。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例を一つ紹介すると&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;for&lt;/code&gt;文より&lt;code&gt;.forEach&lt;/code&gt;で書いた方が短い？-&amp;gt; for文の方が常に短い。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;for i in a {&lt;/code&gt;と&lt;code&gt;a.forEach { i in&lt;/code&gt;を比較すると後者の方が5文字多くなります。&lt;br /&gt;
また、&lt;code&gt;forEach&lt;/code&gt;のクロージャーで省略引数名&lt;code&gt;$0&lt;/code&gt;を使った場合&lt;code&gt;i in&lt;/code&gt;を省略できますが、ここでの省略数は3文字なのでメソッド呼び出し部分だけで結果に2文字多くなり赤字です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 8文字
for i in a {
// 13文字
a.forEach { i in
// 10文字
a.forEach {&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;対戦中&lt;/h1&gt;
&lt;p&gt;先述の通り、対戦中は冒頭に事前知識を用いて頻出のパターンの文字数短縮を行い、&lt;strong&gt;ほとんどの時間を初見パターンや一般化が難しい複雑なパターンの文字数短縮方法を考えることに集中していました。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;具体的に文字数短縮方法を考えるプロセスについて紹介すると、&lt;br /&gt;
各最適化ポイントについて、&lt;strong&gt;文字数短縮方法には主に二つの側面&lt;/strong&gt;があると考えていました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A: 文法・記法を工夫する&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B: ロジックを工夫する&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A: 文法・記法を工夫する&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;既存の記述の文法や記法を工夫することで文字数短縮を行う側面&lt;/strong&gt;です。先述の準決勝での例にあるように変数名を一文字にしたり、&lt;code&gt;String.append(Character)&lt;/code&gt;を&lt;code&gt;+=&lt;/code&gt;に置き換えたりするなどが当たります。&lt;/p&gt;
&lt;p&gt;また、予選の問題ではSwiftの型推論や標準ライブラリの関数・メソッドを応用して文字数を減らす場面がありました。（Swiftらしさが強く現れる問題で特に記憶に残っています。）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;コードバトル予選、ROT13変換問題の私の回答&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;while let p = readLine() {
    print(String(p.map { a in
        let k = a.asciiValue! &amp;amp;- 52

        return a.isLetter
        ? .init(
            .init(
                13...38 ~= k
                ? k % 26 + 65
                : (k - 32) % 26 + 97
            )
        )
        : a
    }))
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Character.init(UnicodeScalar.init(...))&lt;/code&gt;を&lt;code&gt;.init(.init())&lt;/code&gt;で代替&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;型推論によって型名を省略&lt;/li&gt;
&lt;li&gt;&lt;code&gt;String.init&lt;/code&gt;の期待する型（&lt;code&gt;[Character]&lt;/code&gt;）が&lt;code&gt;p.map&lt;/code&gt;の帰り値の型を経由してクロージャーのreturn statementの型推論まで伝播&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Range.contains&lt;/code&gt;を&lt;code&gt;~=&lt;/code&gt;で代替&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;標準ライブラリに演算子定義が存在&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;オーバーフロー演算子&lt;code&gt;&amp;amp;-&lt;/code&gt;を利用して事前計算&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;通常の演算子だとランタイムエラーになるテストケースが存在&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このように、&lt;strong&gt;Swiftの知識（型推論・文法・言語機能・標準ライブラリなど）が活かされる側面&lt;/strong&gt;です。Appleの公式ドキュメントや&lt;a href=&quot;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/&quot;&gt;TSPL (The Swift Programming Language)&lt;/a&gt;を読むことでこの側面の思考力が鍛えられると思います。&lt;/p&gt;
&lt;h2&gt;B: ロジックを工夫する&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;既存のロジックをより短い記述のロジックに変えることで文字数短縮を行う側面&lt;/strong&gt;です。こちらは平たく言えば競プロ的な側面です。ただし、実行速度向上ではなく文字数短縮が目的です。&lt;/p&gt;
&lt;p&gt;実は先述した準決勝の私の回答は最短解ではなく、さらにロジックを工夫することで短縮できる余地がありました。（前回優勝者にご指摘いただきました。）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;while let l = readLine() {
    var r = &amp;quot;&amp;quot;
    for c in l {
        if c.isUppercase, r != &amp;quot;&amp;quot; {
            r += &amp;quot;_&amp;quot;
        }

        r += c.lowercased()
    }

    print(r)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;index &amp;gt; 0&lt;/code&gt;というのは、つまり「最初以外の要素の時」を意味する条件なので、こちらは&lt;code&gt;r != &amp;quot;&amp;quot;&lt;/code&gt;で代替可能なわけです。また、コードバトル予選では有名な「番兵法」を利用することで文字数を大幅に減らすシーンもありました。&lt;/p&gt;
&lt;p&gt;このように、&lt;strong&gt;問題・プログラムの意図を読みとるのがこちらの側面になり、競プロ的な力が活かされる側面&lt;/strong&gt;です。競技プログラミングサイト等で過去問を解くことで、この側面の思考力を鍛えることができると思います。&lt;/p&gt;
&lt;p&gt;また、AとBの側面は排他的ではなく、Bを適用した後の記述に新たにAが適用できる場合もあり、&lt;strong&gt;AとBの組み合わせによって文字数短縮方法が多数存在していました。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;戦略&lt;/h2&gt;
&lt;p&gt;A・Bの例で示したように、&lt;strong&gt;より短い解に辿り着くにはAとB両方の側面を組み合わせて解法を考えることが必要になってきます。&lt;/strong&gt;&lt;br /&gt;
しかし、解法を考える際はコードを書いてみないと実際に文字数短縮が可能か分からないことが多く、制限時間の中で試せる解法の候補数も限られてくるため、&lt;strong&gt;ある程度解法に”あたり”を付ける必要があります。&lt;/strong&gt;ここで、&lt;strong&gt;私は迷ったらAを優先して攻める戦略を立てました。&lt;/strong&gt;理由としては私がAの方が得意というのと、「Aの側面を極めた回答の方がSwift特有のテクニックが現れて面白いだろう」と考えたからです。&lt;/p&gt;
&lt;p&gt;結果的に決勝でもSwiftの文字列展開を利用した手法を選択し、Swiftらしいコードで優勝ができたのは良かったなと考えています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;アスキーアートを出力する決勝問題の私の回答（見やすさのため一部スペースを省略）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let a = &amp;quot;######&amp;quot;, i = a + a, f = &amp;quot;\(i)##&amp;quot;, j = f + f （中略）
print(
&amp;quot;&amp;quot;&amp;quot;
\(f)                                    \(j)
\(f)                                    \(j)
                      ##                    \(a)              \(a)
                      ##                    \(a)              \(a)
\(a)####         ##              \(a)          \(f+a)            \(a)
\(a)####         ##              \(a)          \(f+a)            \(a)
（中略）
&amp;quot;&amp;quot;&amp;quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;Swiftコードバトルは問題に存在する最適化ポイントを発見し、Swiftの知識と競プロ的な知識を総合的に活用して最適な短縮方法を考える競技です。制限時間があるため、事前に頻出パターンの対策を行うことによって対戦時間を有効に活用することができます。&lt;/p&gt;
&lt;p&gt;Swiftコードバトルのテクニックは一見奇妙で役に立たなそうに見えるかもしれません。しかし&lt;strong&gt;その背景にはSwiftの文法・言語機能の知識やロジックを読みとる力などが隠れており、それらは日々の開発でも有益なものだと考えています。&lt;/strong&gt;自分も参戦する中で多くを学ぶことができました。&lt;/p&gt;
&lt;p&gt;また、参戦することで対戦中により良い解法を思いついた際の爽快感や、逆に思い付けなかった際の悔しさを味わうことができ、Swiftコードバトルをより深く楽しむことができます。是非皆さんも来年（開催された場合は）参戦してみてはいかがでしょうか。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最後に、改めてiOSDC Japan 2025・Swiftコードバトル2025 の運営の皆さん、予選・本戦で対戦してくれた選手の皆さん、本当にありがとうございました！ #iosdc #iwillblog&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>【mercari GEARS 2025】ポスターセッションをご紹介</title><link>https://engineering.mercari.com/blog/entry/20251029-mercarigears2025-poster/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251029-mercarigears2025-poster/</guid><description>&lt;p&gt;こんにちは！メルカリ Engineering Office の@mikichinです。 来る11月13日、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます！ 2018年に実施し [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 29 Oct 2025 10:00:49 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは！メルカリ Engineering Office の&lt;a href=&quot;https://x.com/chida_miki&quot; title=&quot;@mikichin&quot;&gt;@mikichin&lt;/a&gt;です。&lt;br /&gt;
来る11月13日、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます！&lt;/p&gt;
&lt;p&gt;&lt;iframe loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/TDXzEjwqbaw?si=QJTLP0JGhJtu2kIP&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;2018年に実施した「Mercari Tech Conf 2018」から7年の時を経て、久しぶりのオフラインでの開催となります。&lt;br /&gt;
テーマは「メルカリエンジニアリングの今」。&lt;br /&gt;
今年の全社的なテーマでもある「AI-Native」についてはもちろん、2018年以降メルカリグループのエンジニアリングがどのように変化してきたかを、技術・組織・カルチャーの観点からご紹介します。&lt;br /&gt;
オンライン配信はありませんので、ぜひ会場でご自身の目と耳で確かめてください！！&lt;/p&gt;
&lt;p&gt;また、今回はプレゼンテーションだけではなくポスターセッションもあります。&lt;br /&gt;
ポスターセッションでは、発表者の方が掲示されているポスターの前に立っているので、質問をしたり、情報交換をしたりできます。ぜひ、お気軽にお立ち寄りください。&lt;br /&gt;
※常に、発表者がポスター前に立っているわけではありません。いない時間帯もありますこと、ご了承ください。&lt;/p&gt;
&lt;p&gt;本記事では、会場でしか見れないポスターセッションを一挙ご紹介！&lt;/p&gt;
&lt;p&gt;プレゼンテーションの紹介は下記記事をご参照ください。&lt;br /&gt;
PASSION Stageのセッション紹介は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251008-mercarigears2025-passion-stage/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;。&lt;br /&gt;
GROW Stageのセッション紹介は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251009-mercarigears2025-grow-stage/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;。&lt;br /&gt;
MECHANISM Stageのセッション紹介は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251010-mercarigears2025-mechanism-stage/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;まだ申し込みをされていない方も、興味のあるセッションがあるはずです。お申し込みは&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;からお願いします。&lt;/p&gt;
&lt;h3&gt;メルカリグループにおけるAI-Nativeなインシデント管理の全貌と未来像&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/be3ee47d-ogp_poster-1_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;LLMの普及により、インシデント対応・管理のあり方も大きく変わりつつあります。&lt;br /&gt;
メルカリグループでは、複雑で負担の大きいインシデント管理を「AI-Native」に進化させることを決定しました。&lt;/p&gt;
&lt;p&gt;すでに導入している「IBIS」をはじめ、その周辺の仕組みや他のAI活用事例も紹介します。&lt;br /&gt;
AIを取り入れることで、MTTRの短縮だけでなく、対応者の負担・ストレス軽減やコスト削減、さらにサービス信頼性の向上が期待できます。&lt;/p&gt;
&lt;p&gt;ただし、人間が担うべき領域も残ります。本発表では、メルカリグループの現在の取り組みと今後の展望をお伝えします。&lt;/p&gt;
&lt;h3&gt;The 3A’s: Simple Steps For Clean Unit Tests&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/2c4e1b08-ogp_poster-2_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ソフトウェア開発のスピードが加速する中、新機能の追加や修正には、既存の機能を意図せず壊してしまうリスクが常に伴います。適切な安全策がなければ小さなミスが本番環境にリリースされ、多くのお客さまに影響を及ぼす可能性があります。 だからこそ、ユニットテストは極めて重要です。優れたユニットテストはコードの動作を検証するだけでなく、プロダクトの安定性と信頼性を確保し、開発チームが自信を持って変更を行える環境を支えます。 では、どうすればテストをシンプルでクリーン、かつ効果的に保つことができるのでしょうか。 そのための実績あるアプローチのひとつが、Arrange（準備）、Act（実行）、Assert（検証）の3Aフレームワークです。この3つのステップに従うことで、明確で保守性が高く、信頼できるユニットテストを簡単に書けるようになります。&lt;/p&gt;
&lt;h3&gt;Autonomous Support &amp;#8211; Leveraging AI Bots for Scalable and Intelligent Operational Assistance&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/2a6e10ea-ogp_poster-3_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;現在、エンジニアはSlack上での場当たり的かつ反復的な質問対応や、絵文字によるトリアージといった非効率なワークフローに多くの時間を費やしており、チームによっては業務時間の10〜20%以上が問い合わせ対応に割かれるという課題があります。私たちはこの課題に対し、Slack上の雑多な問い合わせを迅速で信頼性の高い回答と標準化されたチケットに変換する、AI支援の自律型サポートシステムを構築しています。このbotはSlack上で直接対応を行い、適切なJIRA/GitHubチケットのトリアージや共有ナレッジベース（ドキュメント、過去のチケット、Slack、ソースコード）の検索を行い、回答を提案します。人間の対応が必要な場合は担当者へ引き継ぎ、自己完結可能な場合はその場で対応を完了し、そこから学習を重ねます。これにより、応答と解決の迅速化、中断の削減、そして自律解決率、エスカレーション率、削減できたエンジニア工数、CSAT（顧客満足度）、特定されたナレッジギャップといった指標を通じて効果を測定します。また、既存のプラットフォームツールを再利用することで、迅速な開発を可能にしています。&lt;/p&gt;
&lt;h3&gt;Toward a Global Identity Platform&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/5b41532a-ogp_poster-4_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリは2019年に越境EC事業をスタートしました。開始当時、海外のお客さまは機能の限られた代行業者の購入ページを使って商品を検索し、購入する必要がありました。その後、海外のお客さまにより良い購買体験を提供するため、メルカリはシステムを拡張し、海外でのサービス展開を始めました。この拡大において重要な要件がグローバルアカウントの導入です。本セッションでは、これまでの取り組みで達成した成果を共有するとともに、複数の国のお客さまをサポートするためにアイデンティティ基盤をさらに拡張していく今後の計画についてお話しします。&lt;/p&gt;
&lt;h3&gt;非エンジニア組織のAI-Native化に伴走した実践知&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/8b0ae0b6-ogp_poster-5_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;非エンジニア組織が「AI-Native」へと変革する過程には、エンジニアの伴走が不可欠です。&lt;br /&gt;
本セッションでは、そのリアルな経験を紹介します。&lt;/p&gt;
&lt;p&gt;(1) AI活用で期待する結果を得るために入出力形式を工夫した事例&lt;br /&gt;
(2) AIワークフローが突然停止した事例から学ぶライフサイクル管理の教訓&lt;br /&gt;
(3) AI生成アプリをGASを使って安全にデプロイする手法&lt;/p&gt;
&lt;p&gt;といった、AI活用をプロトタイプから実運用への導くための実践知を共有します。&lt;/p&gt;
&lt;h3&gt;From Cluttered to Clear: Improving the Web Accessibility Design for Screen Reader Users in E-commerce With Generative AI&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/2dbc23f8-ogp_poster-6_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;視覚障がい者や弱視のユーザーは、スクリーンリーダーを使ってオンラインショッピングサイトを利用する際に、多くの障壁に直面しています。複雑なレイアウト、不明確なコンテンツ構造、視覚重視のデザインにより、特に初めて利用するサイトではストレスが多く非効率な体験となっています。これまでの支援ツールは商品説明や画像の代替テキストなど、個別の要素に焦点を当てられてきましたが、ページ全体におよぶ構造やナビゲーションの課題には十分対応できていませんでした。本研究では、生成AIを活用してショッピングサイトのHTMLコンテンツを自動的に再構成し、アクセシビリティを改善する方法を検討しました。研究は3段階の構成で行い、まずスクリーンリーダーのユーザーにインタビューを行い、次に生成AIを搭載したブラウザ拡張機能を開発し、最後に自動監査と実際の使用で評価を行いました。このツールはスクリーンリーダーの操作パターンに合わせてウェブコンテンツを動的に整えます。実験の結果、生成AIで再構成されたページではナビゲーション効率、コンテンツの分かりやすさ、全体的な使いやすさが大きく改善することが分かりました。参加者からは「セクションの順序がより論理的になった」「閲覧による疲労が減った」といった声が寄せられました。この成果は生成AIが既存サイトの構造そのものに作用することで、包括的かつユーザー中心のアクセシビリティ改善を実現できる可能性を示しています。&lt;/p&gt;
&lt;h3&gt;量子インターネット ー安心・安全でサステナブルなオンライン社会の実現に向けてー&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/87d5e951-ogp_poster-7_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリの研究開発組織「R4D」では、近い将来に到来が予想されている「量子前提時代」を生き抜くメルカリを作るために、量子情報通信技術に関する研究を行っています。本ポスターでは、R4Dの量子チームが国内の研究機関と共同で行なっている量子インターネットに関する研究開発の全体像について紹介します。&lt;/p&gt;
&lt;h3&gt;中性原子量子コンピュータにおける表面符号を用いた消失エラー耐性プロトコル&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/fcba554e-ogp_poster-8_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;光ピンセットによる中性原子アレイは、その優れた特性から量子コンピュータの有望な候補とされていますが、非パウリ誤り、消失誤り、リーケージ誤りが大きな課題となっています。従来の研究では、リーケージ誤りを消失誤りに変換できることは示されていましたが、変換された消失誤りが継続的に発生・蓄積する問題が残っていました。本研究では、この課題に対して、脱分極誤りと消失誤りを含む回路ベースのモンテカルロシミュレーションにより、消失誤りが平面コードに与える影響を評価しました。さらに、消失誤りへの耐性を高めるための新しいプロトコルを提案します。このプロトコルでは、オンラインでのコード変形を利用して、消失誤りが蓄積した「トラップ」から論理量子ビットを新しいトラップへと転送します。&lt;/p&gt;
&lt;h3&gt;「使い続ける力、未来をつなぐ」&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/87175dc2-ogp_poster-9_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;人が使わなくなった製品を一度で使い終えず、次の誰かへ繋ぐ「リユース」は、私たち消費者が持続可能な社会のために選べる、最も身近で実践しやすい選択肢の一つです。本プレゼンテーションでは、リユースによって製品の使用期間がどれほど延長され、新品の購入をどれだけ代替できるのか、そのインパクトを明らかにします。&lt;/p&gt;
&lt;p&gt;このお話を通して、皆様がモノを手放すとき、あるいは新しいモノが欲しくなったときに、リユースを当たり前の選択肢として生活に取り入れ、「モノに隠された新たな価値」を実践していただくためのきっかけをご提供します。&lt;/p&gt;
&lt;h3&gt;オンラインフリマアプリにおける商品説明の人間–AI協働執筆：売り手および買い手の視点からの考察&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/79409173-ogp_poster-10_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;オンラインのフリマアプリは、個人が中古品を他の個人に販売する手段として、特に日本で人気を博しています。出品のプロセスには、出品者が写真をアップロードし、潜在的な購入者が閲覧するための商品説明を書く作業が含まれます。近年は、大規模言語モデル（LLM）を用いた商品説明作成を通じて出品時の出品者の負担を軽減する、人間とAIの協働の応用領域が注目されています。本研究では、LLMに基づく支援が出品者の体験や商品の出品価格に与える影響に加え、人間とAIが協働して作成した商品説明の魅力に対する買い手の主観的な印象や選好にどのように影響するかを検討します。本研究の結果は、LLMベースのツールがオンライン中古市場に及ぼし得る影響の理解に寄与するとともに、フリマアプリ向けの人間—AI協働執筆システムの設計上の考慮点や今後の研究の方向性に関する示唆を提供します。&lt;/p&gt;
&lt;h3&gt;メルカリAdsにおけるAIを活用した広告審査の取り組み&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/dfb7883d-ogp_poster-11_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;2024年9月に開始したメルカリAdsは、当初広告主から入稿いただいている広告に対しての審査を手動運用にて審査していました。&lt;br /&gt;
効率的な審査を模索し、AIを活用した広告審査システムを構築することで運用コストを削減しつつ、多くの広告素材に対して審査することが可能となりました。&lt;br /&gt;
そのAI活用事例を共有します。&lt;/p&gt;
&lt;h3&gt;BFF保守の課題とgRPC Federationによる解決アプローチ&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/67c69848-ogp_poster-12_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;マイクロサービスアーキテクチャにおけるBFF開発では、複数サービス間の型変換や依存関係管理により保守コストが増大します。本発表では、この課題に対してgRPC Federationを採用し、Protocol BuffersにDSLで定義することによってBFFを自動生成し、保守コストを大幅に削減した実例を紹介します。また、AIを活用したDSL記述支援の取り組みについても報告します。&lt;/p&gt;
&lt;h3&gt;Engineering Office is a Hub, connecting Engineering together&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/61df5dd2-ogp_poster-13_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Engineering Officeはエンジニアリング部門のあらゆる部分を繋ぐハブの役割を持ちます。共通のオンボーディングからプロジェクトのサポート、エンジニアリングに関する情報まで、さまざまな要素を通してグループが方向性を合わせ、集中できるようにします。&lt;/p&gt;
&lt;p&gt;本発表では、自動化やAI、継続的なサービスライフサイクルを活用してビジネスニーズに迅速に対応し、メルカリ独自のエンジニアリング文化を維持するための、私たちのプロジェクトの一部をご紹介します。&lt;/p&gt;
&lt;h3&gt;Mercari における Recommendation System の概要&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/f0522edc-ogp_poster-14_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリではホームスクリーンや商品詳細画面など様々な所で recommendation が行われており、その裏側にはそれぞれの特性に応じた技術が使われています。&lt;/p&gt;
&lt;p&gt;本発表ではメルカリにおける様々な recommendation とその裏で使われている技術について概要を共有し、議論を通じて情報交換したいと考えています。&lt;/p&gt;
&lt;p&gt;「mercari GEARS 2025」のお申し込みは&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちらから&quot;&gt;こちらから&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;イベント詳細&lt;/h2&gt;
&lt;p&gt;開催日時：&lt;br /&gt;
2025年11月13日（木） 11:00-18:00&lt;/p&gt;
&lt;p&gt;概要：&lt;br /&gt;
mercari GEARS 2025は、メルカリグループのエンジニアリング組織の技術への挑戦と、カルチャーを体感する技術イベントです。&lt;br /&gt;
本イベントは、単なる情報伝達の場ではなく、エンジニアたちが出会い、経験を共有し、交流を通じて新たな機会が生み出されることを目的としています。&lt;/p&gt;
&lt;p&gt;参加費：無料&lt;br /&gt;
会場：TODA HALL &amp;amp; CONFERENCE TOKYO&lt;br /&gt;
参加方法：&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちらのページ&quot;&gt;こちらのページ&lt;/a&gt;にてお申し込みください。&lt;br /&gt;
【&lt;a href=&quot;https://gears.mercari.com/&quot; title=&quot;公式サイト&quot;&gt;公式サイト&lt;/a&gt;】&lt;/p&gt;
&lt;p&gt;本イベントに関する追加情報があれば、随時 &lt;a href=&quot;https://x.com/MercariGears&quot; title=&quot;@MercariGears&quot;&gt;@MercariGears&lt;/a&gt; でお知らせしますので、気になる方はぜひフォローをお願いします。&lt;/p&gt;
</content:encoded></item><item><title>モジュラモノリスの品質を支えるリーダビリティチーム ― AI時代のスケーラブルなコード管理</title><link>https://engineering.mercari.com/blog/entry/20251021-scaling-code-quality-modular-monolith-readability-team-ai-era/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251021-scaling-code-quality-modular-monolith-readability-team-ai-era/</guid><description>&lt;p&gt;1. イントロダクション こんにちは、Cross Border(XB) Engineeringのバックエンドエンジニアのosari.kです。本日は私が所属するリーダビリティチームの活動と、具体例として開発したバックエンド [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 21 Oct 2025 10:43:01 GMT</pubDate><content:encoded>&lt;h2&gt;1. イントロダクション&lt;/h2&gt;
&lt;p&gt;こんにちは、Cross Border(XB) Engineeringのバックエンドエンジニアのosari.kです。本日は私が所属するリーダビリティチームの活動と、具体例として開発したバックエンドの共通パッケージについて紹介します。&lt;/p&gt;
&lt;p&gt;メルカリグローバルアプリは、開発複雑性を抑えながら拡張性を保つためモジュラモノリスアーキテクチャを採用しています。モジュール間の依存関係を厳格化するため、システムはBFF層とTier1-4の階層構造で構成され、リクエストは上位から下位Tierへ流れます。モジュール間通信はProtocol Buffer + gRPCで標準化されています。詳しくは&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251003-mercari-crossborder/&quot; title=&quot;ブログシリーズ&quot;&gt;ブログシリーズ&lt;/a&gt;をご参照ください:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251007-a09afcd49b/&quot; title=&quot;グローバル展開にむけたアプリと基盤の再構築&quot;&gt;グローバル展開にむけたアプリと基盤の再構築&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251007-behind-the-infrastructure-powering-global-expansion/&quot; title=&quot;グローバル展開を支える基盤の裏側&quot;&gt;グローバル展開を支える基盤の裏側&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;しかし、モジュラモノリスを採用するだけでは、マイクロサービス開発で発生した課題を解決できません。サービス間の差異によるメンテナンスコストやオンボーディングコストの増加は、モジュール間でも同様に発生する可能性があります。これらを解決するには、コードのリーダビリティと一貫性の維持が不可欠です。&lt;/p&gt;
&lt;p&gt;そこで、グローバルアプリ開発の当初から&lt;strong&gt;リーダビリティチーム&lt;/strong&gt;が組成されました。このチームは、モジュラモノリスの利点を最大限活かすため、Backendシステム全体のコードのリーダビリティを改善・維持する役割を担っています。メンバーは、アーキテクト、SREメンバー、バックエンドエンジニアで構成され、日本とインドの両拠点から参加しています。&lt;/p&gt;
&lt;p&gt;一貫性のあるコードを維持することで、開発者を柔軟にアサインでき、効率的な配置が可能になります。また、AI Agentを活用した開発においても、明確なガイドライン、自動チェック、一貫したコードベースがAIの活用効果を高めます。&lt;/p&gt;
&lt;h2&gt;2. リーダビリティチームの役割と活動&lt;/h2&gt;
&lt;h3&gt;2.1 目的&lt;/h3&gt;
&lt;p&gt;リーダビリティチームの主な目標は、コードの可読性を向上させることです。可読性が高く、一貫性のあるコードは、以下の効果をもたらします:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新しいエンジニアの学習曲線を緩和し、オンボーディングを加速&lt;/li&gt;
&lt;li&gt;組織内での担当変更やチーム間の貢献を円滑化&lt;/li&gt;
&lt;li&gt;バグの検出や修正を容易にし、開発の品質とスピードを向上&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 活動内容&lt;/h3&gt;
&lt;p&gt;リーダビリティチームは以下の活動を通じて、コードの品質向上と開発スケールを支援しています:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;コードレビュー&lt;/strong&gt;: 複雑度の高いPRを中心にレビュー&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ガイドラインの作成と維持&lt;/strong&gt;: コードおよびPRガイドラインをGitHubリポジトリに集約&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自動化ツールの開発&lt;/strong&gt;: CIによる自動チェックを実装&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ワークショップの開催&lt;/strong&gt;: ベストプラクティスの共有&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隔週ミーティング&lt;/strong&gt;: ガイドライン、課題、改善点の議論&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;メトリクス駆動の改善&lt;/strong&gt;: 開発ボトルネックの特定と継続的な改善&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;中でもメトリクス駆動の改善については、GitHubのメトリクスを活用して目標を設定し、定期的にボトルネックを分析しています。特に以下の2つのメトリクスの改善に取り組んできました:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一人あたり週何個のPull Requestをマージできたか&lt;/li&gt;
&lt;li&gt;Pull RequestをOpenしてからマージまでの時間&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらを可視化することで、開発プロセスのボトルネックを特定し、継続的な改善につなげています。&lt;/p&gt;
&lt;h2&gt;3. AI時代のスケーラブルな品質管理&lt;/h2&gt;
&lt;p&gt;開発初期はメンバーが少なく、リーダビリティチームがほぼ全てのPull Requestをレビューできていました。しかし、開発が本格化し、開発メンバーの増加やAI Agent活用の普及により、PRのボリュームが急増し、従来の方法では対応できなくなってきました。&lt;/p&gt;
&lt;p&gt;幸い、ガイドラインをGitHubリポジトリに集約・維持していたため、LLMを活用したレビューの自動化基盤は整っていました。課題は、この基盤でどうスケーラブルに品質を担保するかでした。以下、具体的な取り組みをいくつか紹介します。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude活用&lt;/strong&gt;&lt;br /&gt;
ClaudeでPull Requestを複雑性とサイズの観点から自動分類し、ラベル付けを行っています。Claudeは、PRをより小さい単位に分割する助言も提供します。&lt;/p&gt;
&lt;p&gt;このラベルを使い、複雑度が高いPull Requestを優先的にレビューできるようになりました。&lt;/p&gt;
&lt;p&gt;また、GitHubに集約したガイドラインを活用したClaudeによるレビューの自動化で、コードレビューとガイドラインの適用を効率化しています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;効率的レビュー&lt;/strong&gt;&lt;br /&gt;
リーダビリティチームのレビューは必須ではありません。少なくとも一人のコードオーナーからレビューをもらえばPRはマージできます。これにより、リーダビリティチームが開発のボトルネックになりません。&lt;/p&gt;
&lt;p&gt;一方で、マージされたPRの中で複雑度が高いものは後追いでレビューします。こうした複雑なPRから課題を検知し、新しいパターンやライブラリを導入することで、今後の開発を改善していきます。このアプローチで、効率的にレビューリソースを配分し、最も重要な部分に注力できています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自動化&lt;/strong&gt;&lt;br /&gt;
ガイドラインを全て把握するのは困難なため、機械的にチェック可能なルールはCIに組み込んでいます。&lt;/p&gt;
&lt;p&gt;例えば、データベースのスキーマ設計では、社内のDatabase リライアビリティエンジニアと相談し、&lt;a href=&quot;https://future-architect.github.io/arch-guidelines/documents/forDB/postgresql_guidelines.html&quot; title=&quot;PostgreSQL ガイドライン&quot;&gt;PostgreSQL ガイドライン&lt;/a&gt;などを参考にガイドラインを策定しました。これを&lt;a href=&quot;https://www.sqlfluff.com/&quot; title=&quot;SQL Fluff&quot;&gt;SQL Fluff&lt;/a&gt;のプラグインとして実装し、自動チェックを行っています。&lt;/p&gt;
&lt;p&gt;これにより、レビュアーの負担を軽減し、人間のレビュアーはより高度な設計判断やアーキテクチャの妥当性に集中できます。&lt;/p&gt;
&lt;h2&gt;4. Feature Flag伝搬システムの設計&lt;/h2&gt;
&lt;p&gt;ここからは、リーダビリティチームが設計した品質管理の具体例として、Backendシステム全体で利用されるFeature Flag伝搬の仕組みを紹介します。&lt;/p&gt;
&lt;h3&gt;4.1 背景: Experiment Platformとモジュラモノリスアーキテクチャ&lt;/h3&gt;
&lt;p&gt;グローバルアプリでは、新機能のリリースを安全に行うため、メルカリの基盤システムである&lt;strong&gt;Experiment Platform&lt;/strong&gt;を利用しています。この中でもFeature Flagを活用しており、お客さまごとに各種機能のオン・オフを管理できます。&lt;/p&gt;
&lt;p&gt;Feature Flagを使うことで、Web UI上で機能をオンにするお客さまを段階的に増やせるため、新機能のリリースを様子を見ながら進めることができます。問題が発生した場合も、システム自体を再デプロイすることなく、Web UI上で機能をオフに設定し、即座に無効化できます。Experiment Platformは割当(Assignments)を管理し、システム側でこの割当を取得して実装に組み込みます。&lt;/p&gt;
&lt;p&gt;Feature FlagはMobileアプリやWebアプリでも利用されています。これらのクライアントアプリでは、1人のお客さまが継続して利用するため、アプリ起動時などにFeature Flagを取得してキャッシュできます。一方、バックエンドシステムではリクエストごとにお客さまが異なるため、Experiment PlatformのAPIを毎回呼び出してデータを取得する必要があります。&lt;/p&gt;
&lt;p&gt;前述の通り、グローバルアプリのBackend systemではモジュラーモノリスを採用しており、クライアントアプリからの１リクエストが内部的には複数のモジュールへのリクエストを発生させます。&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/8eabf989-fig1-product-page-flow.png&quot; alt=&quot;図1. 単純化した商品ページの例&quot; width=&quot;800&quot; class=&quot;size-medium wp-image-29132&quot; /&gt;&lt;br /&gt;
&lt;br /&gt;図1. 単純化した商品ページの例
&lt;/div&gt;
&lt;p&gt;全てのモジュールがFeature FlagのAssignmentsを必要とする可能性があります。 このようなアーキテクチャのため、もし各モジュールが毎回Experiment Platformからデータを取得する場合、APIのレイテンシがその分増加してしまいます。&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/2ecb117a-fig2-latency-issue.png&quot; alt=&quot;図2. 各モジュールがExperiment Platformを呼び出すとレイテンシが増加&quot; width=&quot;800&quot; class=&quot;size-medium wp-image-29132&quot; /&gt;&lt;br /&gt;
&lt;br /&gt;図2. 各モジュールがExperiment Platformを呼び出すとレイテンシが増加
&lt;/div&gt;
&lt;p&gt;グローバルアプリの開発が進み、Feature Flagを利用する状況になってきた中で、レイテンシの増加を防ぐため、効率的にAssignmentsを参照するメカニズムとガイドラインが必要になりました。&lt;/p&gt;
&lt;h3&gt;4.2 設計要件と実装&lt;/h3&gt;
&lt;p&gt;Assignmentsの効率的な参照メカニズムを設計するにあたり、以下の要件を定義しました:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Experiment Platformを一度だけ呼び出す&lt;/li&gt;
&lt;li&gt;ネットワークトラフィックを抑える&lt;/li&gt;
&lt;li&gt;割当に型安全なアクセス手段を提供する&lt;/li&gt;
&lt;li&gt;既存のコードが割当を使いたくなった場合のコードの変更量を最小限にする&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの要件を満たすために、 最終的に採用した解決策は、以下の3つの要素を組み合わせたものです:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;gRPC metadataを使ってモジュール間のAssignments伝搬&lt;/strong&gt;: シリアライズしたAssignmentsをgRPC metadata（HTTP Header）で運ぶ
&lt;ul&gt;
&lt;li&gt;BFF層のServer InterceptorがExperiment PlatformのAPIを呼び出し、Assignmentsを取得&lt;/li&gt;
&lt;li&gt;各モジュールのClient interceptorがAssignmentsをシリアライズしてgRPC metadataに設定&lt;/li&gt;
&lt;li&gt;BFF層以外のServer InterceptorがgRPC metadataから取得し、デリリアライズしてAssignmentsを取得&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;context.Contextを使ってモジュール内のAssignments伝搬&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Server InterceptorがAssignmentsを&lt;code&gt;context.Context&lt;/code&gt;に格納&lt;/li&gt;
&lt;li&gt;アプリケーションロジックはAssignmentsを&lt;code&gt;context.Context&lt;/code&gt;から取得&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protocol Bufferでの型定義&lt;/strong&gt;: Assignmentsを明示的に定義し、型安全性を確保かつコンパクトなシリアライズを実現&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/62997b06-fig3-solution.png&quot; alt=&quot;図3. BFFがExperiment Platformを呼び出し、AssignmentsをgRPC metadataで伝搬&quot; width=&quot;800&quot; class=&quot;size-medium wp-image-29132&quot; /&gt;&lt;br /&gt;
&lt;br /&gt;図3. BFFがExperiment Platformを呼び出し、AssignmentsをgRPC metadataで伝搬
&lt;/div&gt;
&lt;p&gt;BFFモジュールがExperiment platformのAPIを呼び出しAssignmentsを取得し、それを下位Tierのモジュールを呼び出すときにgRPC metadataで伝搬しています。 先程の図と異なり、BFFのみがExperiment platformを呼び出すため、レイテンシへの影響を抑えることができています。&lt;/p&gt;
&lt;p&gt;モジュール内の伝搬に&lt;code&gt;context.Context&lt;/code&gt;を利用しているので、アプリケーションロジックがAssignmentsを参照したくなった場合も、コードの変更は最小限に抑えられます。&lt;/p&gt;
&lt;p&gt;一方でAssignmentsをリクエストに付与することでネットワークトラフィックには影響があります。そこで効率的なシリアライズ方法が必要です。&lt;/p&gt;
&lt;p&gt;Experiment PlatformのAssignmentsは、パラメータ名と値のkey-value形式です。Feature Flagの値は&lt;code&gt;&amp;quot;true&amp;quot;&lt;/code&gt;、&lt;code&gt;&amp;quot;false&amp;quot;&lt;/code&gt;、または未割り当ての3つの状態を取ります。未割り当ては、段階的リリースでお客さまの一部のみが割当対象になっている場合に発生します。&lt;/p&gt;
&lt;p&gt;例えば以下のような形式です（&amp;quot;feature_flag1&amp;quot;は未割り当てのため含まれていない）:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;feature_flag2&amp;quot;: &amp;quot;false&amp;quot;,
  &amp;quot;feature_flag3&amp;quot;: &amp;quot;true&amp;quot;,
  &amp;quot;foo&amp;quot;: &amp;quot;10&amp;quot;,
  &amp;quot;bar&amp;quot;: &amp;quot;OK&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当初は&lt;code&gt;map[string]string&lt;/code&gt;のJSON文字列としてシリアライズする案を検討しました。しかし、この方法では&lt;strong&gt;パラメータ名の長さがシリアライズ結果に影響し&lt;/strong&gt;、数百〜数千のパラメータをサポートする場合に破綻します。&lt;br /&gt;
そこで、&lt;strong&gt;AssignmentsをProtocol Bufferで明示的に定義し、シリアライズする&lt;/strong&gt;ことにしました。&lt;br /&gt;
上記の例をProtocol Bufferで定義するとこのようになります:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-proto&quot;&gt;message Assignments {
  optional bool parameter1 = 1;
  optional bool parameter2 = 2;
  optional bool parameter3 = 3;
  optional int64 foo       = 4;
  optional string bar      = 5;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この方法により、以下の利点が得られます:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;コンパクトなシリアライズ結果&lt;/strong&gt;: パラメータ名はシリアライズ後のバイト長に影響を与えることがなくなり、Experiment Platform利用者はわかりやすい名前を使うことができます&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;型安全性&lt;/strong&gt;: &lt;code&gt;optional&lt;/code&gt;をつけることで、未割り当ての場合とfalseを区別できます。&lt;code&gt;*bool&lt;/code&gt;型とすることで、Experiment Platformの利用時に陥りやすい罠である、未割り当て（&lt;code&gt;nil&lt;/code&gt;）と&lt;code&gt;false&lt;/code&gt;の区別が明示的になりました&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Protocol Bufferで明示的に定義する方法は、新規パラメータ追加時にProtoファイルの変更が必要という欠点があります。しかし、どちらの方法でもGoコードの変更とデプロイは必要です。一方で、&lt;strong&gt;どのパラメータがどこで利用されているか明確に把握できる&lt;/strong&gt;利点を我々は重視しました。&lt;/p&gt;
&lt;h3&gt;4.3 検討の詳細&lt;/h3&gt;
&lt;p&gt;ここでは設計で候補案として上がったものとの比較内容を通じて検討の詳細の一部を紹介します。&lt;/p&gt;
&lt;h4&gt;4.3.1 in-memory cache案&lt;/h4&gt;
&lt;p&gt;gRPC metadataを使ったモジュール間の伝搬以外の方法として、in-memory cacheを利用したデータ共有という案がありました。 Experiment Platform ClientをProxyしてin-memory cacheのデータを返すことで、API呼び出しのレイテンシを削減する方法です。&lt;/p&gt;
&lt;p&gt;in-memory cacheの場合、モジュール間でAssignmentsの伝搬をする必要がないため、ネットワークトラフィックを抑えることも可能です。 特に、多くのモジュールがAssignmentsを利用しない場合にgRPC metadataを使う方法に対して優位性があります。&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/059170eb-fig4-alt-cache.png&quot; alt=&quot;図4. in-memory cacheを利用した方法（BFFとTier2 ProductだけがAssignmentsを利用する場合）&quot; width=&quot;800&quot; class=&quot;size-medium wp-image-29132&quot; /&gt;&lt;br /&gt;
&lt;br /&gt;図4. in-memory cacheを利用した方法（BFFとTier2 ProductだけがAssignmentsを利用する場合）
&lt;/div&gt;
&lt;p&gt;最終的には、Protocol bufferを利用したassignmentsのシリアライズによりネットワークトラフィックの優位性が小さいこと、 &lt;strong&gt;分割して運用が可能なモジュラモノリス&lt;/strong&gt;を実現していく中で、モジュール間でcacheが共有されないケースが増えていくことが予想されるため gRPC metadata を利用した方法を選択しました。&lt;/p&gt;
&lt;h4&gt;4.3.2 明示的なリクエストフィールド案&lt;/h4&gt;
&lt;p&gt;gRPC metadataではなく、Protocol Bufferで明示的なリクエストフィールドとして定義する案も検討しました。グローバルアプリBackendでは、API仕様の可視性や型安全性の観点から、一般的には明示的なフィールドを選択しています。&lt;/p&gt;
&lt;p&gt;しかし、この方法には全てのモジュールの全てのエンドポイントにAssignmentsフィールドを定義しなければならないという問題があります。Tier2のProductモジュールがAssignmentsを必要とする場合、中継するだけのTier1のエンドポイントにも同様のフィールドが必要になります。すでに多くのエンドポイントが開発されており、全てに修正を加えるのは現実的ではありませんでした。&lt;/p&gt;
&lt;p&gt;また、Protocol Bufferを利用することでgRPC metadataの長さも十分小さくなることが分かったため、gRPC metadataによる伝搬を選択しました。&lt;/p&gt;
&lt;h3&gt;4.4 将来対応&lt;/h3&gt;
&lt;p&gt;gRPC metadataを使うため、Headerのサイズ制限に引っかからないよう対策を講じています。ユニットテストで最大バイト長になりうるシリアライズ結果がしきい値を超えていないか検知できるようにしています。&lt;/p&gt;
&lt;p&gt;現在は全てのパラメータが&lt;code&gt;optional bool&lt;/code&gt;型であるため非常にコンパクトですが、しきい値を超えた場合の対応策として、どのモジュールがどのパラメータを参照しているかを静的に解析し、必要なパラメータだけを伝搬するように機能を拡張する予定です。Protocol Bufferで明示的にパラメータを定義しているため、Assignmentsの参照の解析が容易になります。これも、明示的な定義を選択した利点の一つです。&lt;/p&gt;
&lt;h2&gt;5. まとめ&lt;/h2&gt;
&lt;p&gt;本記事では、メルカリグローバルアプリのリーダビリティチームの活動と、Feature Flag伝搬システムの設計について紹介しました。&lt;/p&gt;
&lt;p&gt;リーダビリティチームは、モジュラモノリスアーキテクチャの利点を最大限活かすため、コードの可読性と一貫性を維持し、開発をスケールさせる役割を担っています。GitHubメトリクスを活用した継続的な改善、Claudeによる複雑度分析、CIによる自動チェックなど、様々な活動を通じて品質を担保しています。&lt;/p&gt;
&lt;p&gt;Feature Flag伝搬システムでは、以下を重視しました:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protocol Bufferによる明示的な型定義&lt;/strong&gt;: 型安全性と利用状況の可視化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gRPC metadataとcontext.Contextによる透過的な伝搬&lt;/strong&gt;: 既存コードへの影響を最小限に&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの設計判断は、技術的な実装の問題だけでなく、開発チーム全体のスケーラビリティを考慮したものです。&lt;/p&gt;
&lt;p&gt;AI時代において、ガイドラインの明文化、自動チェック、一貫したコードベースの維持は、AI Agentを活用した開発の重要な基盤となります。リーダビリティチームは、開発の品質とスピードの両立を目指して活動を続けていきます。&lt;/p&gt;
&lt;p&gt;最後までお読みいただき、ありがとうございました。&lt;/p&gt;
</content:encoded></item><item><title>グローバルなメルカリの検索バックエンド設計と検索基盤拡充</title><link>https://engineering.mercari.com/blog/entry/20251016-50fb7b8c1a/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251016-50fb7b8c1a/</guid><description>&lt;p&gt;こんにちは。検索領域でエンジニアをやっております、shinpeiです。 本記事は連載企画：メルカリ初の世界共通アプリ「メルカリ グローバルアプリ」の開発舞台裏の一環として、メルカリグローバルアプリの検索バックエンドをスク [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 16 Oct 2025 16:23:35 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。検索領域でエンジニアをやっております、shinpeiです。&lt;/p&gt;
&lt;p&gt;本記事は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251003-mercari-crossborder/&quot;&gt;連載企画：メルカリ初の世界共通アプリ「メルカリ グローバルアプリ」の開発舞台裏&lt;/a&gt;の一環として、メルカリグローバルアプリの検索バックエンドをスクラッチで開発することに伴い、大事にした設計のポイントをご紹介します。また今回の新たな要求を契機に既存の検索基盤の拡充が必要だったのでそれについても書かせていただきました。&lt;/p&gt;
&lt;h1&gt;グローバルアプリでの検索の要件と課題&lt;/h1&gt;
&lt;p&gt;先日、弊社からの発表の通り、メルカリはグローバルアプリの提供を開始しました。これに合わせて、検索もグローバルな検索を作るべく準備をすすめてきました。&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251007-a09afcd49b/&quot;&gt;グローバル展開にむけたアプリと基盤の再構築&lt;/a&gt;で紹介したようにグローバルアプリを提供するにあたりバックエンド基盤の再構築を行っています。基盤を活かしながら必要な部分は新しく作り直すアプローチをとっており、検索バックエンドもそれに合わせて再設計を行いました。最低要件は、3年で50カ国展開。出品数は累計40億です（2025年9月時点）。日本事業（日本のお客さま向けメルカリアプリ）の検索との違いは複数ありますが、主なものは以下です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;複数国展開をするための多言語対応&lt;/li&gt;
&lt;li&gt;検索対象として、アイテムモデルとプロダクトモデルとの両立&lt;/li&gt;
&lt;li&gt;越境事業においてフォーカスしていく際のエンタメホビー領域に特化できるような独自の検索結果チューニング&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1.は日本語以外を扱うための要件です。世界展開となれば、言語の壁は避けては通れない課題です。全文検索はそれぞれの言語自体を処理する必要があります。中国語のクエリ、英語のクエリ、フランス語のクエリ。これらを商品情報とマッチさせる必要があります。現在は日本で出品された日本語の商品をメインに扱っていますが、長期的には日本語以外の商品を扱う可能性も考慮する必要がありました。&lt;/p&gt;
&lt;p&gt;2.は検索エンジンで検索対象として扱うデータモデルの話です。アイテムとは、現在のメルカリの主流である、単一の商品が検索対象となるようなデータモデルです。一方、プロダクトモデルとは、一つの検索対象だけど、例えば色違い（バリアント）が複数存在させることができるようなデータモデルです。C2Cの商品はアイテムモデルで対応できるのですが、B2Cの商品の一部はバリアントを持つため、プロダクトモデルが必要になります。メルカリはC2Cマーケットプレイスとしてスタートしたため、日本事業の検索では今もなお、アイテムモデルで動いていますが、グローバルアプリではアイテム、プロダクト双方を同等に扱える必要があります。&lt;/p&gt;
&lt;p&gt;3.はグローバルアプリは検索結果を独立してコントロールしたいという意味です。グローバル展開を考えたときに国ごとのチューニングや事業の方向性に合わせた柔軟性が必要になります。例えば、クエリを自分たちで組み立てて、自由に検索結果をチューニングもできるように作る必要があります 。検索は日本事業と越境事業両方で重要な機能であり、開発の優先度やリソースなどい違いに影響を受けないようにすることも重要です。&lt;/p&gt;
&lt;h1&gt;Vertex AI Search for Commerce の採用&lt;/h1&gt;
&lt;p&gt;日本事業の検索ではElasticsearchを使っています。グローバルアプリでは日本にある商品全体を扱う予定だったので、売上に比べてデータ量が桁違いに大きい状態です。売上とコストを両立させるにはElasticsearchの再利用くらいしか思いつきませんでしたが、多言語対応や独立性の確保、リリースまでの時間と開発リソースを考えると、どうしてもその前の交通整理に長い時間がかかります。どうやるか思案していた時に、思いがけず案に上がってきたのがGoogleが提供してるVertex AI Search for Commerce（以下、VAIS:C)です。今回の例では以下の点で魅力的でした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;フルマネージドな小売サイト向け検索サービス&lt;/li&gt;
&lt;li&gt;リクエストベースの課金モデル（トラフィックがなければ課金されない）&lt;/li&gt;
&lt;li&gt;多言語対応あり&lt;/li&gt;
&lt;li&gt;入力するデータはプロダクトモデル（バリアントあり）&lt;/li&gt;
&lt;li&gt;ある程度の検索結果チューニングが可能&lt;/li&gt;
&lt;li&gt;ユーザーイベントベースの検索結果最適化あり&lt;/li&gt;
&lt;li&gt;グローバルエンドポイント（世界中から使える）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これだ！と思いました。要件とVAIS:Cができることを見比べていただければわかりますが、グローバルアプリにとって必要な要求をほぼ満たしており、ネックなのはコストだけです。そこで我々が立てた戦略は以下です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VAIS:Cを導入し、素早くスモールスタートする&lt;/li&gt;
&lt;li&gt;トラフィックが伸びてきて、ペイできないレベルになれば、Elasticsearchに切り替える&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この方法であれば、Elasticsearch側をグローバルアプリに適用するために必要な準備期間も稼げると思いました。一方で、ビジネス側の懸念は現在の日本事業の検索と、同様の売り上げを得る検索結果をだすことができるかどうか、でした。そこはやってみなければわからなかったので、A/Bテストを行うことになりました。&lt;/p&gt;
&lt;p&gt;簡単にVAIS:Cを説明しますと、商品情報と、ユーザーイベントを入れると、最適な検索結果が返ってくるというものです。最適化に必要となるユーザーイベントは、Google Analytics 4(以下、GA4)で収集したものが前提となってましたが、スキーマさえ合わせれば独自イベントの入力も可能でした。メルカリはGA4ではなく、独自のイベント基盤を持っているため、なんとか変換して動かしました。（Google Cloud Japanの皆様には多大なるご協力をいただきまして、大変お世話になりました。）&lt;/p&gt;
&lt;p&gt;結果的には、我々の商品情報と、なんとか変換したユーザーイベントでは、日本事業の検索よりも結果が劣ることを確認できました。また、後述するいくつかの要件を満たすことが難しいこともわかりました。ですが、このリスクを補ってあまるメリットがあるのも事実です。（これを全部、私一人でできてるのですから！）検索エンジンが変わることでのリスクは合意した上で、グローバルアプリの初期リリースはVAIS:Cで行う、と決めました。&lt;/p&gt;
&lt;h1&gt;新しい検索バックエンドの設計&lt;/h1&gt;
&lt;p&gt;検索エンジンの目処が立ったので、次はバックエンドの話です。ここでは、特にこだわった点をご紹介します。&lt;br /&gt;
私は7年前、現在の日本事業の検索システムの設計にも関わっていました。当時は理想や期待を込めて設計しましたが、実際に運用を続ける中で「これは設計から見直した方がいい」と感じる部分も多く、今回はそれらを改善する形にしています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;データモデルに依存したAPI&lt;/p&gt;
&lt;p&gt;データを取得するタイプのインターフェースは”どんなデータ”を対象とするかでガラッと設計が変わります。汎用的になものを作ろうとすると、抽象化が進みすぎて、余計わかりにくくなってしまいがちです。対象が無限に増えるわけではないので、汎用化はせずに、たとえばProductをSearchするためのエンドポイント、というふうに割り切りました。Productのデータモデルは決まっています。タイトルがあって、商品の説明があって、値段があって、、、と、どこに行っても同じです。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;柔軟な処理を可能にするプラグイン機構&lt;br /&gt;
ElasticsearchやSolrなどにもあるように、検索クエリや結果に対して独自の処理を加えたいというニーズはどの検索システムにもあります。日本事業の検索開発でも、これまで多くの条件分岐（if文）がコードに埋め込まれ、整理が難しくなっていました。そこで、今回のグローバルアプリ向け検索バックエンドでは、クエリの前処理・後処理をフックできるプラグイン機構を導入しています。検索機能の開発者は、必要な処理をプラグインとして追加するだけで拡張が可能です。多くのソフトウェアでも似たような仕組みが使われていますが、結局この方法に行き着くよな、という実感があります。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;検索エンジン機能の実装と抽象をわける&lt;br /&gt;
基本的なロジックは抽象層にまとめ、実装部分は切り替え可能な構成にしています。もともと、VAIS:CとElasticsearchの両方で動く必要がありましたが、この要件がなくても同じ設計にしたと思います。というのも、検索エンジンの世界はまさに“戦国時代”で、Vector検索に強い新しいエンジンなど、次々と登場しています。現在はElasticsearchを使っていますが、将来的に他の選択肢が有力になる可能性もあります。このため、抽象化しておくことで、後々の移行や比較コストを大きく減らせると考えてます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;その他&lt;br /&gt;
グローバルアプリでは、もともとバックエンド全体に関する設計方針が明確にあります (&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251007-behind-the-infrastructure-powering-global-expansion/&quot;&gt;グローバル展開を支える基盤の裏側&lt;/a&gt;を参照)。このおかげで、何がどこにあるかが、ある程度明確になってます。特に大事だと思うのは、BFFの切り分けです。BFFはここにあるよ（逆にいうと、ここにしかないよ）というのは、見る箇所が限定できるので探索のコストが減るのです。これまでのマイクロサービスでは、どこの誰が検索APIを叩いて、それをお客さまに出しているかをこちらから把握するのは困難でした。検索バックエンドでも、ドメイン特化のノウハウがあります。何をどこに実装するかを明確に提示してるのは新しい検索バックエンドの特徴です。バックエンド全体の設計でも、期せずして同じようなことをしているのは、これまでの苦痛が似ているからだと思います。だれでもなんでもどこにでも作れるというのは、メンテナンスコストを上げる結果になっているという経験則だと思います。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;プロダクトオーナーからの想定外の要求で冷や汗&lt;/h1&gt;
&lt;p&gt;最初はVAIS:Cを使うとはいえ、長期的にはElasticsearchに切り替えなければなりません。私はこれまで、Elasticsearch as a Service (以下、EaaS) という、&lt;a href=&quot;https://github.com/elastic/cloud-on-k8s&quot;&gt;ECK&lt;/a&gt;などをベースに作った社内のElasticsearchホスト基盤を開発＆運用してきました。チームの活躍で、グループ内のほぼすべての検索サービスはEaaSの上で動いてると言えるところまで来ています。関連するエコシステムも充実させてきました。カスタマイズ性も高く、実際、メルカリの複数のビジネスで使われていることがその証左です。グローバルアプリの準備期間中、責任者である@Suwenさんと直接話す機会が多く、EaaSはなんでもできるので安心してください、と伝えてました。そこで何度もフィードバックされたのが、以下のような点です。&lt;/p&gt;
&lt;p&gt;「日本事業のこの検索機能、グローバルアプリでも使いたいな」「日本事業のこの機能、グローバルアプリだったらこういうふうにカスタマイズしたらいいよね」&lt;/p&gt;
&lt;p&gt;さぁ、ここで私の過去の記事を読んでいただけたみなさまなら、私が冷や汗を流した理由がおわかりですね？EaaSは、Elasticsearchの提供が主であり、その上に作られた機能の再利用までは責任を持っていません。もちろん、プラットフォームとして、機能の再利用性を無視していたわけじゃありません。Elasticsearchにはプラグイン機構があり、実際にそのプラグインを用いた機能なら再利用できます。しかしメルカリではJavaエンジニアは少数派で、Elasticsearchプラグインの開発はほぼ進みませんでした。機能のほぼ全てはGoのマイクロサービスに作り込まれています。以下に、課題感を説明するのに使ったスライドの一部を晒します。日本事業の作り込んだ検索機能資産が使いたいけど、使えないという状態です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/b3e2bd1d-screenshot-2025-10-10-at-21.29.05.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;実はこの問題は以前にもぶつかってました。広告事業からも、EaaSを使ってもらっていたのですが、ビジネス側からの期待値は、EaaSを使えば、日本事業の検索と同じクオリティの検索結果がでてくると思われていたのです。実際には、EaaSは日本事業の検索と同じ検索結果を提供するわけではないので、この時は日本向けに作られたシノニムや日本語形態素解析などのライブラリを広告チームのバックエンドから再利用してもらう形で落ち着きました。この頃にスライドを書き、日本の検索チームへ、Goで作られた検索機能の再利用性を高めるべきだとの問題提起はしてきました。が、当時の日本事業の優先度的には、再利用性にリソースを割いてもらうことはなかなか難しかったのです。&lt;/p&gt;
&lt;h1&gt;All for oneで検索基盤の拡充へ&lt;/h1&gt;
&lt;p&gt;私はここからは迷走しました。日本事業の開発や優先度を邪魔しない範囲で、なんとか機能の再利用化を進める案を捻り出そうとしました。一時は、必要な機能をElasticsearchプラグインで書き直すか、とまで考えていました。ここで肝となった問題点を整理します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EaaSは最初から複数事業での利用を見据えた設計であるが、その上に作った日本事業の検索サービスは日本事業に強く結合しているため、他事業からの再利用が困難である&lt;/li&gt;
&lt;li&gt;日本事業の検索は7年運用してきたコードベースであり、前述のとおり、うまく整理されていないなどの負債がある&lt;/li&gt;
&lt;li&gt;日本事業の検索は引き続き重要であり、このコードベース自体を再利用できるように改変しつつ、日本事業向けの開発を同時並行するのは困難&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;グローバルアプリの技術責任者である@deeeeetとも議論を重ねて、以下のような案を出しました。いまあるEaaSの上に、再利用可能な機能を一個ずつ切り出して使用するというものです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/e37dbfed-screenshot-2025-10-10-at-21.29.30.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;日本事業側にも問題と案を再掲示し、議論を重ねました。最終的に、グローバルアプリの優先度が上がったこともあり、日本事業側からも積極的に動いてくれて、日本事業の検索機能を再利用するための新しい層を作ることが決まりました。この層に関しては、主導権があるわけではないので、私からはEaaSというプラットフォームが大事にしているところをなるべく共有して、いまのところ共通した方針のもと一緒に作っています。具体的には以下です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Platformがその下で使われてるオープンソースソフトウェアに必要以上の制約を加えないこと&lt;br /&gt;
ここでいうオープンソースソフトウェアとは、我々の場合はElasticsearchです。Elasticsearchには我々がただ使ってないだけで、まだまだたくさんの可能性があります。これを現時点では使わないからという理由で上のプラットフォームを経由すると使えなくなるのは本末転倒だと思ってます。実はここは議論になりやすい点で、制約を加えた方が不可測事態が避けられて、メンテが楽になるという話もありますので、慎重な説得と周りの理解が必要です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;各ビジネスドメインから独立して使えること&lt;br /&gt;
共通基盤を作って、それを複数のオーナーから利用する時に問題になるのは機能の開発優先度だと思います。事業Aは規模が小さいから後回し。事業Bのオーナーは発言力があるから、優先。。などなど、いわゆる”政治”になりやすいところだと思います。完全な独立は不可能ですが、これをなるべく避けるためには、基盤への開発が開かれていることが重要です。開かれていると言っても、自由に作れるとはまた違います。ブログの前段で説明した通り、どこでもだれでもいじれると収拾がつかなくなってしまいます。それを解決する一つの手はプラグイン機構です。どこをどうフックするかはまだまだ議論していく必要があるのですが、基本的な方針に関しては同意できています。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;こうして、現在のグローバルアプリの検索は、VAIS:CとEaaSの上に作られた新しい仕組みとのハイブリッドで稼働しています。&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;今回はグローバルアプリでの新たな検索要求に対してどのような方針で対処したのかについて書かせていただきました。従来の日本事業の検索とは毛色の違う要求をこなすために、フルマネージドの検索サービスである、VAIS:Cを一時的に導入しました。また、グローバルアプリの開発要求を起点に議論が広がり、社内検索基盤を拡充させることで、今後の要求についても対処できるように基盤を作っていることについても触れさせていただきました。具体的な多言語対応方法など、今回、書ききれなかった部分もあるので、また次回以降でご紹介できればいいなと思ってます。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;”メルカリの検索基盤の変遷について” &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20220207-776318b784/&quot;&gt;https://engineering.mercari.com/blog/entry/20220207-776318b784/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;月間2000万人が使う　メルカリ検索機能のアーキテクチャ &lt;a href=&quot;https://findy-tools.io/articles/mercari-elasticsearch/38&quot;&gt;https://findy-tools.io/articles/mercari-elasticsearch/38&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>開発者全員が書けるE2Eテスト ─ 普通のgo testで実現するテスト基盤</title><link>https://engineering.mercari.com/blog/entry/20251016-e2e-tests/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251016-e2e-tests/</guid><description>&lt;p&gt;はじめに こんにちは、Cross Border (XB) EngineeringでSRE &amp;amp; Enablingを担当している@ryotaraiです。 本記事は連載企画：メルカリ初の世界共通アプリ「メルカリ グロー [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 16 Oct 2025 12:00:06 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは、Cross Border (XB) EngineeringでSRE &amp;amp; Enablingを担当している&lt;a href=&quot;https://twitter.com/ryot_a_rai&quot;&gt;@ryotarai&lt;/a&gt;です。&lt;/p&gt;
&lt;p&gt;本記事は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251003-mercari-crossborder/&quot;&gt;連載企画：メルカリ初の世界共通アプリ「メルカリ グローバルアプリ」の開発舞台裏&lt;/a&gt;の一環として、このプロジェクトのバックエンドAPIのE2E（End-to-End）テストについて深掘りします。特に、開発者全員がメンテナンスできるE2Eテスト基盤をどのように実現したのか、その設計思想と実装について紹介します。&lt;/p&gt;
&lt;h2&gt;なぜE2Eテストの改善が必要だったのか&lt;/h2&gt;
&lt;h3&gt;従来のE2Eテストが抱えていた課題&lt;/h3&gt;
&lt;p&gt;バックエンドAPIのE2Eテストは、システム全体が正しく動作することを確認する重要な役割を担っています。しかし、多くのプロジェクトで以下のような課題に直面します:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;セットアップの複雑さ: テスト環境の準備に時間がかかり、開発者が気軽に実行できない  &lt;/li&gt;
&lt;li&gt;並列実行の難しさ: テスト間でリソースの競合が発生し、実行時間が長くなる  &lt;/li&gt;
&lt;li&gt;属人化: QAチームが主にメンテナンスし、開発者が触りにくい  &lt;/li&gt;
&lt;li&gt;学習コストの高さ: 専用のフレームワークやDSLを学ぶ必要がある&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;このプロジェクトでも当初、これらの課題に直面していました。特に、QAチームのみがE2Eテストのメンテナンスを担当する状況では、以下の問題が発生します:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;APIの変更時に、E2Eテストの更新が後回しになる  &lt;/li&gt;
&lt;li&gt;テストの追加に時間がかかり、カバレッジが低下する  &lt;/li&gt;
&lt;li&gt;開発者がテストの実装を理解せず、デバッグが困難になる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;目指した姿：開発者全員が参加できるE2Eテスト&lt;/h3&gt;
&lt;p&gt;私たちが目指したのは、QAチームだけでなく、APIのコードを書いている開発者全員がE2Eテストのメンテナンスを行える体制です。&lt;/p&gt;
&lt;p&gt;これを実現するためには、以下の要件を満たす必要がありました：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;開発者が日常的に使っている技術でテストを書ける  &lt;/li&gt;
&lt;li&gt;学習コストが低く、すぐに書き始められる  &lt;/li&gt;
&lt;li&gt;IDEの補完やリファクタリング機能が使える  &lt;/li&gt;
&lt;li&gt;ローカルでもCIでも同じように実行できる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;フレームワークの設計思想&lt;/h2&gt;
&lt;h3&gt;「普通のgo testで書ける」という哲学&lt;/h3&gt;
&lt;p&gt;メルカリ グローバルアプリのバックエンドAPIは、Goで実装されています。そのため、最終的に私たちは普通のGoコード（go test）でE2Eテストを書く方式を選択しました。その理由は：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;学習コストがゼロ: 開発者は既にgo testの書き方を知っている  &lt;/li&gt;
&lt;li&gt;型安全性: &lt;a href=&quot;https://connectrpc.com/docs/introduction/&quot;&gt;Connect&lt;/a&gt;のクライアントコードをそのまま使え、コンパイル時に型をチェックできる  &lt;/li&gt;
&lt;li&gt;IDEの恩恵: 補完、リファクタリング、定義ジャンプなどが使える  &lt;/li&gt;
&lt;li&gt;デバッグのしやすさ: 通常のGoプログラムとしてデバッグできる  &lt;/li&gt;
&lt;li&gt;既存コードの活用: テストヘルパー関数やモックなどを再利用できる&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;この決定により、E2Eテストを特別なものではなく、日常の開発フローの一部にすることができました。&lt;/p&gt;
&lt;p&gt;私たちが実装したE2Eテストフレームワークの核心は、「普通のgo testで書ける」という設計思想です。&lt;/p&gt;
&lt;p&gt;実際のテストコード例を見てみましょう：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func TestUpdateNickname(t *testing.T) {
    t.Parallel()

    tests := []struct {
        name     string
        userID   int64
        nickname string
        wantCode connect.Code
    }{
        {
            name:     &amp;quot;Success&amp;quot;,
            userID:   createTestUser(t).ID,
            nickname: &amp;quot;NewNickname&amp;quot;,
            wantCode: connect.CodeOK,
        },
        {
            name:     &amp;quot;Blank nickname returns error&amp;quot;,
            userID:   readonlyUser().ID,
            nickname: &amp;quot;&amp;quot;,
            wantCode: connect.CodeInvalidArgument,
        },
        {
            name:     &amp;quot;Non-logged in user returns error&amp;quot;,
            userID:   0,
            nickname: &amp;quot;TestNickname&amp;quot;,
            wantCode: connect.CodeUnauthenticated,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()

            testenv.Run(t, func(params env.RunParams) {
                client := accountv1connect.NewBFFAccountServiceClient(
                    http.DefaultClient,
                    params.Server.URL,
                )

                req := connect.NewRequest(&amp;amp;accountv1.UpdateNicknameRequest{
                    Nickname: tt.nickname,
                })

                if tt.userID != 0 {
                    // 認証ヘッダーを設定
                    setAuthHeader(t.Context(), req.Header(), tt.userID)
                }

                _, err := client.UpdateNickname(t.Context(), req)
                if connect.CodeOf(err) != tt.wantCode {
                    t.Errorf(&amp;quot;error code = %v, want %v&amp;quot;,
                        connect.CodeOf(err), tt.wantCode)
                }
            })
        })
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このコードは、go testのテーブル駆動テストという標準的なパターンで書かれています：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;t.Parallel()&lt;/code&gt;で並列実行を指定（通常のgo testと同じ）  &lt;/li&gt;
&lt;li&gt;テストケースを構造体のスライスで定義  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;t.Run()&lt;/code&gt;でサブテストを実行し、各サブテストも並列実行  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;testenv.Run()&lt;/code&gt;の中でテストサーバのURLを取得  &lt;/li&gt;
&lt;li&gt;Connectの自動生成されたクライアントをそのまま使用  &lt;/li&gt;
&lt;li&gt;通常のgo testと同じアサーション&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;E2Eテスト特有の複雑な記述はほとんどなく、普段のユニットテストと同じように書けることが分かります。&lt;/p&gt;
&lt;p&gt;さらに、普通のGoコードで書けるということは、Claude CodeなどのAIコーディングツールも効果的に活用できるということです。テストケースの追加やエッジケースの洗い出しなど、AIの支援を受けながら効率的にテストを書くことができます。&lt;br /&gt;
QAチームなど、バックエンドエンジニア以外でGo言語に慣れていないメンバーもいますが、AIを活用することでテストコードを作成できるようになっています。&lt;/p&gt;
&lt;p&gt;実際に、既存のJestで実装されていたE2Eテストをこのフレームワークに移行する際にも、AIを大いに活用しました。既存のテストコードを参照しながら、AIがGoのテストコードを生成し、開発者がレビュー・調整することで、移行作業を効率的に進めることができました。&lt;/p&gt;
&lt;h3&gt;全体アーキテクチャ&lt;/h3&gt;
&lt;p&gt;E2Eテストの実行方法として、全員がアクセスできる共有のdevelopment環境にデプロイしたアプリケーションに対してテストを実行するという選択肢もあります。しかし、この方法では開発中のバックエンドの変更に対してすぐにテストを実行できないという問題があります。&lt;/p&gt;
&lt;p&gt;私たちは、アプリケーションコードを変更しながらE2Eテストを通したり、追加・修正できたりする環境を重視しました。そのため、テストごとにサーバを動的に起動する設計を採用しています。これにより、開発者は自分の変更をすぐにE2Eテストで検証でき、テスト駆動での開発が可能になります。&lt;/p&gt;
&lt;p&gt;フレームワークの主な責務は：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;テストサーバの自動起動と管理: 必要に応じてサーバを起動し、プールで管理  &lt;/li&gt;
&lt;li&gt;データベースの自動準備: AlloyDB Omniを起動し、論理データベースを作成してマイグレーションを実行  &lt;/li&gt;
&lt;li&gt;並列実行のサポート: 複数のテストが同時に実行できるようリソースを管理  &lt;/li&gt;
&lt;li&gt;クリーンアップの自動化: テスト終了時に自動的にデータをクリーンアップし、リソースをプールに返却&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;開発者から見ると、これらの複雑な処理は完全に隠蔽されており、&lt;code&gt;testenv.Run()&lt;/code&gt;を呼ぶだけでテスト環境が整う仕組みになっています。&lt;/p&gt;
&lt;h2&gt;内部実装の工夫&lt;/h2&gt;
&lt;p&gt;ここからは、フレームワークがどのように並列実行とリソース管理を実現しているか、内部実装を見ていきます。&lt;/p&gt;
&lt;h3&gt;リソースプールによる並列実行&lt;/h3&gt;
&lt;p&gt;E2Eテストの並列実行を実現するために、サーバをプール管理しています。&lt;/p&gt;
&lt;p&gt;重要なのは、&lt;code&gt;testenv.Run()&lt;/code&gt;に渡した関数が終了すると、自動的にサーバがプールに返却される仕組みです。開発者は明示的にリソースを返却する必要がなく、通常のテストと同じように書くだけで、フレームワークが自動的にクリーンアップとプールへの返却を行います。&lt;/p&gt;
&lt;p&gt;この仕組みにより：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;並列実行時にリソースの競合が発生しない  &lt;/li&gt;
&lt;li&gt;サーバの起動コストを最小化（プールから再利用）  &lt;/li&gt;
&lt;li&gt;テスト間のデータ汚染を防ぐ（TRUNCATEで初期化）  &lt;/li&gt;
&lt;li&gt;リソース管理が透明（開発者は意識する必要がない）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;データベースの管理&lt;/h3&gt;
&lt;p&gt;データベースについては、AlloyDB Omniのコンテナは1つだけ起動します。その中で、テストごとに論理データベースを自動的に作成し、マイグレーションを実行します。&lt;/p&gt;
&lt;p&gt;この設計により:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;データベースコンテナの起動コストを削減（1つだけ起動すればよい）  &lt;/li&gt;
&lt;li&gt;並列実行でもデータが分離される（論理DBごとに独立）  &lt;/li&gt;
&lt;li&gt;マイグレーションの実行も自動化（開発者は意識不要）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;論理データベースもプールで管理されており、テスト終了後はTRUNCATEでデータをクリーンアップしてから再利用されます。&lt;/p&gt;
&lt;h3&gt;コードカバレッジの収集&lt;/h3&gt;
&lt;p&gt;このフレームワークは、Go 1.20以降で導入された&lt;a href=&quot;https://go.dev/doc/build-cover&quot;&gt;&lt;code&gt;go build -cover&lt;/code&gt;&lt;/a&gt;をサポートしています。&lt;/p&gt;
&lt;p&gt;通常のテストカバレッジ（&lt;code&gt;go test -cover&lt;/code&gt;）はテストコード内での実行しか計測できませんが、E2Eテストではサーバプロセスとして実行されているコードのカバレッジを計測する必要があります。これを実現するのが&lt;code&gt;go build -cover&lt;/code&gt;機能です。&lt;/p&gt;
&lt;p&gt;フレームワークの実装では:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;サーバごとに独立したカバレッジディレクトリを自動作成
&lt;ul&gt;
&lt;li&gt;各サーバ起動時に一時ディレクトリを作成  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;GOCOVERDIR&lt;/code&gt;環境変数を自動設定  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;並列実行でもカバレッジが正確に収集される
&lt;ul&gt;
&lt;li&gt;各サーバが独立したディレクトリに書き込むため、競合しない  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;テスト終了時に自動マージ
&lt;ul&gt;
&lt;li&gt;すべてのサーバのカバレッジデータを&lt;code&gt;go tool covdata merge&lt;/code&gt;で統合  &lt;/li&gt;
&lt;li&gt;最終的に1つの統合されたカバレッジデータが生成される&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;開発者は特定の環境変数を設定するだけで、複数サーバのカバレッジを自動的に収集・マージできます:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# カバレッジ付きでサーババイナリをビルド
go build -cover -o server ./server

# カバレッジを収集しながらテスト実行
GLOBAL_GOCOVERDIR=/tmp/coverage go test ./e2etest/...

# カバレッジレポート生成
go tool covdata percent -i /tmp/coverage&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この仕組みにより、E2Eテストでのコードカバレッジを正確に計測し、APIの品質を定量的に評価できるようになりました。&lt;/p&gt;
&lt;h2&gt;Kubernetes上での実行&lt;/h2&gt;
&lt;p&gt;E2Eテストは、開発環境ではローカルで実行し、CIではKubernetes上で実行します。ここでは、Kubernetes上での実行方法について、いくつかの興味深い工夫を紹介します。&lt;/p&gt;
&lt;h3&gt;go test -c を使った高速なデプロイ&lt;/h3&gt;
&lt;p&gt;通常、Kubernetes上でテストを実行する場合、以下の手順を踏むかと思います:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;コンテナイメージをビルド  &lt;/li&gt;
&lt;li&gt;イメージをレジストリにプッシュ  &lt;/li&gt;
&lt;li&gt;Kubernetes Podでイメージをプル  &lt;/li&gt;
&lt;li&gt;コンテナを起動&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;しかし、この方法には各ステップに時間がかかるという問題があります。E2Eテストでは実行速度が重要なので、私たちは異なるアプローチを取りました:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# テストバイナリをビルド
go test -c \
    -o package/e2etest \
    ./path/to/e2etest

# サーババイナリをビルド
go build \
    -o package/server \
    ./path/to/server

# tarでアーカイブしてkubectl execで転送
tar -czf - -C ./package . | \
    kubectl exec -c main -i -n ${POD_NAMESPACE} ${POD_NAME} -- \
    tar xzf - -C /tmp/e2e

# Pod内で直接実行
kubectl exec -c main -it -n ${POD_NAMESPACE} ${POD_NAME} -- \
    /path/to/entrypoint.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;go test -c&lt;/code&gt;を使うことで、テストコードを実行可能なバイナリにコンパイルできます。これにより:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;コンテナイメージのビルドが不要  &lt;/li&gt;
&lt;li&gt;レジストリへのプッシュ・プルが不要  &lt;/li&gt;
&lt;li&gt;kubectl execで直接ファイルを転送&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この方法により、テスト実行までの時間を大幅に短縮できました。具体的には1分半程度でビルドからテストの開始ができています。&lt;/p&gt;
&lt;p&gt;なお、Kubernetes上で実行する理由は、並列実行に必要な十分なリソースを確保するためです。並列度を上げるとレースコンディションを防ぐためにその数だけサーバが必要となり、必要なリソースが線形に増えるため、Kubernetesクラスタを使用しています。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;本記事では、メルカリ グローバルアプリのバックエンドAPIのE2Eテストについて紹介しました。&lt;br /&gt;
このアプローチにより、E2Eテストは特別なものではなく、日常の開発フローの一部となります。開発者はAPIを変更した際に、躊躇なくE2Eテストを追加・修正できるようになっています。&lt;/p&gt;
&lt;p&gt;もちろん、まだ改善の余地はあります。例えば:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;テストの実行時間のさらなる短縮
&lt;ul&gt;
&lt;li&gt;AIを利用して、変更箇所に関係するテストのみを実行する取り組みを進めています  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;テストデータのセットアップの簡略化  &lt;/li&gt;
&lt;li&gt;テスト結果のレポーティング&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;しかし、開発者体験を最優先にした設計により、持続可能なE2Eテスト基盤を構築できたと考えています。&lt;/p&gt;
&lt;p&gt;同じような課題を抱えているプロジェクトの参考になれば幸いです。&lt;/p&gt;
</content:encoded></item><item><title>グローバルWebを支えるSREの裏側 — 開発を加速させるための改善アプローチ</title><link>https://engineering.mercari.com/blog/entry/20251013-behind-the-scenes-of-sre-supporting-the-global-web/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251013-behind-the-scenes-of-sre-supporting-the-global-web/</guid><description>&lt;p&gt;Cross Border (XB) EngineeringでSRE &amp;amp; Enablingをしているhatappiです。私たちのチームはSREのみではなく、Team TopologiesにおけるEnablingチー [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 13 Oct 2025 10:00:45 GMT</pubDate><content:encoded>&lt;p&gt;Cross Border (XB) EngineeringでSRE &amp;amp; Enablingをしている&lt;a href=&quot;https://x.com/hatappi&quot;&gt;hatappi&lt;/a&gt;です。私たちのチームはSREのみではなく、&lt;a href=&quot;https://teamtopologies.com/&quot;&gt;Team Topologies&lt;/a&gt;におけるEnablingチームとしての役割を持っています。SREとして信頼性の向上やパフォーマンスの改善を行うだけではなく、XBのプロダクトチームの開発者体験と効率を向上させ、よりスムーズかつ高速に価値提供できるよう、技術的な課題解決や環境整備を通じて支援 (Enable) します。 &lt;/p&gt;
&lt;p&gt;2025年7月にPlatform NetworkチームからXB SRE &amp;amp; Enablingチームへ異動し、最初の仕事としてメルカリ グローバルWebの立ち上げに携わっています。本記事は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251003-mercari-crossborder/&quot;&gt;連載企画：メルカリ初の世界共通アプリ「メルカリ グローバルアプリ」の開発舞台裏&lt;/a&gt;の一環として、アプリと同時に開発が進んでいるグローバルWebをテーマに、新しい環境でSREとして価値を出すために私が実践しているアプローチをご紹介します。&lt;/p&gt;
&lt;h2&gt;Enablingのための課題発見アプローチ&lt;/h2&gt;
&lt;p&gt;7月にXB SRE &amp;amp; Enablingチームへ異動してきて、私の最初のミッションは「グローバルWeb の立ち上げをEnablingすること」でした。しかし異動して間もないためどのような課題や改善ポイントがあるか分からない状態でした。そこでまずは現状を正しく理解することが不可欠だと考え、私は2つのアプローチをとりました。&lt;/p&gt;
&lt;h3&gt;1. まずは自分でやってみる&lt;/h3&gt;
&lt;p&gt;グローバルWebのメンバーがどういった過程で困っていて何を改善できるかを知り、共感するためには、自分で体験してみるのが早いです。そこで１つの機能開発タスクに取り組みました。これによりただローカル環境をセットアップして動作確認するという点だけでなく実装をしてプルリクエストを作成して、レビューを受け、マージするまでの一連の開発サイクルを体験しました。&lt;/p&gt;
&lt;p&gt;このアプローチによって「CIの実行が遅く、フィードバックを得るまでの待ち時間が長い」「ローカルサーバーの起動が遅い」など様々な改善点を特定できました。&lt;/p&gt;
&lt;h3&gt;2. 現場の声を聞く&lt;/h3&gt;
&lt;p&gt;自分１人の体験だけでは、どうしても視野が狭くなってしまいます。特に日常業務としてグローバルWebを開発をしているメンバーの声を聞くのはとても重要です。Slackで改善点を聞いたりプランニングやレトロスペクティブの会議に参加するなどして情報を収集しました。&lt;/p&gt;
&lt;p&gt;これらにより最初のアプローチでは発見できなかった課題を見つけられただけでなく優先度の選定にもつながりました。 例えばCIの実行時間が遅いという改善点を最初のアプローチで特定しました。これは間違いなく改善点です。しかし実際の開発でCIの実行時間が気になるのは、他のメンバーからレビューを受け取る最後の段階です。それよりも、時々不安定になって失敗するCIやローカルサーバーの起動時間の遅さのほうが、より大きな課題だと感じているようでした。&lt;/p&gt;
&lt;h3&gt;Platform Engineeringの経験から得た気づき&lt;/h3&gt;
&lt;p&gt;この2つのアプローチを実践する中で、以前のPlatform Networkチームでの経験を振り返る機会となりました。&lt;/p&gt;
&lt;p&gt;Platform NetworkチームではPlatform Engineeringとして、メルカリの複数のプロダクトで横展開できる共通基盤やツールを提供してきました。メルカリには複数のプロダクトがあり、それぞれが異なるコンテキストやドメイン知識を持っています。そのためPlatform側からすべてのプロダクトの現場に深く入り込んで理解することは難しいという課題がありました。&lt;/p&gt;
&lt;p&gt;今回、XBのSRE &amp;amp; Enablingとして２つのアプローチを実践することで、現場に深く入り込む重要性を再認識しました。一方で、Platform Engineeringの経験から、メルカリ全体を考えたときの横展開の重要性も理解しています。&lt;/p&gt;
&lt;p&gt;メルカリでも、両方の経験を持つエンジニアはまだ多くありません。だからこそ、今はXBで得た経験、例えば今回のグローバルWeb関連の改善内容を、Platformチームに積極的にフィードバックしながら一緒に改善を進めています。&lt;/p&gt;
&lt;h2&gt;AIを活用した課題解決&lt;/h2&gt;
&lt;p&gt;前のセクションの2つのアプローチによって、取り組むべき課題の解像度が大きく上がりました。しかし、改善すべき課題が分かっても、まだ異動して間もない私はグローバルWebやCross Borderのコンテキスト、Web関連の技術など多くの情報をキャッチアップする必要がありました。グローバルWebの立ち上げをスムーズにEnablingするためにもAIを活用することでこのプロセスをスムーズに進められないか試みました。&lt;/p&gt;
&lt;h3&gt;学習・調査&lt;/h3&gt;
&lt;p&gt;例えば CIの実行時間が遅い、という課題に取り組むとします。これを理解するためにはそもそもどのような仕組みで動いているのかを理解する必要があります。そこで下記に説明するように&lt;a href=&quot;https://claude.com/product/overview&quot;&gt;Claude&lt;/a&gt;、&lt;a href=&quot;https://claude.com/product/claude-code&quot;&gt;Claude Code&lt;/a&gt;を活用しながら必要な情報や改善策を検討していきました。&lt;/p&gt;
&lt;p&gt;まず初めに、Claude Codeを使用して既存のCI関連の設定を調査します。メルカリではGitHub Actionsを使用しているためActionの目的を尋ねたり、Job間の依存関係を確認するなどソースコードを読みながら理解を深めます。調査を進めていると、自分の知らない技術が出てきます。例えば&lt;a href=&quot;https://turborepo.com/&quot;&gt;Turborepo&lt;/a&gt;などです。&lt;/p&gt;
&lt;p&gt;知らない技術が出てきた時はそれを理解するためにも一次ソースである公式のドキュメントを読みます。その際にドキュメントの要約などのためにClaudeを活用します。それだけであればClaude Codeをそのまま使うこともできます。Claude を使用した理由は ClaudeのArtifacts機能を利用するためです。(Fig1) ArtifactsはClaudeとの会話中に作成・編集できる独立したコンテンツを作成するための機能です。これを使用することで知らなかった技術を深堀りながら最終的にまとまったドキュメントが完成するため後から見返しやすくなりました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/045563ff--2025-10-10-19.51.07.png&quot;&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/045563ff--2025-10-10-19.51.07.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
Fig1: Claude Artifacts&lt;/p&gt;
&lt;p&gt;最後のステップは改善のための調査です。改善方法の検討のための第一歩として一般的な改善策を収集するためにClaude Researchを活用しました。例えば「Turborepoを使ったリポジトリでCIを高速化する一般的な方法を調査」といった内容です。これにより、キャッシュ戦略の改善や並列実行の最適化など、複数のアプローチを短時間でリストアップでき、改善策の仮説を効率的に立てることが可能になりました。またArtifactsを活用することで調査した内容をもとに具体的な案など最終的な実装へ向けた情報を一緒にまとめることも可能でした。&lt;/p&gt;
&lt;h3&gt;実装・レビュー&lt;/h3&gt;
&lt;p&gt;改善の仮説が立ったら、次に行うのは実装です。調査でまとめた情報はArtifactsに存在しておりMarkdownとして出力できます。それをClaude、 GPT、 Geminiなど任意のツール・モデルで使用できます。&lt;/p&gt;
&lt;p&gt;私はメインでClaude Codeを利用していますが、その理由は&lt;a href=&quot;https://docs.claude.com/en/docs/claude-code/slash-commands&quot;&gt;Slash commands&lt;/a&gt;があるからです。Slash commandsはClaude Code上で&lt;code&gt;/&lt;/code&gt;から始まる特殊なコマンドで、Claude Codeで特定の操作を実行することができます。私は自分が開発の中で行うプロセスをこのSlash commandsに移行しています。 例えば変更内容からプルリクエストを作成するSlash commandがあります。このSlash commandには単なるプルリクエストの作成だけでなく変更内容からコミットメッセージを検討してコミットするなど私がよく行うステップを定義しています。&lt;/p&gt;
&lt;p&gt;実装が終わったらレビューです。 Claude Code 自身にもレビューはさせるのですが自分でもレビューします。今までは &lt;code&gt;git diff&lt;/code&gt; やエディターについている diff viewer などを使用していました。しかしローカルでレビューしたときは問題ないと思ったのにGitHubでプルリクエストを作成して再度レビューすると改善点に気づくということがよくありました。変更を加えて毎回pushしてプルリクエスト上で確認するのは時間がかかります。この問題を解決するために&lt;a href=&quot;https://www.npmjs.com/package/difit&quot;&gt;difit&lt;/a&gt;を使いはじめました。&lt;/p&gt;
&lt;p&gt;difitは、GitHub ライクなビューをローカル環境で実現してくれる CLI です。(Fig2)  npm パッケージとして追加されているのでインストールも簡単ですぐ使い始められます。GitHubライクなビューにより今までプルリクエスト上で行っていたことをローカルで行えるようになりました。またdifitはコメント機能がついており、追加したコメントはAIにプロンプトとして渡せるようなコピー機能がついています。おかげでClaude Codeで開発しながらレビューして改善というサイクルをローカルで完結するようになりました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/fe9cd280-screenshot.png&quot;&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/fe9cd280-screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
Fig2, difit (&lt;a href=&quot;https://github.com/yoshiko-pg/difit&quot;&gt;https://github.com/yoshiko-pg/difit&lt;/a&gt;)&lt;/p&gt;
&lt;h3&gt;デバッグ&lt;/h3&gt;
&lt;p&gt;最後はデバッグです。 私は普段 Chromeを使用することが多いです。Chromeを用いたデバッグではChrome DevToolsが欠かせません。しかし、様々な機能があるため、どの機能を使ってどこを見たら自分が知りたい情報を見ることができるのか毎回苦労していました。&lt;/p&gt;
&lt;p&gt;そこで最近リリースされた&lt;a href=&quot;https://www.npmjs.com/package/chrome-devtools-mcp&quot;&gt;Chrome DevTools MCP&lt;/a&gt;を活用してみました。これは、自然言語で指示するだけでMCP Serverを介してDevToolsを操作して必要な情報を引き出してくれる機能です。例えば、「グローバルWebのこのページのパフォーマンスをチェックして」と入力するだけで、関連する指標を分析してくれます。&lt;/p&gt;
&lt;p&gt;これにより毎回苦労していたDevToolsの操作をスムーズに行うことができ、問題発見までの時間を短縮することができました。&lt;/p&gt;
&lt;h2&gt;Enabling活動から得られた学び&lt;/h2&gt;
&lt;p&gt;今回のグローバルWebのEnablingを通じて学んだことが2つあります。&lt;/p&gt;
&lt;h3&gt;現場に入り一次情報に触れることの重要性&lt;/h3&gt;
&lt;p&gt;1つ目は、現場の一次情報に触れることで初めて、本当に解くべき課題とその優先順位が見えてくるという点です。もし私が客観的なメトリクスだけを見て判断していたら開発メンバーが感じていたCIの実行時間よりも「CIが時々不安定になること」や「ローカルサーバーの起動時間」がより問題であることに気づくのは難しかったでしょう。&lt;/p&gt;
&lt;h3&gt;新しい技術領域へ挑戦する際のAIの有効性&lt;/h3&gt;
&lt;p&gt;2つ目は、AIが新しい技術領域へ挑戦する際のハードルを下げてくれる、ということです。&lt;/p&gt;
&lt;p&gt;1つ目の学びの重要性を理解していても、実践するまでのハードルが高いと躊躇してしまいます。しかしAIを活用することでこの「最初の壁」を乗り越えやすくなったと感じました。もちろんAIがすべてを解決してくれるわけではないですが、まずは動くものを作ってから、その仕組みを深く理解していくという私の好きなアプローチが高速でできるようになったと感じています。&lt;/p&gt;
&lt;h2&gt;今後の展望&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251007-a09afcd49b/&quot;&gt;グローバル展開にむけたアプリと基盤の再構築&lt;/a&gt;でも触れられていたように、今後3年以内に50カ国・地域への展開が予定されており、これは技術的にも非常にチャレンジングです。このスピードで世界に展開するためには、どのような実装や設定が必要か、どのように効率化できるか、データはどこに配置するか、Webサーバーはどこに置くのか、CDNはどのように活用できるかなど、考えるべきことが多数あります。考えることが多いからこそ面白く、SRE &amp;amp; Enablingの腕の見せ所になると感じているので、非常に楽しみです。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;本記事では、Platform Networkチームから異動して新しい環境でグローバルWebのEnablingをどのように進めているかを紹介しました。&lt;/p&gt;
&lt;p&gt;2025年11月13日に、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます。私は異動前の Platform Networkチームの時に行ったCDNマイグレーションについて話します。 他にも面白そうなセッションが沢山あるのでぜひお越しください！&lt;/p&gt;
&lt;p&gt;参加登録はこちら 👉 &lt;a href=&quot;https://gears.mercari.com/&quot;&gt;https://gears.mercari.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;明日の記事は @gia さんです。引き続き&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251003-mercari-crossborder/&quot;&gt;連載企画：メルカリ初の世界共通アプリ「メルカリ グローバルアプリ」の開発舞台裏&lt;/a&gt;をお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>【mercari GEARS 2025】MECHANISM Stageのセッションをご紹介</title><link>https://engineering.mercari.com/blog/entry/20251010-mercarigears2025-mechanism-stage/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251010-mercarigears2025-mechanism-stage/</guid><description>&lt;p&gt;こんにちは！メルカリ Engineering Office の @mikichin です。 来る11月13日、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます！ 2018年に実 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 10 Oct 2025 11:00:21 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは！メルカリ Engineering Office の &lt;a href=&quot;https://x.com/chida_miki&quot; title=&quot;@mikichin&quot;&gt;@mikichin&lt;/a&gt; です。&lt;br /&gt;
来る11月13日、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます！&lt;/p&gt;
&lt;p&gt;&lt;iframe loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/TDXzEjwqbaw?si=QJTLP0JGhJtu2kIP&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;2018年に実施した「Mercari Tech Conf 2018」から7年の時を経て、久しぶりのオフラインでの開催となります。&lt;br /&gt;
テーマは「メルカリエンジニアリングの今」。&lt;br /&gt;
今年の全社的なテーマでもある「AI-Native」についてはもちろん、2018年以降メルカリグループのエンジニアリングがどのように変化してきたかを、技術・組織・カルチャーの観点からご紹介します。&lt;br /&gt;
オンライン配信はありませんので、ぜひ会場でご自身の目と耳で確かめてください！！&lt;/p&gt;
&lt;p&gt;会場は、メルカリのエンジニアリング組織における信念や行動の基盤となる共通認識を明文化した「Mercari Engineering Principles」をモチーフにした「PASSION Stage」「GROW Stage」「MECHANISM Stage」の3つのステージがあります。&lt;/p&gt;
&lt;p&gt;本記事では、「MECHANISM Stage」のセッションをご紹介！&lt;br /&gt;
まだ申し込みをされていない方も、興味のあるセッションがあるはずです。お申し込みは&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちらから&quot;&gt;こちらから&lt;/a&gt;お願いします。&lt;/p&gt;
&lt;h3&gt;13:00 &amp;#8211; 13:20　メルカリハロでのLLM活用&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/df42ca46-ogp_mechanism-1_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリハロはスキマバイト領域におけるメルカリの新規事業です。2024年の3月にサービスがローンチされて以来、成長を続けており、先日登録者数1200万人を突破しました。&lt;/p&gt;
&lt;p&gt;メルカリハロのMLチームはサービスローンチから約半年が経過した2024年10月のタイミングで始動し、以来、AI/MLを用いた多くのプロダクト改善に取り組んできました。&lt;/p&gt;
&lt;p&gt;本セッションではその中でも特にメルカリハロのLLMを用いた機能やそれらを支えるLLMOps基盤について紹介を行います。具体的にはLLMを用いた求人の自動作成機能である「かんたん求人作成」やLLMを用いた求人の掲載前リスク予測等についてです。メルカリハロでは既に多くのLLMを用いた機能をリリースしており、用途の異なる50種類以上のプロンプトを本番で運用しています。そのためプロンプトの品質を管理するためのLLMOps基盤も非常に重要です。&lt;/p&gt;
&lt;p&gt;本セッションを通して、メルカリハロでLLM実装を進める中で得られたLLMのプロダクト実装のための勘所や、プロンプト管理・自動評価基盤などの実践的なLLMOpsのためのTipsといった知見を共有できればと思います。&lt;/p&gt;
&lt;h3&gt;13:30 &amp;#8211; 13:50　FastlyからCloudflareへ 100%移行を達成したMercariのアプローチ&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/2f89dc7c-ogp_mechanism-2_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリでは、2023年よりCDNプロバイダーをFastlyからCloudflareへ段階的に移行を開始し、2025年現在、移行が完全に完了しました。&lt;br /&gt;
このセッションでは、安全かつスムーズに移行を進めるために実施したアプローチと、その過程で得た学びを共有します。&lt;br /&gt;
CDN プロバイダーの比較ではなく、移行プロセスを主に話すため、CDNプロバイダーの変更を考えていない方にも、移行に関する考え方やプロセスのヒントを持ち帰っていただけると考えています。&lt;/p&gt;
&lt;h3&gt;14:15 &amp;#8211; 14:35　The Invisible Backbone: AI-Native Observability for Modern Platforms&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/9d241e46-ogp_mechanism-3_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;もし、自動で設定され、変化にもシームレスに適応し、多すぎるアラートのノイズを一掃してくれるオブザーバビリティがあったら？本セッションでは、メルカリがどのようにAI-Nativeなプラットフォームを構築し、設定不要のモニタリング、一貫した可視性、そしてAI活用型のアラートを標準機能として実現したのかをご紹介します。&lt;/p&gt;
&lt;p&gt;信頼性が高く開発者にやさしいクラウドプラットフォームの未来を、自律型オブザーバビリティがどのように形作るのか。ぜひご参加のうえ、その目でご覧ください。&lt;/p&gt;
&lt;h3&gt;14:35 &amp;#8211; 15:05　Running 1000 End-To-End Web Tests Daily&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/68f5950d-ogp_mechanism-4_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Mercari USでは非常に多くの E2E Webテスト を実行していますが、それらを素早く、かつ本当に役立つテストにすることが大きな課題となっています。本セッションでは、プルリクエストごとにテストを実行する、新しいテストを追加する、各機能領域を対象にしたテストを実行するなど、私たちが行っている工夫をご紹介します。毎日何千件ものE2Eテストをどのように回しているのかを知りたい方には、まさにぴったりのセッションです。&lt;/p&gt;
&lt;h3&gt;15:15 &amp;#8211; 15:35　Mercari&amp;#8217;s Internationalization Journey&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/8bb9742c-ogp_mechanism-5_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この2年間、私たちは海外のお客さまがメルカリのマーケットプレイスで商品を購入することを可能にしてきました。&lt;br /&gt;
本セッションでは、プロダクトを海外展開していくための道のりを、とくにユーザー生成コンテンツ（UGC）の翻訳に焦点を当ててご紹介し、翻訳コストを100分の1に削減するうえでLLM（大規模言語モデル）がどのように役立ったかについてもお話しします。&lt;/p&gt;
&lt;h3&gt;16:00 &amp;#8211; 16:20　EGP &amp;#8211; Mercari’s CRM Platform: Built Once, Powering Many&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/276f4570-ogp_mechanism-6_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;もともとシンプルなハードコードのCRMとして生まれたEGPは、マーケター向けのスケーラブルでUI主導のプラットフォームへと進化してきました。システムが拡大するにつれ、とりわけEGPの利用規模が大きくなると、その複雑さが使いやすさや運用面での課題を引き起こすようになりました。本セッションでは、私たちがシステム設計やAI活用によるUI改善を通じてこれらの課題にどのように取り組んできたのか、そしてその過程でどのような学びを得たのかを共有します。&lt;/p&gt;
&lt;h3&gt;16:30 &amp;#8211; 16:50　Securing the Future of Workflow Automation and AI Agents&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/d89e574e-ogp_mechanism-7_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;企業がワークフローの自動化やAIエージェントを積極的に活用するにつれ、孤立したシステム、過剰な権限を持つエージェント、複雑に絡み合った権限モデルといった新たなリスクが生じています。本セッションでは、これらの課題をどう解決し、自動化とAIが持つ潜在能力を安全かつ最大限に引き出す方法を探ります。さらに、セキュアでスケーラブルな導入を実現しつつ、ユーザーが安心してイノベーションに取り組めるようにするための実践的なアプローチをご紹介します。&lt;/p&gt;
&lt;h3&gt;17:00 &amp;#8211; 17:20　AI/LLMが拓くデータ活用の新時代：人間とデータ分析AI エージェントが協業する分析基盤へ&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/49b455af-ogp_mechanism-8_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;私たちは、自然言語での対話を通じてデータ分析を行えるAIエージェント「Socrates」を開発・提供しています。Socratesの登場は、SQLクエリの生成から実行、結果の可視化や解釈までを誰でも簡単に実行できるような変革をもたらし、データ活用のハードルを大きく下げました。本セッションでは、Socratesが誕生した背景やSocratesを支える技術、そしてAIとの協業によってもたらされるデータ活用体験の未来像についてお話しします。&lt;/p&gt;
&lt;p&gt;「mercari GEARS 2025」のお申し込みは&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちらから&quot;&gt;こちらから&lt;/a&gt;。&lt;br /&gt;
また、その他のセッション紹介は下記をご確認ください。&lt;br /&gt;
PASSION Stageのセッション紹介は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251008-mercarigears2025-passion-stage&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;。&lt;br /&gt;
GROW Stageのセッション紹介は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251009-mercarigears2025-grow-stage&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;イベント詳細&lt;/h2&gt;
&lt;p&gt;開催日時：&lt;br /&gt;
2025年11月13日（木） 11:00-18:00&lt;/p&gt;
&lt;p&gt;概要：&lt;br /&gt;
mercari GEARS 2025は、メルカリグループのエンジニアリング組織の技術への挑戦と、カルチャーを体感する技術イベントです。&lt;br /&gt;
本イベントは、単なる情報伝達の場ではなく、エンジニアたちが出会い、経験を共有し、交流を通じて新たな機会が生み出されることを目的としています。&lt;/p&gt;
&lt;p&gt;参加費：無料&lt;br /&gt;
会場：TODA HALL &amp;amp; CONFERENCE TOKYO&lt;br /&gt;
参加方法：こちらの&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;ページ&quot;&gt;ページ&lt;/a&gt;にてお申し込みください。&lt;br /&gt;
【&lt;a href=&quot;https://gears.mercari.com/&quot; title=&quot;公式サイト&quot;&gt;公式サイト&lt;/a&gt;】&lt;/p&gt;
&lt;p&gt;本イベントに関する追加情報があれば、随時 &lt;a href=&quot;https://x.com/MercariGears&quot; title=&quot;@MercariGears&quot;&gt;@MercariGears&lt;/a&gt; でお知らせしますので、気になる方はぜひフォローをお願いします。&lt;/p&gt;
</content:encoded></item><item><title>【mercari GEARS 2025】GROW Stageのセッションをご紹介</title><link>https://engineering.mercari.com/blog/entry/20251009-mercarigears2025-grow-stage/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251009-mercarigears2025-grow-stage/</guid><description>&lt;p&gt;こんにちは！メルカリ Engineering Office の @mikichin です。 来る11月13日、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます！ 2018年に実 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 09 Oct 2025 11:00:30 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは！メルカリ Engineering Office の &lt;a href=&quot;https://x.com/chida_miki&quot; title=&quot;@mikichin&quot;&gt;@mikichin&lt;/a&gt; です。&lt;br /&gt;
来る11月13日、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます！&lt;/p&gt;
&lt;p&gt;&lt;iframe loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/TDXzEjwqbaw?si=QJTLP0JGhJtu2kIP&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;2018年に実施した「Mercari Tech Conf 2018」から7年の時を経て、久しぶりのオフラインでの開催となります。&lt;br /&gt;
テーマは「メルカリエンジニアリングの今」。&lt;br /&gt;
今年の全社的なテーマでもある「AI-Native」についてはもちろん、2018年以降メルカリグループのエンジニアリングがどのように変化してきたかを、技術・組織・カルチャーの観点からご紹介します。&lt;br /&gt;
オンライン配信はありませんので、ぜひ会場でご自身の目と耳で確かめてください！！&lt;/p&gt;
&lt;p&gt;会場は、メルカリのエンジニアリング組織における信念や行動の基盤となる共通認識を明文化した「Mercari Engineering Principles」をモチーフにした「PASSION Stage」「GROW Stage」「MECHANISM Stage」の3つのステージがあります。&lt;/p&gt;
&lt;p&gt;本記事では、「GROW Stage」のセッションをご紹介！&lt;br /&gt;
まだ申し込みをされていない方も、興味のあるセッションがあるはずです。お申し込みは&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちらから&quot;&gt;こちらから&lt;/a&gt;お願いします。&lt;/p&gt;
&lt;h3&gt;13:00 &amp;#8211; 13:40　Leader’s Talk: Moving Fast Without Breaking Things&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/238b2e00-ogp_grow-1_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリのバリューのひとつに「Move Fast（はやく動く）」があります。&lt;br /&gt;
その一方で、開発者が柔軟に複数のサービスを行き来できるようにすることも必要です。では、その両方をかなえるにはどうすればよいのでしょう？&lt;/p&gt;
&lt;p&gt;本セッションでは、AI時代においてメルカリのエンジニアリング組織がいかに開発プロセスを進化させ、スピードとレジリエンスのバランスを取ろうとしているのかを、リーダーたちが自身の視点から紹介します。&lt;br /&gt;
発表は主に英語で行いますが、日本語での質問も大歓迎です！&lt;/p&gt;
&lt;h3&gt;14:15 &amp;#8211; 14:35　Google Customer Engagement Suiteを使った顧客エンゲージメント変革&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/0ce25121-ogp_grow-2_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;2025年に行われたGoogle Cloud Next Tokyoにて、メルカリは基調講演とブレイキングセッションを通し、Google社が提供するCustomer Engagement Suiteを活用した顧客エンゲージメントの変革について発表しました。このセッションでは、その発表で紹介されたプロダクトがどのような方法で構築されたのかについて紹介します。&lt;/p&gt;
&lt;h3&gt;14:45 &amp;#8211; 15:05　PJ-Auroraが描く未来と、UI品質評価を自動化するエージェント開発&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/8db52528-ogp_grow-3_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリのモノづくりのアプローチを変えることを使命とする「PJ-Aurora」。本セッションではその未来像を紹介し、あわせてUI品質評価を自動化するAIエージェント開発の現在地をお話しします。AI-Native時代における品質保証の可能性を探る試みを共有します。&lt;/p&gt;
&lt;h3&gt;15:15 &amp;#8211; 15:35　なぜ、メルカリはノーコードを選ばなかったのか？ 社内問い合わせ工数を60％削減したLLM活用の裏側&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/4e3430e8-ogp_grow-4_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;生成AIブームの中、多くの企業がノーコードツールを試す一方で、メルカリは既存の生成LLMを社内データで徹底的にチューニングすることで、高い精度と柔軟性を追求してきました。本セッションでは、社内問い合わせ工数を60%削減したLLM活用事例「HiYo-Chan」を中心に、ノーコードツールでは実現できないメルカリ独自の技術的な工夫、そしてその裏側にあるビジョンまで、赤裸々にお話しします。&lt;/p&gt;
&lt;h3&gt;16:00 &amp;#8211; 16:40　AI Native への道のり ― 数字でみる全社推進と現場の実践&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/67235f9b-ogp_grow-5_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリグループは「AI-Native」を掲げ、AI を前提とした新しい開発の型づくりに挑戦しています。&lt;br /&gt;
AI を単なる効率化ツールではなく、プロセス設計の根幹に組み込み、企画から実装までの一連の開発を再構築する取り組みを全社横断で進めています。&lt;/p&gt;
&lt;p&gt;本セッションでは、その全体像をデータと実例で解像度高く共有します。&lt;br /&gt;
まず、AI 活用の広がりと生産性の変化を DX の定量データをもとに可視化し、組織カルチャーとしての AI 浸透の進化を振り返ります。&lt;br /&gt;
次に、AI Agent を前提とした開発を再現性高くスケールさせるための組織設計と運用、既存事業の開発体制を AI-Native へ進化させる実践、そしてその中で得た知見と課題を共有します。&lt;br /&gt;
最後に、新規事業開発を題材に、PM とエンジニアが生成 AI を活用して要件定義から実装までを一気通貫で進める最新の開発プロセスと、標準化ドキュメント「Agent Spec」の中身を紹介します。&lt;/p&gt;
&lt;p&gt;AI と共に開発の在り方を変革し、再現性ある生産性向上を実現する——メルカリの AI-Native な挑戦の最前線をお届けします。&lt;/p&gt;
&lt;h3&gt;17:00 &amp;#8211; 17:30　LTセッション&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/30dd4698-ogp_lightning-talks_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;6人のLTを行います。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;未定 / kuu&lt;/li&gt;
&lt;li&gt;GitHubリポジトリの調査から分かったマイクロサービス開発でのデータベースの課題 / Tomoyuki Koyama&lt;/li&gt;
&lt;li&gt;Claude Codeで仕様駆動開発をする中で考えたエンジニアの役割 / Toshiki Kawamura&lt;/li&gt;
&lt;li&gt;Mercari Ads Optimizations For Profitable Revenue Stream / Kumar Abhinav&lt;/li&gt;
&lt;li&gt;Exploring LLM-Driven Formal Verification for Robust Continuous Integration of Services / Cheng-Hui Weng&lt;/li&gt;
&lt;li&gt;Evaluations for LLM Apps / jd&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「mercari GEARS 2025」のお申し込みは&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちらから&quot;&gt;こちらから&lt;/a&gt;。&lt;br /&gt;
PASSION Stageのセッション紹介は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251008-mercarigears2025-passion-stage/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;。&lt;br /&gt;
MECHANISM Stageのセッション紹介は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251010-mercarigears2025-mechanism-stage/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;イベント詳細&lt;/h2&gt;
&lt;p&gt;開催日時：&lt;br /&gt;
2025年11月13日（木） 11:00-18:00&lt;/p&gt;
&lt;p&gt;概要：&lt;br /&gt;
mercari GEARS 2025は、メルカリグループのエンジニアリング組織の技術への挑戦と、カルチャーを体感する技術イベントです。&lt;br /&gt;
本イベントは、単なる情報伝達の場ではなく、エンジニアたちが出会い、経験を共有し、交流を通じて新たな機会が生み出されることを目的としています。&lt;/p&gt;
&lt;p&gt;参加費：無料&lt;br /&gt;
会場：TODA HALL &amp;amp; CONFERENCE TOKYO&lt;br /&gt;
参加方法：こちらの&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;ページ&quot;&gt;ページ&lt;/a&gt;にてお申し込みください。&lt;br /&gt;
【&lt;a href=&quot;https://gears.mercari.com/&quot; title=&quot;公式サイト&quot;&gt;公式サイト&lt;/a&gt;】&lt;/p&gt;
&lt;p&gt;本イベントに関する追加情報があれば、随時 &lt;a href=&quot;https://x.com/MercariGears&quot; title=&quot;@MercariGears&quot;&gt;@MercariGears&lt;/a&gt; でお知らせしますので、気になる方はぜひフォローをお願いします。&lt;/p&gt;
</content:encoded></item><item><title>グローバル展開を支える基盤の裏側</title><link>https://engineering.mercari.com/blog/entry/20251007-behind-the-infrastructure-powering-global-expansion/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251007-behind-the-infrastructure-powering-global-expansion/</guid><description>&lt;p&gt;Cross Border(XB) EngineeringでArchitect兼SREをしている yanolab です。 ブログシリーズ初日では、グローバル展開にむけた基盤の再構築としてメルカリにおける取り組みの遷移の紹介 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 08 Oct 2025 12:00:18 GMT</pubDate><content:encoded>&lt;p&gt;Cross Border(XB) EngineeringでArchitect兼SREをしている &lt;a href=&quot;https://x.com/yanolab&quot;&gt;yanolab&lt;/a&gt; です。&lt;/p&gt;
&lt;p&gt;ブログシリーズ初日では、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251007-a09afcd49b/&quot; title=&quot;グローバル展開にむけた基盤の再構築&quot;&gt;グローバル展開にむけた基盤の再構築&lt;/a&gt;としてメルカリにおける取り組みの遷移の紹介がありましたが、本記事ではグローバル展開を支える基盤の裏側と題して、バックエンドシステムのアーキテクチャーやフレームワーク、取り組みなどを少し掘り下げて紹介したいと思います。&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;メルカリにおいては長らくMicroserviceアーキテクチャを採用して運用し、そのエコシステムにも投資をしてきました。echoサービスと呼ばれるMicroserviceのテンプレートや、GoでMicroserviceの開発を行うためのSDK、基本的なインフラ関係の設定をまとめたスターターキットと呼ばれるTerraformのモジュール、Kubernetesの設定を抽象化し、少ない記述でDeploymentを管理できるSDKなどがあります。また、Microserviceのリリースに際してはProduction Readiness Check(PRC)と呼ばれるプロセスがあり、新しく開発されたプロダクトやMicroserviceはこのチェックリストに合格する必要があります。これらのエコシステムやプロセスは成熟してきたものの、複雑化したエコシステムは学習コストを高め、肥大化したPRCによって、１つのMicroserviceを立ち上げるのには最低でも3ヶ月かかるようになってしまっていました。また、新しくビジネスを立ち上げる際は構築初期の人数は少ないにもかかわらず、数十のMicroserviceを立ち上げなければならない場合が多く、そのような場合においてMicroservice数×3ヶ月という工数は現実的ではなく、直近のメルカリの新規ビジネスはMonolith的なアプローチを採用することが多くなってきています。(ref: &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20240529-mercari-hallo-tech-stacks/&quot; title=&quot;メルカリ ハロの技術スタックとその選定理由&quot;&gt;メルカリ ハロの技術スタックとその選定理由&lt;/a&gt;)&lt;br /&gt;
グローバル展開にむけた基盤の再構築においては将来的に現在のメルカリMarketplaceと同じような規模になることが想定されるため、単純なMonolithではなく、既存のエコシステムを最大限活用しつつ、Microservice的な運用ができるような特殊なModular Monolithを設計し、運用しています。&lt;/p&gt;
&lt;h2&gt;分割して運用が可能なModular Monolith&lt;/h2&gt;
&lt;p&gt;メルカリのMicroservicesを想定したエコシステムは１レポジトリ、１サービスが基本となっており、大規模かつ複雑な構成のシステムは想定されていません。例えばCI/CDでは１バイナリ、１コンテナ、１Deploymentを想定しています。このような環境から逸脱する場合、システムの実装側で独自にワークフローを作成しメンテナンスを行う必要があります。Cross Borderチームでは独自でメンテナンスし続けるコストを回避するため、この方針に従いつつ、将来のビジネスの成長に伴う運用負荷の分散のため、Microservice的な運用ができるようにシステムを一つのバイナリにコンパイルするが、設定ファイルでモジュールの有効・無効を切り替えられるようにしています。また、モジュール間のインタフェースはProtocol Bufferで定義し、その通信はgRPCを利用するようにすることで、同じインスタンス内の通信にとらわれず、運用の自由度を高めています。これによって、１バイナリ、１コンテナとして既存のCIビルドシステムを利用しつつ、設定ファイルでモジュールをオン・オフしたり、モジュール間の通信相手を任意に設定したMicroservices的な運用を可能にしています。また、モジュール間のインターフェースをProtocol Bufferにしたことで、モジュールの独立性を高めつつモジュールのインタフェースの設計からチームで連携しながらモジュールの開発を行えるようになっています。(Fig. 1)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/1e5201e8-modular-monolith-with-flexible-deployment-1024x399.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align:center;&quot;&gt;Fig.1 Modular Monolith with Flexible Deployments&lt;/p&gt;
&lt;p&gt;新基盤のデータベースにはAlloyDBを用いています。過去のメルカリのMonolithにおいては、システム全体で共有のデータベースを用いており、ドメインごとのテーブルの連結や権限に制限はありませんでした。そのため、サービスが成長するにつれてドメイン間の依存度は高まり、運用コストは増大していきました。それに対してMicroserviceへ移行した際には多くのサービスやチームで、SpannerやCloudSQLが採用されてきました。サービス毎に独自にデータベースを持ってチームで運用することは、ドメインやサービスの独立性が高く、オーナーシップやメンテナンスの面でとても優れている選択でした。しかし、それぞれのチームで独自にデータベースを持ち、少ないリクエストでも安定運用のためにHA構成としなければならないということはコストの面から見ると非効率で、リクエストの少ないサービスでは特にコスト的に無駄が多い状態となっていました。そこで、Cross Borderチームでは、コスト節約のためなるべく同じクラスタを利用するが、モジュールごとにサービスアカウントを分けアクセスできるデータベースを制限し、データベースもモジュール単位で分割することとしました。これによって、コストを抑えつつ将来の分割やスケールに備えています。(Fig. 2)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/b71fa8d2-db-isolation-1024x479.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align:center;&quot;&gt;Fig.2 DB Isolation&lt;/p&gt;
&lt;p&gt;従来、メルカリではMicroserviceの設定を環境変数にて行ってきましたが、Monolithになった場合は設定が非常に多くなり、環境ごとの設定の管理なども煩雑になってしまうことが予想されました。そこで、設定ファイルには&lt;a href=&quot;https://cuelang.org/&quot;&gt;CUE lang&lt;/a&gt;を採用し、デフォルトの設定をシングルソースで管理できるようにし、開発環境や本番環境など、環境ごとに違う値のみを差分管理できるようにしています。これらの設定ファイルはコンテナのビルド時にコンテナに同梱され、環境に応じて、ローカル環境であればローカル用の設定、開発環境・本番環境であればそれに応じた設定が自動的に使われるようになっています。また、実行時にCUE/YAMLで標準の設定を上書きをできるようにすることで、Deploymentごとに違う設定を行うことも可能にしています。(Fig. 3)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/3ec88abb-difference-managemnt-of-config-1024x480.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align:center;&quot;&gt;Fig. 3 Difference management of config&lt;/p&gt;
&lt;p&gt;例えば開発環境と本番環境の標準の設定を標準のコンフィグとして下記（Fig. 4）のように定義します。この場合、ProductモジュールのProductInventoryアプリケーションはSearchモジュ ールのアドレスとしてlocalhostを利用します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cuelang&quot;&gt;
#GRPCClientConfigSpec: {
    address: string | *&amp;quot;localhost:\(#HTTPPort)&amp;quot;
    timeout: =~&amp;quot;^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$&amp;quot; | int | *&amp;quot;3s&amp;quot;
    retry:   int &amp;amp; &amp;gt;=0 | *3
}

components:
    &amp;quot;layers/tire2/product/applications/productinventory&amp;quot;:
        enabled: bool | *false
        search_module: #GRPCClientConfigSpec
    &amp;quot;layers/tire3/search/applications/productsearch&amp;quot;:
        enabled: bool | *false
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align:center;&quot;&gt;Fig. 4 Common part of development and production&lt;/p&gt;
&lt;p&gt;開発環境の共通の設定を下記（Fig. 5）のように定義したとします。この場合、開発環境の一部であるGKEの環境でも、ローカル環境でも全ての機能が有効となり、すべてのモジュールはローカルホストのモジュールを利用します。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cuelang&quot;&gt;
components:
    &amp;quot;layers/tire2/product/application/productinventory&amp;quot;:
        enable: true
    &amp;quot;layers/tire3/search/applications/productsearch&amp;quot;:
        enabled: true
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align:center;&quot;&gt;Fig. 5 Development specific configuration（Enabled all of modules）&lt;/p&gt;
&lt;p&gt;本番環境でGKEのDeploymentを分ける場合は、コンテナに同梱しているものとは別にConfigMapをYAMLとしてマウントし、これを読み込ませます。例えばDeploymentAのProductモジュールのInventoryアプリケーションの接続先をDeploymentBとし（Fig. 6）、DeploymentBではSearchモジュールのProductSearchアプリケーションのみを有効にする（Fig. 7）ことでSearchモジュールのみを独立して運用することが可能となっています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cuelang&quot;&gt;
components:
    &amp;quot;layers/tire2/product/applications/productinventory&amp;quot;:
        enable: true
        search_module:
            address: deploymentB.xxxx.svc.local
    &amp;quot;layers/tire3/search/applications/productsearch&amp;quot;:
        enable: false
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align:center;&quot;&gt;Fig. 6 The Search module used by the Product module can be switched to a different Deployment&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cuelang&quot;&gt;
components:
    &amp;quot;layers/tire2/product/applications/productinventory&amp;quot;:
        enable: false
    &amp;quot;layers/tire3/search/applications/productsearch&amp;quot;:
        enable: true
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align:center;&quot;&gt;Fig. 7 Deployment with only the Search module enabled&lt;/p&gt;
&lt;p&gt;これらの柔軟なアーキテクチャはローカル開発、開発環境ではシングルバイナリで運用し、本番環境においては適切な単位でモジュールを切り分け運用を行うことを可能にしています。これは特にローカル開発において非常に強力で、Microserviceの開発時の課題である、依存するMicroserviceを含めた実行環境を用意する必要がなくなり、開発環境の構築とメンテナンス効率を劇的に向上することができます。ただし、今回の基盤再構築においては、すべてのMicroservicesを置き換えるわけではなく、現存するメルカリのMicroservicesへの依存も存在しています。これらの依存関係に対応するために&lt;a href=&quot;https://metalbear.com/mirrord/&quot;&gt;mirrord&lt;/a&gt;というプロダクトを用いて、ローカル環境からリモートのKubernetes環境に繋いで開発を行っています。また、&lt;a href=&quot;https://github.com/air-verse/air&quot;&gt;air&lt;/a&gt;というプロダクトも利用しており、変更の動的リロードができるようになっており、Webアプリを開発するようなモダンな開発環境を実現しています。&lt;/p&gt;
&lt;h2&gt;モノレポによる変化への対応&lt;/h2&gt;
&lt;p&gt;メルカリのMicroserviceではサービス毎にレポジトリを作成し、Protocol Bufferの定義、Terraformを用いたインフラストラクチャの管理やKubernetesへのデプロイ環境のレポジトリのみ、それぞれ全員が共有するモノレポとして運用しています。このアプローチは有効ですが、メインで利用するレポジトリと異なっていることで、レポジトリ間を移動する必要があります。このコンテキストスイッチが頻繁に発生することは開発者にとって非常にストレスとなります。また、レポジトリをまたいだ自動化は、個別に動作するCIによって処理時間が長くなるだけでなく、問題が発生した場合にどこで何が起きているかを把握することが難しく、開発者の体験を悪くする要因となっています。今回基盤再構築にあたり、これら開発者の体験を見直すため、この構成も見直し、Backendプロジェクト、Frontendプロジェクト、Protobufの定義やTerraformを一箇所に集めて、極力モノレポで開発が完結するような試みを行っています。（Kubernetesへのデプロイのみエコシステムの都合上既存のモノレポを利用しています。）&lt;/p&gt;
&lt;p&gt;Modular Monolithで境界を明確にしつつ、モノレポでBackendプロジェクトのみならずFrontendプロジェクトも管理することで、アプリケーションやアーキテクチャやフレームワークを揃えつつ、言語や役割を超えた貢献をしやすくしています。また、メンテナンスの面においても、スクリプト、Workflow、CIなど一箇所をメンテナンスすれば良く、メンテナンス効率が高いと考えています。メルカリでは長らく組織やチームの生産性を可視化できておらず、開発者の生産性を正確に測定する方法が課題となっていました。2024年より、開発者の生産性を可視化し、改善することを目的に&lt;a href=&quot;https://getdx.com/&quot;&gt;DX&lt;/a&gt;を導入しています。DXではサーベイを用いた定性的データとGitHubなどの生産性に関わるメトリクスなどの定量データを合わせて、効率、スピード、品質、新規性の４点を可視化しています。モノレポを用いたアプローチはこれらの値でメルカリ全体のスコアよりも良い結果が出ていることがわかりました。&lt;/p&gt;
&lt;p&gt;今回構築したモノレポにおいて少しユニークな点としては、インフラストラクチャの管理にTerraformとCUE langを用いているところです（従来通りtfフォーマットも利用可能です）。CIにてCUEからjsonに変換して適用しています。インフラストラクチャの定義をCUEにすることで、上で紹介したModular Monolithの設定管理のように差分を意識した環境構築が可能になります。CUEはYAMLやJSONとマージして利用することが可能なので、自動化の面で非常に有効だと感じています。今後、モノレポのすべてのデータが同じレポジトリにあるというメリットを活かして、Modular Monolithの設定やフレームワークから自動的にインフラストラクチャの構成ファイルを生成するFramework defined Infrastructureに取り組みたいという野望もあります。(Fig. 8)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/3a21ceeb-framework-defined-infrastructure-1024x845.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align:center;&quot;&gt;Fig. 8 Framework Defined Infrastructure&lt;/p&gt;
&lt;h2&gt;複雑化するドメインや依存関係に対するアプローチ&lt;/h2&gt;
&lt;p&gt;現在、メルカリではMarketplace事業のほか、Merpayも含めて数百規模のMicroserviceが稼働しています。これらのサービスは必要以上に細かく分割されていたり、相互に依存し合っていたりしてメンテナンスを困難にするだけでなく、新規に機能を作成しようと思ったときに、どのMicroserviceに機能を追加するべきか、またどのMicroserviceの機能を利用できるのか、そもそも新規にMicroserviceを作成するべきなのかなどの判断を非常に困難にしています。そこで、Cross Borderでは新規にMarketplaceの基盤を再構築するに当たって、Tierという概念と依存関係マップを導入しつつ、Likeサービスのように特定の機能にフォーカスし、小さく分けすぎたサービスをSocialモジュールにまとめるなど、ある程度まとまった大きめのドメインに再集約するなど、ドメインや役割を整理しながら進めてきました。&lt;/p&gt;
&lt;p&gt;このTierコンセプトでは、BFF(Backend for Frontend)/Gateway、Tier 1、Tier 2、&amp;#8230;Tier 4の5つの層に役割を分割し、それぞれの層の役割と制限を追加しました。&lt;/p&gt;
&lt;h3&gt;BFF/Gateway層&lt;/h3&gt;
&lt;p&gt;BFFはよく知られていますが、この層ではMobileやWeb画面に最適化したAPIを定義しすべてのリクエストはBFFを通してから下位の層へ送られます。お客さまに応じた言語の変換や通貨の変換もこちらの層で担当します。Mobileエンジニア、Webエンジニア、バックエンドエンジニアで共同で所有しメンテナンスを行っています。&lt;/p&gt;
&lt;h3&gt;Tier 1&lt;/h3&gt;
&lt;p&gt;主にリクエストオーケストレーションやビジネスフローを担当します。Tier 1の責任は、Tier 2以下のモジュールを使用してビジネスプロセスを構築することです。イメージとしてはMarketplaceの様々な機能を利用してプロセスを構築するので、水平方向の処理を担当する領域です。&lt;/p&gt;
&lt;h3&gt;Tier 2&lt;/h3&gt;
&lt;p&gt;主にMarketplaceのコアの機能を実現するドメイン特化の層になります。ProductモジュールやOrderモジュールなどが該当します。イメージとしては該当のドメインに特化した垂直方向の処理を担当する領域です。&lt;/p&gt;
&lt;h3&gt;Tier 3&lt;/h3&gt;
&lt;p&gt;基本的にMarketplaceに依存しないより汎用的な機能を提供する層になります。SearchやNotificationなどが該当します。&lt;/p&gt;
&lt;h3&gt;Tier 4&lt;/h3&gt;
&lt;p&gt;この層は少し特殊で、特定の特殊な要件を満たさなければならないモジュールや、Tier 1 〜 Tier 3に属することが難しい機能を提供する層になります。他のモジュールとは適用されるセキュリティーや運用要件などが異なる個人情報を専門で取り扱うモジュールをこの層に配置しています。&lt;/p&gt;
&lt;p&gt;リクエストは常に上から下へと流れ、同じTier同士の通信は禁止するという制約を設けています。ただし、上位Tierから下位Tierにアクセスする場合、中間Tierは飛ばして良いというルールを設けており、BFFからNotificationへのアクセスは許容しています。（Fig. 9）データベースもモジュール単位で分かれており、モジュールをまたいでトランザクションを張るということもできません。これらのルールにより、モジュールの独立性が非常に高まるとともに小さなモジュールが乱立するといったことを防いでいます。もし、同じTier同士のモジュールの通信が必要になった場合、そのモジュール同士のドメインが非常に近しいことを意味し、ドメインの境界の見直しの良いシグナルとして捉えています。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/5243b7bd-tier-concept-1024x468.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align:center;&quot;&gt;Fig. 9 Tier Concept&lt;/p&gt;
&lt;p&gt;基盤の再構築はまだまだ始まったばかりですが、PaymentやIdPといったまとまったドメインかつ、環境が安定しているサービス群を活用しつつ、このデザイン手法を用いてMarketplaceのドメインを再整理し実装することで、2025年10月の現時点で18モジュールに留めることができています。&lt;/p&gt;
&lt;h2&gt;現在の課題&lt;/h2&gt;
&lt;p&gt;現状ではモジュール単位でのデプロイを可能にするために、モジュールごとにバージョンをファイルで管理し、リリース時にはそのバージョンをインクリメントすることで、モジュールごとのバージョンアップを検知しています。しかし、この方法はmainブランチをリリース用とするGitHub Flowとは相性が悪く、意図しない変更がリリースに含まれてしまうおそれがあります。現在この問題を解決するために試行錯誤をしています。&lt;/p&gt;
&lt;h2&gt;今後の展開&lt;/h2&gt;
&lt;p&gt;AIによる開発が主流になってきている昨今、競争力確保のためには新規にビジネスを素早く立ち上げる必要があります。今回紹介したCross BorderチームのMonorepo、Modular Monolithアプローチは初期の構築コストがそれなりに高いため、メルカリの今後の新規ビジネスに適用できるようにPlatformチームと連携して、もっと簡単に素早く構築できるように挑戦中です。今後何処かで機会があれば、これらの結果をまた記事にしたいと思います。&lt;/p&gt;
&lt;h2&gt;最後に&lt;/h2&gt;
&lt;p&gt;2025年11月13日に、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます。こちらにもぜひお越しください！&lt;/p&gt;
&lt;p&gt;参加登録はこちら 👉 &lt;a href=&quot;https://gears.mercari.com/&quot;&gt;https://gears.mercari.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;明日の記事は @Garyさんです。引き続き「&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251003-mercari-crossborder/&quot; title=&quot;連載企画：メルカリ初の世界共通アプリ『メルカリ グローバルアプリ』の開発舞台裏&quot;&gt;連載企画：メルカリ初の世界共通アプリ『メルカリ グローバルアプリ』の開発舞台裏&lt;/a&gt;」をお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>【mercari GEARS 2025】PASSION Stageのセッションをご紹介</title><link>https://engineering.mercari.com/blog/entry/20251008-mercarigears2025-passion-stage/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251008-mercarigears2025-passion-stage/</guid><description>&lt;p&gt;こんにちは！メルカリ Engineering Office の @mikichin です。 来る11月13日、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます！ 2018年に実 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 08 Oct 2025 11:00:38 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは！メルカリ Engineering Office の &lt;a href=&quot;https://x.com/chida_miki&quot; title=&quot;@mikichin&quot;&gt;@mikichin&lt;/a&gt; です。&lt;br /&gt;
来る11月13日、メルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます！&lt;/p&gt;
&lt;p&gt;&lt;iframe loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/TDXzEjwqbaw?si=QJTLP0JGhJtu2kIP&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;2018年に実施した「Mercari Tech Conf 2018」から7年の時を経て、久しぶりのオフラインでの開催となります。&lt;br /&gt;
テーマは「メルカリエンジニアリングの今」。&lt;br /&gt;
今年の全社的なテーマでもある「AI-Native」についてはもちろん、2018年以降メルカリグループのエンジニアリングがどのように変化してきたかを、技術・組織・カルチャーの観点からご紹介します。&lt;br /&gt;
オンライン配信はありませんので、ぜひ会場でご自身の目と耳で確かめてください！！&lt;/p&gt;
&lt;p&gt;会場は、メルカリのエンジニアリング組織における信念や行動の基盤となる共通認識を明文化した「Mercari Engineering Principles」をモチーフにした「PASSION Stage」「GROW Stage」「MECHANISM Stage」の3つのステージがあります。&lt;/p&gt;
&lt;p&gt;本記事では、「PASSION Stage」のセッションをご紹介！「PASSION Stage」は同時通訳の提供があります。&lt;br /&gt;
まだ申し込みをされていない方も、興味のあるセッションがあるはずです。お申し込みは&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちらから&quot;&gt;こちらから&lt;/a&gt;お願いします。&lt;/p&gt;
&lt;h3&gt;12:15 &amp;#8211; 12:45　Keynote&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/475313d0-ogp_passion-1_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;13:00 &amp;#8211; 13:20　Techniques for Reliable Code Generation Using AI Agents&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/7852fb7e-ogp_passion-2_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;今年、コードの書き方は大きな変化が見られました。コード変更は主にAIエージェントが行うようになり、私たち人間の仕事は全体的な調整や成果物の修正が中心となってきています。しかし大規模かつレガシーなコードベースを扱う場合、AIエージェントがどこまで自律的に作業できるかには明確な限界があります。プロジェクト全体の文脈を十分に理解できていない、ガイドラインが守られていないという理由から、生成されたコードはマージ前に大幅な手直しが必要となることも少なくありません。&lt;br /&gt;
本セッションでは、AIエージェントが自律的にコード変更を行えるように設定する方法について、特にマイグレーションや定型処理の多いコードを扱う場面で有効なテクニックを紹介します。&lt;/p&gt;
&lt;h3&gt;13:30 &amp;#8211; 13:50　AIの礎 ——プロダクトを支える、目に見えない力をつくる。&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/a8633258-ogp_passion-3_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;「見た目が似た商品」から始まった画像埋め込みの小さな実験は、やがて“Embeddings”革命へと発展し、メルカリのプロダクト、カルチャー、そしてビジネスに大きな変革をもたらしました。本講演では、その歩みを振り返りながら、画像検索からAI Listing、セマンティック検索に至るまで、埋め込み技術がいかにブレークスルーを実現してきたのかを紐解きます。また、プロトタイプから堅牢なインフラへと拡張していく過程で直面した課題や、そこから得られた学びについてもご紹介します。&lt;/p&gt;
&lt;h3&gt;14:15 &amp;#8211; 14:55　Building Foundation for Mercari’s Global Expansion&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/11ef9b1f-ogp_passion-4_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;メルカリは創業当時よりグローバルなマーケットプレイスを実現することをビジョンとして掲げてきました。これまでの挑戦から得られた学びや反省を踏まえ、現在は“Global One Product”というよりグローバルへの展開を加速させるための新たな共通基盤の構築に取り組んでいます。本セッションではなぜこのアプローチに至ったのか、どのようなアーキテクチャや実装で支えているのか、組織的なチャレンジと技術の両面から詳しく紹介します。複数リージョン展開における開発・運用上のチャレンジや、組織横断での意思決定の工夫についても共有します。&lt;/p&gt;
&lt;h3&gt;15:15 &amp;#8211; 15:35　メルカリにおけるフィッシング対策の軌跡と未来&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/75dad922-ogp_passion-5_ja-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;フィッシング攻撃は進化を続け、サービスやユーザーを狙う手口は年々巧妙化しています。メルカリでも、その進化に対抗するために多様な防御策を講じてきました。そしてパスキー導入を契機に、戦いの焦点は大きく変わり、「いかにフィッシングを防ぐか」から、「いかに守れるユーザーや機能を広げるか」、「いかに強固でありながらUXを損なわない認証体験を実現するか」へとシフトしてきました。本セッションでは、攻撃手法の変遷と、それに呼応して発展してきたフィッシング対策や認証・リカバリー施策の歩みを振り返ります。&lt;/p&gt;
&lt;h3&gt;16:00 &amp;#8211; 16:40　The Future of Platform in the Age of AI&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/26858497-ogp_passion-6_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;本セッションでは、私たちが現在AIを社内でどのように活用しているか、社内のエンジニアリング組織のニーズがどのように進化すると考えられるか、そしてAIエージェントを正規のユーザーとしてサポートできるプラットフォーム構築とは何なのかについてお話しします。 AI時代におけるプラットフォームエンジニアリングの姿や、今後3年から5年の間に取るべき大胆な一手について一緒に探っていきましょう。&lt;/p&gt;
&lt;h3&gt;17:00 &amp;#8211; 17:40　Backend Standardization with MCP&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/99fb0ced-ogp_passion-7_en-1024x538.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;他のチームのサービスを理解するのに頭を抱えたことはありませんか？各チームがそれぞれ異なるコード構造を使っていたり、部門ごとに分断されていたりして、作業がなかなか進まない。そんな状況をAIとModel Context Protocol (MCP) でどう変えられるのかをご紹介します。本セッションではまずMCPとは何かを説明し、なぜこれが社内のバックエンド開発を標準化し、投資対効果（ROI）を高める「ゲームチェンジャー」になり得るのかをお話しします。その後、実際にMCPが動くデモをご覧いただき、現在直面している課題や今後の設計の可能性についても探っていきます。&lt;/p&gt;
&lt;p&gt;「mercari GEARS 2025」のお申し込みは&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;こちらから&quot;&gt;こちらから&lt;/a&gt;。&lt;br /&gt;
GROW Stageのセッション紹介は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251009-mercarigears2025-grow-stage&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;。&lt;br /&gt;
MECHANISM Stageのセッション紹介は&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251010-mercarigears2025-mechanism-stage/&quot; title=&quot;こちら&quot;&gt;こちら&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;イベント詳細&lt;/h2&gt;
&lt;p&gt;開催日時：&lt;br /&gt;
2025年11月13日（木） 11:00-18:00&lt;/p&gt;
&lt;p&gt;概要：&lt;br /&gt;
mercari GEARS 2025は、メルカリグループのエンジニアリング組織の技術への挑戦と、カルチャーを体感する技術イベントです。&lt;br /&gt;
本イベントは、単なる情報伝達の場ではなく、エンジニアたちが出会い、経験を共有し、交流を通じて新たな機会が生み出されることを目的としています。&lt;/p&gt;
&lt;p&gt;参加費：無料&lt;br /&gt;
会場：TODA HALL &amp;amp; CONFERENCE TOKYO&lt;br /&gt;
参加方法：こちらの&lt;a href=&quot;https://www.eventbrite.com/e/mercari-gears-2025-tickets-1637585555479&quot; title=&quot;ページ&quot;&gt;ページ&lt;/a&gt;にてお申し込みください。&lt;br /&gt;
【&lt;a href=&quot;https://gears.mercari.com/&quot; title=&quot;公式サイト&quot;&gt;公式サイト&lt;/a&gt;】&lt;/p&gt;
&lt;p&gt;本イベントに関する追加情報があれば、随時 &lt;a href=&quot;https://x.com/MercariGears&quot; title=&quot;@MercariGears&quot;&gt;@MercariGears&lt;/a&gt; でお知らせしますので、気になる方はぜひフォローをお願いします。&lt;/p&gt;
</content:encoded></item><item><title>初挑戦のインターンで得た学び – メルカリ『お問い合わせ対応システム』開発の1ヶ月</title><link>https://engineering.mercari.com/blog/entry/20251007-553f1424b6/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251007-553f1424b6/</guid><description>&lt;p&gt;自己紹介 こんにちは、@KiKiと申します。今年9月に1ヶ月間、メルカリのインターンに参加させていただきました。 大学では情報系を専攻していて、大学の授業ではハードウェアからアプリケーションに至るまで幅広い分野について学 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 07 Oct 2025 10:59:36 GMT</pubDate><content:encoded>&lt;h2&gt;自己紹介&lt;/h2&gt;
&lt;p&gt;こんにちは、@KiKiと申します。今年9月に1ヶ月間、メルカリのインターンに参加させていただきました。&lt;br /&gt;
大学では情報系を専攻していて、大学の授業ではハードウェアからアプリケーションに至るまで幅広い分野について学んでいます。&lt;br /&gt;
今回のメルカリのインターンは自分にとって初めて参加するインターンでしたが、多くのことを学びながら大きく成長することができたと感じています。本記事では実際にインターンで取り組んだ内容と学んだことをご紹介できればと思います。どうぞよろしくお願いいたします。&lt;/p&gt;
&lt;h2&gt;参加したインターンについて&lt;/h2&gt;
&lt;p&gt;私が今回参加したのは「Build@Mercari」というプログラムの一部であるインターンシップです。&lt;br /&gt;
なお、Build@Mercariのプログラム自体の詳しい内容については、他の記事でとても詳しく紹介されていますので、そちらをご覧ください。&lt;br /&gt;
&lt;a href=&quot;https://careers.mercari.com/mercan/articles/40098/&quot;&gt;https://careers.mercari.com/mercan/articles/40098/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;どうしてBuildインターンに申し込んだのか&lt;/h2&gt;
&lt;p&gt;一般的なインターンに応募する際、技術要件や事前知識の高さにハードルを感じる方も多いのではないでしょうか。特に、情報系を専攻していない方はもちろん、専攻している方でも、インターンの応募時に求められる技術要件や知識に不安を感じることはあるかもしれません。&lt;/p&gt;
&lt;p&gt;大学では、アルゴリズムやハードウェア、OSの基本原理など基礎的な内容が中心で、Webアプリケーション開発などの実践的なスキルを学ぶ機会は限られることもあります。&lt;/p&gt;
&lt;p&gt;私自身、フロントエンドやバックエンドといったWeb関連技術は全くの未経験で、「どこから始めればいいのかわからない」と感じていました。そんな中、STEM分野・IT分野におけるマイノリティである女性や、LGBT+コミュニティの方を対象にトレーニングとインターンシップの機会を提供する「Build@Mercari」というオンラインプログラムを知りました。&lt;br /&gt;
このプログラムは「Web関連技術の知識は全くないけれど、この業界に興味がある！」という気持ちひとつで応募できる懐の深さが魅力でした。「これなら私にも挑戦できるかもしれない」と思い、思い切って応募することにしました。&lt;/p&gt;
&lt;h2&gt;配属されたチームについて&lt;/h2&gt;
&lt;p&gt;私が配属されたのは「Contact Center」という、メルカリでのお問い合わせ対応をサポートする社内システムを開発しているチームです。&lt;/p&gt;
&lt;p&gt;現在、メルカリのお問い合わせ対応は、お客さまがフォームからお問い合わせし、お客さまからいただいたお問い合わせ内容などの情報を元にCS（カスタマーサービス）オペレーターが対応する、という流れになっています。ただし、このCSオペレーションではお問い合わせの解決までに時間がかかりすぎる、という課題がありました。&lt;/p&gt;
&lt;p&gt;このチームではそういったCSオペレーションを抜本的に再構築するプロジェクトを進めていました。具体的には、お客さまのお問い合わせにリアルタイムでBotが対応し、Botで解決が難しい場合は、有人チャットを通じてCSオペレーターが対応にあたる、という方式への移行を目指しています。インターン期間中は、この新しいチャット体験への移行を進めているフェーズだったので、メルカリの未来のCS体験を支える重要な仕組みに関われるのは、とても魅力的なポイントでした。&lt;/p&gt;
&lt;h2&gt;実際に取り組んだ内容&lt;/h2&gt;
&lt;p&gt;プロダクトに関わる業務は大きく分けて、バックエンドとフロントエンドに分かれます。&lt;br /&gt;
バックエンドはサーバー側やデータベースの処理を担い、フロントエンドはユーザーに直接触れる画面や操作部分を担当します。今回私は、バックエンドとフロントエンドのタスクを１つずつ担当させていただきました。&lt;/p&gt;
&lt;h3&gt;バックエンド開発&lt;/h3&gt;
&lt;h4&gt;使用した言語・ツール&lt;/h4&gt;
&lt;p&gt;言語: Go, SQL&lt;br /&gt;
ツール: GCP, Spanner, Kubernetes, BigQuery, Spinnaker, &lt;a href=&quot;https://github.com/cloudspannerecosystem/yo&quot; title=&quot;yo&quot;&gt;yo&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;背景&lt;/h4&gt;
&lt;p&gt;お問い合わせ対応システムの開発を進める中で、会話履歴や関連データに紐づく識別情報をスムーズに取得することが、調査や分析作業を円滑に進める上での課題となっていました。&lt;/p&gt;
&lt;p&gt;このチャットのシステムは、GCPのサービスを使った実装になっています。個々のチャットを特定するためのIDは連携・保存していましたが、ConversationIDと呼ばれる、Botが会話したIDはそれとは異なるIDとなっており、これはシステム上では保存していませんでした。&lt;/p&gt;
&lt;p&gt;そのため、これまでの運用では、対象となる会話データから、ConversationIDを取得するまでに、複数の手順を踏む必要がありました。例えば、会話の記録から情報を一つひとつ検索したうえで、目的のデータを特定するといったプロセスが発生します。このような手間は、迅速な問題解決が求められる場面では特に大きな障壁となっていました。&lt;/p&gt;
&lt;h4&gt;実装したこと&lt;/h4&gt;
&lt;p&gt;こうした課題を解決するため、ConversationIDを自動的に収集・格納する仕組みを検討しました。具体的には、会話終了時に必要なデータを自動的に取得し、これまでテーブルに保存していなかったConversationIDも、会話終了時にテーブルへそのまま保存するように変更しました。この仕組みを活用することで、調査プロセスを大幅に簡略化し、より効率的な対応を目指しました。&lt;/p&gt;
&lt;h4&gt;結果&lt;/h4&gt;
&lt;p&gt;その結果、インターンの期間を通して、メンターさんをはじめとしたチームの方の助力もいただき、この仕組みを実際のシステムに反映させることができました。現場では「調査の高速化に役立っている」といった声もいただいており、自分が関わった仕組みが実際に使われていると実感でき、とても嬉しく感じています。この経験を通じて、開発したものが誰かの作業を少しでも助けられることのやりがいを改めて感じました。&lt;/p&gt;
&lt;h3&gt;フロントエンド開発&lt;/h3&gt;
&lt;h4&gt;使用した言語・ツール&lt;/h4&gt;
&lt;p&gt;言語: TypeScript, GraphQL&lt;br /&gt;
ツール: Ant Design&lt;/p&gt;
&lt;h4&gt;背景&lt;/h4&gt;
&lt;p&gt;お問い合わせに関する情報にお客さまのメールアドレスが登録されていない場合、そのお問い合わせにはダミーのメールアドレスが登録されます。ただしそのような場合でも、CSオペレーターが操作する画面上には、「お客様のメールアドレス宛にメッセージを送信するボタン」が表示されていました。&lt;br /&gt;
しかし、有効なメールアドレスではないため、このボタンを押してもメールの送信は実行されません。それにもかかわらず、CSオペレーターの画面上にはその旨が表示されないため、メール送信が完了したという誤認識を招く可能性があるUIとなっていました。&lt;/p&gt;
&lt;h4&gt;実装したこと&lt;/h4&gt;
&lt;p&gt;この問題を解決するため、ダミーのメールアドレスが設定されている場合に送信ボタンが押されたら、エラーメッセージをモーダルで表示する処理を追加しました。&lt;/p&gt;
&lt;p&gt;この実装を行うために、送信先メールアドレスの情報を取得できるように、データ取得クエリの一部を変更しました。この変更により、画面初期読み込み時に必要な情報が整う仕様に改めました。&lt;/p&gt;
&lt;h4&gt;結果&lt;/h4&gt;
&lt;p&gt;画面上にエラーメッセージが正しく表示されることを確認できました。これにより、CSオペレーターが誤認識をするリスクが減少し、日々の業務をより正確に進めることに役立つ改善が実現しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/d58a92ab-contact_tool.png&quot; alt=&quot;contact tool screen&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;チーム開発ならではの学び&lt;/h2&gt;
&lt;p&gt;プライベートの個人開発では、自分の思いつくままに自由に実装することが多いかと思います。しかし、個人では達成が難しい大きな目標も、チームであれば実現できることがあります。一人では膨大な時間と労力がかかる作業も、チームで取り組むことで、それぞれの得意分野を活かし、知識やスキルを共有しながら効率よく進めることができます。&lt;/p&gt;
&lt;p&gt;さらに、チーム開発では単なる作業の分担にとどまらず、互いにフィードバックを与え合うことでプロダクトの可能性を広げていきます。&lt;/p&gt;
&lt;p&gt;今回のインターンは、私にとって初めて「お仕事でのチーム開発」や「大規模な開発」に触れる機会となりました。私がここで得た学びを、次にご紹介します。&lt;/p&gt;
&lt;h3&gt;プロダクトへの携わり方は業種によってさまざま&lt;/h3&gt;
&lt;p&gt;今回、Contact Centerチームに配属させていただき、チームの方々にサポートしていただいたり、働く様子を間近で見る中で、チームメンバーそれぞれの役割や業務内容について理解を深めることができました。&lt;br /&gt;
私が今回のインターンにおいては、以下のようなポジションの方と関わりがありました。&lt;/p&gt;
&lt;h4&gt;Product Manager&lt;/h4&gt;
&lt;p&gt;プロダクトを使うお客さま（私達の場合Customer Serviceのメンバー）からのニーズを取りまとめて、最適な形で実装できるように仕様を決定する役割です。今回のインターンでは、フロントエンドの実装を行う際、エラーメッセージの内容やデザインについて相談させていただきました。&lt;/p&gt;
&lt;h4&gt;Engineering Manager&lt;/h4&gt;
&lt;p&gt;エンジニアの意見をチームやプロジェクトに反映させるため、多くの会議に出席し関係者と調整を行ったり、他のエンジニアが意思決定に迷った際に相談に乗るのが主な役割です。また、チームのエンジニア一人ひとりと毎週1on1を行い、困りごとや課題に耳を傾けるなど、コードを書くこと以外にもチームメンバーとのコミュニケーションを重視している印象を受けました。&lt;br /&gt;
私もインターン期間中に何度か1on1を設定していただき、直接お話をする機会がありました。&lt;/p&gt;
&lt;h4&gt;Frontend Engineer&lt;/h4&gt;
&lt;p&gt;フロントエンドエンジニアはユーザーが直接目に見える部分を実装するエンジニアです。チームでの会議の際に、実装が出来上がった部分を紹介する時間があるのですが、フロントエンドのデモンストレーションは華があって見応えがあるので、いつも私も楽しみにしていました。&lt;/p&gt;
&lt;h4&gt;Backend Engineer&lt;/h4&gt;
&lt;p&gt;フロントエンドエンジニアとは逆に、表からは見えない部分を担当するエンジニアです。プロダクトの機能のに関わる裏側の処理を行うことができるという点が魅力です。適切なデータ構造やAPIの決定、システムのパフォーマンスに関わる仕事もできるのが個人的に面白いと考えています。縁の下の力持ちという印象です。&lt;/p&gt;
&lt;h3&gt;開発の流れ&lt;/h3&gt;
&lt;p&gt;チーム開発では、メンバーと協力して働くからこそ、個人開発にはないステップが必要になります。&lt;/p&gt;
&lt;p&gt;ここでは、そのリリースまでの流れを簡単にご紹介します。&lt;/p&gt;
&lt;h4&gt;Planning (何をするか決める)&lt;/h4&gt;
&lt;p&gt;解決すべき課題と作るものを明確にする段階です。Product Managerの方を中心に議論し、作業の方向性を定めます。&lt;/p&gt;
&lt;h4&gt;Spec作成 / チケット起票&lt;/h4&gt;
&lt;p&gt;Planningで決まった内容をもとに、仕様を具体化し、タスク管理ツールに登録します。この時点でレビューを一度受けることもあります。&lt;/p&gt;
&lt;h4&gt;Technical Spec / 詳細設計&lt;/h4&gt;
&lt;p&gt;技術的な詳細設計を行い、データの流れやAPI選定など、具体的な実装内容を詰めていきます。今回のインターンでは、チームの方がすでにチケット起票までを終えてくださっていたため、詳細設計の作成から作業を引き継ぎました。&lt;/p&gt;
&lt;h4&gt;開発&lt;/h4&gt;
&lt;p&gt;設計に基づいてコードを実装します。&lt;/p&gt;
&lt;h4&gt;Pull Request（PR）作成&lt;/h4&gt;
&lt;p&gt;GitHub上でコードを共有し、実装の意図やテスト内容を説明します。扱ったリポジトリでは同時に、PR作成時には自動的にCIツールが実行され、コードに対してlintや単体テスト（unit tests）が走る仕組みになっていました。&lt;/p&gt;
&lt;h4&gt;レビュー&lt;/h4&gt;
&lt;p&gt;チームメンバーがコードの品質や設計の意図を確認します。レビューの結果次第では4の開発に戻ってコードを書き直し、再度レビューを受け、承認が得られるまで繰り返すことになります…。&lt;/p&gt;
&lt;h4&gt;リリース準備&lt;/h4&gt;
&lt;p&gt;環境設定や実行権限の取得を行い、開発用の環境でテストを経たうえで本番リリースに備えます。&lt;/p&gt;
&lt;h4&gt;リリース&lt;/h4&gt;
&lt;p&gt;完成したコードを本番環境にリリースし、ユーザーが利用できる状態にします。&lt;/p&gt;
&lt;h3&gt;大規模開発に向けた設計思想「Clean Architecture」について&lt;/h3&gt;
&lt;p&gt;今回、バックエンド開発で触ることになったリポジトリは、「Clean Architecture」に基づいて設計されていました。&lt;/p&gt;
&lt;p&gt;コードの設計思想とは、特に大規模な開発において重要となる概念です。これは、「コードをどのように整理し、配置するか」を決める際に参考にするポリシーのことで、チーム全体での効率的な作業を支えます。たとえば、「このコードはここに置かれているに違いない」とチーム全員が共通認識を持てることで、開発効率が大きく向上します。&lt;/p&gt;
&lt;p&gt;Clean Architectureでは、プログラムの役割や責任に応じてコードがレイヤーに分かれています。それぞれのレイヤーは、独立して役割を果たせるように設計されており、異なるレイヤー間の依存関係を最小限に抑えることが特徴です。この設計により、変更や拡張がしやすくなるという利点があります。&lt;/p&gt;
&lt;p&gt;大規模開発に触れたことがなかった私にとって、コードの設計思想という概念に触れること自体、非常に大きな学びとなりました。&lt;/p&gt;
&lt;h2&gt;生成AIを用いた開発について&lt;/h2&gt;
&lt;p&gt;メルカリでは積極的に業務に生成AIを導入しています。&lt;/p&gt;
&lt;p&gt;今回のインターンを通じて、ソフトウェア開発の現場では「言語の文法を覚えてスラスラと書くだけがスキルではないんだ」と実感しました。生成AIの進化によって、コードを書く作業がかなり効率化されており、大規模なプロジェクトのコードを理解する際にも非常に有用だということを学びました。&lt;/p&gt;
&lt;p&gt;一方で、それ以上に重要だと感じたのは、大規模な開発に適した設計思想や、将来的に仕様変更がしやすい設計、自分以外の人にも分かりやすいコードを書くことの大切さです。また、モジュール化やメンテナンス性を意識した開発の考え方が、現場では重視されていることを強く感じました。&lt;/p&gt;
&lt;p&gt;これらの経験から、「知らない言語を使うプロジェクトだから…」と機会を逃すのは、少しもったいないと気付かされました。言語自体の知識は必要に応じて身につけていけばよく、現場で学べる設計や開発の考え方こそが、より長く自分の糧になり、生成AIに取って代わられることのない人材へと成長することにつながるのではないかと思います。&lt;/p&gt;
&lt;h2&gt;終わりに&lt;/h2&gt;
&lt;p&gt;この記事では、私が初めてのインターンを通して学んださまざまなことについて、紹介させていただきました。&lt;/p&gt;
&lt;p&gt;１ヶ月という期間はとても短く感じましたが、フルタイムで社員の方々に混ざって働き、たくさんお話をさせていただく中で、この記事には書ききれないほどの大きな学びを得ました。なにより、本当に楽しかったです。メンターをしてくださった&lt;a href=&quot;https://engineering.mercari.com/blog/author/Peranikov/&quot; title=&quot;Peranikov&quot;&gt;Peranikov&lt;/a&gt;さんをはじめ、Contact Centerチームの皆様方、ありがとうございました。&lt;/p&gt;
</content:encoded></item><item><title>グローバル展開にむけたアプリと基盤の再構築</title><link>https://engineering.mercari.com/blog/entry/20251007-a09afcd49b/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251007-a09afcd49b/</guid><description>&lt;p&gt;Cross Border (XB) Engineeringの @deeeeeeeeeet です． 先日の事業戦略発表会において共有しましたが，今後更にメルカリの海外展開を加速させるためにグローバル版のメルカリアプリを先日 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 07 Oct 2025 09:29:53 GMT</pubDate><content:encoded>&lt;p&gt;Cross Border (XB) Engineeringの &lt;a href=&quot;https://www.deeeet.com/&quot;&gt;@deeeeeeeeeet&lt;/a&gt; です．&lt;/p&gt;
&lt;p&gt;先日の事業戦略発表会において共有しましたが，今後更にメルカリの海外展開を加速させるためにグローバル版のメルカリアプリを先日リリースしました．&lt;/p&gt;
&lt;p&gt;このアプリは現在提供してる日本版・アメリカ版のメルカリとは異なる新しいアプリであり，またアプリだけではなくその裏側のバックエンド基盤も新たに再構築しています．本記事では，エンジニアリングの観点からメルカリ グローバルアプリ（以下、グローバルアプリ）とその基盤の戦略やアーキテクチャーをこれまでのメルカリの挑戦から得られた学びを振り返りつつ紹介します．&lt;/p&gt;
&lt;h1&gt;メルカリにおける越境取引&lt;/h1&gt;
&lt;p&gt;「メルカリ」に出品したことがあるみなさんの中には，自分の商品が一般のお客さまではなく事業者によって「代理購入」された経験がある方もいらっしゃるかもしれません．これは，海外のお客さまが日本の「メルカリ」に出品されている商品を購入できる越境 (Cross-Border, XB) 取引という仕組みによるものです．  &lt;/p&gt;
&lt;p&gt;メルカリにおける越境取引は代理購入パートナーとの連携によって実現されています．海外のお客さまは，まず提携パートナーのWebサイト上で「メルカリ」の商品を注文します．すると，パートナーが購入代行者として「メルカリ」上で商品を購入し，支払い手続きを行います．国内の出品者は，この代理購入者であるパートナーの指定する日本の倉庫へ，通常の国内取引と同じように商品を発送します．商品が倉庫に到着後，パートナーが検品や海外向けの梱包を行い，海外のお客さまの元へ国際発送するという流れで実現されています．&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/f31f0b65-dairikounyuu.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この仕組みは，海外と国内のお客さまの双方にメリットがあります．海外のお客さまにとっては，言語の壁や通貨の違いを気にすることなく，日本のユニークな商品を手軽に購入できます．一方で，国内のお客さまにとっては，海外のお客さまとの直接的なコミュニケーションや国際発送といった複雑な手続きは一切不要で，国内取引と同じように販売の機会を世界中に広げることができます．&lt;/p&gt;
&lt;p&gt;越境取引事業は2019年に始まり，近年さらに成長しており, GMVとしては過去3年で15倍に成長しています．特に，アニメ，コミック，ゲーム，エンタメ関連グッズのカテゴリーが取引全体の多くを占めており海外のお客さまからの強い需要があります．&lt;/p&gt;
&lt;p&gt;このような強い需要と成長を顧みて，代理購入パートナーのサイトを通じた仕組みに加え，&lt;a href=&quot;https://jp.mercari.com/&quot;&gt;日本のメルカリのWebサービス&lt;/a&gt;を通じて代理購入を可能にする取り組みも開始しました．この仕組みにより，海外のお客さまは直接「メルカリ」上でアカウントを作成し，「メルカリ」が提供する体験を通じて商品の検索と購入を行うことが可能になりました (引き続きパートナー企業を間に挟む形式は継続しています)．この取り組みは2024年にリリースし, 現在台湾と香港から利用可能で利用者数を伸ばしています．&lt;/p&gt;
&lt;p&gt;こうして越境取引事業は順調に成長してきましたが，同時にいくつかの重要な課題も見えてきました．以下で説明するように既存の日本のシステムは日本市場に特化して作られており，単一通貨・単一言語を前提とした設計になっています．越境取引機能はこの上に追加的に実装したため，複数国への展開や各国固有の商習慣への対応を実現していくには限界がありました．特にアジア市場ではEC利用の多くがモバイル経由という状況において，Web版のみの提供では競争力に欠けるという問題もありました．&lt;/p&gt;
&lt;p&gt;このような課題を抱えながらも，海外市場からの需要は確実に存在しており，特にアニメ・ゲーム関連商品への関心は非常に高いことがわかっています．現在は台湾と香港の2か所のみですが，東南アジアや欧米にも同様の潜在的な需要があることは明らかでした．この機会を最大限に活かすためにはより早く展開国を拡大していく新たなアプローチが必要でした．&lt;/p&gt;
&lt;p&gt;そこで私たちは，単に既存システムを拡張するのではなく，グローバル展開を前提とした新たなアプリケーションとその基盤を構築するという決断に至りました．これは越境取引から始めて，将来的には各国でのローカルマーケットプレイスの立ち上げ，そして最終的には国境を越えたグローバルなマーケットプレイスの実現を見据えた戦略的な判断でした．&lt;/p&gt;
&lt;h1&gt;海外展開のアプローチ&lt;/h1&gt;
&lt;p&gt;グローバルなマーケットプレイスの実現は，メルカリ創業当時からのビジョンであり，海外展開への挑戦は今回が初めてではありません．これまでにもアメリカでの事業展開に挑戦し，現在もその成長に注力しています．過去にはイギリスへの展開を試みた経験もあります．&lt;/p&gt;
&lt;p&gt;これまでの海外展開では，それぞれの国において，日本と同様のローカルなC2Cマーケットプレイスをゼロから立ち上げるというアプローチを取ってきました．しかし，今回のグローバル展開は越境取引の成功と課題から学んだ新たなアプローチを取っています．日本から海外へ商品を届ける「越境取引」を事業の軸に据え，そこで構築した顧客基盤を活かしながら段階的にサービスを拡大していく戦略です．また展開エリアも3年以内に50カ国と地域を目指しており，スピード感も従来とは大きく異なります．これは日本のお客さまや事業者に出品していただいたユニークで豊富な商品を世界中に届けることを起点とし，そこから更なる可能性を模索していく戦略への転換を意味しています．&lt;/p&gt;
&lt;p&gt;この事業戦略の転換によりエンジニアリング戦略も大きく変えました．&lt;/p&gt;
&lt;p&gt;これまでの日本とアメリカ，そしてイギリスへの展開はそれぞれ独立した異なるシステムにより実現してきました．もちろん当初は共通のコードベースを各国にデプロイする方式 (ただしデータは分離) を取っていました．しかし，日本向けに作られたシステムを各国の事情に対応させていくことによるコードベースの複雑化 (e.g., 国のスイッチのためのif文が多くの場所で書かれることになった) や，国間での方針のアラインが必要であるために各国の意思決定のスピードの低下といった課題にぶつかりました. 最終的にはフォークを決定し，それぞれ独立したシステムとなり，開発運用の体制も分離していくことになりました．アメリカはその後アプリ自体も現地のUI/UXに合わせて刷新を行い，独自の機能をその上に実装していくことになり，日本とアメリカのシステムは今日でも分離されています．&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/6e6ea979-fork.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この方法は，迅速に事業を立ち上げ，各国の市場に深く最適化できる点では有効なアプローチでした．各国の事業をそれぞれで伸ばしていくために独立した組織作りとシステムを開発していくのも重要だったと思います．一方でより長期的な視点に立ったときに以下のような課題があり，次の展開へと繋げることが困難になっていました．&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;展開のコストとスピード&lt;/strong&gt;: 展開国を増やすという観点での共通の基盤の整備はできておらず，次の国を考えたときに新たなアプリケーションとバックエンド基盤を構築し直すこと，もしくは既存のシステムの大規模な改修を考える必要がある．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;開発リソースの非効率性&lt;/strong&gt;: 同じような機能がそれぞれの国で個別に実装され，各基盤に専任のチームが必要となるため，開発リソースの重複や運用の非効率性が発生する．&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;現状の「越境取引」自体は既存の日本のシステム上に構築できています．しかし，以下でより詳しく述べるように既存のシステムは複雑化しており，今後のより高速に展開国を増やしていく，グローバルに向けたより良いUI/UXの提供を行っていくのは限界がきていました．そして「越境取引」の次，例えば新たな国でローカルのマーケットプレイスを展開するといったことに繋げることは非常に困難です．&lt;/p&gt;
&lt;p&gt;このような課題を根本的に解決し，そして「越境取引」を中心とした新たな海外展開を効率的に加速させるためには新たな戦略が必要でした．そこで「国や地域ごとに個別のシステムを構築するのではなく，単一のグローバルな基盤で全ての国や地域をサポートする」という新たなビジョンを打ち立てその基盤の開発を始めました．&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/aad7b50d-global-be.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;グローバル基盤の開発戦略&lt;/h1&gt;
&lt;p&gt;この単一のグローバル基盤の開発の戦略にはいくつかのアプローチが考えられますが「拡張と再構築のハイブリッドなアプローチ」を選択しています．このアプローチに至った背景をこれまでのメルカリのバックエンドシステムの変遷から説明します．&lt;/p&gt;
&lt;h2&gt;メルカリのバックエンドシステムの変遷&lt;/h2&gt;
&lt;p&gt;メルカリのバックエンドシステムはMonolithアーキテクチャ (単一コードベースに全ての機能を実装する方式) として始まっています．アメリカ事業やイギリス事業を開始するときにフォークという選択肢をとることができたのはこのためです (それぞれの国のスケールを支えるために裏側のインフラやツールとして多くの仕組みがありそれらを複製するのは容易ではなかったはずですが)．&lt;/p&gt;
&lt;p&gt;2017年あたりから特に日本の組織の規模は急激に拡大を始めます．組織の拡大により単一の巨大なコードベースに多くの人が同時に開発を行うことが困難になり，また一部の機能のバグでサービス全体に障害が波及する事も多く発生しました．加えてほとんどのシステムがオンプレ上に構築されており，その運用や拡張がボトルネックになっていました．このような問題を解決するためにMicroservicesアーキテクチャ移行とクラウド移行 (それに伴うDevOps化への移行)  を開始します．私自身が入社したのはこの直前で，移行プロジェクトの推進とMicroservices開発の基盤やツールを整える&lt;a href=&quot;https://engineering.mercari.com/blog/entry/2020-07-16-083548/&quot;&gt;Platform Engineeringチームの立ち上げと拡大&lt;/a&gt;を担ってきました．&lt;/p&gt;
&lt;p&gt;Microservicesアーキテクチャ移行のアプローチとしては&lt;a href=&quot;https://microservices.io/patterns/refactoring/strangler-application.html&quot;&gt;Stranglerパターン&lt;/a&gt;を採用しました．これは既存のシステムの前段にGatewayを置き，そのGatewayを軸にトラフィックを徐々に新しいシステムに移行していくという方針です．より具体的には，(1) 既存システムに実装されている機能群をMicroservicesとして切り出し (2) Gatewayからその機能の利用トラフィックを徐々にMicroservices側に流す，を繰り返すことで段階的に新システムに移行していくアプローチです．移行開始から数年が経過しましたが，多くの機能をMonolithから切り出し，またその上で新しい機能を開発してきました．またほぼ全てのサービスのクラウド移行も完了しています (サービス数でいうと100を超えています)．&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/1be7ea5b-strangler.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Microservices化以降では日本ではメインとなるC2Cマーケットプレイス事業に加えて複数の新規事業の立ち上げが始まることになります．フィンテック事業のメルペイ，暗号資産のメルコイン，B2C事業のメルカリShops，そしてスキマバイトサービス事業のメルカリハロです．メルペイはメルカリの決済システムを切り出しており，MicroservicesアーキテクチャとしてC2Cと同じインフラ基盤上に構築しています．メルコインはセキュリティのためにインフラは大きく分離していますが，基本的には同様のアーキテクチャパターンで開発しています．ShopsはMicroservicesアーキテクチャですがC2Cとは切り離した独立したシステムになっています (モバイルアプリとしては一つですが，裏側のバックエンドは分離しています)．&lt;/p&gt;
&lt;p&gt;この数年に渡るMicroservices移行と複数事業の立ち上げに合わせて推進してきたのは共通基盤の整備です．自分がリードしてきたPlatform Engineeringのレイヤーとしての開発基盤やツールだけではなく，&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20221018-mtf2022-day2-5/&quot;&gt;ID基盤&lt;/a&gt;や&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250605-bf42ce60cf/&quot;&gt;決済基盤&lt;/a&gt;，マーケティング基盤のような複数事業にまたがって利用できる基盤も開発してきました．これらが創業以来メルカリのバックエンドシステムの変遷です．&lt;/p&gt;
&lt;h3&gt;既存システムの課題&lt;/h3&gt;
&lt;p&gt;2025年の現在，既存のシステムを俯瞰したときにいくつかの構造的な課題を抱えています．&lt;/p&gt;
&lt;p&gt;最も大きな問題は，C2Cマーケットプレイスとして重要な機能がMonolith基盤に残っているという点です．Stranglerパターンとしていくつかの機能をMicroserviceとして抜き出すことはできてはいますが，この方式はProxy的に上物の機能を抜き出すに止まりデータ移行まで進まなかった部分も多くあります．特に「トランザクション管理」「配送」といった機能をMonolithとそのDBから抜き出すことができていません．これらはロジックとして密結合が強くうまく分離を進められなかったというのも大きな理由です．そのためMonolithへの強い依存が未だに残っています．この部分は今でも多くの開発と変更が必要な一方で複雑なコードベース上に残ってるために，日本事業の継続的な改善においても早急な対処が必要です．Microservices移行の初期から関わってきた人間としては，この重要部に初期から挑まなかったのは大きな反省です．&lt;/p&gt;
&lt;p&gt;グローバル展開を見据えてもこれは大きな課題になります．Monolithに残るトランザクション管理と配送システムは日本市場に特化した設計になっています．トランザクション管理は日本円のみを前提としており，複数通貨での取引，為替処理，各国異なる税制への対応を追加することは非常にコストは高いです．配送システムも日本国内の配送業者のシステムと密結合しており，各国のローカル配送業者，異なる配送オプションへの対応は根本的な作り直しなしには実現は難しくなっていました．&lt;/p&gt;
&lt;p&gt;また，C2CマーケットプレイスとB2C Shopsのシステム乖離問題があります．現状は別々のトランザクション，配送システムをそれぞれが持っているだけでなく，プロダクトの管理も分かれており，結果として日本のお客さまに対しても統一的な体験を提供できていません．これは，もともとのビジョンとして独立したサービスが考慮されていたこと，方向性が変わり統合しようと思っても上のMonolith問題により実行が難しかったことが原因として挙げられます．&lt;/p&gt;
&lt;p&gt;Microservicesアーキテクチャ自体にも課題があります．各サービスのオーナーシップと自由度を重視し，サービス間で十分な抽象化を行えていなかったこと，適切なドメイン分離を行えておらず分割の単位も非常に小さくしてしまったことが原因で，多くの小さな，作り方が微妙に異なるMicroservicesが数多く構築されてしまいました．このためMicroservicesの運用のコストが非常に高くなってしまっています．メルカリはスピード感を持って物事を進めるため組織変更も頻繁に行いますが，そのたびにMicroservicesのオーナーシップの移管が必要になり，実装の差異によりオンボードのコストも高くなっています．&lt;/p&gt;
&lt;p&gt;これらの制約により，既存システムの延長線上でグローバル展開を進めることは，技術的にもビジネス的にも限界があることが明確になりました．&lt;/p&gt;
&lt;h3&gt;グローバル基盤の方向性&lt;/h3&gt;
&lt;p&gt;このような変遷と現状の課題をベースにグローバル基盤の開発方針としていくつかのアプローチを考慮しました．まず，過去のアメリカ展開のようにフォークという選択肢を取ることは非常に難しくなっています．Microservices化された多くのシステムを複製していくのは現実的ではありません．全てをゼロから再構築することも考えましたが，これもコストと効率の観点から選択肢から外しました．結論として「既存のシステムの拡張と再構築のハイブリッドなアプローチ」を選択しています．&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/f80e787f-rebuild.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;このアプローチでは，どこまでを拡張とし，どこまでを再構築するか? のラインを決めるのが重要でした．既存のシステムの多くは日本の市場に特化したものになっており，また多くのサービスがMicroservices化されています．それら全てを拡張していくのは現実的ではありませんし，日本事業は引き続き重要であるため，グローバル展開はそこから独立して進められることも重要でした．また未だ残るMonolithにグローバルから依存することも避けたいという強い気持ちもありました．&lt;/p&gt;
&lt;p&gt;「拡張」としては複数事業の立ち上げとともに発展した共通基盤を主に活用することにしました．特に強い専門性が求められる，そして拡張性を考慮して設計されてるサービスを選定しています．以下で詳しく述べますがMicroservicesからの脱却も同時に考えており，小さな細かなサービスには依存するのではなく，十分な大きさかつ独立した「ドメイン」(SaaSとして置き換えられるレベル) に依存することも決めました．この基準により，例えば，ID基盤はグローバルで共通に，また決済基盤はメルペイ基盤を通じてStripeに接続しグローバルの通貨やローカルの決済手段に対応していく，といったことを進めています．他にも検索基盤，マーケティング基盤なども既存のシステムを拡張することで活用しています．&lt;/p&gt;
&lt;p&gt;それ以外の部分は「再構築」の選択肢をとっています．特に上述したC2Cサービスとしての「トランザクション管理」「配送」「アイテム・プロダクト管理」は作り直すしかありませんでした．日本と同じ問題を避けるために，(1) それぞれを疎に長期的な拡張性を容易にする (2) CとBの商品を同等に扱い，統一したUI/UXを提供することができることを考慮し，また複数カ国展開や別の国において新たなローカルマーケットプレイスを実現できるようにするために (3) 各国の通貨，言語，税制・関税，法規制に柔軟に対応できる (複数であることを前提にする．以下のTenetsを参照) (4) 日本以外の国の商品や配送手段を扱うことになっても対応できるようにする，を前提として構築しています．&lt;/p&gt;
&lt;p&gt;また単に作り直すだけではまた新たな別基盤が誕生するだけです．初期はグローバルでの成功をメインとしつつも，最終的には日本のC2CとB2Cの基盤も置き換えていく，という想定で動き始めています (実際にリリースまで達成したのでこの基盤を日本でも活用していくためのプロジェクトを始動しています)．&lt;/p&gt;
&lt;p&gt;モバイルアプリとWebに関してもグローバルでは異なるUI/UXは必須なので作り直しの選択になっています．加えてバックエンドを刷新することでAPI自体も切り替えることもでき，実装自体もより良くできます．&lt;/p&gt;
&lt;h2&gt;MicroservicesからModular Monolithへ&lt;/h2&gt;
&lt;p&gt;上述したMicroservicesアーキテクチャの抱える課題に取り組むため「再構築」したバックエンド基盤はModular monolithアーキテクチャとして開発しています．&lt;/p&gt;
&lt;h3&gt;Microservicesの課題&lt;/h3&gt;
&lt;p&gt;メルカリにおいてMicroservicesアーキテクチャの運用コストが高くなってしまった大きな理由は，各サービスの開発の自由度を高めてしまったところにあります．サーバー実装はGoで，データベースとしてSpanner/CloudSQL (MySQL)，インフラとしてKubernetesを利用する，という最低限の技術スタックの統一は進めてきました．一方で，レポジトリ戦略はPolyrepo (1サービス = 1 GitHub レポジトリ)として，基準となるテンプレートや最低限の共通ライブラリはあれど，レポジトリの構成や実装方針は各チームに委ねる形になっていました．そのため，マクロで見ると同じGoのMicroservicesですが，ミクロでみるとかなり異なるサービスが量産されました．一つ一つのサービスの運用のコストは小さくても，異なるサービスを複数面倒見る必要があるとその差異により共通化ができず，コストが高くなるということが起こっています．&lt;/p&gt;
&lt;p&gt;これに加えてメルカリはとにかくスピード感を持って物事を進め方向性を転換していくため組織変更も頻繁に行います．そのためMicroservicesのオーナーシップの移管も頻繁に行う必要があります．移管のたびにオンボードが必要ですが実装の差異によりそのコストも高くなります．また共通化を進めるのも難しいです．&lt;/p&gt;
&lt;p&gt;また特にMonolithから移行を進めたC2C側はドメインの適切な分離ができていないところも多く，サービスの凝集度が低いところが多くあります．これにより機能追加のために複数のサービス，チームに跨った変更が必要になり，コミュニケーションコストの増大にも繋がっています．サービスごとのオーナーシップを強化するという方向性は逆に外からの変更を受け入れにくくするということにも繋がっています．&lt;/p&gt;
&lt;p&gt;このような課題に対して上手くMicroservicesアーキテクチャによる実装を進めたのが&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20210810-mercari-shops-tech-stack/&quot;&gt;メルカリShopsによるMonorepoを使ったアプローチ&lt;/a&gt;です．この方式ではShopsに関わる全てのMicroservicesを1つのRepoにまとめ，サービス間の実装を抽象化・統一化するということを実現しており，複数サービスによる運用のコストを削減しています．開発体験としてはMonolith的に，裏ではサービスが分離されてデプロイされる(これにより耐障害性のメリットを得られる)，という両方の良い部分を取り入れることができています．&lt;/p&gt;
&lt;p&gt;一方でこのアプローチであっても課題はあります．こうしたMonorepoのためのインフラや自動化の仕組みを管理維持していくのは非常にコストが高いです (既存のPlatformと大きく分けて構築されたため共通基盤チームとの連携が難しくなっていたことも原因として挙げられます)．そもそもMicroservicesのテスト，デプロイ，開発環境の構築は複雑にならざるを得ません．例えばテスト環境は全部のサービスをPRごとに複製するという富豪的なアプローチをとっています．またサービスごとにDBを分けるなどを厳密に行なっており，インフラのコストも高くなってしまっています．&lt;/p&gt;
&lt;h3&gt;Modular Monolith&lt;/h3&gt;
&lt;p&gt;このような背景もあり，新しい基盤の構築にはModular monolithアーキテクチャーを選択しています．単なるModular monolithではなく特定のModuleを必要とあらば独立してデプロイ可能な形にしています (Service Weaverのコンセプトに近い)．&lt;/p&gt;
&lt;p&gt;初期のメルカリのMonolithでは適切なドメイン分離・モジュール分離ができなかったためにコードの密結合とそれによる複雑化が発生したと思っています．サービスの境界，依存関係をモジュールごとに明確に整理することで同様の問題に当たらないようにしています．Microservicesのように細かく分離しすぎることで複雑になるのを避け，十分に機能が凝縮されたモジュールを作るようにしています．また必要なときに独立したデプロイを可能にすることでMicroservices的な耐障害性の利点も可能にしています．&lt;/p&gt;
&lt;p&gt;初期の開発フェーズでは人数も多くないので基本は特定のモジュールにオーナーシップを限定することはしていません (もちろん特定の領域に強い人はいる)．皆がコードベース全体にオーナーシップを持ってもらうようにしています．これにより，プロダクト開発の優先度によってモジュールのアサインが動的に決まり，Microservicesで発生した無駄なコミュニケーション調整コストをなくしています．一方で，今後組織が拡大したとしても，モジュール単位でのアサインは可能であり，かつてのMonolithでハマった問題も解決できる余地もあります．&lt;/p&gt;
&lt;p&gt;Monolithであることで，ワンバイナリによるローカル開発環境の構築は容易になり，またテストやデプロイもシンプルになり，Microservicesによって生じていた開発負荷もなくすことでより良い開発体験を作れています．インフラやCI/CD基盤もPlatform Engineeringチームが提供するものをそのまま使うことができ，ShopsのMonorepoアプローチで陥った基盤運用コストを抑えることができています (より詳細は次のポストで @yanolabより紹介します)．&lt;/p&gt;
&lt;p&gt;一方でこの方針は組織全体の中では新しく，既存のMicroservicesアプローチとどう共存していくかという課題があります．現実的に一度分離したMicroservicesを全てMonolithへと戻していくことは簡単ではありません．そのためMicroservices自体は今後も残ることになると思います．Microservices開発と運用のコストを減らしていくために，サービスの分割単位をより適切なレベルに合わせていくことや，さらに言えばShopsで実現したようなMonorepoアプローチによる統一化を高めていくことが重要だと思っています．そして将来的な新規事業でこれからMicroservicesアーキテクチャーを初手で選択することは，特別な理由がない限りは，しない方がいいと思っています．このグローバル基盤のModular monolith構築パターンを横に展開し，実装パターンを共通化していくことも考えています．&lt;/p&gt;
&lt;h3&gt;技術スタック&lt;/h3&gt;
&lt;p&gt;以下はこの基盤の構築に利用している技術スタックの一覧になります．基本的にはメルカリがこれまで培ってきたスタックを前提に大きく変えないでそれらをうまく活用するようにしています．&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;インフラ: 引き続きメインのクラウドにはGoogle Cloudを採用しています．メインのリージョンは東京を使っていますが，将来的には(特にパフォーマンスの観点から) 別のリージョンを利用する可能性も考慮しています．アプリケーションの実行基盤にはPlatform Engineeringチームが管理するKubernetes (GKE) を利用しています．&lt;/li&gt;
&lt;li&gt;データベース: データベースにはAlloyDBを選択しました．これまではメルペイを中心にSpannerを選定してきましたが， (1) 長期的な展開を考慮した時にGoogle Cloud 全てで担えない可能性も考慮し，なるべくロックインを避けること (2) PostgreSQLによるより良い開発体験エコシステムを利用すること，を考慮してAlloyDBを選定しました．他にもCockroachDBも考えており，今後の展開によっては乗り換えも考慮する可能性があります&lt;/li&gt;
&lt;li&gt;言語・フレームワーク: サーバーはGo，iOSはSwift，AndroidはKotlin，WebはNext.js (TypeScript)としています．ここは大きく変えていません&lt;/li&gt;
&lt;li&gt;Monorepo: より詳細は別のブログがこのあと書かれますが，iOS, Android，Webはそれぞれ日本のサービスのレポジトリをMonorepoとして拡張することで開発されています．日本とグローバルで共有可能なモジュールを切り出し，CI/CDを共通化することで，開発と運用の効率化をしています．&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Tenets&lt;/h2&gt;
&lt;p&gt;この新たな基盤とアプリの開発には日本のみではなくインド拠点のメンバーを含めて多くが参加しています．さまざまな背景のメンバーが参加しても，これまで上で紹介してきたような方向性を実現するには，皆が同等の指針にしたがって意思決定を行えることが重要です．&lt;/p&gt;
&lt;p&gt;これを実現するために「Global Engineering Tenets」を策定しました．TenetsはAmazonの&lt;a href=&quot;https://aws.amazon.com/blogs/enterprise-strategy/tenets-supercharging-decision-making/&quot;&gt;Tenets: supercharging decision-making&lt;/a&gt;を参考にしています．&lt;/p&gt;
&lt;p&gt;主なTenetsをいくつか紹介します: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Design for two&lt;/strong&gt;: ソフトウェア開発においてある機能のサポートを1から2に増やすよりも，2から3に増やす方が容易であることは実感としてわかると思います．例えば，アプリケーションが既に2つの言語をサポートしている場合，3つ目の言語を追加するのは簡単です．一方、アプリケーションが1つの言語しかサポートしていない場合、2つ目の言語を追加するにはi18nの仕組みなど多くの準備が必要になります．これはグローバル展開においても同様のことが言えます．既に複数国・地域に対応している基盤に新規の地域や国を追加するのは、単一地域/国向けのアプリケーションを拡張するよりもはるかに容易です．機能やシステム設計においては常に2カ国・地域以上を想定するようにしています．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global by default but enable localization&lt;/strong&gt;: グローバル利用に向けたシステム開発を進める一方で，単に複数国へ事業を拡大するだけでなく，主要市場ではローカライゼーション施策を実施します．そのため，システムは複数の国々へ迅速かつ容易に拡張できつつ，かつ特定の国の要件をサポートする柔軟性も考慮する必要があります．長期的にはローカライゼーションのため現地にエンジニアリングチームを設置する可能性もあり，彼らが独立してローカライズ機能を開発できるようにする必要があります．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn and unlearn from the past experience:&lt;/strong&gt; 今回新規で「再構築」する部分が多くあります．ただし，これは完全に新規であるべきではなく，上で紹介したような過去の学びを重要な資産として活用するべきです．自分は概要を説明しましたが，それぞれの領域，モバイル開発，Web開発，プロダクト開発などさまざまな領域で見直すべき課題があります．新しく採用したメンバーに関してもこれらの活用は強くお願いしました．&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep each country&amp;#8217;s business isolated&lt;/strong&gt;: 既存の基盤やプラットフォームを活用する場合でも、相互に影響を与えないようにすべきです．例えば，グローバルで発生したバグやインシデントが日本の事業に影響を与えたり，その逆が生じることを避ける必要があります．&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらはデザインドキュメントを書く時など多くの場面で指針として利用されています (特にDesign for Twoは各所で言われた)．もちろん，多くの人が参加してる，長期的なプロジェクト、である以上は細かな部分ではズレは発生していますが，大きな方向性としては皆が同じ方向を向けたのではないかと思っています．&lt;/p&gt;
&lt;h2&gt;今後の展望&lt;/h2&gt;
&lt;p&gt;今回のリリースでは基本となる機能の実装が完了した状態です．今後はこの上に越境取引にとって重要になる機能，例えば事業者商品の予約販売機能や鑑定機能，などを実装しつつ，展開国をどんどん増やしていくことを目標としています．横展開だけではなく，特定の国へのローカライズとグロースを行っていく必要もあり，さらに基盤を活用していくフェーズになります．また上で紹介したように基盤自体は日本でも使えることを想定しており，その置き換えのプロジェクトも始めています．これによりこれまで抱えていた技術的な負債も同時に返済していくことを考えています．&lt;/p&gt;
&lt;h2&gt;mercari Gears 2025&lt;/h2&gt;
&lt;p&gt;2025年11月13日に、実に7年ぶりとなるメルカリグループのテックカンファレンス「mercari GEARS 2025」が開催されます．@yanolob とともに「Building Foundation for Mercari’s Global Expansion」と題して登壇いたします．&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/0883bd6a-ogp_passion-4_en.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;グローバルアプリを開発するにあたり、なぜこのアプローチに至ったのか，どのようなアーキテクチャや実装で支えているのか，組織的なチャレンジと技術の両面から詳しく紹介します．複数リージョン展開における開発・運用上のチャレンジや，組織横断での意思決定の工夫についても共有します．&lt;/p&gt;
&lt;p&gt;ぜひ，セッションを聞きに来てください！ 参加登録はこちら 👉 &lt;a href=&quot;https://gears.mercari.com/&quot;&gt;https://gears.mercari.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;明日の記事は @yanolobさんです。引き続き&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251003-mercari-crossborder/&quot;&gt;連載企画：メルカリ初の世界共通アプリ『メルカリ グローバルアプリ』の開発舞台裏&lt;/a&gt;をお楽しみください。&lt;/p&gt;
</content:encoded></item><item><title>連載企画：メルカリ初の世界共通アプリ「メルカリ グローバルアプリ」の開発舞台裏</title><link>https://engineering.mercari.com/blog/entry/20251003-mercari-crossborder/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20251003-mercari-crossborder/</guid><description>&lt;p&gt;こんにちは。Cross Border (XB) Engineeringの @deeeeet です。 先日、2025年9月30日に越境取引事業の新戦略を発表し、メルカリ初の世界共通アプリ「メルカリ グローバルアプリ」（以下 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 06 Oct 2025 09:27:32 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。Cross Border (XB) Engineeringの &lt;a href=&quot;https://x.com/deeeet&quot;&gt;@deeeeet&lt;/a&gt; です。&lt;/p&gt;
&lt;p&gt;先日、2025年9月30日に越境取引事業の新戦略を発表し、メルカリ初の世界共通アプリ「メルカリ グローバルアプリ」（以下、グローバルアプリ）の提供を開始しました。&lt;br /&gt;
そこで今回は、グローバルアプリの開発プロジェクトの開発舞台裏をご紹介する連載企画をスタートいたします。&lt;br /&gt;
トピックはバックエンド開発のみではなく、モバイル開発、Web開発、SRE &amp;amp; Enablingなどなど多岐にわたるのでお楽しみに。&lt;/p&gt;
&lt;h2&gt;「メルカリ グローバルアプリ」の概要　&lt;/h2&gt;
&lt;p&gt;メルカリ初となる世界共通アプリで、 海外の購入者は「グローバルアプリ」を通じて日本の「メルカリ」と「メルカリShops」の商品を閲覧・購入することができます。言語や決済、複雑な手続きなどの課題が解消され、海外の購入者は日本の「メルカリ」と同様、かんたんかつ安心・安全にお買い物を楽しめます。2025年9月30日より台湾・香港で提供を開始し、今後、展開する国や地域を順次拡大する予定です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/10/d14b0526--1024x373.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;公開予定表&lt;/h2&gt;
&lt;p&gt;こちらは、後日、各記事へのリンク集になります。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Title&lt;/th&gt;
&lt;th style=&quot;text-align: left;&quot;&gt;Author&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251007-a09afcd49b/&quot; title=&quot;グローバル展開にむけたアプリと基盤の再構築&quot;&gt;グローバル展開にむけたアプリと基盤の再構築&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@deeeet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251007-behind-the-infrastructure-powering-global-expansion/&quot; title=&quot;グローバル展開を支える基盤の裏側&quot;&gt;グローバル展開を支える基盤の裏側&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@yanolab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251009-from-local-to-global-building-seamless-b2c-product-integration-at-mercari/&quot; title=&quot;From Local to Global: Building Seamless B2C Product Integration at Mercari&quot;&gt;From Local to Global: Building Seamless B2C Product Integration at Mercari&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@ahsun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251010-order-management-in-mercari-global-marketplace/&quot;&gt;Order management in Mercari Global Marketplace&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@takady&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251012-the-journey-of-user-generated-content-translation/&quot;&gt;The Journey of User-Generated Content Translation&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@aymeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251013-behind-the-scenes-of-sre-supporting-the-global-web/&quot;&gt;グローバルWebを支えるSREの裏側 — 開発を加速させるための改善アプローチ&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@hatappi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251014-toward-a-global-identity-platform/&quot;&gt;Toward a Global Identity Platform&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@gia&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251016-e2e-tests/&quot;&gt;開発者全員が書けるE2Eテスト ─ 普通のgo testで実現するテスト基盤&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@ryotarai&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251016-50fb7b8c1a/&quot;&gt;グローバルなメルカリの検索バックエンド設計と検索基盤拡充&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@shinpei&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251018-global-web-app/&quot;&gt;Building a region‑aware, SEO‑friendly global web app&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@gary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20251021-scaling-code-quality-modular-monolith-readability-team-ai-era/&quot;&gt;モジュラモノリスの品質を支えるリーダビリティチーム ― AI時代のスケーラブルなコード管理&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@osari.k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251022-how-we-deliver-mobile-app-updates-faster/&quot;&gt;How We Deliver Mobile App Updates Faster&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@manoj&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251024-evolving-mercaris-ios-codebase-into-a-multi-product-monorepo/&quot; title=&quot;Evolving Mercari’s iOS codebase into a multi-product monorepo&quot;&gt;Evolving Mercari’s iOS codebase into a multi-product monorepo&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@shingt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251025-internationalization-in-web-monorepo/&quot;&gt;Enabling internationalization in our web Turbo monorepo&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@gary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251028-the-ai-lied-to-me-and-thats-when-i-learned-how-to-use-it/&quot;&gt;The AI Lied to Me — And That’s When I Learned How to Use It&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@andrei&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251030-taming-agents-in-the-mercari-web-monorepo/&quot;&gt;Taming Agents in the Mercari Web Monorepo&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@maxi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251117-benchmarking-databases-for-global-app/&quot;&gt;BenchMarking Databases For Global APP&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@amit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251120-behind-the-global-launch-decoding-the-android-engineering-strategy-for-our-new-app/&quot;&gt;Behind the Global Launch: Decoding the Android Engineering Strategy for Our New App&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Karthi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20251120-data-fetching-strategy-for-mercari-global-marketplace-web-app/&quot;&gt;Data-fetching strategy for Mercari Global Marketplace Web App&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@vb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;TBD: How we overcome Project management challenges (How to plan a product launch in 6 months)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@g-bansal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Guest post from FT payment platform — Engineering for Multi-Currency and Multi-Provider Payments&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@ryuyama&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;TBD&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@manas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;TBD: distributed transactions on checkout flow, specially error handling, retry&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@ahsun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Something about global payment and checkout&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@huhu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;TBD: Ops development with AI&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@waiting.lau&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Sync Saga&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Shishir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;TBD: High output teams&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Atif&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;TBD: Ordering Features&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Shreyasi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;TBD&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@Chong (チョン)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;TBD&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;@chris&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;
ひとつでも気になる記事がある方は、この記事をブックマークしておくか、 &lt;a href=&quot;https://x.com/mercaridevjp&quot; title=&quot;エンジニア向け公式X&quot;&gt;エンジニア向け公式X&lt;/a&gt; をフォロー＆チェックしてくださいね！&lt;/p&gt;
</content:encoded></item><item><title>メルカリ ハロのインターンで求人のリスク予測モデル作成・比較してみた</title><link>https://engineering.mercari.com/blog/entry/20250912-d05fee6c7e/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250912-d05fee6c7e/</guid><description>&lt;p&gt;はじめに 2025年度のBuild@Mercariに参加し、メルカリ ハロのMLチームでインターンをしている＠Ariaと@Ririkoです。私たちはメルカリ ハロの求人のリスク予測に取り組みました。この記事では、インター [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 12 Sep 2025 12:00:29 GMT</pubDate><content:encoded>&lt;h1&gt;はじめに&lt;/h1&gt;
&lt;p&gt;2025年度のBuild@Mercariに参加し、メルカリ ハロのMLチームでインターンをしている＠Ariaと@Ririkoです。私たちはメルカリ ハロの求人のリスク予測に取り組みました。この記事では、インターンで取り組んだこと・感想などについて書いていきたいと思います！&lt;/p&gt;
&lt;h1&gt;自己紹介&lt;/h1&gt;
&lt;p&gt;＠Aria&lt;/p&gt;
&lt;p&gt;こんにちは！大学１年の＠Ariaです。私は高校生の時Build@Mercariに参加し、夏休みでBuildインターンをしています！機械学習・AIについて学んでみたいと思い、メルカリハロのMLに応募しました。&lt;/p&gt;
&lt;p&gt;＠Ririko&lt;/p&gt;
&lt;p&gt;大学の学部3年の@Ririkoです！大学では電子情報工学を専攻しています。春休みにBuild@Mercariに参加しました。機械学習やAIに以前から興味があり、今回のBuildインターンに応募しました。本格的にインターンに参加するのはこれが初めてです。&lt;/p&gt;
&lt;h1&gt;背景&lt;/h1&gt;
&lt;p&gt;メルカリ ハロでは「求人の内容が適切か」「不適切な表現が含まれていないか」などといったリスクのある求人がないかを全件チェックしています。&lt;/p&gt;
&lt;p&gt;今回、私たちは、様々な機械学習手法で求人のリスク予測モデルを作成し、それぞれのコスト・精度・管理のしやすさなどの検証を行いました。&lt;/p&gt;
&lt;h1&gt;やったこと&lt;/h1&gt;
&lt;p&gt;以下のモデルを試し、コスト・精度・管理のしやすさなどの比較を行いました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;統計モデル
&lt;ul&gt;
&lt;li&gt;ロジスティック回帰&lt;/li&gt;
&lt;li&gt;LightGBM&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NNモデル
&lt;ul&gt;
&lt;li&gt;BERT&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LLM&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;各モデルの構築について&lt;/h1&gt;
&lt;h2&gt;概要&lt;/h2&gt;
&lt;p&gt;求人のリスク予測においては、全体数に対してリスクのある求人数が少ないです。モデル作成の際に最も重要なことはリスクのある求人を取りこぼさずに検知することです。モデルが誤ってリスクありと判断した求人の数(False Positive数)がある程度増えてしまってでも、モデルが誤ってリスクなしと判断した求人の数(False Negative数)をゼロにすることが重要になります。&lt;/p&gt;
&lt;p&gt;また、統計モデルやニューラルネットワークモデルを扱う際、訓練時に愚直に学習させると、データに不均衡があるためにすべての求人をリスクなしと判断するようになってしまいます。そのため、正例(リスクがある求人データ)の数を拡張したり、損失関数に重みづけをしたりするなどして学習を工夫する必要があります。&lt;/p&gt;
&lt;h2&gt;統計モデル&lt;/h2&gt;
&lt;p&gt;ロジスティック回帰とLightGBM、どちらのモデルを用いる場合でも、コンピューターが文章を理解できるよう前処理が必要です。まず、文章のテキストデータを形態素解析によって単語に分解します。例えば、「猫はコタツで丸くなる」という文は、「猫」「は」「コタツ」「で」「丸く」「なる」といった単語に分けられます。&lt;/p&gt;
&lt;p&gt;次に、TF-IDF(Term Frequency Inverse Document Frequency)という手法を用います。TFは文書内での単語の頻出度を表し、IDFは単語の希少度を表します。TFとIDFを掛け合わせることで、単語を数値化し、文書全体をベクトルとして表すことが可能になります。このベクトル化の際には、正例に特徴的な単語をFeature(ベクトルの基底)として抽出しました。TF-IDFを用いることで、「は」や「で」のように頻繁に出現するため重要度が低い単語ではなく、特定の文章にのみ現れる重要度の高い特徴的な単語を際立たせることができます。&lt;/p&gt;
&lt;p&gt;このようにして作られたベクトルを入力として、各モデルは、求人にリスクがある場合は1、ない場合は0のラベルを出力するよう学習させました。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/09/4f0dc22f--2025-09-11-12.47.37.png&quot; alt=&quot;テキストデータの前処理&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;ロジスティック回帰&lt;/h3&gt;
&lt;p&gt;ロジスティック回帰は、機械学習において最も基本的な分類モデルで、結果が0か1かといった二値である事象を予測するための統計的手法です。特定の事象が起こる確率を、説明変数を使って計算します。このモデルは、確率を0から1の間に収めるためのシグモイド関数という特殊な関数を用いるのが特徴です。&lt;/p&gt;
&lt;p&gt;訓練データを愚直に学習させると、データに不均衡があるために、うまく学習できずすべてリスクなしと判断するモデルになってしまいます。そのため、今回のロジスティック回帰では損失関数の計算時に、数少ない正例のペナルティが大きくなるように重み付けをしながら計算して学習を回しました。また、ハイパーパラメータのグリッドサーチなどによりモデルの性能向上を目指しました。&lt;/p&gt;
&lt;h3&gt;LightGBM&lt;/h3&gt;
&lt;p&gt;LightGBMは決定木をベースとする機械学習アルゴリズムです。特に、大規模なデータセットを扱う際に、その高速性と高い予測性能から、データサイエンスの分野で広く利用されています。&lt;/p&gt;
&lt;p&gt;LightGBMには、ハイパーパラメータと呼ばれる設定項目がたくさんあります。これらのパラメータを適切に設定することで、モデルの性能は大きく変わります。そのため、グリッドサーチという手法を使って、最適なパラメータの組み合わせを自動的に探索し、モデルの改善に取り組みました。&lt;/p&gt;
&lt;h2&gt;BERT&lt;/h2&gt;
&lt;p&gt;BERTは、2018年にGoogleにより開発されたTransformerのエンコーダー部分を基盤とする事前学習済み言語モデルです。大量のテキストデータから単語の文脈を双方向で学習するため、文全体の意味を深く理解できます。これにより、質問応答や文章要約など、さまざまなNLPタスクで高い性能を発揮します。&lt;/p&gt;
&lt;p&gt;BERTは正例と負例の数の不均衡なデータセットの学習が苦手です。そのため、LLMを用いて合成データを作成し、正例の数を増やしました。データ拡張の際にLLMに渡したプロンプトには元の正例の中からランダムに選んだデータをfew-shotとして組み込み、生成されたデータの文体に多様性が生まれるように工夫しました。さらに、BERTの学習において重要なパラメータ(トークン化の際の最大文字数、バッチサイズ、エポック数、学習率など)をグリッドサーチにより探索し、モデルの性能向上に取り組みました。&lt;/p&gt;
&lt;h2&gt;LLM&lt;/h2&gt;
&lt;p&gt;LLMに学習は必要ないので、データの前処理なしでプロンプトを工夫して、精度改善をしました。単に「リスクがあるか、ないか」のラベルだけでなく、なぜそのように判断したのかという理由も出力させました。LLMの出力から、誤った判断を下した理由や、不足している情報がないかを分析し、足りない指示をプロンプトに追加し改善しました。また、求人審査にはルールがあるため、そのルールをしっかり理解し、テキストデータを見直すことも精度改善につながりました。&lt;/p&gt;
&lt;h1&gt;実用上の観点から考えた各モデルの長所・短所&lt;/h1&gt;
&lt;table style=&quot;width:100%;&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 30%;&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;width: 35%;&quot;&gt;長所&lt;/th&gt;
&lt;th style=&quot;width: 35%;&quot;&gt;短所&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ロジスティック回帰/&lt;/p&gt;
&lt;p&gt;Light GBM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2値のクラスに分類するための境界線である閾値の調整によってどこまで検知するか決められる&lt;/p&gt;
&lt;p&gt;処理が非常に早い&lt;/p&gt;
&lt;p&gt;求人件数が増えても運用コストがあまり変わらない&lt;/td&gt;
&lt;td&gt;説明性が低い&lt;/p&gt;
&lt;p&gt;ルールが改定されたときに新たな学習データの用意、学習に手間がかかる&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BERT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2値のクラスに分類するための境界線である閾値の調整によってどこまで検知するか決められる&lt;/p&gt;
&lt;p&gt;文脈の理解が得意&lt;/td&gt;
&lt;td&gt;説明性が低い&lt;/p&gt;
&lt;p&gt;ルールが改定されたときに新たな学習データの用意、学習に手間がかかる&lt;/p&gt;
&lt;p&gt;推論にGPU、または多くのCPUを使うので統計モデルに比べて運用コストが高い&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;管理がプロンプトの更新のみで簡単&lt;/p&gt;
&lt;p&gt;ルールが改定された時も更新が容易&lt;/p&gt;
&lt;p&gt;過去にデータがないものへの対応が可能&lt;/p&gt;
&lt;p&gt;なぜそのように判断したかの理由が自然言語で説明できる&lt;/td&gt;
&lt;td&gt;件数が増えると、処理時間・運用コストが線形的に増える&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;比較結果 &lt;/h1&gt;
&lt;p&gt;統計モデルは複雑な文脈の問題に関しての求人のリスク検知は苦手であるものの、今回取り組んだタスクにおいては、学習方法を工夫することにより、LLMと同等の精度を出すことができました。&lt;/p&gt;
&lt;p&gt;BERTについても、学習に使うデータを拡張して正例・負例の数を同程度にすることにより、LLMと同等の精度を出すことができましたが、運用コストが統計モデルに対して高いので、今回の求人のリスク予測に関してはBERTを選択するメリットがないという結果になりました。&lt;/p&gt;
&lt;p&gt;運用コスト面で比較すると、統計モデルは小さなインスタンスで動くので、冗長性を考慮しても運用コストが低く、また求人件数が増えてもコストが変化しにくいです。BERTは統計モデルに比べてCPUがたくさん必要なので、元々のコストが高くなります。一方、LLMはトークンごとの課金のため、求人件数が増えるごとにコストも線形的に高くなります。&lt;/p&gt;
&lt;p&gt;これらの結果から、求人件数が少ない段階ではLLMが柔軟に活用できる一方、求人数が増えるとLLMではないモデルへの切り替えがコスト削減になることが分かります。&lt;/p&gt;
&lt;h1&gt;インターンでの学び・気づいたこと&lt;/h1&gt;
&lt;p&gt;今回のインターンを通して、テキストデータを前処理して統計モデルに適応する手法や今まで学ぶ機会がなかったBERTなどのモデルについて理解を深めることができました。モデルの性能を向上させるためにやるべき手法についても実際に手を動かしながら学ぶことができました。また、モデルに変更を加えて性能向上を目指すだけでなく、与えられたデータを自分の目でよく確認してデータの特徴を掴むことも非常に重要であることも学びました。&lt;/p&gt;
&lt;p&gt;実際の業務においては、自分が考えていることや試してみようと思っていることを他の人が確認できる形で言語化しておくことで、コミュニケーションがスムーズになるということを認識しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/09/a784698f-image.png&quot; alt=&quot;&quot; width=&quot;1720&quot;  class=&quot;size-full wp-image-34581&quot; srcset=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/09/a784698f-image.png 1720w, https://storage.googleapis.com/prd-engineering-asset/2025/09/a784698f-image-300x200.png 300w, https://storage.googleapis.com/prd-engineering-asset/2025/09/a784698f-image-1024x683.png 1024w, https://storage.googleapis.com/prd-engineering-asset/2025/09/a784698f-image-768x512.png 768w, https://storage.googleapis.com/prd-engineering-asset/2025/09/a784698f-image-1536x1024.png 1536w, https://storage.googleapis.com/prd-engineering-asset/2025/09/a784698f-image-1200x800.png 1200w&quot; sizes=&quot;(max-width: 1720px) 100vw, 1720px&quot; /&gt; 左から@Aria, @Ririko&lt;/p&gt;
&lt;h1&gt;終わりに&lt;/h1&gt;
&lt;p&gt;本記事では、インターンで取り組んだタスク、感想についてお話しさせていただきました。今回のインターンを通して、開発に必要な知識、またキャリア面での知識など様々な学びを得ることができました。一ヶ月という時間はあっという間でしたが、とても濃い時間を過ごせました。&lt;/p&gt;
&lt;p&gt;メンターの@ku-muさん、アドバイスをくださった@arr0wさん、ML teamの皆さん、本当にありがとうございました！&lt;/p&gt;
</content:encoded></item><item><title>文系の私がBuild@Mercariでエンジニアへの第一歩を踏み出せた話</title><link>https://engineering.mercari.com/blog/entry/20250910-c647c9a463/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250910-c647c9a463/</guid><description>&lt;p&gt;はじめに はじめまして！８月の１ヶ月間、Buildインターンに参加したkyoroです。 文系の私にとって、Build@Mercariは「エンジニアへの第一歩」となった大変貴重な成長機会でした。私がBuild@Mercar [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 10 Sep 2025 14:13:45 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;はじめまして！８月の１ヶ月間、Buildインターンに参加したkyoroです。&lt;/p&gt;
&lt;p&gt;文系の私にとって、Build@Mercariは「エンジニアへの第一歩」となった大変貴重な成長機会でした。私がBuild@Mercariで学んだことや経験したことを共有することで、同じように「エンジニアになりたいけど、非STEM領域出身で自信がない」や、「成長の機会に恵まれていない」と感じている方々に、Build@Mercariという選択肢があることを知ってほしいと思いました。&lt;br /&gt;
私に似たバックグラウンドをお持ちの方や、これから参加を検討されている方々の参考になれば幸いです。&lt;/p&gt;
&lt;h3&gt;なぜBuild@Mercariに参加したか&lt;/h3&gt;
&lt;p&gt;私は大学１年生で受講したデータサイエンスの授業をきっかけにプログラミングに興味を持ちました。しかし、私の学部は完全に文系でCS関連の授業もなかったため、それ以降は独学で学習を進めていました。&lt;br /&gt;
独学で学習を進めていたものの、実践の機会が少なく、あまり成長を実感できていない部分がありました。インターンシップを通して実践的に成長できたら、と思っていましたが、応募時点で一定の開発経験や制作物を求められることが多く、なかなか受け入れてもらえない状況が続いていました。そんな時、テックコミュニティ経由でBuild@Mercariの存在を知りました。現時点での経験が浅くても参加できるエンジニア育成プログラムと聞いてすぐに応募を決めました。&lt;/p&gt;
&lt;h3&gt;Build@Mercariってどんなプログラム？&lt;/h3&gt;
&lt;p&gt;性自認が女性である方を対象に、ソフトウェアエンジニアリングのスキルトレーニングとインターンシップの機会を提供するプログラムです。&lt;br /&gt;
現在STEM領域では女性がマイノリティとなっています。業界全体のD&amp;amp;Iを推進するべく、メルカリではこうした学習機会を私たちに提供してくれています。&lt;/p&gt;
&lt;h2&gt;Buildトレーニングについて&lt;/h2&gt;
&lt;p&gt;２週間で、メルカリを想定した簡易出品アプリを個人で構築します。&lt;br /&gt;
この課題を通じて、Gitの使い方からAPI開発、フロントエンドの実装、Dockerによるコンテナ化まで、Webアプリケーション開発の基本を一通り学ぶことができました。アプリ構築以外にも「アルゴリズムとデータ構造」や「データ分析」を学ぶステップも用意されており、非常に内容が充実しています。&lt;/p&gt;
&lt;h3&gt;選考の話&lt;/h3&gt;
&lt;p&gt;選考では、志望動機とコーディングテストを提出します。コーディングテストは、コンピュータサイエンスに関する基礎知識を確認するためにオンラインで実施されます。Buildトレーニングプログラムは、通常のインターンシップ選考とは異なり、多くの方にチャレンジしていただきたいという想いから、コーディングテストの難易度は低めに設定されています。そのため、現時点でのスキルに自信がない方も応募をためらう必要はありません。&lt;/p&gt;
&lt;h3&gt;このトレーニングの良かった点&lt;/h3&gt;
&lt;p&gt;１つ目は「数人のチームに分かれて、各自課題に取り組む」ことです。平日は毎日、グループ内で進捗共有の時間があり、メンターさんに質問したり、他のメンバーの進み具合を知ることができました。ここで自分の遅れを認識し、良い意味で焦れたことが、トレーニングを完遂できた大きな要因でした。初学者が一人で学習していると、わからないところで立ち止まってしまい、そのまま学習を中断してしまうことも多いです。そういった意味でも、このように仲間やサポートがある環境は、とても心強かったです。&lt;/p&gt;
&lt;p&gt;２つ目は、「参加者は１年間無料でUdemyの講義を受講できる」ことです。&lt;br /&gt;
参加時点では周辺知識がほとんどなかったため、とにかくトレーニング中はコードを動かすことで精一杯でした。しかし、トレーニング終了後にUdemyを大いに活用し、点と点だった理解を少しずつつなげて腹落ちさせ、「なぜそのコードが必要なのか」「どんな仕組みで動いているのか」を理解することができました。&lt;/p&gt;
&lt;h3&gt;トレーニングの成果&lt;/h3&gt;
&lt;p&gt;参加前の私は git と github の違いすらわからないレベルでした。しかし、トレーニング終了の２ヶ月後に１人で参加したハッカソンで企業賞をもらえるレベルに成長していました。Buildトレーニングは独学で伸び悩んでいた私に大きな成長を与えてくれました。&lt;/p&gt;
&lt;h2&gt;Build インターンについて&lt;/h2&gt;
&lt;h3&gt;選考の話&lt;/h3&gt;
&lt;p&gt;構築した出品APIが動作確認のテストに通ったトレーニング参加者は、１ヶ月間のBuildインターンに進むことができます。&lt;br /&gt;
基本的には、トレーニング終了後に提出するアンケートに回答した希望ポジションと部署を前提に選考が進みます。&lt;br /&gt;
また、就業型インターンに応募して不合格となった場合でも、再度Buildインターンへ応募することも可能だそうです。&lt;br /&gt;
Buildインターンの選考では３０分程度の面接が１回設けられます。この面接は開発経験などの深掘りというよりも、配属先のエンジニアマネージャーさんとインターンへの参加意欲の確認を行いました。&lt;/p&gt;
&lt;h3&gt;配属先&lt;/h3&gt;
&lt;p&gt;私はメルカリのお客さまのお問い合わせ管理システムを構築しているContact Centerチームにバックエンドエンジニアとして配属されました。&lt;/p&gt;
&lt;h3&gt;取り組んだこと&lt;/h3&gt;
&lt;p&gt;現状のシステムでは、お問い合わせに対する自動返信や通知メールの文言がハードコードされており、文言の変更コストがかかることが課題でした。私は、その文言をデータベースから取得できるようにするタスクに取り組みました。&lt;br /&gt;
将来的にこの機能を拡張させ、管理画面から文言に変更を加えられるようにすることで、実際にお問い合わせに対応する非エンジニアの方でも文言の変更が可能となり、運用コスト削減や、変更スピードの向上が期待できます。&lt;/p&gt;
&lt;p&gt;大きく分けて３つのフェーズでタスクに取り組みました。&lt;/p&gt;
&lt;h4&gt;1. DB設計&lt;/h4&gt;
&lt;p&gt;どの文言をデータベースに移行するかを、文言が使われる場面や、その変更可能性も考慮しながら検討しました。&lt;br /&gt;
最初は深く考えずに、既存の他のDB設計を参考に設計していました。チームの方に設計をレビューしていただいた際、なぜそこでこの制約を入れたのかという質問に対してすぐに答えられなかった経験から、自分の設計根拠の甘さに気づくことができました。&lt;br /&gt;
DB設計のみならず、設計や方針に唯一の正解はなく、エンジニア間で議論する中で自分一人では気付けなかった点に気づくことで、最適な解が導き出されることを学びました。議論を円滑に進めるためにも、常になぜ自分がその方針が良いと思ったのか、他に考えられる方針はないのか比較検討した上で、設計の意図や根拠を明確にもつ重要性を学びました。&lt;/p&gt;
&lt;h4&gt;2. DBへの文言挿入&lt;/h4&gt;
&lt;p&gt;実際にデータベースへ文言を挿入する段階では、移行する大量の文言を既存の文言と一言一句のずれも起きないよう、正確なクエリを作成する作業が発生しました。&lt;br /&gt;
また、Spanner CLIの仕様上、文言に含まれる空行をSQL文の終了と認識してしまうため、全ての空行を改行文字に変更する必要があり、非常にミスが発生しやすい作業でした。作成したSQL文の最終確認は大変骨の折れる作業でした。最終確認をしていただいたチームの方々、日常の忙しい業務の最中、時間を割いていただいてありがとうございました。&lt;/p&gt;
&lt;h4&gt;3. 文言取得の実装&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;詳細設計の話&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;実装時の大幅な手戻りを回避するため、詳細設計を詰めてから実装に入りました。詳細設計を立てるには、５年分のコンテキストがある既存コードの流れを短期間で把握する必要があり、最も時間がかかりました。また、プロジェクト初期ほど見積もりの誤差が大きくなるという、「&lt;a href=&quot;https://en.wikipedia.org/wiki/Cone_of_uncertainty&quot; title=&quot;不確実性コーン&quot;&gt;不確実性コーン&lt;/a&gt;」のお話が興味深かったです。&lt;br /&gt;
最初に私が立てた詳細設計は見積もりが甘く、もう少し内容を詰めるようレビューをいただきました。その結果、後の実装工程では迷わず手を動かすことができました。開発プロセスの中で設計を疎かにするとその分の見積もりの誤差が実装に引き継がれてしまうということを身をもって学ぶことができました。&lt;br /&gt;
チームでは「Architecture Decision Record」と呼ばれる意思決定記録に設計方針を残していました。「なぜ、そういう設計になったのか」や「他の案はあったのか？」などの議論や決定事項を記録しておくことで、今後新しい機能を実装しようとした時に、過去の設計をそのまま使用できたり、参考にして拡張することができる利点があると教えていただきました。ハッカソンのチーム開発経験とは比べものにならない「現場のチーム開発」を学ぶことができました。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;テストコードの話&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;テストコードを組み立てるのは今回が初めてでした。今までは正常に動作していることを確認するだけで満足していましたが、コードの品質や保守性を高める重要な手段であることを学びました。Go特有の「テーブル駆動テスト」や「テスト駆動開発」、正常に値が返されるかだけではなく、渡す値などを変えた時、エラーがきちんと返されるかなど、「幅広いテストケースの想定」が必要であることを学び、テストコードの奥深さを知りました。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Goの話&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Contact Centerチームではバックエンド開発言語にGoが採用されていました。Goに触れるのは今回のインターンが初めてだったため、開発を進めながら多くのGo独自の構文や考え方を学べました。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;全体を通して学んだこと&lt;/h2&gt;
&lt;h3&gt;エンジニアの仕事&lt;/h3&gt;
&lt;p&gt;インターンを通して実務に入ったことで、エンジニアに求められることはコードを書く力だけではないことを強く実感しました。特に印象的だった学びが、以下の３つです。&lt;/p&gt;
&lt;p&gt;１つ目は、「プログラミングは、コードを”書く”時間よりもコードを”読む”時間の方が多い」だということです。機能を新しく追加するにしても、まずは既存のコードを読み解いて、どこをどう変更すべきかを理解する必要があります。&lt;/p&gt;
&lt;p&gt;２つ目は、開発には期限があり、その中でいかに優先順位をつけてタスクを進めていくかも仕事では求められるということです。チームに進捗を伝える際も、「まだ終わってません」と伝えるのではなく、「現状で〜%進んでいます。ここまでに〜日かかったので、残りも同じくらいのペースで進めば、あと〜日程度かかりそうです」というように、進捗と予測をセットで伝える報告の仕方がチームとしての動きやすさにもつながるというお話もお聞きし、勉強になりました。&lt;/p&gt;
&lt;p&gt;３つ目は、仕事をチームで進める上で技術力と同じくらい、「伝える力」が大切ということです。PRを作成する際もレビューする側の目線に立って、意図や背景を丁寧に書くことであったり、１on１での質問においても、ただ「ここがわかりません」と言うのではなく、「〜について調べたり、〜を試したけれど、この時点で詰まっています」というふうに、自分の思考プロセスを整理して伝えることで、より的確なアドバイスをもらえると感じました。&lt;/p&gt;
&lt;h3&gt;アーキテクチャ&lt;/h3&gt;
&lt;p&gt;アーキテクチャに関する話の中で、メルカリがモノリシックな構成からマイクロサービス化へと移行していった背景や、その過程で起きた技術的・組織的な変化について伺いました。&lt;br /&gt;
特に印象的だったのは、マイクロサービス化によって各モジュールが自律的に機能するようになったことで、開発チームもそれぞれが独立して動けるようになり、組織構造そのものにも変化があったという点です。&lt;br /&gt;
また、既存コードの流れを把握する際に、「ドメイン駆動設計」や「クリーンアーキテクチャ」といった考え方も教えていただきました。&lt;br /&gt;
こうした設計思想は、単に「きれいなコードを書く」ためではなく、長期的に安定したシステムを作るための考え方であることを学びました。&lt;/p&gt;
&lt;h3&gt;メルカリ文化&lt;/h3&gt;
&lt;h4&gt;社内勉強会&lt;/h4&gt;
&lt;p&gt;社内勉強会も活発で、私はOpenAI社の講師によるトレーニングプログラムに参加しました。会話をするAIを、「指示、知識、アクション」の観点から細かく設定できるカスタムGPTの活用事例を実演形式で学びました。&lt;br /&gt;
普段からノンカスタムGPTは回答の情報量が多く、何が質問に対する回答の本質なのか見失いがちだったため、「なるべく不必要な部分を削ぎ落とし、シンプルで初学者にとってわかりやすい説明を心がけること」、「より人間らしく、後輩から慕われるようなエンジニアとして振る舞うこと」を指示したカスタムGPTを作成しました。インターン期間中このGPTへの質問でよりスピーディーに疑問を解消しながら開発できたこともあり、実際に社内勉強会での学びの恩恵を受けました。こうした様々な勉強会に参加できる環境が非常に魅力的であると感じました。&lt;/p&gt;
&lt;h4&gt;開発手法&lt;/h4&gt;
&lt;p&gt;チームでは「スクラム」という開発手法が取り入れられていました。スプリントと呼ばれる数週間の単位で開発期間を短く区切り、毎回「今回のスプリントではこれをやる」という目標をチームできめてから開発に入る形でした。&lt;br /&gt;
スプリント期間中は毎朝のDaily Scrum（朝会）でチーム内で進捗や課題を共有しあい、タスクの進行を常に可視化しながらチーム全体の開発を前に進めていたことが印象的でした。&lt;/p&gt;
&lt;h4&gt;その他&lt;/h4&gt;
&lt;p&gt;インターン期間中、Contact Centerチームの方々やトレーニングで担当していただいたメンターさんと何度かランチに行かせていただきました。「キャリアの話」、「日本人チームと外国人チームの違い」、「メルカリの昔と今」、「エンジニアの成長ステップ」といったテーマについて、実際に働くエンジニアの方々から直接お話を伺うことができました。普段なかなか聞くことができないような、リアルな現場の話や考えに触れることができたことで、自分自身のキャリアや働き方を考えるきっかけにもなり、大変貴重な学びとなりました。&lt;/p&gt;
&lt;h2&gt;終わりに&lt;/h2&gt;
&lt;h3&gt;メルカリと配属先チームの皆さんへ&lt;/h3&gt;
&lt;p&gt;Build@Mercariという成長機会を提供してくれたメルカリ、そしてインターンで受け入れてくださったContact Centerチームのメンターさんをはじめとするメンバーの皆さんに心から感謝の気持ちを述べたいと思います。短い期間でしたがここには書ききれないほどたくさんのことが学べました。本当にありがとうございました。&lt;/p&gt;
&lt;h3&gt;これから参加しようと考えている方へ&lt;/h3&gt;
&lt;p&gt;ほぼゼロだった私がここまで多くのことを学び成長することができたのはBuild@Mercariのおかげです。もし今の自分のスキルに自信がなくても、エンジニアとして成長したいと考えているなら、ぜひBuild@Mercariへ参加して欲しいです。また、Buildインターンや就業型インターンで実際にメルカリのサービスに触れることで得られる学びは非常に大きいです。ぜひBuildトレーニングで終わらずに、有給インターンシップまで進んで欲しいです。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※この体験記は2025年度（今年度）のプログラム内容です。来年度以降のプログラムにおいては内容が変更になる可能性がありますので、ご了承ください。&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>オフサイトで実践したふりかえりワークショップ設計の工夫と学び</title><link>https://engineering.mercari.com/blog/entry/20250807-16d7cf7b4b/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250807-16d7cf7b4b/</guid><description>&lt;p&gt;メルカリハロで QA Engineering manageをしている @____rina____です。 本記事では、プロジェクトチームで実施したオフサイトについて、スクラムマスターとしてワークショップデザインを担当した経 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 12 Aug 2025 08:00:54 GMT</pubDate><content:encoded>&lt;p&gt;メルカリハロで QA Engineering manageをしている &lt;a href=&quot;https://engineering.mercari.com/blog/author/underscore42rina/&quot; title=&quot;@\_\_\_\_rina\_\_\_\_&quot;&gt;@____rina____&lt;/a&gt;です。&lt;/p&gt;
&lt;p&gt;本記事では、プロジェクトチームで実施したオフサイトについて、スクラムマスターとしてワークショップデザインを担当した経験を共有します。&lt;/p&gt;
&lt;p&gt;リモートワークも継続する中で、対面でのオフサイトをどのように設計し、初回参加者への配慮をどのように実践したかについて詳しく解説します。&lt;/p&gt;
&lt;h4&gt;この記事から読者が学べること：&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;長期プロジェクトの効果的なふりかえり手法（タイムラインふりかえり）&lt;/li&gt;
&lt;li&gt;AIを活用したワークショップデザインの実践例&lt;/li&gt;
&lt;li&gt;初回参加者への配慮と心理的安全性の確保方法&lt;/li&gt;
&lt;li&gt;5グラウンドルールを活用した質の高い議論の実現方法&lt;/li&gt;
&lt;li&gt;対面でのチームビルディングの重要性と効果&lt;/li&gt;
&lt;li&gt;リモートワーク環境でのコミュニケーション技術的課題と解決策&lt;/li&gt;
&lt;li&gt;アナログ手法による対面ワークショップの効果と重要性&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;執筆者自身の学び：&lt;/h4&gt;
&lt;p&gt;スクラムマスターとしてワークショップデザインを担当した経験を通じて、参加者の心理的安全性を確保することの重要性を改めて実感しました。特に、初回参加者への丁寧な説明や視覚的な資料の活用、段階的な進行が、ワークショップの成功に直結することを学びました。また、AIを活用した効率的なワークショップ設計の可能性も実感でき、人間ならではの創造性や配慮と組み合わせることで、より効果的なワークショップを設計できることを確認しました。&lt;/p&gt;
&lt;h2&gt;開催概要&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/882ea1af-member-1024x437.jpg&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
今回実施したのは、メルカリハロで事業者向けサービスを開始するにあたり、事業者から手数料を徴収する仕組みを構築するプロジェクトチーム向けのオフサイトです。このチームのメンバーが、福岡市内の会場に集まり、5時間にわたってオフサイトを開催しました。普段はリモートで業務を進めているメンバーですが、この日は全国からメンバーが一堂に会し、対面ならではの熱量と一体感を感じながら、プロジェクトのこれまでとこれからについてじっくりと語り合う貴重な時間となりました。&lt;/p&gt;
&lt;p&gt;参加者はPM、エンジニア、EM、デザイナー、QAから10名を超えるメンバーが参加し、初回参加者も含めて多様なバックグラウンドを持つメンバーが集まりました。&lt;/p&gt;
&lt;h2&gt;背景・目的&lt;/h2&gt;
&lt;p&gt;今回のオフサイトを企画した背景には、長期にわたる手数料プロジェクトをふりかえり、今後の改善やチームの連携強化を図りたいという思いがありました。リモートワークも続く中、日々のコミュニケーションはどうしてもテキストやオンライン会議に偏りがちです。だからこそ、対面で集まり、普段は話せないような深い議論や、カジュアルな交流を通じて、チームとしての結束力を高めることが不可欠だと考えました。&lt;/p&gt;
&lt;p&gt;手数料プロジェクトでは私がスクラムマスターを務めており、今回のオフサイトはワークショップデザインから全体の進行まで、一貫して設計・運営を担当しました。特に意識したのは、初回参加者が安心して参加できる環境を整えることでした。&lt;/p&gt;
&lt;h2&gt;ワークショップデザインの工夫&lt;/h2&gt;
&lt;h3&gt;AIを活用したアジェンダ作成とアイスブレイク設計&lt;/h3&gt;
&lt;p&gt;今回のオフサイトの準備段階では、AIを積極的に活用しました。まず、ワークショップ全体のアジェンダ作成において、AIに手数料プロジェクトの特性や参加者の構成、目的などを入力し、最適な進行スケジュールの提案を受けました。AIが提案した時間配分やセッション構成をベースに、実際の参加者数や会場の制約を考慮して調整を加えることで、効率的で効果的なアジェンダを作成できました。&lt;/p&gt;
&lt;p&gt;特に印象的だったのは、アイスブレイク用のクイズ作成です。AIに福岡の文化や目にする予定の建築物に関する問題を生成してもらい、参加者の滞在をもっと楽しくする内容にしました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/7dfcd268-quiz-1024x582.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;初回参加者への配慮：丁寧なチェックインと説明&lt;/h3&gt;
&lt;p&gt;ワークショップデザインにおいて最も重要視したのは、初めてワークショップに参加するメンバーへの配慮でした。参加者の中には、付箋を使ったワークショップや、グループディスカッションに不慣れな方もいました。そのため、各セッションの開始時には必ず丁寧な説明を行い、参加者が迷わないよう配慮しました。&lt;/p&gt;
&lt;h3&gt;各セッションでの具体的な配慮&lt;/h3&gt;
&lt;p&gt;タイムラインふりかえりでは、具体的な手順を視覚的な資料とともに説明しました。グルーピング作業では、抽象的な指示ではなく具体的な例を示すことで、参加者が迷わずに作業を進められるよう工夫しました。&lt;/p&gt;
&lt;p&gt;各セッションの開始時には必ず目的、手順、期待する成果物を明確に伝え、質問しやすい雰囲気を作ることで、参加者全員が安心してワークショップに参加できる環境を整えました。&lt;/p&gt;
&lt;h2&gt;当日の流れ&lt;/h2&gt;
&lt;p&gt;オフサイトは、参加者全員が最大限に集中し、活発な議論ができるよう、綿密にデザインされたプログラムで進行しました。&lt;/p&gt;
&lt;h3&gt;アイスブレイクの意味と目的の説明&lt;/h3&gt;
&lt;p&gt;ワークショップの冒頭では、まず「アイスブレイクとは何か」「なぜアイスブレイクが必要なのか」について、参加者全員に丁寧に説明しました。この配慮を特に重視したのは、私自身が過去にワークショップに参加した際、突然ゲームが始まって「これをする意味がよくわからない」と混乱した経験があったからです。&lt;/p&gt;
&lt;p&gt;アイスブレイクが単なる場を和ませるための時間ではなく、その後の議論の質を左右する重要な要素であることを理解してもらうため、以下の4つの目的を明確に伝えました：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;参加者同士の緊張をほぐす&lt;/strong&gt;: 初対面や久しぶりの対面で生じるぎこちなさを解消し、心理的安全性を高める&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;場の雰囲気を明るくする&lt;/strong&gt;: ポジティブな空気を作り出し、その後の議論が活発になる土台を築く&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;親近感を高める&lt;/strong&gt;: 共通の体験を通じて、お互いへの理解を深め、チームとしての繋がりを強化する&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集中力を高める&lt;/strong&gt;: 軽いアクティビティを通じて、参加者の意識をオフサイトのテーマへと自然に引き込む&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;この説明により、参加者はアイスブレイクの重要性を理解し、積極的に参加することができました。この事前の配慮が、次のアイスブレイクセッションでの自然な参加につながりました。&lt;/p&gt;
&lt;h3&gt;アイスブレイク：緊張をほぐし、一体感を育む時間&lt;/h3&gt;
&lt;p&gt;アイスブレイクとして、事前にAIを活用して作成した福岡にちなんだクイズからスタートしました。アイスブレイクの目的は、参加者同士の緊張をほぐし、場の雰囲気を明るくし、親近感を高めることです。お互いの意外な一面を知るような質問を投げかけることで、笑い声が絶えない和やかな雰囲気を作り出すことができました。&lt;/p&gt;
&lt;p&gt;特に、ワークショップ形式に不慣れなメンバーからも自然な笑い声が聞こえ、緊張していた表情が和らいでいく様子が印象的でした。このアイスブレイクを通じて、参加者全員が同じ土俵に立ち、次のセッションに向かう準備が整いました。&lt;/p&gt;
&lt;h3&gt;5グラウンドルールの共有&lt;/h3&gt;
&lt;p&gt;アイスブレイクの後、ワークショップを円滑に進めるための「&lt;a href=&quot;https://biztool.jp/updates/updates_detail/fivegroundrules&quot; title=&quot;5グラウンドルール&quot;&gt;5グラウンドルール&lt;/a&gt;」を参加者全員で共有しました。このルールは、今年私が参加した社外イベントで講師の方が使用していたもので、運営者として参加した私がその効果を実感し、今回のオフサイトで採用することにしました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ほめる&lt;/li&gt;
&lt;li&gt;聴く&lt;/li&gt;
&lt;li&gt;受けとめる&lt;/li&gt;
&lt;li&gt;待つ&lt;/li&gt;
&lt;li&gt;愉しむ&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;実際に体験者として参加したことで、これらのルールが参加者の心理的安全性を高め、質の高い議論を生み出す効果を実感できました。今回のオフサイトでも、参加者全員が同じ価値観でワークショップに臨むことができました。&lt;/p&gt;
&lt;h3&gt;タイムラインによるプロジェクトのふりかえり&lt;/h3&gt;
&lt;p&gt;アイスブレイクで場が温まり、「5グラウンドルール」で参加者全員の認識がそろったところで、メインコンテンツである「タイムラインを使ったプロジェクトのふりかえり」へと移りました。手数料プロジェクトは長期にわたるため、過去の出来事を時系列で整理し、共通認識を持つことが非常に重要だと考えました。&lt;/p&gt;
&lt;h4&gt;ステップ1：できごとと感じたことを書く&lt;/h4&gt;
&lt;p&gt;まず、ロール上の模造紙にプロジェクトのタイムラインを引き、各メンバーが印象に残っているできごとや感じたことを付箋に書き出し、該当する時期に貼り付けていきました。このプロセスでは、まず事実としての「できごと」を書き出し、それに対して「感じたこと」を複数書き出すという明確な手順を示しました。&lt;/p&gt;
&lt;p&gt;事前準備として、これまでの議事録をNotebookLMに読み込み、音声出力サマリーを作成しました。このサマリーを参加者全員で聞くことで、プロジェクトの全体像を共通認識として持つことができ、より具体的で深い議論につながりました。&lt;/p&gt;
&lt;p&gt;付箋には色分けを採用し、できごとは黄色、感じたことはその他の色で分類することで、視覚的に情報を整理しやすくしました。アナログな手法だからこそ、参加者が直接手を動かして情報を整理でき、デジタルでは得られない物理的な体験を通じて、より深い議論が生まれました。&lt;/p&gt;
&lt;h4&gt;ステップ2：付箋を貼る&lt;/h4&gt;
&lt;p&gt;タイムライン上に、重要なマイルストーンを設定し、各参加者が該当する時期の付箋を貼り付けていきました。&lt;/p&gt;
&lt;p&gt;このステップでは、できごとについて簡単に説明する時間も設けました。これにより、他のメンバーが知らなかった出来事や、異なる視点での捉え方を共有することができ、プロジェクトの多面的な理解が深まりました。&lt;/p&gt;
&lt;p&gt;特に印象的だったのは、書いた人が読み上げることで、「これもあった」「これもあった」と次々と思い出が湧き上がり、付箋がどんどん増えていったことです。この段階で参加者全員の熱量が一気に上がり、ワークショップの雰囲気が大きく変わりました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/41ebff3b-retro1-1024x261.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;ステップ3：グルーピングする&lt;/h4&gt;
&lt;p&gt;タイムラインに沿って書き出された付箋を、関連性のあるもの同士でまとめる作業を行いました。同じような課題や、同じ時期の出来事、同じチームに関連するものをグループ化することで、プロジェクト全体の課題がより明確になりました。&lt;/p&gt;
&lt;h4&gt;ステップ4：話を深掘りしたい付箋にシールを貼る&lt;/h4&gt;
&lt;p&gt;グルーピングされた付箋の中から、さらに詳しく議論したいトピックや課題にシールを貼ることで、優先順位付けや深掘りの対象を明確にしました。このプロセスを通じて、プロジェクトの成功体験や課題、転換点などが視覚的に明確になり、参加者全員が同じ視点でプロジェクトの全体像を把握できるようになりました。&lt;/p&gt;
&lt;h3&gt;グループディスカッション&lt;/h3&gt;
&lt;p&gt;タイムラインのふりかえり後、抽出されたトピックを基にグループに分かれ、グループディスカッションを実施しました。各グループには45分間の時間を設け、特定の課題やテーマについて深掘りし、具体的な課題の抽出や次のネクストアクションを導き出すことに注力しました。&lt;/p&gt;
&lt;h3&gt;ネクストアクションの抽出と優先度決め&lt;/h3&gt;
&lt;p&gt;グループディスカッションで出された多くのアイデアや課題の中から、最も重要で実行可能な「ネクストアクション」を特定し、その優先順位を決定しました。この段階では、各アクションの実現可能性や影響度を考慮し、チーム全体で合意できる優先順位を設定することができました。&lt;/p&gt;
&lt;h3&gt;オフサイト自体のふりかえり&lt;/h3&gt;
&lt;p&gt;今回のオフサイトがどれだけ効果的だったかを評価するため、「ふりかえりのふりかえり」も実施しました。早めに全員がパソコンを閉じて、オフサイトに集中できる環境を作ったことや、福岡という場所を選んだことで普段参加が難しいメンバーも参加できたことが、特に良かった点として挙げられました。長時間の開催にも関わらず、議論が途切れることなく活発に進行できたのは、リモートワークでは実現困難な対面ならではの集中力と一体感の賜物でした。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;今回のオフサイトは、手数料プロジェクトの現状や課題を整理し、今後のアクションにつなげるだけでなく、チームとしての結束力を高める貴重な機会となりました。スクラムマスターとしてワークショップデザインを担当した経験は、今後のプロジェクト運営にも大きな学びとなりました。&lt;/p&gt;
&lt;h4&gt;おもな成果：&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;長期プロジェクトの効果的なふりかえり手法（タイムラインふりかえり）の実践&lt;/li&gt;
&lt;li&gt;AIを活用したワークショップデザインの効果検証&lt;/li&gt;
&lt;li&gt;初回参加者への配慮と心理的安全性の確保による質の高い議論の実現&lt;/li&gt;
&lt;li&gt;5グラウンドルールを活用したチーム全体の価値観統一&lt;/li&gt;
&lt;li&gt;対面でのチームビルディングによる信頼関係の強化&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Agile Testing Days 2024参加記：探索的テストとテスト自動化を学んだ海外ワークショップ体験</title><link>https://engineering.mercari.com/blog/entry/20250806-bafba5b2e8/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250806-bafba5b2e8/</guid><description>&lt;p&gt;メルカリハロで QA Engineering manageをしている @____rina____です。 昨年2024年11月に開催されたAgile Testing Daysとそこで参加したワークショップについて紹介します [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 08 Aug 2025 08:00:13 GMT</pubDate><content:encoded>&lt;p&gt;メルカリハロで QA Engineering manageをしている &lt;a href=&quot;https://engineering.mercari.com/blog/author/underscore42rina/&quot; title=&quot;@\_\_\_\_rina\_\_\_\_&quot;&gt;@____rina____&lt;/a&gt;です。&lt;br /&gt;
昨年2024年11月に開催された&lt;a href=&quot;https://agiletestingdays.com/&quot; title=&quot;Agile Testing Days&quot;&gt;Agile Testing Days&lt;/a&gt;とそこで参加したワークショップについて紹介します。&lt;/p&gt;
&lt;p&gt;&lt;!--more--&gt;&lt;/p&gt;
&lt;h2&gt;Agile Testing Daysとは何か&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/e52e448e-agiletestingdays-1024x383.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;　&lt;a href=&quot;https://agiletestingdays.com/atd-experience/2024/&quot; title=&quot;Agile Testing Days&quot;&gt;Agile Testing Days&lt;/a&gt;とは、ドイツのポツダムで毎年開催されているカンファレンスです。参加者層はテスター、アジャイルテスター、QAエンジニア、テストリード、テストオートメーションエンジニアといったQAやテストに関するエンジニアに加え、ディベロッパー、ソフトウェアエンジニア、アジャイルコーチ、スクラムマスター、プロダクトオーナーやチームリードなど様々な方が対象のカンファレンスです。今回参加した日本語話者の参加者は数名だったこと、ヨーロッパの参加者が多かったようで、コミュニケーションは英語でおこないました。&lt;/p&gt;
&lt;p&gt;Agile Testing Daysでは、1日のワークショップと3日間のカンファレンスで構成されています。カンファレンスでは、キーノートをはじめ、セッション、パネルディスカッション、ワークショップなどが開催されます。また、セッションの合間にはコーヒーブレイクやランチなどもが提供され、朝のジョギングから夜の音楽イベントまで、丸一日イベントを楽しむための仕掛けがたくさん用意されていました。&lt;/p&gt;
&lt;p&gt;　カンファレンス会場はアジャイルテストの第一人者とも言える&lt;a href=&quot;https://lisacrispin.com/&quot; title=&quot;Lisa Crispin&quot;&gt;Lisa Crispin&lt;/a&gt;に会うことができたり、たくさんの有識者に会えたり、同じ悩みを持ったエンジニアと交流できたりし、とても刺激的なイベントでした。&lt;br /&gt;
　スピーカーはシニアエンジニアに限らす、はじめて登壇される方のセッションなど、幅広い登壇者のたくさんの発表を聞くことができました。&lt;/p&gt;
&lt;h2&gt;Getting a grip on exploratory testing with test charters&lt;/h2&gt;
&lt;p&gt;　Ewald WassinkSconewile氏とRob van Sttenbergen氏による、探索的テストのワークショップです。探索的テストのワークショップは国内でも時々見かけるようになりました。&lt;br /&gt;
　このワークショップはいくつかのテーブルに分かれてグループワークをしました。私のグループは4名で、2名はドイツからの参加で、英語でコミュニケーションをしました。&lt;br /&gt;
次にワークショップの流れを紹介します。&lt;/p&gt;
&lt;h3&gt;ワークショップの流れ&lt;/h3&gt;
&lt;p&gt;以下のような流れでワークショップをデザインしていました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;講師の自己紹介とワークショップの説明&lt;/li&gt;
&lt;li&gt;フリースタイルで探索的テストをする&lt;/li&gt;
&lt;li&gt;2で見つけた不具合の紹介&lt;/li&gt;
&lt;li&gt;テストチャーターを利用した探索的テスト&lt;/li&gt;
&lt;li&gt;グループディスカッションと発表&lt;/li&gt;
&lt;li&gt;登壇者の考案したフレームワークを利用した探索的テスト&lt;/li&gt;
&lt;li&gt;グループディスカッションと発表&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;1. 講師の自己紹介とワークショップの説明&lt;/h4&gt;
&lt;p&gt;講師2人の自己紹介がありました。海外のキーノートなどは国内でも見る機会がありましたが、自己紹介をしない印象があったので、少し意外でした。彼らのバックボーンなどを知ることができました。&lt;/p&gt;
&lt;h4&gt;2. フリースタイルでテストを実行する&lt;/h4&gt;
&lt;p&gt;以下のURLにアクセスして、個人でフリースタイルでテストをしました。&lt;br /&gt;
&lt;a href=&quot;https://www.eviltester.com/page/tools/thepulper/&quot;&gt;https://www.eviltester.com/page/tools/thepulper/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;題材はWebサイトで、個々に不具合を出していきます。テスト設計はせずに、経験ベースでテストを実行します。&lt;/p&gt;
&lt;p&gt;&lt;figure id=&quot;attachment_34505&quot; aria-describedby=&quot;caption-attachment-34505&quot; style=&quot;width: 500px&quot; class=&quot;wp-caption aligncenter&quot;&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/2d902f41-test1-300x111.jpg&quot; alt=&quot;&quot; width=&quot;500&quot; class=&quot;size-medium wp-image-34505&quot; srcset=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/2d902f41-test1-300x111.jpg 300w, https://storage.googleapis.com/prd-engineering-asset/2025/08/2d902f41-test1-1024x378.jpg 1024w, https://storage.googleapis.com/prd-engineering-asset/2025/08/2d902f41-test1-768x283.jpg 768w, https://storage.googleapis.com/prd-engineering-asset/2025/08/2d902f41-test1-1200x443.jpg 1200w, https://storage.googleapis.com/prd-engineering-asset/2025/08/2d902f41-test1.jpg 1252w&quot; sizes=&quot;(max-width: 300px) 100vw, 300px&quot; /&gt;&lt;figcaption id=&quot;caption-attachment-34505&quot; class=&quot;wp-caption-text&quot;&gt;HTMLエンコードが行われていないため、入力された値がそのままHTMLとして表示される不具合&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;figure id=&quot;attachment_34508&quot; aria-describedby=&quot;caption-attachment-34508&quot; style=&quot;width: 500px&quot; class=&quot;wp-caption aligncenter&quot;&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/e7b3ee21-test2-300x207.jpg&quot; alt=&quot;&quot; width=&quot;500&quot; class=&quot;size-medium wp-image-34508&quot; srcset=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/e7b3ee21-test2-300x207.jpg 300w, https://storage.googleapis.com/prd-engineering-asset/2025/08/e7b3ee21-test2-1024x707.jpg 1024w, https://storage.googleapis.com/prd-engineering-asset/2025/08/e7b3ee21-test2-768x530.jpg 768w, https://storage.googleapis.com/prd-engineering-asset/2025/08/e7b3ee21-test2-1200x829.jpg 1200w, https://storage.googleapis.com/prd-engineering-asset/2025/08/e7b3ee21-test2.jpg 1254w&quot; sizes=&quot;(max-width: 300px) 100vw, 300px&quot; /&gt;&lt;figcaption id=&quot;caption-attachment-34508&quot; class=&quot;wp-caption-text&quot;&gt;HTMLエンコードをしていないために、入力値とinput formの表示が変わってしまう不具合&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;figure id=&quot;attachment_34509&quot; aria-describedby=&quot;caption-attachment-34509&quot; style=&quot;width: 500px&quot; class=&quot;wp-caption aligncenter&quot;&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/0825129e-test3-300x93.jpg&quot; alt=&quot;&quot; width=&quot;500&quot; class=&quot;size-medium wp-image-34509&quot; srcset=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/0825129e-test3-300x93.jpg 300w, https://storage.googleapis.com/prd-engineering-asset/2025/08/0825129e-test3-1024x318.jpg 1024w, https://storage.googleapis.com/prd-engineering-asset/2025/08/0825129e-test3-768x239.jpg 768w, https://storage.googleapis.com/prd-engineering-asset/2025/08/0825129e-test3-1200x373.jpg 1200w, https://storage.googleapis.com/prd-engineering-asset/2025/08/0825129e-test3.jpg 1230w&quot; sizes=&quot;(max-width: 300px) 100vw, 300px&quot; /&gt;&lt;figcaption id=&quot;caption-attachment-34509&quot; class=&quot;wp-caption-text&quot;&gt;日付を入力する欄に大きな数字をいれたためにNumber Format Exceptionが発生してしまった。これを不具合とするかどうかは仕様次第&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;h4&gt;3. 発見された不具合の共有&lt;/h4&gt;
&lt;p&gt;　参加者全員で、探索的テスト中に発見した不具合について発表しました。私は自身が報告しようとしていた不具合が、既に他の参加者によって報告済みであるか確信が持てませんでした。そこで、念のため隣席の方に不具合の画面を見せながら、「このバグについて報告しようと思っているのですが、既に発表された方はいますか？」と確認しました。すると、その方は私の発表をフォローしてくださり、安心して発表に臨むことができました。&lt;/p&gt;
&lt;h4&gt;4. 探索的テストとテストチャーターの解説&lt;/h4&gt;
&lt;p&gt;ここでは、探索的テストとは何か、そしてテストチャーターとは何かについて解説がありました。講師からは、著名なソフトウェアテスト研究者である&lt;a href=&quot;https://en.wikipedia.org/wiki/Cem_Kaner&quot; title=&quot;Cem Kaner&quot;&gt;Cem Kaner&lt;/a&gt;氏の言葉を引用しつつ、「テストチャーターは、テストのゴールを明確にするための計画である」という説明がありました。&lt;/p&gt;
&lt;p&gt;以下はテストチャーターの例です。&lt;br /&gt;
&lt;code&gt;&lt;/p&gt;
&lt;pre&gt; define your goal; charter template&lt;br&gt; &amp;nbsp;&amp;nbsp;target&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;where are you exploring?&lt;br&gt; &amp;nbsp;&amp;nbsp;resources&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;what resources do you need ?&lt;br&gt; &amp;nbsp;&amp;nbsp;information&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;what kind of information do you want to discover&lt;br&gt; &lt;/pre&gt;
&lt;p&gt; &lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;5. テストチャーターを用いた探索的テスト&lt;/h4&gt;
&lt;p&gt;隣の席の方とペアになり、先ほどのテスト対象のWebアプリケーションに対して、付箋を使ってテストチャーターを作成しました。ペアワーク後、グループ全体でそれぞれのペアがどのような考えでテスト項目を選び、テストチャーターを作成したかを共有し、議論しました。&lt;/p&gt;
&lt;h4&gt;6. BRIEFフレームワークの提案&lt;/h4&gt;
&lt;p&gt;講師から、Elizabath Hendricson氏が提唱するテストチャーターについての説明がありました。私はテストチャーターについて、彼女の著書で知ってはいたものの、改めて説明を受けることで、自身の抱いていた違和感が明確になりました。それは、受け入れ条件（Acceptance Criteria）を書く際、つまりテスト設計時に、情報やテスト観点から記述している点でした。&lt;/p&gt;
&lt;p&gt;そして、講師らが提唱する新しいフレームワーク「BRIEF」の説明がありました。&lt;strong&gt;BRIEFは、Behavior（行動）、Result（結果）、Impediments（障害）、Expectation（期待）、Feeling（感情）&lt;/strong&gt;の頭文字を取ったものです。このフレームワークを用いることで、振る舞いを軸としたテストチャーターを作成することができます。最後に、このBRIEFフレームワークを使ってペアワークを行い、その後、他のグループと入れ替わってディスカッションを行いました。&lt;/p&gt;
&lt;p&gt;&lt;figure id=&quot;attachment_34510&quot; aria-describedby=&quot;caption-attachment-34510&quot; style=&quot;width: 296px&quot; class=&quot;wp-caption aligncenter&quot;&gt;&lt;img loading=&quot;lazy&quot; src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/205afa60-test4-296x300.jpg&quot; alt=&quot;&quot; width=&quot;296&quot; height=&quot;300&quot; class=&quot;size-medium wp-image-34510&quot; srcset=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/205afa60-test4-296x300.jpg 296w, https://storage.googleapis.com/prd-engineering-asset/2025/08/205afa60-test4.jpg 712w&quot; sizes=&quot;(max-width: 296px) 100vw, 296px&quot; /&gt;&lt;figcaption id=&quot;caption-attachment-34510&quot; class=&quot;wp-caption-text&quot;&gt;ペアワークで使った付箋&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;このワークショップで特に素晴らしいと感じたのは、講師自身が考案したフレームワーク「BRIEF」を活用していた点です。探索的テストのワークショップでは、手を動かす演習や参加者同士の意見交換、テストチャーターの作成などはよく行われます。しかし、今回のように新しいチャーターのフレームワークを実際に試す機会は初めてで、非常に有意義でした。個人的にも、BRIEFのフレームワークは普段私がテストを考える際の思考回路に近く、とてもしっくりきました。&lt;/p&gt;
&lt;h2&gt;Journey From Manual to Automation Pythonic Tester&lt;/h2&gt;
&lt;p&gt;続いて、Mateusz Adamczak氏とMichal Pilarski氏による、テスト自動化の初学者を対象としたワークショップについてご紹介します。このワークショップはハンズオン形式で行われ、参加者は自身のPCを使って実際に自動テストを作成しました。&lt;/p&gt;
&lt;h3&gt;ワークショップの流れ&lt;/h3&gt;
&lt;p&gt;ワークショップでは以下のような流れでワークショップをデザインしていました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;必要なツールとリポジトリのダウンロード&lt;/li&gt;
&lt;li&gt;Scrachを使ってアニメの作成&lt;/li&gt;
&lt;li&gt;Pythonのコードにコンバートする&lt;/li&gt;
&lt;li&gt;テストコードの作成&lt;/li&gt;
&lt;li&gt;グループディスカッションと発表&lt;/li&gt;
&lt;li&gt;登壇者の考案したフレームワークを利用した探索的テスト&lt;/li&gt;
&lt;li&gt;グループディスカッションと発表&lt;br /&gt;
こちらもひとつずつ紹介します。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;1. 必要なツールとリポジトリのダウンロード&lt;/h4&gt;
&lt;p&gt;GitLabのリポジトリ、Python、JetBrainsをダウンロードします。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitLab&lt;br /&gt;
&amp;#8211;&amp;nbsp;  &lt;a href=&quot;http://gitlab.com/&quot; title=&quot;GitLab.com&quot;&gt;GitLab.com&lt;/a&gt;&lt;br /&gt;
&amp;#8211;&amp;nbsp;  &lt;a href=&quot;https://gitlab.com/michpil/python_kids/-/tree/ATD_workshop_manual2auto&quot; title=&quot;Files · ATD_workshop_manual2auto · Michal Pilarski / python_kids · GitLab&quot;&gt;Files · ATD_workshop_manual2auto · Michal Pilarski / python_kids · GitLab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Python.org&lt;br /&gt;
&amp;#8211;&amp;nbsp;  &lt;a href=&quot;https://www.python.org/&quot; title=&quot;Welcome to Python.org&quot;&gt;Python.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;JetBrains&lt;br /&gt;
&amp;#8211;&amp;nbsp;  &lt;a href=&quot;https://www.jetbrains.com/pycharm/download/download-thanks.html?platform=macM1&amp;amp;amp;code=PCC&quot; title=&quot;PyCharm&quot;&gt;PyCharm&lt;/a&gt;&lt;br /&gt;
&amp;#8211;&amp;nbsp;  &lt;a href=&quot;https://www.jetbrains.com/pycharm/download/download-thanks.html?platform=macM1&amp;amp;amp;code=PCC&quot;&gt;https://www.jetbrains.com/pycharm/download/download-thanks.html?platform=macM1&amp;amp;code=PCC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.Scratchを使ったアニメーション動画作成&lt;/h4&gt;
&lt;p&gt;Scratchを使ってアニメーション動画を作成しました。最初に講師が画面共有しながら作り方を説明し、参加者はそれに倣って作業を進めました。Scratchは、子供向けのプログラミング学習ツールとして日本でも人気があり、直感的な操作でアニメーションを作成できます。そのため、プログラミング初学者でも問題なく取り組むことができました。講師の指示に従い、キャラクターを前後に動かしたり、音を鳴らしたりといった簡単な動作を作成しました。&lt;/p&gt;
&lt;h4&gt;3. Pythonコードへの変換&lt;/h4&gt;
&lt;p&gt;Scratchで作成したアニメーションが完成したので、次はそれをPythonのコードに変換します&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/5cd367f5-test5-1024x324.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;変換に必要なコードは事前に用意されていました。&lt;/p&gt;
&lt;p&gt;まずターミナルで&lt;br /&gt;
&lt;code&gt;python -V &lt;/code&gt;&lt;br /&gt;
を実行してPythonのバージョンを確認し、次に&lt;br /&gt;
&lt;code&gt;pip install -r requirements.txt&lt;/code&gt;&lt;br /&gt;
を実行して必要なライブラリをインストールします。その後、Scratchで作成した &lt;code&gt;kitty.sb3&lt;/code&gt; ファイルを変換することで、Pythonのコードが生成されました。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/731f140b-test6-1024x428.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;講師から基本的なPythonコードの説明があり、その後は参加者自身でコードの実装を行いました。私の作成したアニメーションには背景がなかったため、生成されたコードの6行目をコメントアウトする必要がありました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/16b1f59e-testkitty-1024x610.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;4. テストコードの作成&lt;/h4&gt;
&lt;p&gt;いよいよテストコードの作成です。先ほどコンバートしたコードに対してテストコードを実装します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/08/119a79c6-test7-1024x565.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;　講師が簡単なテストのサンプルコードを紹介してくれました。このサンプルコードは、Scratchで作成したアニメーションの動きをテストするもので、pytestライブラリを使用していました。例えば、キャラクターが指定された位置に移動することを検証するテストや、特定の音が鳴ることを検証するテストなどがありました。講師がコードを画面共有しながら説明してくれたので、私たちも自身のPCで同じようにコードを書き写しました。エラーが発生した箇所は、エラーメッセージを読みながら修正したり、講師に質問したりして解決しました。その後、ターミナルでpytestコマンドを実行してテストを実行し、テストが成功することを確認しました。詳細なテストコードについては、ぜひGitLabのリポジトリ（&lt;a href=&quot;https://gitlab.com/michpil/python_kids/-/tree/ATD_workshop_manual2auto&quot;&gt;https://gitlab.com/michpil/python_kids/-/tree/ATD_workshop_manual2auto&lt;/a&gt;）&lt;br /&gt;
にアクセスして確認してください。ワークショップで使用したすべてのテストコードや関連資料が公開されていますので、テストコードの全体像を把握したり、実際にテストを実行したりすることができます。&lt;/p&gt;
&lt;p&gt;このワークショップの特筆すべき点は、まず初学者の参加者が実際に動くプログラムを作成できたことです。Scratchを活用することで、参加者が動くプログラムを自ら作れるようにするというアイデアは非常に素晴らしいと感じました。通常の自動テストに関するハンズオンでは、多くの場合、事前に用意されたプログラムに対してテストコードを書くことが一般的です。しかし、今回のワークショップでは、動くものをゼロから自分で作り、そのテストコードまで書くという一連の流れを、数時間という短い時間で体験できる点が素晴らしいと思いました。ワークショップの時間内ですべてのテストコードを実装することはできませんでしたが、参加者全員が何らかの形でテストコードを実装することができました。初学者向けにScratchでアプリ作成を体験させ、それをPythonコードに変換し、自身が作成したプロダクトコードに対してテストコードを実装するというワークショップのデザインは、本当に素晴らしいと感じました。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;今回の記事では参加した2つのワークショップについてご紹介しました。どちらのワークショップも、短時間で成果を実感できるような工夫が凝らされており、その内容とともに大変勉強になりました。ワークショップのオーナーの方々には、直接お会いして感謝の気持ちをお伝えし、ぜひこの素晴らしいセッションを日本にいるみんなにも広めたいので、サイトのURLなどを公開しても良いか確認させていただきました。&lt;/p&gt;
&lt;p&gt;　私自身、長年英語に苦手意識があり、ワークショップへの参加は、他の参加者や講師の方々にご迷惑をかけてしまうのではないかという不安がありました。しかし、同じ志を持つ仲間たちと、互いの伝えたい内容を理解しようとする姿勢に触れ、そのうちの一人とは数週間後に私の住む街へ偶然旅行に来るとのことで、食事の約束までできました。また、参加者の中には第二外国語として英語を学んでいる方もいたようで、みんなが真剣に耳を傾けてくれる姿勢が印象的でした。&lt;br /&gt;
　結果として、私自身も非常に楽しく学習できる時間を過ごすことができました。同じグループになった参加者の方々も、とても親切で助けられました。私は英語が得意ではありませんが、Agile Testing Daysではキーノートをはじめ、数多くのセッションやワークショップ、パネルディスカッションが開催され、登壇者の経験に基づいた発表が多く、共感できる内容が数多くありました。&lt;br /&gt;
　セッション以外の時間にも食事が提供され、参加者同士が楽しく交流できる場が設けられており、3日間を通して多くの方々と話すことができました。&lt;br /&gt;
　今年の&lt;a href=&quot;https://agiletestingdays.com/&quot; title=&quot;Agile Testing Daysの参加受付&quot;&gt;Agile Testing Daysの参加受付&lt;/a&gt;も始まりました。このブログをきっかけに、読者のみなさまがワークショップを試してみたり、海外のカンファレンスへの参加に挑戦してみようと思っていただけたら、とてもうれしいです。&lt;/p&gt;
</content:encoded></item><item><title>Build@Mercari 2025で圧倒的に成長できた話</title><link>https://engineering.mercari.com/blog/entry/20250731-57864d7e6b/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250731-57864d7e6b/</guid><description>&lt;p&gt;はじめに こんにちは。2025年度のBuild@Mercariに参加して、現在はメルカリのCS Tool Teamでインターンをしている@Aokaと申します。この記事では、私がBuild@Mercariに参加した感想や成 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 31 Jul 2025 14:00:12 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;こんにちは。2025年度のBuild@Mercariに参加して、現在はメルカリのCS Tool Teamでインターンをしている@Aokaと申します。この記事では、私がBuild@Mercariに参加した感想や成長したことについて書いていきたいと思います。&lt;/p&gt;
&lt;h3&gt;Build@Mercariって何？&lt;/h3&gt;
&lt;p&gt;Build@Mercariとは、これまでさまざまな事情で機会が巡ってこなかった方、特にSTEM分野・IT分野におけるマイノリティである性自認が女性の方々を中心として、スキルトレーニングとインターンシップの機会を提供するプログラムです。&lt;/p&gt;
&lt;h2&gt;トレーニングの話&lt;/h2&gt;
&lt;h3&gt;選考&lt;/h3&gt;
&lt;p&gt;Build@Mercariの選考は書類とコーディングテストの結果で進みます。他の多くのインターンシップとは異なり、面接がないのが特徴的でした。&lt;br /&gt;
書類審査では、これまでの自分の経験やBuild@Mercariに参加したい理由などを記入しました。コーディングテストについては、プログラミング言語の基礎文法を理解していれば解ける問題が中心で、競技プログラミングのような高度な問題ではありませんでした。&lt;br /&gt;
選考は志望理由とコーディングテストの結果を総合的に判断しますが、志望理由が重要視されているそうです。&lt;/p&gt;
&lt;h3&gt;初日のオリエンテーション&lt;/h3&gt;
&lt;p&gt;初日のみオフラインでの開催で、メンターや同じチームの参加者とオフラインで交流する機会がありました。また、GitHubを使った課題をみんなで進めて、使い方に慣れることができました。オフラインで交流できたことが二週間のトレーニング期間を通じて心の支えとなり、モチベーションを維持することができました。&lt;/p&gt;
&lt;h3&gt;トレーニングでやったこと&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/abf8f193-428333800-c776fd5d-01b5-412e-990c-4ab356a0e6ab-300x154.png&quot; alt=&quot;Build@Mercari&quot; /&gt;&lt;/p&gt;
&lt;p&gt;トレーニングではソフトウェアエンジニアリングに必要な基礎知識を一通り学びました。&lt;br /&gt;
トレーニング期間中は、5〜6人のチームにそれぞれメンターの方がついてくださる形で、各自が課題に取り組みました。課題の内容は以下の通りで、各STEPを進めながら、最終的にはメルカリのような商品を登録できるWebアプリを作りました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;STEP1 Git&lt;/li&gt;
&lt;li&gt;STEP2 Setup environment&lt;/li&gt;
&lt;li&gt;STEP3 Algorithms and Data Structures&lt;/li&gt;
&lt;li&gt;STEP4 Develop API&lt;/li&gt;
&lt;li&gt;STEP5 Database&lt;/li&gt;
&lt;li&gt;STEP6 Writing Tests&lt;/li&gt;
&lt;li&gt;STEP7 Docker&lt;/li&gt;
&lt;li&gt;STEP8 Continuous Integration(CI)&lt;/li&gt;
&lt;li&gt;STEP9 (Stretch) Frontend&lt;/li&gt;
&lt;li&gt;STEP10 (Stretch) Run multi service&lt;/li&gt;
&lt;li&gt;EXTRA1 (Stretch) Data Analysis&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;トレーニング内容は公開されており、以下のレポジトリから確認できます。&lt;br /&gt;
&lt;a href=&quot;https://github.com/mercari-build/mercari-build-training/tree/main&quot;&gt;https://github.com/mercari-build/mercari-build-training/tree/main&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;つまづいた時にメンターの方々にSlackで質問したり、課題の各STEPごとにレクチャーをしてくださったりと、手厚いサポートのおかげで、最後まで楽しく開発を続けることができました。&lt;/p&gt;
&lt;h3&gt;学んだこと&lt;/h3&gt;
&lt;p&gt;プログラム期間中はたくさんのことを学びましたが、特に以下の知識がついたと思います。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Git, Githubを使ったチーム開発の基礎&lt;/li&gt;
&lt;li&gt;Backend開発の流れ&lt;/li&gt;
&lt;li&gt;DockerやCIなどインフラの知識&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このトレーニングを通じての一番の大きな成長は、開発することに対する心理的なハードルが下がったことだと感じています。参加する前は、フロントエンドの開発しかしたことがなかったのですが、フロントエンドからインフラまでの基礎知識を身につけたことにより、新しい技術について学習する際や、開発でつまづいた時にどの領域の知識を深めていけばいいのか判断できるようになりました。&lt;/p&gt;
&lt;p&gt;また、プログラムの参加者はUdemy Businessを1年間無料で使うことができます。はじめて学ぶ知識が多いなか、基礎から体系的に学習することができるので、非常に役にたちました。&lt;/p&gt;
&lt;h3&gt;難しかったこと&lt;/h3&gt;
&lt;p&gt;実装を始める前に、新しい概念を理解する必要があり、なぜこの技術が必要なのかというところから理解しなければならなかったことが大変でした。理解が浅いまま進めてしまうと、後でつまずくことが多かったため、基礎知識を身につけるためにUdemyの動画を視聴したり、AIとの壁打ちを通じて概念を整理したりして理解しながら進めました。&lt;/p&gt;
&lt;p&gt;特に、STEP4のAPI開発では、Python言語を使用してRESTful APIを実装したのですが、デバッグ作業に苦労しました。エラーが発生した際に、どの部分でなぜエラーが起きているのかを特定するのが難しく感じました。しかし、この経験を通じてデバッグスキルの重要性を痛感するとともに、バグに対する耐性もついたと思います。&lt;/p&gt;
&lt;p&gt;また、STEP7のDockerの部分が大変でした。初めてコンテナ技術に触れたため、仮想化の概念から始まり、イメージとコンテナの概念、Dockerfileの書き方まで、すべてが新しい知識でした。特に、依存関係の管理は理解するのに時間がかかり、何度もエラーと向き合いながら少しずつ実装を進めました。また、公式ドキュメントを読む大切さも学びました。はじめはとても読みづらく感じましたが、理解するのに大変役に立つことを実感しました。&lt;/p&gt;
&lt;h3&gt;自身の変化&lt;/h3&gt;
&lt;p&gt;プログラムを通して、技術力の向上はもちろんのこと、様々な方との関わりを通じて自分自身に大きな変化があったと感じています。&lt;/p&gt;
&lt;p&gt;参加者の方々のバックグラウンドは多様で、情報系学部の人に限らず、文系学部や美術系学部の人、また高校生から社会人まで幅広い年齢層の方がいらっしゃいました。私のようにWeb開発が初心者の方も多かったです。&lt;/p&gt;
&lt;p&gt;私がこのプログラムを修了できた最も大きな要因は、一緒に切磋琢磨し合える仲間がいたからだと感じています。お互いに教え合いながら頑張る雰囲気がとても心地よく、他の参加者の方々の進捗は良い刺激になりました。&lt;/p&gt;
&lt;p&gt;また、プログラムの初日には社員の方々のキャリアに関するプレゼンテーションがあり、直接質問できる機会もいただきました。出産や子育てと仕事の両立、転職に関するお話など、貴重なお話を聞くことができ、自分自身のキャリアについて深く考える時間となりました。&lt;/p&gt;
&lt;h2&gt;Buildインターンの話&lt;/h2&gt;
&lt;h3&gt;選考&lt;/h3&gt;
&lt;p&gt;トレーニング期間終了後、一定の基準を満たした参加者は、インターンシップへの応募資格を得ることができます。&lt;br /&gt;
選考プロセスでは面接が実施され、主にトレーニングを通じて得た学びや気づき、これまでの技術的な経験、そしてメルカリのバリューに対する理解や共感について質問されました。&lt;br /&gt;
選考通過後には、実際のインターンシップ開始前に事前面談の機会が設けられています。この面談では、配属予定のチームについての説明や、不安な点の相談などができるため、安心してインターンシップをスタートすることができました。&lt;/p&gt;
&lt;h3&gt;やったこと&lt;/h3&gt;
&lt;p&gt;タスクは、カスタマーサポート業務で使用される社内ツールであるCS ToolのPHPで記述されている既存エンドポイントをGo言語とマイクロサービスアーキテクチャで再構築するというものでした。&lt;/p&gt;
&lt;p&gt;開発の背景として、CS Toolの多くの機能は古いモノリスなアプリケーションとして運用されています。古いBackendはPHPで書かれており、現在これを保守性の観点などから、新しいマイクロサービスアーキテクチャに移行する取り組みが進められています。&lt;/p&gt;
&lt;p&gt;現在CS Toolでは、開発方針として既存機能の移行作業を進めながら、新機能については新しいサービスで開発するというルールがあり、今回私は前者を担当しました。&lt;/p&gt;
&lt;h3&gt;学んだこと&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;新しい技術(Go, Kubernetes, gRPC)&lt;br /&gt;
アーキテクチャの概念が実際のサービス運用でどのように活用されているかを理解することができました。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test QA Releaseの工程&lt;br /&gt;
実際の開発現場での品質管理プロセスを経験できました。単体テストの書き方から、QAのケースの作成と実施、リリースまで一連の流れを学ぶことができました。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;コードの可読性を考える&lt;br /&gt;
自分だけが理解できるコードではなく、チームメンバー全員が理解できるコードを書くことの重要性を学びました。変数名や関数名の命名、レポジトリ内でコードの一貫性を保つことなど実務で重要なポイントを学ぶことができました。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;コードの拡張性を意識する&lt;br /&gt;
将来的な機能追加や仕様変更に対応しやすいコード設計について学びました。将来的に、同じエンドポイントに新しいフィルターを追加するなどする時に、コードが書きやすいか考えました。&lt;br /&gt;
また、自身が書く部分が既存のエンドポイントの実装と関わっていたり、使う関数が似ていたりするときに、コードを分割したり、まとめたりすることの重要性を学びました。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AIの活用&lt;br /&gt;
メルカリでは、AIの活用も積極的に進められており、開発にはCursorなどのAIコーディングツールを使うことができました。ただし、やみくもにAIに頼るのではなく、知識をもった上で、生成されるであろう答えを予想してから入力することで、より効果的にツールを活用することが大切だと学びました。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;インターンを通じて、今まで触れる機会のなかった大規模開発ならではの知識や技術を多く学ぶことができました。特に、前述のコードの可読性の点では、コードのみならず、PRやQAシートを作成する際にも、チームの人が理解しやすく、レビューをしやすいかどうかを意識して書くことの重要性を学びました。&lt;/p&gt;
&lt;p&gt;またコードレビューを通じて、開発に必要なコードの書き方を具体的に学ぶことができました。自分の実装がベストプラクティスかどうか、様々な視点から評価していただけました。&lt;/p&gt;
&lt;p&gt;特に印象的だったのは、単にこの書き方の方が良いと教えていただくだけでなく、なぜその書き方が推奨されるのかという理由まで丁寧に説明していただいたことです。例えば、エラーハンドリングの実装では、ただ起こりうるエラーを処理するだけでなく、適切なログ出力やユーザーが分かりやすいエラーメッセージを考慮する必要があることを学びました。&lt;/p&gt;
&lt;h3&gt;交流を通じた学び&lt;/h3&gt;
&lt;p&gt;チームでは定期的に勉強会が開催されており、チームに役に立ちそうな技術や知見を積極的に共有する文化がありました。難しい内容も多かったですが、CursorのMCP serverについての回は特に興味深かったです。&lt;/p&gt;
&lt;p&gt;チームには地方に住んでいるメンバーも多くいましたが、普段はSlackで通話を繋いでコミュニケーションを取り、定期的に出社日を設けてオフラインでの交流機会を作ったり、チームビルディングイベントを開催したりと、チームのコミュニケーションを積極的に取ろうとされていることが印象的でした。&lt;/p&gt;
&lt;p&gt;困った時はいつでも相談に乗っていただき、ペアプログラミングも積極的にしてくださいました。特に、課題がある時に手取り足取り教えるのではなく、私にとってよい学習機会になるようにサポートしてくださったのがとても印象的でした。&lt;/p&gt;
&lt;p&gt;また、メンターランチや1on1での社員の方々との交流を通じ、自身のキャリアについて考えるとてもよい機会になりました。新卒入社の方のお話から、他の会社も経験された方のお話まで色々なバックグラウンドをお持ちの方との会話を通じて視野が広がりました。また、これからの大学生活に関して具体的なアドバイスをいただき、残り2年半の大学生活をどのように過ごすのか考える機会になりました。参加してくださった社員の方々に改めて感謝申し上げます。&lt;/p&gt;
&lt;h2&gt;終わりに&lt;/h2&gt;
&lt;p&gt;今回のBuild@Mercariとインターンでの経験を通じ、開発に必要な幅広い知識を身につけることができました。また、技術的な面以外でも多くの学びがありました。この半年間で得た学びを糧に、一流のエンジニアを目指して、さらに力をつけていきたいです。&lt;/p&gt;
&lt;p&gt;このプログラム期間中、多くの方々にサポートしていただいたおかげで、ここまで成長することができました。Build@Mercariのメンターの皆さんや、インターン期間中にメンターをしてくださった&lt;a href=&quot;https://x.com/ukiuki_cherry&quot; title=&quot;@a-uki&quot;&gt;@a-uki&lt;/a&gt;さんをはじめとするCS Toolチームの皆さんに本当に感謝の気持ちでいっぱいです。ありがとうございました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※この体験記は2025年度（今年度）のプログラム内容です。来年度以降のプログラムにおいては内容が変更になる可能性がありますので、ご了承ください。&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>Locked Shields 2025 参加レポート</title><link>https://engineering.mercari.com/blog/entry/20250728-ceec77c0d4/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250728-ceec77c0d4/</guid><description>&lt;p&gt;はじめに 5月上旬、NATO Cooperative Cyber Defence Centre of Excellence（CCDCOE）が主催する世界最大級のサイバー防衛演習 Locked Shields 2025 が [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 29 Jul 2025 10:00:41 GMT</pubDate><content:encoded>&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;5月上旬、NATO Cooperative Cyber Defence Centre of Excellence（CCDCOE）が主催する世界最大級のサイバー防衛演習 Locked Shields 2025 が開催されました。今年は 約40 か国・約 4,000 名が参加し、17 の多国籍ブルーチームが国家レベルのICTインフラを防御するシナリオに挑みました。&lt;br /&gt;
昨年に引き続き、メルカリは今年もLocked Shieldsに参加しました。今回はセキュリティチームから3人のメンバーが参加しました。本記事では国際共同演習の最前線で得られた知見を共有します。&lt;/p&gt;
&lt;h2&gt;チーム構成と参加概要&lt;/h2&gt;
&lt;p&gt;今回次の3名が参加しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Yuto Iso:主に日本の防衛対象の全情報システムの保全および重要システムの侵害防止を担当しました。&lt;/li&gt;
&lt;li&gt;Hiroki Akamatsu: プラットフォームおよびWebアプリケーションの脆弱性ハンティング・修正を担当しました。&lt;/li&gt;
&lt;li&gt;Sana Okumura: 侵害兆候の分析、証跡の確認・報告を担当しました。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;メルカリのメンバーは攻撃の兆候の検知・証跡確認、そして脆弱性の特定・修正を行いました。&lt;/p&gt;
&lt;h2&gt;取り組み内容&lt;/h2&gt;
&lt;p&gt;Locked Shieldsでは防衛対象の多数の情報システムが高度なサイバー攻撃を受けます。保護対象の全ての情報システムを自動的に調査する仕組みをIsoが開発し、各システムの保護・復旧にかかる労力を大きく軽減させました。これにより、脆弱性の事前特定・攻撃を受けたシステムの迅速な回復に貢献しました。&lt;/p&gt;
&lt;p&gt;また、Locked ShieldsではAI機能を含む様々なサービス・認証基盤・ネットワーク、そしてそれらを運用するためのプラットフォームが存在していました。AIやWebアプリケーションおよびコンテナ技術に対応できる人材としてAkamatsuが複数のWebアプリケーションの堅牢化・コンテナの安全なデプロイ構築などを支援しました。&lt;/p&gt;
&lt;p&gt;そして、攻撃者は様々な攻撃パターンで情報システムへの侵害を試みます。Okumuraは複数の証跡を確認することで正確に攻撃の影響範囲を特定・報告し、攻撃の検出から封じ込めに貢献しました。&lt;/p&gt;
&lt;h2&gt;得られた学びと成果&lt;/h2&gt;
&lt;p&gt;演習内ではそれぞれが自身の専門領域を活かしながら取り組みましたが、Locked ShieldsではOT（Operational Technology）系のシステムなど経験のない領域の攻撃に直面することもあり、学びつつ多数の攻撃からシステムを防衛しました。&lt;/p&gt;
&lt;p&gt;また技術面以外でも、様々な専門分野の演習参加者とともに、それぞれの知識や経験を活かしながら、サイバーセキュリティ防衛における円滑なコミュニケーションと協力関係を築く方法を実践的に学ぶことができました。&lt;/p&gt;
&lt;h2&gt;さいごに&lt;/h2&gt;
&lt;p&gt;Locked Shieldsは他に類を見ないほどの大規模な演習であり、今回の参加もメルカリのメンバーにとって非常に貴重な経験となりました。各メンバーがそれぞれの専門性を最大限に発揮し、各システムへの技術的な支援を横断的に行いました。自動化によるシステム調査・保全、そして複雑な環境下での迅速な脆弱性対応や影響範囲特定といった実践的なスキルを磨くとともに、新たな攻撃手法や防御戦略についても多くの学びを得ました。&lt;/p&gt;
&lt;p&gt;特に、国境を越えた多様な専門家との連携を通じて、サイバー防衛におけるコミュニケーションと協力体制の重要性を再認識しました。刻一刻と変化する状況の中で、迅速かつ正確に情報を共有し、共通の目標に向かって協力することの難しさと、それを乗り越えた際の達成感を肌で感じることができました。&lt;/p&gt;
&lt;p&gt;本演習で得た知見や経験は、メルカリのサービス全体のセキュリティ強化、インシデント対応能力の向上、そして将来のサイバー脅威への備えに大きく貢献するものと確信しています。メルカリは今後も、このような国際的な取り組みへ積極的に参加し、サイバーセキュリティ技術の向上と、安全・安心なサービス提供に努めてまいります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/4386a252-lockedshields-ja.jpeg&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
出典:&lt;a href=&quot;https://x.com/ModJapan_jp/status/1920770496632627647&quot;&gt;https://x.com/ModJapan_jp/status/1920770496632627647&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>SRE NEXT 2025参加レポート</title><link>https://engineering.mercari.com/blog/entry/20250723-srenext-2025-report/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250723-srenext-2025-report/</guid><description>&lt;p&gt;こんにちは。SREチームの @foostan です。 弊社は2025年7月11~12日に開催されたSRE NEXT 2025に、PLATINUMスポンサーとして協賛し、ブース出展およびセッション発表を行いました。本記事で [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 23 Jul 2025 11:29:27 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。SREチームの @foostan です。&lt;/p&gt;
&lt;p&gt;弊社は2025年7月11~12日に開催された&lt;a href=&quot;https://sre-next.dev/2025/&quot;&gt;SRE NEXT 2025&lt;/a&gt;に、PLATINUMスポンサーとして協賛し、ブース出展およびセッション発表を行いました。本記事では当日の様子とアンケートの収集結果をご紹介します。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/0cbc52ad-image.jpg&quot; alt=&quot;sponsors&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;ブース出展&lt;/h2&gt;
&lt;p&gt;弊社としては久しぶりのブース出展であり、私個人としては初めての経験となりました。2日に渡り200人以上の来訪者にお越しいただき、SREに関するお話をたくさんさせていただきました。ありがとうございました。このように共通の話題で盛り上がれる機会は非常に貴重であり良い経験となりました。なお我々のブースでは自由記述形式で以下の2種類のアンケートを行いました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SREをやっていて良かった瞬間は？&lt;/li&gt;
&lt;li&gt;SREに関する業務でAIに任せたいことは何ですか？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;詳細は後述しますが、昨今のAI利活用の盛り上がりはSREの領域でも大きな影響を受けており、当日はAIに関する話題が尽きることはありませんでした。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/6a6bb5a5-image-1.png&quot; alt=&quot;booth&quot; /&gt;&lt;/p&gt;
&lt;p&gt;来訪者やアンケートに答えていただいた方向けにノベルティの配布も行っていました。特にロゴ入りのキーキャップは今回のイベント向けに用意したものでしたが高評をいただけて何よりでした。今後のイベントでも機会があれば配布しようと思いますのでその際は受け取っていただけると幸いです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/c4edeb1c-image-2.png&quot; alt=&quot;keycaps&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;セッション発表&lt;/h2&gt;
&lt;p&gt;弊社の yakenji より、「複雑なシステムにおけるUser Journey SLOの導入」を発表させていただきました。我々がどのような経緯でUser Journey SLOを導入し、これをどのように運用しているのか、また今後の展望について共有しております。&lt;/p&gt;
&lt;p&gt;&lt;script defer class=&quot;speakerdeck-embed&quot; data-id=&quot;97cd14ef91ec4fc193a4e2bbb86212f0&quot; data-ratio=&quot;1.7777777777777777&quot; src=&quot;//speakerdeck.com/assets/embed.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;
&lt;p&gt;なお本発表の内容は2024年末のブログ &lt;a href=&quot;https://engineering.mercari.com/blog/entry/20241204-keeping-user-journey-slos-up-to-date-with-e2e-testing-in-a-microservices-architecture/&quot;&gt;https://engineering.mercari.com/blog/entry/20241204-keeping-user-journey-slos-up-to-date-with-e2e-testing-in-a-microservices-architecture/&lt;/a&gt; にも記載がありますので、よろしければこちらもご覧になっていただけると幸いです。&lt;/p&gt;
&lt;h2&gt;アンカンファレンスへの参加&lt;/h2&gt;
&lt;p&gt;当日のコンテンツはどれも興味深くさまざまな方の発表を聞いたり、各ブースを巡ってお話を伺うことでSREチームとして今後どうしていくか考えたり、またどのような技術の進歩があるのか考えたりと非常に楽しかったです。特に1日目の最後に行われたアンカンファレンスでは、具体的なテーマごとにグループに分かれてディスカッションを行うことで、各社のこれまでの経験や思いを知る良い機会となりました。&lt;/p&gt;
&lt;p&gt;私は「SREとその組織類型」のグループに参加させていただきました。普段はエンジニアリングマネージャーとしてチームや組織編成について考える機会もあるため、SREが組織にどう組み込まれるべきか、過去の事例や現在抱えている問題など、自分の視点からでは見れなかった意見を知ることができました。組織の規模やフェーズ、置かれている状況に応じてSREの組織もそれぞれにあるべき姿が存在することを改めて認識しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/90401b88-screenshot-2025-07-22-at-22.44.08.png&quot; alt=&quot;unconference&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;アンケート結果&lt;/h2&gt;
&lt;p&gt;我々のブースで行ったアンケート結果をまとめましたのでご参照いただければ幸いです。各結果を同じような内容のグループに分類しそれぞれの割合を出しています。なお記載している内容は公開用に文章を変更しておりますのでご了承ください。&lt;/p&gt;
&lt;h3&gt;SREをやっていて良かった瞬間は？&lt;/h3&gt;
&lt;p&gt;合計で53件の意見をいただきました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;トラブルシューティング: 24.6%&lt;/strong&gt;&lt;br /&gt;
大規模アクセスとなるイベントを無事乗り切った時や障害を迅速に解消できた時の達成感が最も多く挙げられました。難しい障害やボトルネックの特定・解消、原因不明のバグ修正などの技術的な課題を解決した時の喜びも大きいようです。また、インシデント対応フローの改善や開発チームの意識向上など、組織全体の成長を実感できる瞬間も良かった点として挙げられています。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自動化/トイル削減: 21.1%&lt;/strong&gt;&lt;br /&gt;
インフラや運用の自動化によって手動作業が不要になったり、高速にデプロイできるようになったことに達成感を感じたという声が多く見られました。コードによるインフラ管理やオートスケーリングの導入、リリースフローの改善などを通じて、生産性向上や自律的なシステム構築を実現できた点が良かったこととして挙げられています。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;開発体験向上: 14.0%&lt;/strong&gt;&lt;br /&gt;
開発者から「楽になった」といった声があった時や、生産性の向上を実感できた時にやりがいを感じたという意見が多く寄せられました。開発チーム全体のキャパシティを高める取り組みや、継続的に楽しく働ける環境づくりができていることも、良かった点として挙げられています。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ビジネス貢献: 12.3%&lt;/strong&gt;&lt;br /&gt;
コスト削減やリソースの最適化によって、事業への具体的な貢献を実感できたという声が挙げられました。また、ビジネスへの関心・意識の高まりなど、技術面だけでなく事業視点での行動や成果を評価する動きが広がっていることも印象的です。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;データ活用: 10.5%&lt;/strong&gt;&lt;br /&gt;
トレースやメトリクスの導入により、システムの状態を可視化・定量化できるようになったことが大きな成果として挙げられました。オブザーバビリティの強化やSLI/SLOの導入などを通じて、計測や統計的な判断を行う文化が広がり、データに基づいた改善や意思決定が可能となってきているようです。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/41df16e2-image-3.png&quot; alt=&quot;question result 1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;SREに関する業務でAIに任せたいことは何ですか？&lt;/h3&gt;
&lt;p&gt;合計で100件の意見をいただきました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;インシデントレスポンス: 22.2%&lt;/strong&gt;&lt;br /&gt;
インシデントの一次対応やエラー調査の自動化をAIに任せたいという声が多く見られました。またログの収集・要約や過去のポストモーテムとの類似ケースの検索、初動対応の判断支援、インシデントレポートの下書き作成といった業務など、人手による作業の負荷の軽減に期待が寄せられています。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分析/予測: 16.0%&lt;/strong&gt;&lt;br /&gt;
障害やエラーの原因調査、影響範囲の把握、再発防止策の提案といった分析・改善フェーズにおいてAIの支援が期待されているようです。また、アップデートや構成変更に伴う影響調査、さらには事業計画やBillingデータからの将来予測といった業務にもAIを活用したいというニーズが見られました。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;アップデート: 11.1%&lt;/strong&gt;&lt;br /&gt;
システムやコードの保守や改善に関してAIの活用を望む声が多く挙げられました。特にEOL対応や、アップデート作業の自動化・効率化に対する期待が高まっています。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ドキュメンテーション: 10.5%&lt;/strong&gt;&lt;br /&gt;
ポストモーテムや障害レポートなど、定型的かつ情報整理を要するタスクに対してAIの支援を求める声が挙げられました。また、社内向けの説明資料やダッシュボードの自動生成、ドキュメントの検索や要約といった情報の可視化・再利用性の向上に関してもAIへの期待が寄せられています。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;モニタリング/アラート: 9.9%&lt;/strong&gt;&lt;br /&gt;
アラートのトリアージや原因調査、優先度の判断、リリース後の自動モニタリングや外部サービスの障害速報の把握など、AIによるリアルタイム性と網羅性が期待されています。判断や対応をより迅速かつ確実にし、システムの安定性を高めるための基盤が整えられることが望まれているようです。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/23830139-image-4.png&quot; alt=&quot;question result 2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;改めて来場してくださった皆様、また弊社のブースに立ち寄っていただいた皆様に感謝を申し上げます。また本イベントを開催し、運営していただいた皆様も本当にありがとうございました。弊社としても今後の発展に貢献していきますのでまた来年もよろしくお願いいたします。&lt;/p&gt;
&lt;h2&gt;おまけ: ノベルティーキーキャップの発注方法&lt;/h2&gt;
&lt;p&gt;今回作成したノベルティーキーキャップについてはクオリティの高いものを製作していただきました。需要は不明ですが、ノベルティーキーキャップの発注については直近ではあまり情報がないようでしたのでこちらにまとめておきます。&lt;/p&gt;
&lt;p&gt;今回発注したのはYUZU Keycapsさん( &lt;a href=&quot;https://yuzukeycaps.com/&quot;&gt;https://yuzukeycaps.com/&lt;/a&gt; )です。こちらでは昇華印刷と呼ばれる方法を利用しており、熱と圧力によって浸透させて色をつけるため耐久性が高く剥がれることもありません。この方法は境界が滲んてしまうことがあるのですがとてもきれいな仕上がりでした。&lt;/p&gt;
&lt;p&gt;発注は以下の画面から行えます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/6288e824-image-5.png&quot; alt=&quot;uzu keycap step1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;まずはレイアウトを選びます。ノベルティーキーキャップ用のレイアウトは用意されていないので、適当なものを選びカスタマイズしていきます。1Uサイズのものが一般的なのでOrtholinearのものを選ぶとあとの作業が多少楽になります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/aa1bc96d-image-6.png&quot; alt=&quot;uzu keycap step2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;「Options」 からレイアウトをカスタマイズできます。今回はすべて同じキーにしたいので 「Add/remove keys」 を選択して詳細の編集画面に移ります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/468eb83f-image-7.png&quot; alt=&quot;uzu keycap step3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ここではすべてのキーを自由に選択することができます。各行 R1 ~ R4 も自由に選べます。ノベルティーキーキャップはEscとしてつけることを想定するためかR1であることが多いです。なお今回配布したメルカリのノベルティーキーキャップはMにつけることを想定してR4にしました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/76fb161a-image-8.png&quot; alt=&quot;uzu keycap step4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;説明用に作成したR1 10&amp;#215;5のものをこちらに置いておきます。&lt;br /&gt;
&lt;a href=&quot;https://yuzukeycaps.com/keyboards/121dfa73-a55e-492c-870a-2b94e490d040&quot;&gt;https://yuzukeycaps.com/keyboards/121dfa73-a55e-492c-870a-2b94e490d040&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/d03e1e03-image-9.png&quot; alt=&quot;uzu keycap step5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;次にキーキャップのテンプレートを作成していきます。ひとつひとつ設定することもできますが、テンプレートを作成しておくと後の作業が楽になります。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/139add2d-image-10.png&quot; alt=&quot;uzu keycap step6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;後でロゴを配置できるように ICON タイプでテンプレートを作成します。なお、デフォルトのアイコンとしてアップロードした画像を選択することはできないようです。なのでここでは適当なものを選択しておきます。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/22d25ae0-image-11.png&quot; alt=&quot;uzu keycap step7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;次にキーを選択して、詳細の編集画面を開きます。「Select icon」を選択するとアイコンを選択する画面になるのでセレクトフォームの右側のアップデートボタンから利用するロゴファイルを選択します。画像の作成方法については下記に説明があります。基本的には2色のみしか使えないので注意が必要です(選べるカラーは豊富にありますが自分で自由に色を作ることもできません)。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.notion.so/e5e214dc80c047ada20d97d3418eb2de?pvs=21&quot;&gt;https://fkcaps.notion.site/Custom-image-upload-e5e214dc80c047ada20d97d3418eb2de&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;なおこの画面からロゴの位置やサイズの微調整はできますが、同じ作業をすべてのキーに対してすることになるので、ここでは何もせずに済むようにテンプレートを調整することをおすすめします。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/29167e67-image-12.png&quot; alt=&quot;uzu keycap step8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;あとはひたすらこの繰り返しです。アップロードしたデータはMy Iconsから選択できるようになっているので2回目以降は多少は楽です。なお私が発注したときはjsonファイルで入出力することができたので、jsonファイルを編集してこの繰り返し作業をもっと楽にすることはできましたが、この記事の執筆段階ではそれができなくなっていました。&lt;/p&gt;
&lt;p&gt;データが完成したら「ADD TO CART」に進み発注して完了です。データチェック後に製造され、手元に届くのには2~3週間ほどかかりますので余裕を持って発注することをおすすめします。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/44e12ff6-image-13.png&quot; alt=&quot;uzu keycap step9&quot; /&gt;&lt;/p&gt;
&lt;p&gt;以上です。ノベルティーキーキャップ作成の参考になれば幸いです。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/49a9b094-image-14.png&quot; alt=&quot;novelty keycap&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>QAはAIをどのように捉えるべきか &amp;#8211; QAカンファレンス参加レポート</title><link>https://engineering.mercari.com/blog/entry/20250718-e92e0e5563/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250718-e92e0e5563/</guid><description>&lt;p&gt;こんにちは。メルペイQAチームの@uni0110です。 私は6月にスコットランドのエディンバラで開催されたEuroSTARカンファレンスに参加しました。EuroSTARは世界的に有名なQAカンファレンスの一つで、今年は4 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Tue, 22 Jul 2025 10:00:40 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイQAチームの@uni0110です。&lt;/p&gt;
&lt;p&gt;私は6月にスコットランドのエディンバラで開催された&lt;a href=&quot;https://conference.eurostarsoftwaretesting.com/conference/2025/programme/&quot; title=&quot;EuroSTARカンファレンス&quot;&gt;EuroSTARカンファレンス&lt;/a&gt;に参加しました。EuroSTARは世界的に有名なQAカンファレンスの一つで、今年は4日間にわたり60以上のチュートリアル、セッション、キーノートが行われました。約350社から1000人以上が参加した大規模なカンファレンスです。&lt;/p&gt;
&lt;h2&gt;テーマはAI on Trial&lt;/h2&gt;
&lt;p&gt;今年のカンファレンスで最も注目されたテーマはAIでした。参加者との会話でも、「あなたの会社ではAIをどのように活用していますか？」という質問が最も多く、議論が盛り上がりました。&lt;/p&gt;
&lt;p&gt;当時、メルペイQAチームは自動化にAIを活用して、それ以外の工程ではさまざまなツールを試している段階でした。そのため、私自身もAIに関するトピックに最も期待していました。&lt;/p&gt;
&lt;p&gt;全セッションの半分以上がAI関連のトピックで、内容はそれぞれ異なりましたが、どのセッションも共通して強調していたのは「AIがもたらす効率性や利便性よりも、AIの誤用や不確実性に対する注意」でした。初日のチュートリアルでこれを実際に体験できたので、簡単に共有したいと思います。&lt;/p&gt;
&lt;h2&gt;Test by Human vs. by AI&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/491f1158-photo2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;初日のチュートリアルでは、人間とAIがそれぞれ同じ内容のテストを実施しました。その結果、人間によるテストではシステムに潜在するバグが発見されましたが、AIが作成・実行したテストケースではバグを見つけることができませんでした。&lt;/p&gt;
&lt;p&gt;この違いは、人間だからできる批判的な思考です。人がテストケースを作成する際には、まず仕様を把握し、「どのような改修が行われたか」、「特定のケースではどうなるか」といった疑問があったらチューターに質問し、解決しました。このプロセスを通じて、不要なケースを削除し、必要なケースを追加することで、テストケースを完成させました。&lt;/p&gt;
&lt;p&gt;しかし、AIの場合は、どのAIツールを使用しても入力された指示に従ってテストケースを作成するだけで、バグを発見するケースは作成できませんでした。&lt;/p&gt;
&lt;p&gt;このことから、優れたQAエンジニアになるためには、クリティカルシンキングに基づいた積極的なコミュニケーション能力が必要であると痛感しました。&lt;/p&gt;
&lt;h2&gt;QAが見るAI&lt;/h2&gt;
&lt;p&gt;このチュートリアル以外にもAIが持っている以下の弱点のため、AIを使う時は十分気をつけないと行けないという内容のセッションが多かったです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;プライバシー＆セキュリティ&lt;/li&gt;
&lt;li&gt;バイアス&lt;/li&gt;
&lt;li&gt;ハルシネーション&lt;/li&gt;
&lt;li&gt;不慣れな人による誤用&lt;/li&gt;
&lt;li&gt;過度な自動化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ここまでの内容だと、カンファレンス全体がAIに対して批判的な見方をしているように思われるかもしれませんが、どのセッションもAIが作業に役立つツールであることを前提としていたため、Anti-AI的な雰囲気ではありませんでした。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/733dc170-photo3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ただ、警戒心を何度も与えた理由は、私達のロールがQAだからです。QAは他のエンジニアロールとは異なり、問題やリスクを発見する役割を担っています。そのため、AIに対しても厳しく警戒心を持たなければ、品質が損なわれる可能性があるからです。&lt;/p&gt;
&lt;h2&gt;終わりに&lt;/h2&gt;
&lt;p&gt;AIに関するベストプラクティスを期待して参加したカンファレンスでしたが、最後には自分は良いQAエンジニアなのか、もっとできることはないか、といった課題ばかり持ち帰ることになりました。また、QAエンジニアとしてAIに負けない私だけの価値について考え続けています。&lt;/p&gt;
&lt;p&gt;しかし、さまざまな場所から参加した多様なQAエンジニアと話す中で、皆が同じ悩みを抱えていることが分かり、良い刺激を受けました。&lt;/p&gt;
&lt;p&gt;特にAIについては、単なる便利なツールであり、silver bulletではないことを念頭に置いて活用していきたいと改めて感じました。&lt;/p&gt;
</content:encoded></item><item><title>Elasticsearchのベクトル検索におけるフィルター使用時のパフォーマンス改善</title><link>https://engineering.mercari.com/blog/entry/20250718-03e1dbe909/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250718-03e1dbe909/</guid><description>&lt;p&gt;Search Infra Teamのmrkm4ntrです。 画像検索にElasticsearchのベクトル検索(kNN検索)を活用しています。しかし、従来のキーワード検索と比較して、同等のリソースで処理できるQPS（Qu [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Fri, 18 Jul 2025 10:10:08 GMT</pubDate><content:encoded>&lt;p&gt;Search Infra Teamのmrkm4ntrです。&lt;/p&gt;
&lt;p&gt;画像検索にElasticsearchのベクトル検索(kNN検索)を活用しています。しかし、従来のキーワード検索と比較して、同等のリソースで処理できるQPS（Queries Per Second）が大幅に低いという課題がありました。そこで、Elasticsearch 8を基に、kNN検索のパフォーマンスをどこまで改善できるのかを調査しました。&lt;/p&gt;
&lt;h2&gt;kNN検索の構成と課題&lt;/h2&gt;
&lt;p&gt;今回の検証で使用したkNN検索のクエリ構成は以下の通りです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;size&amp;quot;: 100,
  &amp;quot;query&amp;quot;: {
    &amp;quot;knn&amp;quot;: {
      &amp;quot;image_embedding&amp;quot;: {
        &amp;quot;vector&amp;quot;: [
          0.1,
          0.2,
          ... (128次元のベクトル)
        ],
        &amp;quot;k&amp;quot;: 100,
        &amp;quot;num_candidates&amp;quot;: 100,
        &amp;quot;filter&amp;quot;: {
          &amp;quot;term&amp;quot;: {
            &amp;quot;status&amp;quot;: &amp;quot;on_sale&amp;quot;
          }
        }
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このクエリは、「status」フィールドが「on_sale」に一致するドキュメントの中から、与えられたベクトル（image_embedding）に類似した上位100件のドキュメントを検索するものです。ベクトルの次元数は128です。&lt;/p&gt;
&lt;p&gt;検証当初はElasticsearch 8.12.1を使用しており、async-profilerを用いてCPUプロファイルを取得した結果、以下の箇所がボトルネックとなっていることが判明しました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/2cb0ad58-8.12.1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;特に目立つのは、赤枠で囲まれた&lt;code&gt;jint_disjoint_arraycopy&lt;/code&gt;と&lt;code&gt;jlong_disjoint_arraycopy&lt;/code&gt;です。これらのメソッドは、&lt;code&gt;OffHeapQuantizedByteVectorValues.vectorValue&lt;/code&gt;から呼び出されており、JVMのヒープ外（つまりファイルシステムキャッシュ上）に格納されているベクトルデータをJVMのヒープ内にコピーする処理を行っています。&lt;/p&gt;
&lt;p&gt;Luceneでは高速なkNN検索を実現するためにPanama Vector APIを活用しています。このAPIはベクトル計算でプロセッサのSIMD命令（AVX命令）を使用し、演算効率を向上させるものです。しかし、ベクターデータを一度JVMヒープ内にコピーしてからPanama Vector APIに渡す無駄が発生しているため、パフォーマンスが大きく制約されています。&lt;/p&gt;
&lt;p&gt;このボトルネックを軽減する可能性がある修正がLuceneに施されていることが分かりました。具体的にはhttps://github.com/apache/lucene/pull/13339 の変更で、JVMヒープ外メモリから直接Panama Vector APIに渡す実装に改善されています。&lt;/p&gt;
&lt;h2&gt;Elasticsearch 8.17.1へのアップデートとパフォーマンスの変化&lt;/h2&gt;
&lt;p&gt;上記の改善が既に含まれるElasticsearch 8.17.1にアップデートし、パフォーマンスを検証しました。&lt;/p&gt;
&lt;p&gt;検証環境&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Elasticsearch Version: 8.17.1&lt;/li&gt;
&lt;li&gt;k: 100&lt;/li&gt;
&lt;li&gt;num_candidates: 100&lt;/li&gt;
&lt;li&gt;ベクトルの次元数: 128&lt;/li&gt;
&lt;li&gt;リアルタイムなドキュメントの追加/更新/削除&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;フィルタリングの有無と度合いがパフォーマンスに与える影響を評価するため、以下の3つのケースで同一のスペックで捌けるQPSをパフォーマンスとして計測しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;フィルタなし: フィルタリングなし&lt;/li&gt;
&lt;li&gt;緩いフィルタ（loose filter）: 約50%のドキュメントがフィルタ条件に一致&lt;/li&gt;
&lt;li&gt;絞り込みフィルタ（selective filter）: 約10%のドキュメントがフィルタ条件に一致&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;フィルタなしのケースにおいて、async-profilerでCPUプロファイルを再取得したところ、flame graphからPanama Vector APIの痕跡が消え、代わりに赤枠で囲んだ&lt;code&gt;dot7u&lt;/code&gt;というメソッドが表示されるようになりました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/a551abd1-8.17.1-no-filter.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これは、Elasticsearch 8.15から、LuceneのPanama Vector APIではなく、より効率的なElasticsearch独自のベクトル化モジュールが使用されるようになったためです (&lt;a href=&quot;https://www.elastic.co/search-labs/blog/vector-similarity-computations-ludicrous-speed&quot;&gt;https://www.elastic.co/search-labs/blog/vector-similarity-computations-ludicrous-speed&lt;/a&gt;) 。この独自実装では、不要なデータコピーはそもそも発生しません。&lt;/p&gt;
&lt;p&gt;8.12.1と8.17.1のパフォーマンスの比較は以下です。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;8.12.1&lt;/th&gt;
&lt;th&gt;8.17.1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;フィルタなし&lt;/td&gt;
&lt;td&gt;350&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;緩いフィルタ（loose filter）&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;絞り込みフィルタ（selective filter）&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Elasticsearch 8.17.1にアップデート後、全体的にパフォーマンスが改善しましたが、フィルタありのケースでは依然としてフィルタ無しのケースと比べるとパフォーマンスが低い状態です。&lt;/p&gt;
&lt;h2&gt;絞り込みフィルタリング時のHNSWグラフ探索の最適化&lt;/h2&gt;
&lt;p&gt;絞り込みフィルタ（selective filter）を適用した場合のCPUプロファイルを確認したところ、赤枠で囲まれた部分の&lt;code&gt;HNSWGraphSearcher.search&lt;/code&gt;の処理時間が大幅に増加していることがわかりました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/6ad279c8-8.17.1-selective-filter.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これは近傍のノードをチェックする際に、類似度の計算を行わず、諦めて次をチェックする回数が増加していることを意味します。 つまり、HNSWグラフの構造上、フィルタ条件に合致するドキュメントが少ない場合、グラフのリンクを効率的に辿ることができないために発生する問題です。類似度が高い方向にグラフを探索しても、フィルタ条件に合致するドキュメントが見つからない場合、大きく迂回したり、戻ったりする必要が生じ、探索効率が低下します。&lt;/p&gt;
&lt;p&gt;この問題を解決するために、ACORNと呼ばれるアルゴリズム(&lt;a href=&quot;https://arxiv.org/pdf/2403.04871&quot;&gt;https://arxiv.org/pdf/2403.04871&lt;/a&gt;) が提案されており、様々なベクトル検索エンジンが採用しています。Luceneのupstreamでも、ACORN風のアルゴリズムが実装されているため、このPR (&lt;a href=&quot;https://github.com/apache/lucene/pull/14160&quot;&gt;https://github.com/apache/lucene/pull/14160&lt;/a&gt;) をcherry-pickして試したところ、特に絞り込みフィルタの場合に大きなパフォーマンス改善が見られました。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;8.17.1&lt;/th&gt;
&lt;th&gt;8.17.1 + ACORN&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;フィルタなし&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;緩いフィルタ（loose filter）&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;絞り込みフィルタ（selective filter）&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;とはいえ、緩いフィルタ（loose filter）の場合はあまり改善されていません。&lt;/p&gt;
&lt;h2&gt;緩いフィルタリング時のBitSet合成の最適化&lt;/h2&gt;
&lt;p&gt;緩いフィルタ（loose filter）を適用した場合のCPUプロファイルを確認したところ、赤枠で囲んだ部分である&lt;code&gt;AbstractKnnVectorQuery.createBitSet&lt;/code&gt;の処理時間が大部分を占めていることがわかりました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/faf66afc-8.17.1-acorn-loose-filter.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;フィルタが固定で、kNN検索に指定するベクトル値のみを変更している場合、フィルタの結果のBitSetはクエリキャッシュに保存されるため、フィルタ自体のコストはほぼ無視できるはずです。&lt;/p&gt;
&lt;p&gt;コードを解析した結果、createBitSetメソッド内で、フィルタのBitSetとliveDocsのBitSetを合成した新しいBitSetを作成していることが判明しました。LuceneのSegmentはimmutableであるため、削除されたドキュメントを管理するために別のデータ構造（liveDocs）が必要になります。liveDocsもBitSetで表現されており、フィルタのBitSetとliveDocsのBitSetを合成する際に、BitSetの中身をiterateしていました。緩いフィルタの場合、フィルタ条件に合致するドキュメントが多いため、この処理に大きなコストがかかっていました。&lt;/p&gt;
&lt;p&gt;しかし、グラフを辿る際に、見つけたドキュメントがフィルタに合致するかをチェックするだけであれば、BitSetを合成する必要はありません。また、カーディナリティ（BitSet内で1が立っているビット数）を計算する場合も、iterateして合成する必要はなく、FixedBitSetのintersectionCountメソッドを使用することで高速に計算できます。&lt;/p&gt;
&lt;p&gt;これらの点を修正した結果、特に緩いフィルタを使用した場合のパフォーマンスが大幅に改善しました。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;8.17.1 + ACORN&lt;/th&gt;
&lt;th&gt;8.17.1 + ACORN + BitSet合成最適化&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;フィルタなし&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;緩いフィルタ（loose filter）&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;絞り込みフィルタ（selective filter）&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;170&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;この修正はLuceneのupstreamにPRとして送りました(&lt;a href=&quot;https://github.com/apache/lucene/pull/14771&quot;&gt;https://github.com/apache/lucene/pull/14771&lt;/a&gt;) 。しかし、その少し前に追加されていた修正 (&lt;a href=&quot;https://github.com/apache/lucene/pull/14674&quot;&gt;https://github.com/apache/lucene/pull/14674&lt;/a&gt;) にて改善されていたためこのPRは不要でした。正確には、このPRそのものはcherry pickして試していましたが、&lt;code&gt;applyMask&lt;/code&gt;の非default実装は別PRで対応されていたため、それを見落としていました。&lt;/p&gt;
&lt;h2&gt;FieldExistQueryによるパフォーマンス低下の解消&lt;/h2&gt;
&lt;p&gt;改善後のCPUプロファイルを確認したところ、依然としてcreateBitSet内の&lt;code&gt;Lucene99ScalarQuantizedVectorsReader$QuantizedVectorValues.advance&lt;/code&gt;の処理時間が残っていました。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/5fe9d775-8.17.1-acorn-bitset-loose-filter.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;これは、フィルタにLucene内部で追加されるFieldExistQueryによるものであることがわかりました。インデックス内のすべてのドキュメントにkNNの対象となるベクトルフィールドが存在するわけではない場合、その存在をチェックする追加の処理が必要になります。今回のケースでは、埋め込み処理でエラーが発生した場合にベクトルが存在しないドキュメントが存在していました。&lt;/p&gt;
&lt;p&gt;これらのドキュメントをインデックスに含めないように修正したところ、パフォーマンスがさらに改善しました。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;8.17.1 + ACORN + BitSet合成最適化&lt;/th&gt;
&lt;th&gt;8.17.1 + ACORN + BitSet合成最適化 + FieldExistQueryの除外&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;フィルタなし&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;緩いフィルタ（loose filter）&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;絞り込みフィルタ（selective filter）&lt;/td&gt;
&lt;td&gt;170&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一般にFieldExistQueryは対象のドキュメントのfieldを全てチェックする必要があるため高コストです。FieldExistQueryと今回のフィルタはQuery Cacheの対象のため全てのドキュメントをチェックしているわけではないはずですが、Query Cacheの対象とならないようなサイズのセグメントのみが対象だったとしても、緩いフィルタの場合は高コストであったと考えられます。&lt;/p&gt;
&lt;h2&gt;さいごに&lt;/h2&gt;
&lt;p&gt;Elasticsearch 8のkNN検索において、フィルタリング時のパフォーマンスを改善するために、以下の施策を実施しました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Elasticsearch 8.17.1へのアップデート: Elasticsearch独自のベクトル化モジュールを使用することで、フィルタなしのケースにおけるパフォーマンスを向上させました。&lt;/li&gt;
&lt;li&gt;ACORN風アルゴリズムの導入: 絞り込みフィルタ適用時のHNSWグラフ探索を最適化しました。&lt;/li&gt;
&lt;li&gt;BitSet合成の最適化: 緩いフィルタ適用時のBitSet合成処理を効率化しました。&lt;/li&gt;
&lt;li&gt;ベクトルフィールドの存在チェックの排除: ベクトルフィールドが存在しないドキュメントをインデックスから排除することで、不要な存在チェックを削減しました。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これらの改善により、フィルタリングの有無に関わらず、kNN検索のパフォーマンスを大幅に向上させることができました。以下が最終的な結果です。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;改善前&lt;/th&gt;
&lt;th&gt;改善後&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;フィルタなし&lt;/td&gt;
&lt;td&gt;350&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;緩いフィルタ（loose filter）&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;絞り込みフィルタ（selective filter）&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ベクトルフィールドの存在チェック以外の改善は将来のElasticsearchのリリース(9.0.4以降?)に含まれる予定です。&lt;/p&gt;
&lt;p&gt;これらの改善は、ElasticsearchのkNN検索をHybrid Searchなどに活用し、より高度な検索サービスの提供に繋がるものと考えています。&lt;/p&gt;
</content:encoded></item><item><title>メルペイ Payment Coreチームで学んだ2ヶ月間の振り返り</title><link>https://engineering.mercari.com/blog/entry/20250716-paymentcore-internship/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250716-paymentcore-internship/</guid><description>&lt;p&gt;こんにちは。メルペイ Payment Core Teamで2ヶ月間インターンシップをした@taichiです。 この記事は、Merpay &amp;amp; Mercoin Tech Openness Month 2025 の22 [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Wed, 16 Jul 2025 10:03:55 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイ Payment Core Teamで2ヶ月間インターンシップをした@taichiです。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250528-merpay-mercoin-tech-openness-month-2025/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2025&lt;/a&gt; の22日目の記事です。&lt;/p&gt;
&lt;h1&gt;はじめに&lt;/h1&gt;
&lt;p&gt;私は4月の中旬から6月の中旬の間、 バックエンドエンジニアとしてメルペイのインターンシップに参加しました。今回はインターン期間中に取り組んだタスクを振り返り、 そこで得た学びをまとめたいと思います。&lt;br /&gt;
この記事が、 メルペイのインターンに挑戦してみたいと考えている未来のHackerの参考になれば幸いです。&lt;/p&gt;
&lt;h2&gt;取り組んだタスク&lt;/h2&gt;
&lt;p&gt;私が担当したタスクは大きく分けると3つあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;外部パートナーへの接続に関するCredentialの管理方法の変更&lt;/li&gt;
&lt;li&gt;Re-arch中のソースコードへのPub/Sub基盤統合&lt;/li&gt;
&lt;li&gt;個人情報難読化ポリシーの実装&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下で1つずつ話していきます。&lt;/p&gt;
&lt;h1&gt;外部パートナーへの接続に関するCredential管理方法の変更&lt;/h1&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;私が所属するPayment Coreチームでは、決済基盤マイクロサービスである『Payment Service』の開発を担当しています。このサービスは、メルカリ、メルペイ、メルコインが展開する多数のマイクロサービス群から参照されており、グループ全体の決済ドメインにおけるコアな責務を担っています。&lt;/p&gt;
&lt;p&gt;メルペイでは決済機能の一部で外部パートナーさまのAPIを活用しているため、そのCredentialを適切に管理する必要があります。&lt;/p&gt;
&lt;p&gt;私がタスクに取り組むまでの運用では、 新しく追加したCredentialを暗号化しSpannerにSQL raw queryを叩くことで保存していました。&lt;/p&gt;
&lt;p&gt;従来の運用方法は以下に示す危険性と面倒さを有しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;加盟店追加のたびにSQLクエリを叩く必要がある&lt;/li&gt;
&lt;li&gt;加盟店追加後にSQLクエリを発行し忘れる可能性がある&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;やったこと&lt;/h2&gt;
&lt;p&gt;上記の課題を解決するために、 以下のような設計と実装を行いました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;外部パートナーの加盟店さまのパスワードの格納先をGoogle Secret Managerに変更&lt;/li&gt;
&lt;li&gt;Spannerに直接パスワードを保存せず, SecretのKeyとVersionだけを格納する&lt;/li&gt;
&lt;li&gt;Secretの情報を入力として渡すだけで, SQLが走ってSecretのKeyとVersionをSpannerに保存するK8sのJobテンプレートを作成&lt;/li&gt;
&lt;li&gt;SecretManagerを通して加盟店さまのパスワードを取得できるようにClientCodeを修正&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最初はCLIを作ってチームに提供することも考えましたが、 メンテナンスの負荷が新たに発生すること、 K8sのJob基盤がすでにチームに存在することを理由にCLIは避けました。&lt;br /&gt;
K8sのJobテンプレートはこちらが参考になると思います。&lt;br /&gt;
インターンに参加して1週間くらいで設計を行いドキュメントを作成したのですが, 設計書のレビューが手厚くチームメンバーと技術的な議論を繰り返すことでタスクやPayment Serviceの全体像を掴むことができました。&lt;/p&gt;
&lt;h1&gt;Re-arch中のソースコードへのPub/Sub基盤統合&lt;/h1&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;私が所属していたPayment Coreチームが開発しているPayment Serviceは、そのソースコードが非常に複雑なため、大規模な再構築プロジェクト、通称「Re-arch（リアーキテクチャ）」が進められていました。&lt;/p&gt;
&lt;p&gt;このRe-archプロジェクトの目的は、既存のPayment ServiceのソースコードをClean Architectureのような設計思想に基づいて書き直すことです。&lt;/p&gt;
&lt;p&gt;現状のPayment Serviceでは、非同期処理のためにGoogle Cloud Pub/Subが利用されています。しかし、Re-arch後の新しいソースコードには、Pub/Subを利用するための基盤がまだ整備されていない状況でした。&lt;/p&gt;
&lt;h2&gt;やったこと&lt;/h2&gt;
&lt;p&gt;私はRe-arch後のソースコードのコンテキストにマッチするようにPub/Subの基盤を統合しました。&lt;br /&gt;
Pub/Sub基盤の設計は、 Payment ServiceがPub/SubからSubscribeしか行わないことを前提にメンターと進めました。&lt;br /&gt;
Subscriberに渡すHandlerのInterface設計や、 Usecaseに渡す依存関係を一括で管理するContainerとの親和性を考慮して設計する経験は非常に勉強になりました。&lt;br /&gt;
Re-archのPRは変更が大きいものもあるので、 Conflictの解消やContextを理解し直すのに苦労したこともありましたが、 あれほど大規模なソースコードを読むこともないのでとても良い経験になりました。&lt;br /&gt;
開発体験としても、 Payment ServiceではMockの生成は&lt;a href=&quot;http://moq.go&quot; title=&quot;moq.go&quot;&gt;moq.go&lt;/a&gt;を使っているので、 interfaceだけ設計すればMockを簡単に生成できるので、 素早くTestableなコードを書くことができました。&lt;/p&gt;
&lt;h1&gt;個人情報難読化ポリシーの実装&lt;/h1&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;個人情報保護法の改正を受け、メルペイ全社で「PII Deletion（個人情報難読化）プロジェクト」が進行中です。これに伴い、Payment Serviceもこの対応を行う必要がありました。&lt;br /&gt;
個人情報の難読化はメルペイ全体で取り組むべき課題であるため、すでにそのためのマイクロサービス「PII Deletion Service」が構築されていました。&lt;br /&gt;
しかし、Payment ServiceからPII Deletion Serviceを叩きに行くことができない状態でした。&lt;/p&gt;
&lt;h2&gt;やったこと&lt;/h2&gt;
&lt;p&gt;PII Deletion Serviceのアーキテクチャは下図のようになっています。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/b2f49001--2025-07-16-9.46.53.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上図を解釈すると, 処理の流れとしては以下になります。(Payment Serviceは上図における右端に位置すること, 各マイクロサービスはgRPCで通信を行うことを念頭においてください)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;PII Deletion Managerから難読化すべき個人情報の情報がPub/SubにPushされる &lt;/li&gt;
&lt;li&gt;Payment ServiceがPub/SubからPullして難読化する対象を見つける&lt;/li&gt;
&lt;li&gt;難読化する&lt;/li&gt;
&lt;li&gt;難読化が成功したかのステータスをPII Deletion Managerに返す&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1はすでに仕組みとして存在するので、 私は2-4を実装すれば良いことに気づきます。&lt;br /&gt;
以下ではステップ2についてどのように対応したかを述べます。(3、 4はそこまで難しいことがないのでスキップします。)&lt;/p&gt;
&lt;h2&gt;Pub/SubからPullして難読化対象を見つける&lt;/h2&gt;
&lt;p&gt;PII DeletionのHandlerはRe-arch後のソースコードに実装するので、 私が統合したPub/Sub基盤を使用すればすぐに実現できるので簡単に思えます。&lt;br /&gt;
しかし、 Pub/SubのSubscriberが行う処理をそのまま記述しようとすると2点好ましくない点があります。&lt;br /&gt;
普段慣れ親しんでいるgRPCエンドポイントと異なる体験で開発しないといけない&lt;br /&gt;
Subscriberのロジックが他のAPIのロジックから独立しがち&lt;br /&gt;
これらの課題を避けるために、 メルペイはPub/Sub gRPC Pusherという内製化サービスを持っています。Pub/Sub gRPC Pusherの仕組みは簡単で、 以下のようなアーキテクチャになります。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/07/afa3c4c3--2025-07-16-9.48.57.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;gRPC Pusherはマイクロサービスの代わりにPub/SubからPullし, gRPCリクエストに変換してマイクロサービス側のエンドポイントを叩いてくれます。&lt;br /&gt;
gRPC Pusherを使うことでメルペイのエンジニアは、Pub/SubのSubscriberロジックを慣れ親しんだgRPCエンドポイントとして実装でき、Pub/Subという特定のInfrastructureを意識しなくて良くなります。&lt;/p&gt;
&lt;p&gt;今回はこちらのgRPC Pusherを利用するためのInfrastructureリソースをTerraformで作成し、 個人情報難読化を行うロジックはgRPCエンドポイントとして実装を行いました。&lt;br /&gt;
ただ、gRPC Pusherを使うならいろいろと考えるべきことがあります。&lt;br /&gt;
Pub/SubのPull型Subscriptionの大きな利点は、Pullする側のスケールやワークロードの都合に合わせて処理を実行できる点にあります。gRPC Pusherはその都合をPullするマイクロサービスの代わりに受け持っていると考えることもできます。&lt;/p&gt;
&lt;p&gt;Kubernetesをはじめとしたスケーリング技術は、必要になった時にすぐにスケールするわけではないので、ワークロード量によってはgRPC Pusherの使用は適切でない場合もあります。しかし、PII Deletionのリクエストはスケールが追いつかないほど大量のリクエストが飛んでくることは想定し難いため、gRPC Pusherの使用を決断しました。&lt;br /&gt;
全ての個人情報を難読化するところまでは完了できませんでしたが、 基本的なロジックは全て実装し終えました。&lt;/p&gt;
&lt;h1&gt;学んだことと感想&lt;/h1&gt;
&lt;h2&gt;ハード面&lt;/h2&gt;
&lt;p&gt;使用した技術としては以下です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;Google Cloud (Pub/Sub, Secret Manger, Spanner)&lt;/li&gt;
&lt;li&gt;gRPC&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;どの技術も触ったことはあったものの深く触ったことはなかったので勉強になりました。&lt;br /&gt;
特にTerraformには興味があるので、 大学院の研究が落ちつく7月は何かしらのProviderを自前で実装するつもりです。Kubernetesに関しても基本的な概念やリソースの役割のみならず、 カスタムコントローラの実装等、 面白い部分はたくさんあるのでこれからさらに深く勉強していこうと思います。&lt;br /&gt;
また、 Payment Serviceのソースコードは大規模かつ複雑だったので読み解くのに苦労しましたが、 パフォーマンスと冪等性を意識した設計になっているのでとても勉強になりました。&lt;/p&gt;
&lt;h2&gt;ソフト面&lt;/h2&gt;
&lt;p&gt;インターンを通して自身の英語スキルが向上したと感じました。&lt;br /&gt;
Payment Coreチームでは、 チームのStandupも月曜日から木曜日は全て英語で行われます。&lt;br /&gt;
また、私が参加していたPII Deletion ProjectのMeetingやGitHub上のやり取りも基本全て英語ですし、最終成果発表も全て英語で行いました。普段の業務から英語と身近に触れられたことは自身の成長に大きく繋がったと感じています。&lt;br /&gt;
それから, チームメンバーはもちろん、他のチームの方々ともコミュニケーションを積極的にとることも心がけました。チームメンバーはもちろんのこと、他チームの方々も私に親切にしてくださり最終成果発表にも参加してくださいました。&lt;br /&gt;
インターンシップを通じてメルカリのカルチャーを存分に味わえて本当に楽しかったです。&lt;/p&gt;
&lt;p&gt;メルカリグループは全社的にAI活用を推進しており、私も積極的にAIを活用しました。私は以前からAIを使ってソースコードを生成することに違和感を覚えていました。&lt;br /&gt;
Junior EngineerはIntermediate、 Seniorとタイトルを上げていく必要があります。タイトルはエンジニアの各ソースコードのレベルだけで決まるわけではないですが、当然相関はあります。Juniorである私がAIを使ってソースコードを書いてしまったら、成長する機会がなくなりいつまで経っても自らのスキルが伸びないのではないか？と考えていました。&lt;/p&gt;
&lt;p&gt;ですが、Payment Serviceの莫大なドメインと複雑なソースコードを理解するには到底2ヶ月では足りないため、効率の良いキャッチアップが必要です。チームメンバーに相談してみると、彼らは皆AI（Cursor）を使って効率よくドメイン知識を吸収したり、UnitTestを書いたりしていました。彼らの助言を受けて私もインターン中にCursorを徹底的に活用し、AIと協調して、今までよりもより本質的な作業に時間を使えるようになりました。&lt;/p&gt;
&lt;p&gt;AIを使用しても出力されるコードや価値は本人の能力にキャップされるため、依然として強くなるために勉強は必要です。&lt;br /&gt;
私が最初に抱いていた違和感は今でも間違っていないと思います。&lt;br /&gt;
しかし、成長する機会は自分でいくらでも作り出せるので、仕事と成長をイコールで結ばず両方全力で取り組めば、より早く、より大きな価値を届けられるエンジニアになれると考えを改めました。&lt;br /&gt;
インターンを通して最も大きく変わった部分はここだと思います。&lt;/p&gt;
&lt;h1&gt;最後に&lt;/h1&gt;
&lt;p&gt;チームメンバーをはじめ、たくさんの方々にお世話になりました。&lt;br /&gt;
メルカリのカルチャーを存分に体感しながら技術的に難しい課題に取り組まさせていただき、本当に感謝しています。&lt;br /&gt;
2ヶ月間ありがとうございました。&lt;/p&gt;
</content:encoded></item><item><title>KubeCon + CloudNativeCon 2025 Japan 参加レポート（おまけ:Envoy拡張のWasmフィルタのデモ実装）</title><link>https://engineering.mercari.com/blog/entry/20250630-1224ec5881/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250630-1224ec5881/</guid><description>&lt;p&gt;こんにちは。メルカリモバイルのソフトウェアエンジニアの@keiitajです。 この記事は、Merpay &amp;amp; Mercoin Tech Openness Month 2025 の21日目の記事です。 概要 本記事は [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 30 Jun 2025 18:07:44 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルカリモバイルのソフトウェアエンジニアの&lt;a href=&quot;https://x.com/keiitaj&quot;&gt;@keiitaj&lt;/a&gt;です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250528-merpay-mercoin-tech-openness-month-2025/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2025&lt;/a&gt; の21日目の記事です。&lt;/p&gt;
&lt;h2&gt;概要&lt;/h2&gt;
&lt;p&gt;本記事は、2025年6月16-17日に日本で初開催されたKubeCon + CloudNativeCon 2025 Japan の参加レポートです。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;この記事の内容：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日本初開催のKubeConイベントレポート&lt;/li&gt;
&lt;li&gt;注目セッション&lt;/li&gt;
&lt;li&gt;Envoy拡張用Wasmフィルタのローカル環境上のデモ実装&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;執筆者の学び：&lt;/strong&gt;&lt;br /&gt;
EnvoyとWebAssemblyを活用したAPI管理手法を実際に実装することで、セッションで得た知識をより深く理解できました。また、参加前は技術的ハードルが高いと感じていましたが、実際は職種やレベルを問わず楽しめるイベントであることを発見しました。&lt;/p&gt;
&lt;h2&gt;目次&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#はじめに&quot;&gt;はじめに&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#参加の動機&quot;&gt;参加の動機&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#イベントの規模と熱気&quot;&gt;イベントの規模と熱気&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#注目したkeynote--session&quot;&gt;注目したKeynote &amp;amp; Session&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#参加して感じたこと&quot;&gt;参加して感じたこと&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#おまけenvoy--wasmフィルタの実装とローカル環境のデモ&quot;&gt;おまけ：Envoy + Wasmフィルタのデモ実装&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#まとめ&quot;&gt;まとめ&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;私は普段、Platform Engineerではなく、KubernetesやCloud Nativeの技術で構成された基盤の上でサービス開発を行うエンジニアとして、Tekton、Argo、Istio、Envoyなどを使って仕事をしています。&lt;/p&gt;
&lt;h2&gt;参加の動機&lt;/h2&gt;
&lt;h3&gt;技術的な興味&lt;/h3&gt;
&lt;p&gt;業務でKubernetesやCloud Nativeの技術（Tekton、Argo、Istio、Envoy、Spinnakerなど）に日々触れる中で、これらの技術の最新動向やコミュニティの活動に強い興味を持つようになりました。&lt;br /&gt;
また、AI技術の進化により、サービス開発エンジニアがPlatform構築により深く関わる機会が増えていくと感じました。&lt;br /&gt;
そのため、Cloud Nativeエコシステムの全体像を把握しておきたいという思いもありました。&lt;/p&gt;
&lt;h3&gt;日本初開催の歴史的なイベント&lt;/h3&gt;
&lt;p&gt;CNCF（Cloud Native Computing Foundation）の主要イベントとして世界各地で開催されてきたKubeConが、日本で初めて開催されることも参加の大きな動機となりました。&lt;/p&gt;
&lt;p&gt;実は、この日本開催実現までには長い道のりがあったそうです。&lt;br /&gt;
以前、5月に開催されたCloudNative Daysで、Linux FoundationのNoriaki Fukuyasu氏による&lt;a href=&quot;https://event.cloudnativedays.jp/cnds2025/talks/2633&quot;&gt;KubeConを日本に招致するまでの経緯&lt;/a&gt;についての講演を聞く機会があり、その内容がとても印象的で、今回のKubeCon参加のきっかけの一つにもなりました。&lt;/p&gt;
&lt;p&gt;Fukuyasu氏の話によると：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2023年以前、日本はクラウドネイティブ技術後進国と言われていた
&lt;ul&gt;
&lt;li&gt;大企業での採用実績が少ない&lt;/li&gt;
&lt;li&gt;アジャイル、マイクロサービス、コンテナを知らない人が多数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://community.cncf.io/cloud-native-community-japan/&quot;&gt;Cloud Native Community Japan&lt;/a&gt;を立ち上げ、月に複数回のmeetupを開催&lt;/li&gt;
&lt;li&gt;継続的なロビイング活動を通じて、ついに日本への招致に成功&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このような背景を知ったことで、日本初開催のKubeConに参加することの意義をより深く感じるようになりました。&lt;/p&gt;
&lt;h2&gt;イベントの規模と熱気&lt;/h2&gt;
&lt;h3&gt;会場と参加者&lt;/h3&gt;
&lt;p&gt;会場となったヒルトン東京お台場で開催されたこのイベントは、1,500枚のチケットが完売し、日本のCloud Nativeコミュニティの盛り上がりを肌で感じることができました。&lt;/p&gt;
&lt;p&gt;ちなみに、今年ロンドンで開催されたKubeConは12,418人が参加する&lt;a href=&quot;https://www.cncf.io/wp-content/uploads/2025/06/KubeCon_EU_25_TransparencyReport.pdf&quot;&gt;大規模なイベント&lt;/a&gt;でしたが、日本開催はコンパクトながらも内容の濃いイベントでした。&lt;/p&gt;
&lt;h3&gt;イベントの楽しみ方&lt;/h3&gt;
&lt;p&gt;KubeConの魅力は、単にセッションを聞くだけではありません。初参加で感じた楽しみ方をご紹介します：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;セッション参加&lt;/strong&gt;：&lt;a href=&quot;https://kccncjpn2025.sched.com&quot;&gt;スケジュール&lt;/a&gt;を見て、興味のあるKeynoteやセッションに参加&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ブース巡り&lt;/strong&gt;：セッションの合間に、OSSパビリオンや企業ブースを訪問
&lt;ul&gt;
&lt;li&gt;OSSプロジェクトの開発者と直接話す貴重な機会&lt;/li&gt;
&lt;li&gt;最新プロダクトのデモを体験&lt;/li&gt;
&lt;li&gt;各社のノベルティグッズ収集&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;企業ブースでの発見&lt;/h4&gt;
&lt;p&gt;各企業ブースを回ることで、最新のプロダクトやサービスの動向を直接確認できました：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PagerDuty&lt;/strong&gt; AIエージェントが過去の対応履歴を基に、インシデントの原因分析や重要度判定を支援&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Splunk&lt;/strong&gt; Observability Cloudの新機能を展示、AIチャット機能による差別化を強調&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Toyota&lt;/strong&gt; コネクテッドカーの研究開発でCloud Native技術を活用し、自動車業界でもCloud Nativeが浸透していることを実感&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;注目したKeynote &amp;amp; Session&lt;/h2&gt;
&lt;h3&gt;Opening Keynote： Community Opening Remarks&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=mh7Cmei3pmI&quot;&gt;Community Opening Remarks&lt;/a&gt; &amp;#8211; Chris Aniszczyk氏（CNCF CTO）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=mh7Cmei3pmI&quot; title=&quot;Community Opening Remarks&quot;&gt;&lt;img src=&quot;https://img.youtube.com/vi/mh7Cmei3pmI/0.jpg&quot; alt=&quot;Community Opening Remarks&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;開会の挨拶では、以下の印象的な発表がありました：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1,500枚のチケットが完売したことへの感謝&lt;/li&gt;
&lt;li&gt;CNCF、Kubernetesへのコントリビューションで日本がTOP10入り&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2026年も日本でKubeConを開催することが決定！&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;日本のCloud Nativeコミュニティの成長が認知されていることを実感しました。&lt;/p&gt;
&lt;h3&gt;技術セッション： Full Lifecycle API Management in Kubernetes With Envoy and WebAssembly&lt;/h3&gt;
&lt;p&gt;特に印象に残ったのは、&lt;a href=&quot;https://www.youtube.com/watch?v=McMBwWPlYTE&quot;&gt;Full Lifecycle API Management in Kubernetes With Envoy and WebAssembly&lt;/a&gt;というセッションです。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=McMBwWPlYTE&quot; title=&quot;Full Lifecycle API Management in Kubernetes With Envoy and WebAssembly&quot;&gt;&lt;img src=&quot;https://img.youtube.com/vi/McMBwWPlYTE/0.jpg&quot; alt=&quot;Full Lifecycle API Management in Kubernetes With Envoy and WebAssembly&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;セッションの概要&lt;/h4&gt;
&lt;p&gt;KubernetesにおけるAPI管理の課題に対して、EnvoyとWebAssembly（Wasm）を活用した革新的なアプローチが紹介されました：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;L3/L7プロキシ機能の統合&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;JWT認証とルーティングによる高度なAPIトラフィック管理&lt;/li&gt;
&lt;li&gt;eBPFとOpenTelemetryを活用したオブザーバビリティの向上&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebAssemblyフィルタの活用&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;複数言語での開発が可能&lt;/li&gt;
&lt;li&gt;より高速な配布時間&lt;/li&gt;
&lt;li&gt;ランタイムでのセキュリティロジックの実装&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;実践的なデモ&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Authorizationヘッダーのチェック機能をWasmで実装&lt;/li&gt;
&lt;li&gt;認証なしのトラフィックをブロックする仕組みの構築&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;技術的な洞察&lt;/h4&gt;
&lt;p&gt;このセッションで特に興味深かったのは、WebAssemblyの用途が拡大していることです。元々はブラウザ上でのパフォーマンス向上のために開発されたWebAssemblyが、今やサーバーサイドのプラグイン機構として活用され始めています。&lt;/p&gt;
&lt;h2&gt;参加して感じたこと&lt;/h2&gt;
&lt;h3&gt;技術トレンドの観察&lt;/h3&gt;
&lt;p&gt;イベント全体を通じて感じた技術トレンド：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OpenTelemetry + eBPF&lt;/strong&gt;：オブザーバビリティ関連のセッションで頻繁に言及&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebAssembly&lt;/strong&gt;：サーバーサイドでの活用事例が増加&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI統合&lt;/strong&gt;：各種ツールにAI機能が標準装備&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;コミュニティの多様性&lt;/h3&gt;
&lt;p&gt;印象的だったのは、専門的な内容から初心者向けまで幅広いセッションが用意され、ライトニングトークやパネルディスカッションでは専門以外の話題（コミュニティでの友達作りやコントリビューションのモチベーションなど）も語られていたことです。&lt;/p&gt;
&lt;p&gt;参加のハードルが高いイメージがありましたが、実際は職種やエンジニアのレベルを問わず、誰でも楽しめるイベントであると感じました。&lt;/p&gt;
&lt;h3&gt;言語の壁と対策&lt;/h3&gt;
&lt;p&gt;Keynoteやセッションは全て英語で行われるため、英語が苦手な方には少しハードルが高いかもしれません。&lt;/p&gt;
&lt;p&gt;私がとった対策は、Google Meetのマイクでスピーカーの音声を拾い、Geminiに議事録を作成してもらって内容を把握することでした。&lt;/p&gt;
&lt;p&gt;Google Docsの音声入力は途中で音声が途切れると入力がストップしてしまったり、AppStoreに公開されている幾つかの音声の書き起こしアプリは有料でさらに時間制限があったりしたので、色々試行錯誤した結果、この方法が一番良かったと感じました。&lt;/p&gt;
&lt;p&gt;また、参加した仲間とセッションの内容を共有し合うことで、理解を深めていました。&lt;/p&gt;
&lt;h3&gt;効率的な参加のコツ&lt;/h3&gt;
&lt;p&gt;同一時間帯に複数のセッションが開催されるため、私は興味のあるセッションを絞って参加しましたが、複数人で参加する場合はより効率的な立ち回りができると感じました：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;手分けして異なるセッションに参加&lt;/li&gt;
&lt;li&gt;Coffee Breakで情報交換したり、お互いにセッション内容のメモを共有&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;おまけ：Envoy + Wasmフィルタの実装とローカル環境のデモ&lt;/h2&gt;
&lt;p&gt;KubeConで紹介されたEnvoyとWebAssemblyによるAPI管理の技術についてより深く理解するため、実際にEnvoyを拡張するGolang製のWasmフィルタを実装し、ローカル環境上で動作確認を行いました。&lt;/p&gt;
&lt;p&gt;実装の詳細やソースコードは、&lt;a href=&quot;https://github.com/keitaj/envoy-wasm-sample&quot;&gt;GitHub&lt;/a&gt; で公開しています。&lt;/p&gt;
&lt;h3&gt;デモの概要&lt;/h3&gt;
&lt;p&gt;このデモでは、Bearer トークン認証を行うWasmフィルタを実装し、以下の機能を備えています：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bearer トークンによる認証機能&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/health&lt;/code&gt; エンドポイントの認証スキップ&lt;/li&gt;
&lt;li&gt;認証成功時のカスタムヘッダー追加&lt;/li&gt;
&lt;li&gt;認証失敗時のJSONエラーレスポンス&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;実装のポイント&lt;/h3&gt;
&lt;h4&gt;1. 使用技術・ツール&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Envoy&lt;/strong&gt;: v1.34-latest以降&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt;: 1.24以降 (wasip1/wasm target)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;proxy-wasm/proxy-wasm-go-sdk&lt;/strong&gt;: Wasm plugin development SDK for Envoy&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. シンプルな認証ロジック&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main

import (
    &amp;quot;github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm&amp;quot;
    &amp;quot;github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types&amp;quot;
)

func main() {}

func init() {
    proxywasm.SetVMContext(&amp;amp;vmContext{})
}

type vmContext struct {
    types.DefaultVMContext
}

func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
    return &amp;amp;pluginContext{}
}

type pluginContext struct {
    types.DefaultPluginContext
}

func (p *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
    proxywasm.LogInfo(&amp;quot;plugin started&amp;quot;)
    return types.OnPluginStartStatusOK
}

func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
    return &amp;amp;httpAuthContext{contextID: contextID}
}

type httpAuthContext struct {
    types.DefaultHttpContext
    contextID uint32
}

func (ctx *httpAuthContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
    // Get path
    path, err := proxywasm.GetHttpRequestHeader(&amp;quot;:path&amp;quot;)
    if err != nil {
        proxywasm.LogErrorf(&amp;quot;failed to get path: %v&amp;quot;, err)
        path = &amp;quot;/&amp;quot;
    }

    proxywasm.LogInfof(&amp;quot;Processing request to path: %s&amp;quot;, path)

    // Skip health check
    if IsHealthCheckPath(path) {
        proxywasm.LogInfo(&amp;quot;Health check endpoint, allowing request&amp;quot;)
        return types.ActionContinue
    }

    // Get Authorization header
    authHeader, err := proxywasm.GetHttpRequestHeader(&amp;quot;authorization&amp;quot;)
    if err != nil {
        authHeader = &amp;quot;&amp;quot;
    }

    // Validate token
    authResult := ValidateToken(authHeader)
    if !authResult.IsValid {
        proxywasm.LogWarnf(&amp;quot;Authentication failed: %s&amp;quot;, authResult.Reason)
        return ctx.denyRequest(authResult.Reason)
    }

    // Add user type header
    proxywasm.LogInfof(&amp;quot;Valid %s token&amp;quot;, authResult.UserType)
    proxywasm.AddHttpRequestHeader(&amp;quot;x-auth-user&amp;quot;, authResult.UserType)
    return types.ActionContinue
}

func (ctx *httpAuthContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
    proxywasm.AddHttpResponseHeader(&amp;quot;x-wasm-filter&amp;quot;, &amp;quot;go-auth&amp;quot;)
    return types.ActionContinue
}

func (ctx *httpAuthContext) denyRequest(reason string) types.Action {
    body := CreateErrorResponse(reason)

    err := proxywasm.SendHttpResponse(401, [][2]string{
        {&amp;quot;content-type&amp;quot;, &amp;quot;application/json&amp;quot;},
        {&amp;quot;x-wasm-filter&amp;quot;, &amp;quot;go-auth&amp;quot;},
    }, []byte(body), -1)

    if err != nil {
        proxywasm.LogErrorf(&amp;quot;failed to send response: %v&amp;quot;, err)
    }

    return types.ActionPause
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ローカル環境でのデモ&lt;/h3&gt;
&lt;p&gt;Docker Composeを使用してローカル環境で動作確認を行えるようにしました：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client → Envoy Proxy (Wasmフィルタ) → Backend Service&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下の3つのシナリオで動作を確認できます：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;認証成功&lt;/strong&gt;: 正しいBearerトークンでのリクエスト&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;認証失敗&lt;/strong&gt;: 無効なトークンでのリクエスト&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;認証スキップ&lt;/strong&gt;: ヘルスチェックエンドポイントへのアクセス&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;学びと今後の可能性&lt;/h3&gt;
&lt;p&gt;このデモ実装を通じて、Envoy Wasmフィルタの実用性と多くのメリットを実感できました：&lt;/p&gt;
&lt;h4&gt;Wasmフィルタの主要メリット&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;言語の自由度&lt;/strong&gt; &amp;#8211; Go、Rust、C++など慣れ親しんだ言語で開発でき、既存のツールチェーンを活用可能&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;安全性&lt;/strong&gt; &amp;#8211; Wasmのサンドボックス環境で実行されるため、Envoyプロセスをクラッシュさせるリスクが低く、メモリ安全性も保証&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;パフォーマンス&lt;/strong&gt; &amp;#8211; ネイティブコードに近い実行速度を実現&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;配布とバージョニング&lt;/strong&gt; &amp;#8211; 単一の.wasmファイルとして配布でき、バージョン管理やデプロイメントパイプラインへの組み込みが簡単&lt;/p&gt;
&lt;p&gt;特に実際のプロジェクトでは、既存のGo/Rustコードベースがある場合、同じ言語でプロキシレイヤーのロジックを実装できることが大きな価値となります。&lt;/p&gt;
&lt;p&gt;認証、ログ処理、メトリクス収集などのビジネスロジックを統一言語で管理でき、JWT検証やレート制限、OpenTelemetry連携など、より高度な機能への拡張も現実的です。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;初参加したKubeCon + CloudNativeCon 2025 Japanは、日本のCloud Nativeコミュニティの盛り上がりを実感できる貴重な体験となりました。&lt;/p&gt;
&lt;p&gt;技術面では、EnvoyとWebAssemblyを活用したAPI管理手法が特に印象深く、実際にWasmフィルタを実装してローカル環境でデモを動かすことで、セッションで学んだ概念をより深く理解できました。また、OpenTelemetryとeBPFを組み合わせたオブザーバビリティ技術や、WebAssemblyのサーバーサイド活用、AI統合の進展など、Cloud Native技術の進化を直接体感することができました。&lt;/p&gt;
&lt;p&gt;また、参加前は技術的なハードルが高いイメージがありましたが、実際は職種やエンジニアレベルを問わず楽しめるイベントであることも発見できました。多様な参加者との交流や企業ブースでの最新技術の体験も、魅力だと感じました。&lt;/p&gt;
&lt;p&gt;来年2026年の日本開催も決定しており、Cloud Native技術に興味がある方にはぜひ参加をお勧めします。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本記事で紹介したセッションの動画は、&lt;a href=&quot;https://www.youtube.com/@cncf&quot;&gt;CNCFの公式YouTubeチャンネル&lt;/a&gt;で公開されています。興味のある方はぜひご覧ください。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>PJ-Aurora：メルカリにおけるUI生成・評価の取り組み</title><link>https://engineering.mercari.com/blog/entry/20250630-94c2e6b283/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250630-94c2e6b283/</guid><description>&lt;p&gt;こんにちは。メルペイで機械学習とAIのチームのEMをしている@hiroです。 この記事は、Merpay &amp;amp; Mercoin Tech Openness Month 2025 の21日目の記事です。 メルカリ、メル [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Mon, 30 Jun 2025 10:00:10 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイで機械学習とAIのチームのEMをしている@hiroです。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250528-merpay-mercoin-tech-openness-month-2025/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2025&lt;/a&gt; の21日目の記事です。&lt;/p&gt;
&lt;p&gt;メルカリ、メルペイでは生成AIの活用を非常に積極的に推進しています。今回の「Merpay＆Mercoin Tech Openness Month 2025」においても、自然発生的に多くのメンバーが生成AIをテーマに選択しており、これは会社全体でのAI活用の機運の高まりを示していると感じています。従前からAIの取り組みはありましたが、会社としてのコミットメントの深まりとエンジニアを含むメンバーたちの熱量が高くなっており、数々のプロジェクトが生まれています。 この記事ではその中から、CXOの@naricoと共に推進している「PJ-Aurora」（プロジェクトオーロラ）について共有します。&lt;/p&gt;
&lt;h2&gt;Design/Creative x AI&lt;/h2&gt;
&lt;p&gt;2024年のメルカンの記事『&lt;a href=&quot;https://careers.mercari.com/mercan/articles/47129/&quot; title=&quot;「Must」を「Fun」に！メルカリCPOとCXOが語る、“AI-Led Growth Company”としてのAI活用の未来&quot;&gt;「Must」を「Fun」に！メルカリCPOとCXOが語る、“AI-Led Growth Company”としてのAI活用の未来&lt;/a&gt;』にも書かれていますが、以前より「pj-ai-creator」を立ち上げ、デザイン領域における生成AIの活用について実験的な取り組みを行ってきました。PJ-AuroraはAI Creatorを経て正式に立ち上がったプロジェクトです。&lt;br /&gt;
PJ-Auroraは生成AIの活用を通じて、メルカリ、メルペイにおけるものづくりのアプローチを変革し、よりMove Fastにアイデアを実現してお客さまに届けることができるようになることを目指しています。あしもとでは特に、アイデアからのUI生成や評価、UXシミュレーション工程のプロセスイノベーションに挑戦しています。&lt;br /&gt;
とはいえ、基盤モデルの性能が日毎月毎に改善されていく中、生成AIを使って達成できることは何か、という問いの答えも変わり続けています。私たちも常に学習し続け、期待値とスコープを更新し続ける必要があると認識しています。&lt;/p&gt;
&lt;h2&gt;アイデアからUIを生成する&lt;/h2&gt;
&lt;p&gt;プロンプトからUIを生成するタスクは生成AIの発展とともに流行しているタスクの一つで、Figma社のFigma Makeをはじめさまざまなサービスが生まれています。メルカリでは各サービスの試験的な利用もしつつ、Agentic Workflow（複数のAIエージェントが連携して作業を進める仕組み）を構築し、独自のUI生成プロセスを構築する営みも並行して進めています。&lt;br /&gt;
このタスクは昨今、精度向上が目覚ましく、正直なところ、最終的には内製のシステムではなく外部のサービスを使う形になるかもしれません。しかし、この取り組みを通じて得られる知見と資産は、AI時代における競争優位性の源泉になると考えています。&lt;/p&gt;
&lt;h2&gt;AI時代のデザイン資産&lt;/h2&gt;
&lt;p&gt;たとえば、デザインシステムは人間のデザイナー/エンジニアが理解し活用するために作られていることが多いですが、AI時代においては「AIが理解し活用できる形」での資産化が重要になってくると思います。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;構造化されたデザイン言語&lt;/strong&gt;: ブランドアイデンティティやデザイン原則をAIが解釈可能な形で体系化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI-Friendlyなデザインシステム&lt;/strong&gt;: コンポーネントやパターンをMCP（Model Context Protocol）等を通じてAIが参照・活用できる状態に整備&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ドメイン特化の知識ベース&lt;/strong&gt;: 特有のUXパターンやユーザー行動をAIが理解できる形で蓄積&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの資産は、外部サービスだけでは実現困難な「メルカリらしさ」を保ちながらAI活用を進めるための基盤となります。仮に将来的に外部サービスを利用することになったとしても、これらの構造化された資産は他のAIツールとの連携や、独自のワークフロー構築において価値を持ち続けると想定しています。&lt;br /&gt;
一方、基盤モデルを利用するだけで高精度のUI生成が可能になった場合、外部サービスを利用するのではなく、メルカリ内部のシステムやワークフローに接続しやすいようにあえて「基盤モデルのAPIを使った内製のUI生成システム」に着地する可能性もありえるかもしれません。&lt;/p&gt;
&lt;h2&gt;UI生成の実装の概要&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/06/d613b138-aurora-demo.png&quot; alt=&quot;Aurora Agent Demo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;いくつかのコンセプトから構成されていますが、ここでは2つのAgentを紹介します。Agentic Workflowの実装は現時点では、GoogleのADK（Agent Development Kit）を使っています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;コンセプトリファインAgent&lt;/li&gt;
&lt;li&gt;UI生成Agent&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;コンセプトリファイン&lt;/h3&gt;
&lt;p&gt;UI生成ツールのユーザーが記述した機能やサービスのアイデアを、UI生成Agentに入力するためのデータ（プロンプト）に変換するための機能です。基盤モデルの性能向上によって簡単な指示でもある程度の品質のUIを生成してくれるようになりつつあります。一方で、実現したいことや作りたいもの、制約等をよく言語化し、構造化することの価値は変わらず高く、生成されるものの品質を大きく左右すると感じています。&lt;/p&gt;
&lt;p&gt;以下は実装のイメージです。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def create_concept_refinement_agent() -&amp;gt; LlmAgent:
    &amp;quot;&amp;quot;&amp;quot;
    ユーザーのコンセプト案をブラッシュアップするためのシンプルなLlmAgentを作成します。
    &amp;quot;&amp;quot;&amp;quot;
    return LlmAgent(
        name=&amp;quot;ConceptRefinementAgent&amp;quot;,
        model=&amp;quot;gemini-2.5-pro-preview-05-06&amp;quot;,
        description=&amp;quot;ユーザーのデザインコンセプト案を受け取り、より詳細で具体的なアイデアにブラッシュアップするエージェント。&amp;quot;,
        instruction=(
            &amp;quot;あなたはデザインコンセプトを具体化・洗練させるAIアシスタントです。\n&amp;quot;
            &amp;quot;ユーザーから与えられたコンセプト案を読み、**それを元に最大限具体的で魅力的なコンセプト案を生成してください。**\n&amp;quot;
            &amp;quot;生成する際は、以下の点を考慮・推測し、具体的に記述してください:\n&amp;quot;
            &amp;quot;- **ターゲットユーザー:** (例: 20代後半のテクノロジーに関心のある都市部在住者)\n&amp;quot;
            &amp;quot;- **解決する課題/提供価値:** (例: 煩雑なスケジュール調整をAIで自動化し、自由な時間を創出する)\n&amp;quot;
            &amp;quot;- **コア機能/体験:** (例: 自然言語でのイベント登録、参加者の都合の良い時間を自動提案、ビデオ会議連携)\n&amp;quot;
            &amp;quot;- **差別化要因:** (例: 業界特化のテンプレート、独自のレコメンデーションエンジン)\n&amp;quot;
            &amp;quot;- **雰囲気・トンマナ:** (例: ミニマルで洗練されたデザイン、直感的でスムーズな操作感)\n&amp;quot;
            &amp;quot;- **具体的なUI構造:** (例: イベント登録するためのフォーム、参加者の都合の良い時間を提案するカレンダー)\n&amp;quot;
            &amp;quot;**ユーザーに質問を返さないでください。\n&amp;quot;
            &amp;quot;**与えられた情報からコンセプト案を構築し、完成したコンセプト案のテキストのみを返してください。\n&amp;quot;
            &amp;quot;**前置きや挨拶、説明は不要です。&amp;quot;
        ),
        tools=[],
        sub_agents=[],
    )&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;UI生成&lt;/h3&gt;
&lt;p&gt;以下がプロンプトをもとにHTMLでUIを生成するAgentのサンプルコードです。 既出のセクションにも書きましたがUI生成はさまざまなサービスが生まれており、群雄割拠です。前述の通り最終的に内製のAI Agentをさらに作り込んでいくかどうかはわかりません。一方、生成ロジック以外の要素、例えば自社のデザインシステムをAIからReadableな状態にすること、デザインのアイデンティティやコンセプトを言語化・構造化すること、AIを前提としたアプリ制作のワークフローを発明すること等は、組織の資産であり、独自性であり、差別化要因になりうる点だと想像しています。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def create_page_generate_agent_v0_3(model_name: str) -&amp;gt; LlmAgent:
    &amp;quot;&amp;quot;&amp;quot;HTML生成エージェントを構築します。&amp;quot;&amp;quot;&amp;quot;

    mcp_toolset = get_ds_mcp_tools()
    html_generation_agent = LlmAgent(
        model=model_name,
        instruction=&amp;quot;&amp;quot;&amp;quot;
ユーザーのアイデアを基にデザインを新規生成してください。
特に指示されない限りはスマホアプリのデザインを生成してください。
メルカリのデザインシステムに厳密に従った高品質なHTMLを生成するため、以下のステップを順守してください。

**ステップ1: デザインシステム参照画像の確認**
メルカリのデザインシステムに準拠した表現を行うため、関連するコンポーネントやパターンの参照画像を必ず確認してください。
- `mcp_get_ds_master_image_map` ツールを呼び出し、利用可能なDS Master Imageのカテゴリと画像のリストを取得します。
- ユーザーの要求や生成する画面のコンテキストに合致する画像があれば、その画像の `category` と `filename` を引数として `mcp_get_ds_master_image_details` ツールを呼び出し、詳細な説明と画像URLを取得します。
- 取得した画像URLは、必ず `download_and_save_image_as_artifact` ツールを使用してその内容をシステムに読み込ませ、実際に画像を確認してください。
- これらの参照画像を十分に確認し、スタイル、レイアウト、インタラクションなどがメルカリのデザインシステムに沿っているかを確認した上で、HTML生成に活かしてください。

**ステップ2: アイコン・ロゴ素材の利用**
アプリケーションで使用する汎用的なアイコンやロゴは、必ず `mcp_get_image_asset_map` ツールを使用して画像アセットのURLリストを取得し、そこから適切なものを選択してHTMLに埋め込んでください。

**ステップ3: HTML生成と出力**
上記のステップで得られた情報と、以下の「基本デザイン情報」を総合的に判断し、HTMLを生成します。
- **出力形式:** 純粋なHTMLコードのみを出力し、他の説明やコードブロックは含めない。完全な HTML コードのみを出力してください。説明や ```html ... ``` は不要です。
- **単一ファイル:** CSS/JavaScriptは外部参照せず、`&amp;lt;style&amp;gt;`タグや`&amp;lt;script&amp;gt;`タグで内部に埋め込む。

プレースホルダー画像
ユーザーアイコンや商品画像のプレースホルダーが必要な場合は、`get_random_sample_image_url` ツールを使ってください。

**画像のURLをmcpやツールで取得した場合：**
   1. mcpやツールから取得したURLの画像を処理する必要がある場合 (例: 画像の内容を説明する、画像から情報を抽出する、画像に基づいて何かを生成する)、まず `download_and_save_image_as_artifact` ツールを使用して画像を取得し、システムに保存してください。
   2. このツールを実行すると、システムが自動的に画像を読み込み、あなたがその内容を理解できるようになります。
   3. もし画像URLへのアクセスにMCPの認証が必要だと判断される場合は、ツールの `use_mcp_auth` 引数を `True` に設定してください。それ以外の場合は `False` に設定してください。
&amp;quot;&amp;quot;&amp;quot;,
        name=&amp;quot;HtmlGeneratorAgent&amp;quot;,
        description=&amp;quot;Generates a single HTML page based on user idea.&amp;quot;,
        output_key=&amp;quot;generated_html&amp;quot;,
        tools=[
            get_random_sample_image_url,
            check_link_status,
            download_and_save_image_as_artifact,
            mcp_toolset,
        ],
        before_model_callback=before_model_load_artifact,
    )
    return html_generation_agent&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;UI/UX評価の取り組み&lt;/h2&gt;
&lt;p&gt;以上はUIを生成する仕組みの一端ですが、並行して、生成されたものをUI/UXの観点で評価する仕組みの構築にも取り組みはじめています。品質評価観点を定め、仮想的なペルソナを生成し、自動生成されたUIにフィードバックをします。&lt;br /&gt;
静的な画面の評価に加えて、Browser Use等を用いて画面操作をさせつつ、フィードバックを獲得していく仕組みです。全体のイメージとしては、Amazon社の「&lt;a href=&quot;https://arxiv.org/abs/2502.12561&quot; title=&quot;UXAgent: An LLM Agent-Based Usability Testing Framework for Web Design&quot;&gt;UXAgent: An LLM Agent-Based Usability Testing Framework for Web Design&lt;/a&gt;」のような構成で、AIによるUX評価の概念実証に着手しており、納得性の高いフィードバックを得られるケースも確認できています。&lt;br /&gt;
UI生成については内製のフロントエンドがあるのですが、UI/UX評価に関してはCursor等のエージェントを使いながら小さく開始しています。有用性が確認できたら、どういう実装であるべきか、アプリ開発のワークフローの中でどう使っていくのが有効かを検討していきたいと考えています。&lt;/p&gt;
&lt;h2&gt;おわりに&lt;/h2&gt;
&lt;p&gt;PJ-Auroraの取り組みを通じて、生成AIの可能性と課題の両面を実感しています。技術の進歩は目覚ましく、UI生成の精度は日々向上していますが、同時に「何を作るか」「どう作るか」「私たちの仕事の仕方はどう変わるか」という本質的な問いに向き合うことがより重要になってきていると感じます。&lt;br /&gt;
メルカリでは引き続き、お客さまにより良いサービスと体験を提供するために、新しい技術と向き合いながら開発をしています。生成AIという新しい道具を使いこなすために、私たち自身も学び続けていきます。&lt;/p&gt;
</content:encoded></item><item><title>AI時代の組織変革：エンジニアリングマネージャーが見たメルカリグループの半年間の軌跡</title><link>https://engineering.mercari.com/blog/entry/20250625-acfc60bbea/</link><guid isPermaLink="true">https://engineering.mercari.com/blog/entry/20250625-acfc60bbea/</guid><description>&lt;p&gt;こんにちは。メルペイ Payment &amp;amp; Customer Platform Manager of Managers の@abcdefujiです。 この記事は、Merpay &amp;amp; Mercoin Tech  [&amp;hellip;]&lt;/p&gt;
</description><pubDate>Thu, 26 Jun 2025 10:00:31 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。メルペイ Payment &amp;amp; Customer Platform Manager of Managers の&lt;a href=&quot;https://twitter.com/_abcdefuji&quot;&gt;@abcdefuji&lt;/a&gt;です。&lt;br /&gt;
この記事は、&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250528-merpay-mercoin-tech-openness-month-2025/&quot;&gt;Merpay &amp;amp; Mercoin Tech Openness Month 2025&lt;/a&gt; の19日目の記事です。&lt;/p&gt;
&lt;h2&gt;要旨&lt;/h2&gt;
&lt;p&gt;2024年末から2025年6月の半年間で、メルカリではAIツールの導入において劇的な変化を遂げました。数十名から始まったパイロットプロジェクトが、わずか4ヶ月で1,100アカウントを超える全社規模の導入に成功しました。エンジニアの9割以上がAIコーディングアシストを活用する組織へと変貌しています。&lt;/p&gt;
&lt;p&gt;この記事では、エンジニアリングマネージャーの視点から、組織変革の成功要因を分析し、技術負債解消への新しいアプローチや個人の開発体験の変化について紹介します。特に、トップダウンのビジョン、ボトムアップの自発性、環境整備、そして可視化による推進力の4つの要素がどのように組み合わさって変革を実現したかを詳しく解説します。&lt;/p&gt;
&lt;h2&gt;始まりは一つのリクエストから&lt;/h2&gt;
&lt;h3&gt;2024年末の状況&lt;/h3&gt;
&lt;p&gt;全ての始まりは、同僚からの「Cursorを使ってみたいのですが、導入可能でしょうか？」というシンプルなリクエストでした。&lt;/p&gt;
&lt;p&gt;当時のメルカリには、既にAI Code AssistsツールとしてGitHub Copilotが導入されていましたが、実際の利用状況は以下の通りでした：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一部のアーリーアダプターである数十名〜100名（weekly active user）規模で利用されている&lt;/li&gt;
&lt;li&gt;しかし、多くのエンジニアが「まだ使うには早い」と感じている状況&lt;/li&gt;
&lt;li&gt;高度なコンテキスト理解が不十分で、単一ファイルや行単位の修正提案が主流&lt;/li&gt;
&lt;li&gt;プロジェクト全体を理解した提案には至らず、AIアウトプットの質がプロダクション開発に適応できていない状況&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正直、私自身もGithub Copilotを使っていましたが、開発の現場で本格的にAIを活用するレベルには程遠いと感じていました。&lt;/p&gt;
&lt;h3&gt;転換点：2025年2月の承認&lt;/h3&gt;
&lt;p&gt;Cursorは2024/04〜07頃に一度検討されましたが、当時は導入の判断には至りませんでした。&lt;br /&gt;
しかし、2025年2月、会社として本格的なAI導入の承認が下りました。数十名のパイロットプロジェクトとしてCursor導入がスタートしました。&lt;/p&gt;
&lt;p&gt;初めてCursorを触った時の感動は今でも鮮明に覚えています。特に印象的だったのは、Cursorの「コードのインデックス化」機能でした：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;プロジェクト全体のコンテキスト理解&lt;/li&gt;
&lt;li&gt;既存のパターンを学習した文脈に沿った提案&lt;/li&gt;
&lt;li&gt;大規模リポジトリでのコード理解スピードの向上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;まだ数万行を超える巨大なリポジトリでは不安定さや上手く機能しない場面もありましたが、主にオンボーディングの側面でスピードが爆速になりました。マネージャーである自分はコードへのコミット機会が減りつつありましたが、Cursorを使ってどの機能がどのように実装されているか非常に容易に特定できるようになりました。&lt;/p&gt;
&lt;h2&gt;爆発的普及の4ヶ月間&lt;/h2&gt;
&lt;h3&gt;驚異的な成長&lt;/h3&gt;
&lt;p&gt;2月から6月現在までの数字を見ると、その変化の大きさが分かります：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/06/11856ff3--2025-06-25-15.30.48-1024x121.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
(図: Cursor Usage Summary)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;アカウント数&lt;/strong&gt;：数十名 → 1,100アカウント超&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;エンジニア利用率&lt;/strong&gt;：9割以上が何らかのAIコーディングアシストを活用（Cursor以外にもJetBrains AI、Google Code Assist、Claude Codeなど）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;普及範囲&lt;/strong&gt;：エンジニアからPdM、デザイナーまで拡大&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;社内の熱量の変化&lt;/h3&gt;
&lt;p&gt;数字以上に驚いたのは「熱量」でした。3月から5月の間、社内で起こっていたことは以下の通りです：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;毎週複数のAI関連イベントが社内で開催&lt;/li&gt;
&lt;li&gt;一つのイベントに100名以上の参加者がいるケースも&lt;/li&gt;
&lt;li&gt;エンジニアだけではなく、PdMなどの別職種への広がり&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;社内のオフライン、Slack上で聞こえる会話がAI一色に染まっていきました。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/06/53cf1200--2025-06-23-16.30.52-1024x804.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;(図: 社内AI開発支援ツールについて語ろう企画)&lt;/p&gt;
&lt;p&gt;「隣のチームでCursorの勉強会やるらしい」&lt;br /&gt;
「今度MCPについて話すイベントやります」&lt;br /&gt;
「AIで○○を楽にしました」&lt;/p&gt;
&lt;p&gt;エンジニアから始まった波は、さまざまな職種の人たちにも広がっていきました。マネージャーとして、この自発的な学習意欲の高まりを目の当たりにできたのは本当に感動的でした。&lt;/p&gt;
&lt;h2&gt;成功の4つの要因&lt;/h2&gt;
&lt;p&gt;この爆発的な普及を振り返ると、4つの要因が絶妙に組み合わさっていたことが分かります。&lt;/p&gt;
&lt;h3&gt;1. トップダウンの明確な意志&lt;/h3&gt;
&lt;p&gt;経営層からの「AIを活用していく」というメッセージは、単なる推奨ではありませんでした。会社の未来への投資であり、明確なビジョンの表明でした。トップが本気だからこそ、現場も本気になれる。その土台があったからこそ、後に続く変化が可能になったのです。&lt;/p&gt;
&lt;p&gt;そしてそれを推進するリーダーがいました。私はCursor導入を担当しましたが、Cursorだけではなく、エンジニア職種を超えて生成AIを導入する非常に強いリーダーシップが社内にモメンタムを生み出しました。&lt;/p&gt;
&lt;h3&gt;2. ボトムアップの自発的な情熱&lt;/h3&gt;
&lt;p&gt;トップダウンが生み出したモメンタムをさらに加速・継続させたのは「Move Fastな人たち」の存在でした。通常、組織変革では推進役を各チームに配置し、計画を立て、ロードマップを作成しますが、今回のCursorに関しては以下のような声が、あちこちから自然発生的に生まれました：&lt;/p&gt;
&lt;p&gt;例として&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250613-64e628404a/&quot;&gt;メルカリモバイルのAI Hackathon&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250604-pcp-llm-week/&quot;&gt;PCP LLM Week&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;アーリーアダプターがいる。挑戦する文化がある。そして何より、学んだことを共有したがる人たちがいたことにより推進が大きく加速しました。&lt;/p&gt;
&lt;h3&gt;3. 環境の整備&lt;/h3&gt;
&lt;p&gt;このモメンタムの維持には環境整備を支えてくれたチームも大きな要因です。CursorだけではなくさまざまなAIツールが登場し、社内で利用したい声が多く上がりました。Cursorもここまでの人数規模が利用できる状態にするために社内のプロセス整備が行われました。ワンボタンでアカウント申請できる仕組みや、新規ツール導入時のセキュリティレビュー・予算レビューなどのさまざまなプロセスを、一体となって環境整備をしてくれました。&lt;/p&gt;
&lt;p&gt;Cursorにおいては、Slackから簡単にアカウント発行までできるように調整していただきました。&lt;br /&gt;
&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/06/435d6e92--2025-06-25-15.18.35-1024x235.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
(図: Cursorアカウント発行アナウンスメッセージ)&lt;/p&gt;
&lt;h3&gt;4. 可視化という触媒&lt;/h3&gt;
&lt;p&gt;最後の要素は、「ダッシュボード」による可視化です。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/06/9c56c906--2025-06-23-23.39.29-1024x583.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
(図: Cursor Dashboardn)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/prd-engineering-asset/2025/06/3b360113--2025-06-23-23.50.31-1024x212.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
(図: Devin Dashboard)&lt;/p&gt;
&lt;p&gt;CursorをはじめDevin、Claude Code with LiteLLM、GitHub Copilotのダッシュボードを用意し、チームがどのくらいAIを使っているかを可視化しました。これにより、使いこなしているチームとまだ利用頻度が高くないチームを把握し、それぞれの背景を深掘りしていくことで、さらなる浸透のためのアクション定義につなげました。&lt;/p&gt;
&lt;p&gt;可視化による競争ではなく、「触発」が生まれました。同僚の活用方法を見て学び、自分なりの使い方を発見する。そんなポジティブなサイクルが会社全体に広がりました。&lt;/p&gt;
&lt;h2&gt;チームレベルの開発における変革&lt;/h2&gt;
&lt;p&gt;ここからは組織ではなく1チームの開発状況の変化に関して話します。&lt;/p&gt;
&lt;h3&gt;技術負債への新しいアプローチ&lt;/h3&gt;
&lt;p&gt;AIの導入が進む中で、私たちのチームに予想外の変化が起こりました。&lt;/p&gt;
&lt;p&gt;私たちPayment &amp;amp; Customer Platformチームは、リリースから6年以上が経つシステムです。6年間、さまざまなプロダクトチームからの要求に応え、機能を追加し、時には妥協しながら成長を続けてきました。その結果、技術負債が蓄積されていました：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;従来の課題：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;yak shaving状態：積み重なった実装・複雑な仕様により、一つの小さな改善に大きなコストが伴う課題&lt;/li&gt;
&lt;li&gt;改善系のタスクの優先度が低く、時間が確保できず放置された課題&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;AI（Cursor / Claude Code）による変革：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大規模な内部リファクタリング・リアーキテクチャのような優先度が低く設定されやすいタスクの解消スピードが向上&lt;/li&gt;
&lt;li&gt;ルール・コンテキストの共有による一貫性のある修正が可能&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これまで「いつかやろう」で終わってしまいがちだった大規模な改修プロジェクトに、以前よりも継続的かつ素早く技術負債解消できる可能性が出てきたと感じています。&lt;/p&gt;
&lt;h2&gt;今後への展望&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;さらなる活用領域の拡大&lt;/strong&gt; &amp;#8211; 開発だけではなく、開発プロセス全体でAIを活用していく仕組み作り（&lt;a href=&quot;https://engineering.mercari.com/blog/entry/20250611-one-person-one-release/&quot;&gt;One Person, One Release&lt;/a&gt;）&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;PMもEngineerも壁を越えていきます。一人の人が企画から開発、QA、リリースまで一気通貫で出来ることを目指します。&lt;br /&gt;
技術の壁を越え、ドメイン知識を越え、役割を越えて行くためのAIの活用とし、それらを使い熟すのです。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;標準化&lt;/strong&gt; &amp;#8211; 個人やチームの知見や開発手法の横展開していく仕組みづくり&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例えば、CLAUDE.mdをどのように作成し、どのようなルールを記載しているか、どのようにドメインを表現しているか、チームによって独自に進化が進んでいます。それぞれのチームがAIによってさらなる生産性を得るために、導入や利用のプロセス自体の標準化を行いたいと考えています。&lt;/p&gt;
&lt;p&gt;このAIトレンドのスピードの中で、さまざまな手法が即座に古くなっていくと思います。古くなったものを即座に捨て去る覚悟を持ちつつ、AIを活用し生産性を向上させていく未来への道のりを作り始めています。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;この半年間で、メルカリはAIツールの導入において以下を実現しました：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;組織的な成功&lt;/strong&gt;：1,100アカウントを超えるユーザーへのCursor導入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文化的な成功&lt;/strong&gt;：自発的な学習・普及文化の醸成&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;成功の鍵は、トップダウンのビジョン、ボトムアップの自発性、環境整備、そして可視化による推進力の組み合わせにありました。AI時代の組織変革において、技術導入だけでなく、文化と人の変革が重要であることを改めて実感しています。&lt;/p&gt;
&lt;h2&gt;最後に&lt;/h2&gt;
&lt;p&gt;この目まぐるしい変化に楽しく向き合えているのは、周りの同僚たちの存在です。&lt;/p&gt;
&lt;p&gt;毎日のように新しいツール、開発体制、ユースケースなどのインプットとアウトプットする機会に溢れており、AI関連の情報交換は純粋に楽しく刺激的でした。この「集合知」でAIに対して前向きに挑めています。&lt;/p&gt;
&lt;p&gt;以上、ありがとうございました。&lt;/p&gt;
&lt;p&gt;明日の記事は cyanさんです。引き続きお楽しみください。&lt;/p&gt;
</content:encoded></item></channel></rss>