技術 約11分で読めます

HLS動画抽出の非同期パイプラインとPinterest社内MCP基盤

いけさん目次

DEV Communityに、Pinterestの動画をHLSストリームから取り出してMP4に変換し、非同期に配信するダウンローダーの設計記事が出ていた。
Pinterest公式のメディア配信基盤の話ではなく、外側からHLSとReactの状態ツリーをほどいて動画を取り出すサービスの設計メモだ。

別途、Pinterest Engineering Blogには社内MCPエコシステムの設計記事も公開されている。

PinterestのHTMLから動画URLを取り出す

Pinterestの動画は単純なMP4リンクとしてHTMLに置かれていない。
ページのHTMLにはJSON-LDブロックとPWS_DATAと呼ばれるスクリプトブロックが埋め込まれている。

JSON-LDはSEO向けの構造化メタデータで、動画のサムネイルや基本情報は入っているが高解像度のソースURLは含まない。
抽出に使うのはPWS_DATAのほうで、ReactのRedux状態ツリーそのものが格納されている。
高ビットレートのメディアソースは深いネスト階層にあり、Schema Parserが状態ツリーを動的にマッピングして最高ビットレートのリソースを特定する。

ブラウザ自動化ツールがDOMを操作するのに対して、この種の抽出器はDOMよりもページ内の埋め込み状態とネットワーク応答の解析が中心になる。
Chrome DevTools MCPのようなMCPサーバーがDevToolsプロトコル経由でDOMやネットワークを操作するのとはアプローチが異なり、抽出器はHTMLパース時点で必要なデータを静的に取り出す。

ただし、PWS_DATAはPinterestの内部実装であり公開APIではない。
フロントエンドのデプロイで構造が変わると即座に壊れる。
安定した連携が必要なら公式APIか埋め込みメタデータの範囲で組むべきで、内部状態への依存は保守コストを丸ごと抱え込む。

HLSの構造とFFmpeg copy mux

Pinterestの動画はHLS(HTTP Live Streaming)で配信されている。
ユーザーに.m3u8をそのまま渡しても一般的なプレイヤーでは扱えないため、抽出器側でTSセグメントを集めてMP4にmuxして返す必要がある。

HLSの配信構造

HLSはAppleが策定したアダプティブビットレート配信プロトコルで、動画を小さなセグメントに分割してHTTPで配信する。
構造は3層になっている。

graph TD
    A["マスタープレイリスト<br/>.m3u8"] --> B["メディアプレイリスト 1080p<br/>.m3u8"]
    A --> C["メディアプレイリスト 720p<br/>.m3u8"]
    A --> D["メディアプレイリスト 480p<br/>.m3u8"]
    B --> E["segment0.ts"]
    B --> F["segment1.ts"]
    B --> G["segment2.ts"]
    C --> H["segment0.ts"]
    C --> I["segment1.ts"]
    C --> J["segment2.ts"]

マスタープレイリストが複数の解像度・ビットレート別のメディアプレイリストを参照し、各メディアプレイリストがTSセグメントの実体を指す。
プレイヤーはネットワーク帯域に応じてメディアプレイリストを切り替えるので、低速回線でも途切れにくい。

TSセグメントの中身はMPEG-2 Transport Streamコンテナに格納されたH.264またはHEVCの映像ストリームとAACの音声ストリームだ。
抽出器がやるのは、これらのセグメントを順に取得してひとつのMP4コンテナへまとめ直す処理になる。

copy muxと再エンコードの違い

FFmpegで-c copyを指定すると、映像・音声のコーデックストリームをデコード・再エンコードせずにコンテナだけを変換する。
ピクセルの再計算をしないので、処理は桁違いに速く画質劣化もない。

copy mux (-c copy)再エンコード
処理内容コンテナの入れ替えのみデコード→フィルタ→エンコード
CPU負荷低い(I/Oバウンド)高い(CPUバウンド)
処理速度5分動画が数秒5分動画に数十秒〜数分
画質劣化なし再エンコードで劣化あり
適用条件入力コーデックが出力コンテナで有効常に可能

TSセグメントに入っているのがH.264+AACなら、MP4コンテナはそのまま受け入れられるのでcopy muxが成立する。
HEVCの場合もMP4(ISO BMFF)はサポートしているが、HEVCのパラメータセット(VPS/SPS/PPS)の配置がTS→MP4変換時に問題を起こすケースがある。
そのときは-bsf:v hevc_mp4toannexbのようなビットストリームフィルタが必要になる。

設計記事では、in-memoryの循環バッファにTSセグメントを蓄えてコルーチンプールで並列取得し、FFmpegに流し込む構成を取っている。

非同期ストリーミングパイプライン

設計記事で最も踏み込んでいるのは、取得した動画をサーバーに保存せずにチャンク単位でユーザーへ流す「ゼロストレージパイプ」の構成だ。

パイプラインの流れ

graph LR
    A["クライアント"] --> B["FastAPI"]
    B --> C["Pinterest CDN<br/>TSセグメント取得"]
    C --> D["メモリバッファ"]
    D --> E["FFmpeg<br/>copy mux"]
    E --> F["StreamingResponse<br/>チャンク送信"]
    F --> A

FastAPIのasyncioイベントループでPinterest CDNからのTSセグメント取得を非同期に待ち、取得できたチャンクをメモリ上の循環バッファに書き込む。
FFmpegプロセスがバッファからTSを読んでMP4にmuxし、その出力をFastAPIのStreamingResponseでクライアントへ流す。

ディスクに書き込まないからストレージコストがゼロで、取得と配信が並行するからTTFBが短い。
記事では200ms未満のTTFBと、従来比85%のメモリ使用量削減を主張している。

セグメント取得失敗時の問題

ゼロストレージ配信の難所は成功時ではなく失敗時に出る。

TSセグメントの一部だけ取得に失敗すると、mux中のMP4ストリームは壊れる。
クライアントにはすでに先頭部分が送られているのでレスポンスを巻き戻せない。
クライアントが途中で切断した場合は上流へのTSセグメント取得を止める必要があるが、FFmpegプロセスにstdinをcloseするタイミングとasyncioタスクのキャンセル伝播を正しく連携させないとゾンビプロセスが残る。

Redis clusterは分散セッション管理に使われていて、短寿命のクレデンシャルを保持してPinterest APIへの認証リクエストの重複を減らす。
ジョブの状態管理やレート制御にも使えるが、パイプラインの最小構成としてはRedisなしでも動く。
FastAPI + asyncio + FFmpegのstdoutパイプだけでプロトタイプは作れて、セッション共有やレート制御が必要になった段階でRedisを入れるのが現実的だ。

Cloudflare Workersでリアルタイム分析基盤を作った話でも同じことを書いたが、低レイテンシのパイプラインは平均速度よりも「途中で壊れたストリームをどう閉じるか」のほうが運用品質に効く。

WAF回避設計の問題

設計記事で一番引っかかったのは、WAF回避、TLSフィンガープリントの模倣、HTTP/2フレーム特性の再現を「アーキテクチャ」の一部として堂々と書いている部分だ。

HLS取得、copy mux、非同期ストリーミングは自社コンテンツの配信処理にもそのまま使える汎用技術だ。
一方で、相手サービスのbot検知やレート制限を回避するためのフィンガープリント偽装は、利用規約やアクセス制御に正面から抵触する。

自社コンテンツや許諾済み素材なら、ここまでのパイプライン設計はそのまま活きる。
第三者サービスからの抽出なら、公式API、ユーザーのデータエクスポート権限、利用規約の確認が先で、WAF回避を中心に据えるとプロダクトとしての説明責任が重くなる一方だ。

Pinterest社内MCPエコシステム

Pinterest Engineering Blogが2026年3月に公開したMCP記事は、エンタープライズ規模でMCPを本番運用している具体例として読みごたえがあった。

MCP(Model Context Protocol)はLLMが外部ツールやデータソースにアクセスするための標準プロトコルで、Anthropicが2024年末に公開した。
個人がローカルでstdio経由のMCPサーバーを立ててCLIから使うのが初期の典型だったが、PinterestはこれをクラウドホストのHTTPベースで社内全体に展開している。
Cloudflare MeshとエンタープライズMCP参照アーキテクチャの方向性と一致するが、Pinterestは実際に本番運用した数字を出している点で一歩具体的だ。

MCPサーバーの構成

Pinterestは用途ごとにドメイン特化の小さなMCPサーバーを複数立てる設計を採っている。
巨大な万能MCPサーバーは作らない。

MCPサーバー用途特徴
Presto MCPデータ分析クエリの実行最もトラフィックが多い。分析ワークフローでオンデマンドにPrestoバックのデータを取得
Spark MCPSparkジョブの障害診断ジョブ失敗時のログ集約と構造化された根本原因分析を返す
Knowledge MCP社内ナレッジ検索社内ドキュメントや知識ベースへの汎用検索エンドポイント

各サーバーは統一デプロイパイプラインでインフラが提供され、サーバー固有のビジネスロジックだけを実装すればいい。
MCPサーバーのツール呼び出しはLLMから見ると他のツール呼び出しと同じインタフェースで、社内チャット、IDE、Webチャットなどサーフェスを問わず統一的にアクセスできる。
特定のツールを特定のコミュニケーションチャンネルに制限することも可能だ。

中央レジストリ

MCPサーバーの管理には中央レジストリを置いている。

レジストリは承認済みサーバーの一覧、接続情報、所有チーム、サポートチャンネル、ステータスを一元管理する。
Web UIで各サーバーの状態やツール一覧を確認でき、APIを通じてクライアント側のサーバー発見、バリデーション、アクセス制御のクエリを処理する。
レジストリに登録されていないサーバーは本番利用が認められない。

MCPサーバー50件スキャン記事で指摘したように、個人が公開するMCPサーバーは入力検証や権限境界が弱いものが多い。
Pinterestの中央レジストリはこの野良MCPの問題を組織レベルで封じている。

認可の二層構造

PinterestのMCPは認可を二層で処理しており、MCP仕様が想定するサーバーごとのOAuth同意画面は使っていない。
既存の社内認証スタックにMCPを載せる形だ。

graph TD
    A["ユーザー"] --> B["社内認証スタック"]
    B --> C["JWT発行"]
    C --> D["MCPレジストリ"]
    C --> E["Envoyプロキシ"]
    E -->|"JWT検証"| F["X-Forwarded-User<br/>X-Forwarded-Groups"]
    F --> G["MCPサーバー"]
    H["サービス間通信"] --> I["SPIFFEベース<br/>mesh identity"]
    I --> G

第1層 エンドユーザーJWT

ユーザーが社内のLLMチャットやIDE統合からMCPサーバーにアクセスすると、社内認証スタックがJWTを発行する。
JWTがレジストリと対象MCPサーバーに送られ、EnvoyプロキシがJWTを検証してX-Forwarded-UserX-Forwarded-Groupsヘッダに変換する。

MCPサーバー側ではツールごとに@authorize_tool(policy='...')デコレータで細粒度のアクセス制御を掛けられる。
Prestoのような機微なデータにアクセスするサーバーでは、特定のビジネスグループに所属するユーザーだけがセッションを確立できるようにゲーティングしている。

第2層 サービス間mesh identity

低リスクな読み取り専用のシナリオでは、エンドユーザーのJWTなしでSPIFFE(Secure Production Identity Framework for Everyone)ベースのmesh identityだけでサービス間認証を行う。
バッチ処理やシステム間連携で、人間が介在しないMCPツール呼び出しに使う。

サーバーごとにOAuth同意画面を実装するのではなく、既存のセッションに便乗する設計は運用面で合理的だ。
社内の認証基盤がすでにある環境なら同じアプローチが取れる。

セキュリティレビューと本番投入プロセス

MCPサーバーを本番環境に投入する前に、Security、Legal/Privacy、GenAIの3チームがレビューチケットを発行する。
承認されて初めてレジストリに本番登録され、レビュー結果に基づいてアクセスポリシーが設定される。
どのサーフェスがどのサーバーにアクセスできるかはこのポリシーで決まる。

危険な操作にはhuman-in-the-loopが必須で、エージェントがアクションを提案して人間が承認または拒否する。
データの上書きのような破壊的操作ではMCP仕様のelicitation(確認ダイアログ)を挟む。
バッチ承認にも対応しており、大量の定型操作を一括で処理できる。

月66,000回の呼び出しと7,000時間の削減

2025年1月時点の運用数字が公開されている。

指標
月間MCPツール呼び出し回数66,000回
月間アクティブユーザー844人
月間削減時間約7,000時間

削減時間はMCPサーバーのオーナーが「1回の呼び出しで削減される分数」を申告し、呼び出し回数と掛けて算出している。
概算ではあるが、MCPエコシステムの価値を「サーバー数」や「ツール数」ではなく「手作業をどれだけ置き換えたか」で測っている。

Prestoクエリを手動でコンソールに打つ代わりにチャットから実行する。
Sparkの障害ログを自分で辿る代わりにMCPサーバーが構造化して返す。
こういう具体的な時間削減が844人の月間ユーザーに効いている。

stdioベースのローカルMCPとの違い

個人がローカルで使うMCPサーバーとPinterestのエンタープライズMCPでは、設計の前提が根本的に違う。

ローカルMCPPinterest エンタープライズMCP
ホスティングローカルプロセス(stdio)クラウドホスト(HTTP)
サーバー発見mcp.json手書き中央レジストリAPI
認可なし or 環境変数トークンJWT + mesh identity二層
セキュリティレビューなしSecurity / Legal / GenAI三者レビュー
危険な操作ツール側の実装依存human-in-the-loop必須
監視なし呼び出し回数、MAU、削減時間を計測
サーバー所有権個人チーム単位で登録・管理

参考