同じAIなのにウチの方が遅い問題を本気で調べた話──Acompanyセキュアチャット速度改善の記録

公開日:2026.07.29

この記事は、自社サービスの「遅さ」と向き合い、ユーザーの体感速度を主要 LLM サービスと同じくらいまで近づけるために何をやったか の記録です。Web アプリのバックエンドを触ったことがある人なら追えるよう、固有の専門用語は初出でひとこと補足します。
本記事は二部構成で公開しており前半部分の記事です。

はじめまして、Acompany の Confidential AI Suite 事業で Acompany セキュアチャット(以下、セキュアチャット)開発をしているイナミです。
最近は SRE っぽいことがメインになるシーンが多いですが、イケてる機能開発しているエンジニアが眩しく指を咥えて見ております😶‍🌫️

日頃 セキュアチャットという、プライバシー保護に特化した LLM プロキシサービスを開発しています。

まず、このサービスが何をしているかをざっくり説明します。

  1. ユーザーが AI への入力(プロンプト)を送る
  2. TEE(Trusted Execution Environment:サーバー管理者ですらメモリの中身を覗けない、ハードウェアで隔離された実行環境)の中で、入力に含まれる個人情報(氏名・メールアドレスなど)を検出して マスキング(伏せ字化)する
  3. マスキング済みの入力を、外部の主要 LLM(ChatGPT や Gemini など)サービスへ送る
  4. 返ってきた応答を TEE 内で受け取り、伏せ字を元の個人情報に復元してユーザーへ返す

セキュリティ上のキモは、TEE の外からはサーバー管理者からも外部 LLM ベンダーからも個人情報そのものが一切見えないことです。

図1:Acompanyセキュアチャットは主要 LLM サービスの前段にプライバシー保護のプロキシ層を挟んで中継する。個人情報は TEE の中だけで扱われ、外部 LLM には伏せ字だけが渡る。

なお、セキュアチャットには、外部の主要 LLM サービスの代わりに TEE 内で自分たちがホスティングしている AI モデル(機密 LLM) へ問い合わせる「プライベートモード」も用意しています。このモードでは 2 のマスキングそのものを行わずに、入力をそのまま LLM へ渡します(伏せ字にしないので 4 の復元も要りません)。やり取りはすべて TEE の中だけで完結します。あとで出てくる負荷試験は、この自前の AI を対象にしています。

本記事のタイトルでもある「セキュアチャットを主要 LLM サービスの Web サイト水準に早く」するためにやったことは大きく 4 ステップに分かれます。

  1. そもそも「どこが遅いか」を計測できる状態をつくる(可観測性の確保)
  2. 自分たちのサーバーにわざと負荷をかけ、ボトルネックを観察する(負荷試験)
  3. 重さの原因を特定して潰す(改善)
  4. 改善が効いたかを負荷試験で検証する(効果測定)

背景:なぜ「速度」に取り組むのか

私たちプラットフォーム開発者は主に事業部で扱うインフラ、SRE 部分を多く担当しています。その中でセキュアチャットの速度改善に取り組むきっかけは 2 つあります。

1 つ目は、社内アンケート(満足度調査)の結果です。 集計すると「動作が遅い」「AI の返事が返ってくるまで遅い」という声が、想像以上にたくさん挙がってきました。AI の処理がある程度重いのは事実なのですが、それを差し引いても「遅い」と言われ続けるのは見過ごせません。

2 つ目は、開発者自身の体感です。セキュアチャットは主要 LLM サービスを呼び出して応答を作っています。にもかかわらず、セキュアチャットの画面から使うと、主要 LLM サービスを直接使うより明らかに遅い。これは開発チームの誰もが薄々気づいていました。

ここに、この記事で一番大事な視点があります。
セキュアチャットも主要 LLM サービスも、最終的には 同じ AI(モデル) に問い合わせている。
なのにセキュアチャットの方が遅いなら、その差は 主要 LLM サービスの前段に挟んでいるプロキシ層のオーバーヘッド のはずだ。

つまり、削れる遅延は必ずどこかにある。あとはそれを見つけて削るだけです。さらに、セキュアチャットはこれからユーザー数を大きく増やしていくことを見据えています。同時アクセスが増えたときに何が起きるかを早めに掴んでおくため、遅さの原因を素早く調べられる状態を、早いうちに作っておきたい という狙いもありました。


ステップ 1:正確に「計測できる」ようにする

遅延に取り組むとき、いちばんやってはいけないのが「たぶんここが遅いだろう」という 勘でコードをいじること です。直したつもりが、本当のボトルネックは別の場所だったというのはよくある話です。まずは事実を計測できるようにします。

ふつうの「ログ」では追いきれない

多くのシステムは、「処理を開始しました」のような行を 1 行ずつ吐き出す ログ で状況を追っています。少人数で使う間はこれで十分なのですが、たくさんのユーザーが同時アクセスする本番環境では弱点が出てきます。

  • 並行リクエストのログが時系列で混ざり、「今の 1 リクエスト分」だけを抜き出す のが難しい
  • ある処理が次にどの処理を呼んだのか、という 呼び出しの親子関係(つながり)が分からない
  • そのログを AI に渡して「なぜ遅い?」と分析させても、つながりが欠けているぶん ハルシネーション(見当違いの回答) を起こしやすい

1 リクエストを「1 本の線」で追えるようにする(分散トレーシング)

そこで導入したのが 分散トレーシング です。覚えてほしい用語は 2 つだけです。

  • スパン:処理 1 つ分の記録。開始・終了の時刻と「何の処理か」のラベルを持つ、計測の最小単位
  • トレース:1 リクエストで発生したスパンを親子関係でつなぎ、1 本の流れにまとめたもの
図2:トレースなら、1 リクエスト分のスパンが時系列に並ぶ。長く伸びているスパンを見れば「どこで時間を食ったか」が一目で分かる。

トレースがあると、リクエストごとに振られる ID を 1 つ指定するだけで、「そのリクエストが・どのスパンを・どの順番でたどり・どこで時間を食ったか」をまるごと取り出せます。ログのように混ざらず、呼び出しのつながりも保持されるので、AI に分析させたときの精度も上がります。

さらに、データベースが遅いときの調査も、このトレースに組み込みました。クエリが実行された瞬間に、データベースがそのクエリをどう処理したか、インデックスを使って一直線に引いたのか、それともテーブルを総なめするフルスキャンになったのか、という 実行計画 を一緒に記録するようにしたのです。これで「遅いクエリを見つけて、その場で理由(実行計画)まで分かる」状態になりました。

ここまでが「計測できる状態をつくる」フェーズです。ポイントは 勘ではなく事実で語れるようになった こと。ここから先は、この基盤をフル活用していきます。

OpenTelemetry「トレース」公式ドキュメント

OpenTelemetry「オブザーバビリティ入門」公式ドキュメント


ステップ 2:自分たちのサーバーにわざと負荷をかける(負荷試験)

計測できるようになったら、次は わざと大量のアクセスを浴びせて、サーバーがどう苦しくなるかを観察 します。1 人で触っている間は見えない問題が、同時アクセスが増えると一気に表面化するからです。

大量の「仮想ユーザー」で一斉にアクセスする

負荷試験には、Grafana 社が公開している k6 というツールを使いました。VU(Virtual User:仮想ユーザー)を多数立ち上げ、同時にサーバーへアクセスさせるためのツールです。

ここで工夫したのが シナリオ(ユーザーの操作手順)の用意 です。Acompanyセキュアチャットではすでに 本物のブラウザを自動操作して画面が正しく動くかを確かめる E2E テストPlaywright で書いています。これを使わない手はありません。Grafana の Playwright から k6 への移行ガイド を参考に、E2E のシナリオをそのまま負荷試験のシナリオへ変換 して再利用しました。1 つのシナリオを「正しく動くか」と「速さ・耐久性」の 2 つの目的で使い回せるわけです。

今回流用したのは「チャットを送り、AI の応答を受け取る」という、実ユーザー操作そのままのフローです。

負荷・パフォーマンステストツール「Grafana k6」公式ドキュメント

Grafana k6「Playwright スクリプトから k6 への移行」

今回は「自前の機密 LLM(プライベートモード)」を対象にした

ここが大事なポイントです。今回の試験対象は、外部の主要 LLM サービスではなく、冒頭で触れた「プライベートモード」TEE 内で自分たちがホスティングしている機密 LLM にしました。

外部の主要 LLM サービスを相手にすると、そのとき向こうが混んでいるかどうか で応答時間がばらつきます。これが混ざると、遅延が 自分たちのプロキシ層由来なのか、外部 LLM 側の都合なのか を切り分けられません。自前の AI なら、このばらつき(外乱)を排除でき、プロキシ層そのもののオーバーヘッドだけを、きれいに計測できる のです。

「だったら LLM の部分をモック(ニセの固定応答)に差し替えれば済むのでは?」と思うかもしれません。ですが、もともとサービスにあるプライベートモードの機密 LLM は 本物の AI モデル なので、わざわざモックを用意する必要がありません。むしろ 本物の推論時間も込み で、実際のユーザーとまったく同じ処理経路を素直に観察できます。モックでは抜け落ちてしまう「本物の AI を相手にしたときのプロキシ層のふるまい」まで、そのまま測れるわけです。

結果:エラーが出ないのは当然。本当の問題は「素の API より明確に遅い」こと

まず、試験を通して エラー率はゼロ でした。とはいえ、これは取り立てて報告することではありません。正しく動くのは前提であって、負荷試験で本当に見たいのはそこではないからです。

注目すべきは レイテンシ(応答時間) でした。セキュアチャット経由の応答時間を、まったく同じモデルを API で直接叩いたときの応答時間 と並べてみると、VU(同時接続数)の多寡にかかわらず、はっきりした差 が出ていたのです。これは負荷をかけたときだけ急に悪化するのではなく、1 リクエストごとに、常に一定のオーバーヘッドが上乗せされていたことが問題の 1 つでした。

図3:同じモデルでも、Acompanyセキュアチャット経由(赤)の応答時間は LLM API を直接叩いたとき(緑)より明確に遅い。その差は同時接続数(VU)を増やしてもほぼ一定で、1 リクエストごとに構造的に上乗せされるオーバーヘッドだった。※具体的な数値は伏せ、傾向を示しています。

この上乗せ分こそ、背景で立てた仮説「セキュアチャットが主要 LLM サービスの前段に挟んでいるプロキシ層のオーバーヘッド」の正体です。これは 1 リクエストごとに常に乗ってくる分なので、同じ性能のサーバーを台数だけ増やしても 1 件あたりは縮みません。中身を分解して、上乗せ分を一つずつ削るしかない(これがステップ 3)。なお、この上乗せの一部は データベースへの保存まわり にもありました(詳しくはステップ 3 の犯人 1)。ただしそれは複数ある要因の 1 つで、犯人はほかにもいます。

そしてもう一つ、大勢が同時に押し寄せたとき には別の問題が隠れていました。1 台の中で同時に処理できる数(並列度)が足りず、リクエストが 「順番待ち」(待ち行列) の列に並んでしまっていたのです。これは 1 件あたりの上乗せとは別物で、並列度を上げれば解消できます(こちらはステップ 4 で扱います)。

セキュアチャットの遅さは、主に 1 リクエストごとに常に上乗せされる構造的なオーバーヘッド から来ていた。さらに、大勢が同時に使うと処理の並列度が足りず、「順番待ち」 も生じていた。 だから、まずこのオーバーヘッドの内訳を分解して一つずつ削り(ステップ 3)、順番待ちは並列度を上げて解消する(ステップ 4)それが次のステップです。


まとめ

今回は「主要 LLM サービスと同じ AI に問い合わせているのに、セキュアチャットの画面の方が遅い」この差を、原因を見つけるために試験し検証しました。

  • ステップ 1(測る):分散トレーシングを整え、1 リクエストを 1 本の線で追えるようにして、「どこが遅いか」を勘ではなく事実で言えるようにした
  • ステップ 2(負荷をかける):E2E のシナリオをそのまま負荷試験へ変換し、自前の AI に大量アクセスを浴びせた。エラー率は当然ゼロ。本質は、同じモデルを API で直接叩いたときと比べ、Acompanyセキュアチャット経由の応答時間に VU によらず明確な差(1 件ごとの構造的なオーバーヘッド)がある と突き止めたこと。あわせて、大勢が同時に来たときの 「順番待ち」 も見つかった

本記事中の図は傾向を伝えるための概念図で、具体的な計測値は含みません。

Atsuto Inage のプロフィール画像

WRITER

Atsuto Inage

Acompany / プロダクトエンジニア

プロダクト開発・運用に従事し、DevOps、SRE(サイト信頼性エンジニアリング)を担当

入力された情報は学習には使われませんが、念のため個人情報の入力はお控えください。

読書の進捗

0%