PaPoo
cover

ロードバランサーの裏でMCPセッションが消える理由と、その直し方

MCP server を load balancer の後ろに置いた瞬間、なぜか「さっきの続き」が消える。この記事は、その地味だけど厄介な事故をかなり実践的にほどいています。しかも面白いのは、壊れ方が派手なエラーではなく、​静かに間違うところです。これ、運用している側からするとかなり嫌なタイプの不具合だと思います。

この記事の要点

MCP は、AI agent や tool 実行の文脈で出てくることが多い仕組みです。ざっくり言うと、外部ツールとやり取りするための共通の会話ルールみたいなものです。ローカル開発では、サーバーを同じマシンで起動して、そのまま session を持ち続けられるので問題が出にくい。ところがこれを本番で Streamable HTTP にして、複数の backend instance の前に load balancer を置くと話が変わります。

load balancer は「今どの server が空いてるか」で request を振り分けます。ここに「この session は前回の続きです」という期待を持ち込むと、すれ違いが起きる。前回の request を見ていた server とは別の instance に飛んでしまい、その instance には session の記憶がない。結果として、server は新しい session だと思って始め直してしまうわけです。

これが厄介なのは、エラーで派手に止まるのではなく、​何事もなかったように進んでしまうことです。たとえば長い database migration の tool call を agent が投げて、その後に進捗確認をしたのに、server 側には「そんな session は知らない」となる。ログを見ても一見平和で、でも裏では仕事が進んでいない。こういうのは本当に気づきにくいです。

記事では、まず「ありがちなダメな実装」を出しています。in-memory の Map に session を入れるやり方です。デモなら十分ですが、instance が 2 台になった瞬間に崩れます。片方の server は session を持っていても、もう片方は知らない。round-robin で交互に振られたら、会話の半分くらいが消えるイメージです。

最初の対策は sticky sessions、つまり session affinity です。要するに「この session はこの server に固定する」方式ですね。nginx の例では、mcp-session-id を hash して、同じ session ID が同じ upstream に届くようにしています。これがわりと現実的で、私はかなり好きです。シンプルで、理解しやすくて、効果もわかりやすい。

ただし sticky sessions は万能ではありません。instance が落ちたら、その instance にぶら下がっていた session も一緒に死にます。failover が弱い。ここはきれいごと抜きで、トレードオフです。セッションが短命で、多少の再試行で許容できるなら十分。でも「絶対に途中で失いたくない」なら、これだけでは足りません。

image_0003.svg

そこで出てくるのが Redis などへの externalized state です。session を process memory に閉じ込めず、共有ストアに置く。こうすると、どの instance に request が飛んでも同じ session を引けます。コードの見た目もわかりやすくて、redis.get() して、なければ作って、処理したあと redis.set() で保存する。TTL を付けて寿命を管理するのも自然です。

これは実運用ではかなり筋がいいと思います。ただし、当然ながら Redis に依存するぶん、別の意味での運用責任は増えます。Redis が詰まれば session も詰まる。とはいえ、少なくとも「どの instance に行ったかで記憶が消える」よりはずっとマシです。状態を外に出す、というのはクラウド時代のかなり基本的な発想ですが、こういう session 問題ではやはり強いです。

そして 3 つ目が、state を持たない設計に寄せる考え方です。これはちょっと発想がきれいです。session を server の記憶に頼らず、resumable streams のような仕組みで「途中から再開できる」前提にする。つまり、server が覚えていなくても、クライアント側やストリームの仕組みで継続性を担保する方向です。

個人的には、これは一番思想が良いと思います。ただし、実装とプロトコル設計の難易度は上がります。何でも stateful にしておけば楽、という誘惑に勝たないといけないので、現場ではすぐには採用できないかもしれません。でも長期的には、こういう方向のほうが scale しやすいのではないかとも感じます。

この記事で特に良いのは、「壊れ方」をちゃんと問題にしている点です。単に“session を共有しましょう”ではなく、​silent state loss は本当に危ないと指摘している。エラーなら直しやすい。でも、成功したように見えて失敗しているのは、後工程まで被害が出る。AI agent が「終わりました」と自信満々に返してくるタイプの事故は、笑えません。

MCP を本番で動かすとき、つい「HTTP で公開できるなら簡単そう」と思ってしまいますが、実際には session の扱いが一気に面倒になります。ローカル開発の感覚のまま進めると、load balancer が入った瞬間に世界が変わる。この記事は、その境目をかなり具体的に教えてくれます。地味だけど、知っているかどうかで事故率がかなり変わる話です。


参考: Why MCP Servers Lose Session State Behind Load Balancers

同じ著者の記事