多くの人は、カスタマーサポートシステムとは単なるチャットボックスだと思っています。
しかし、実際はそうではありません。
あの小さなフローティングウィジェットの背後には、予測不能なトラフィックの下でも低レイテンシでメッセージを配信しなければならない、リアルタイムメッセージングの中核があります。数千、時には数十万に及ぶ同時接続を管理するための並行制御があります。非効率なアロケーションパターンが静かにレイテンシスパイクを引き起こしかねない、負荷下でのメモリ管理があります。あるテナントのワークロードが他のテナントの安定性に影響を与えないようにするためのマルチテナント分離があります。SaaS とオンプレミス環境とでは根本的に異なるデプロイ戦略があります。メッセージが「ちょうど一度」届くのか、「少なくとも一度」届くのか、あるいはエッジケースで失われるのかを左右する信頼性保証があります。そしてそのすべての下には、数週間ではなく数年単位でシステムの進化を形づくる、長期的なアーキテクチャ上のトレードオフが存在します。
表面上はシンプルに見えるものも、実際にはパフォーマンス、分離性、信頼性、保守性のバランスを取る多層的なシステムです。インターフェースはミニマルに見えるかもしれませんが、その背後にあるエンジニアリングは決して単純ではありません。
この数年間、私は ShenDesk というリアルタイムカスタマーサポートシステムを構築してきました。SaaS とオンプレミスの両方に対応し、高い同時実行性、保守性、そしてアーキテクチャの明確さを重視して設計されています。最初は最小限のプロトタイプとして始まりましたが、やがて本番運用に耐えうるシステムへと進化しました。そこには本格的なメッセージングパイプライン、テナント分離、ビジタートラッキング、拡張可能な API、そして偶発的な複雑さを積み重ねるのではなく変化に適応できる構造が備わっています。
本シリーズは、その進化の技術的記録です。ゼロからリアルタイム基盤システムを構築し、改良していく過程での意思決定、制約、修正、そして得られた教訓をまとめたものです。
なぜこのシリーズを書くのか?
エンジニアリングの透明性は信頼を生み出します。特にインフラレベルのソフトウェアにおいてはなおさらです。
カスタマーサポートの分野には、Zendesk や Intercom のような確立されたプラットフォームを含め、多くの製品が存在します。これらは十分に成熟したシステムであり、豊富なリソースに支えられ、長年の反復によって磨き上げられてきました。外から見れば、洗練されたインターフェースと優れたユーザー体験が提示されています。
しかし、そうしたシステムが大規模に成立するためのエンジニアリング上の思考は、往々にして表に出てきません。UI はシンプルに見えるかもしれませんが、その下のインフラは負荷のかかる状況下で継続的に稼働し、トラフィックの急増に耐え、テナントを確実に分離し、自身の複雑さに押し潰されることなく進化し続けなければなりません。そうした設計上の選択――トレードオフ、制約、そして改訂の積み重ね――が公に語られることはほとんどありません。
持続的な高並行環境下でも安定して動作するリアルタイムメッセージングコアを .NET でどのように設計するのか。10万単位の WebSocket 接続を、レイテンシスパイクを引き起こさずにどう管理するのか。アロケーションパターンが応答性に直結するシステムにおいて、どのように GC 圧力を抑えるのか。マルチテナント分離とは、理論図を超えて実際にはどのような姿をしているのか。そして、純粋な SaaS 提供ではなくオンプレミス導入を求めるエンタープライズ顧客に対応する際、どのようなアーキテクチャ上の妥協が生じるのか。
これらの問いは、機能一覧では答えられません。制約によって形づくられたアーキテクチャ思考によってこそ答えられます。
本シリーズでは、これらの問いをエンジニアリングの視点から掘り下げます。当時は合理的に思えたものの、後になって修正を余儀なくされた意思決定を検証します。シンプルさとスケーラビリティ、抽象化とパフォーマンス、即時的な実装と長期的な保守性との間に存在するトレードオフを議論します。
これはマーケティングキャンペーンではありませんし、本番用コードをそのまま配布するステップバイステップのチュートリアルでもありません。そうではなく、時間をかけてリアルタイムシステムを構築し進化させていく過程で下したアーキテクチャ上の決定、現実で直面した制約、そしてそこから得た教訓を体系的に探求する試みです。
本シリーズで扱う内容
本シリーズでは、システムの最初期の前提から、より成熟したアーキテクチャへと至る進化の過程をたどります。まずは「そもそもなぜカスタマーサポートシステムをゼロから構築するという選択が合理的だったのか」という根本的な問いから始め、その判断を正当化した制約条件を検討します。そこから、最小限のプロトタイプが本番運用に耐えるアーキテクチャへと移行していく過程を追い、初期のシンプルさがどのようにして構造的な厳密さへと置き換わっていったのかを分析します。
議論は、.NET におけるリアルタイムメッセージングコアの設計や、持続的な高負荷環境下で高並行 WebSocket 接続を処理する際の実践的な課題に及びます。メモリアロケーションのパターンや GC の挙動についても、理論的な話題としてではなく、ユーザー体験に直接影響を与える要因として掘り下げます。さらに、バックプレッシャー、キュー管理、障害分離といった安定化メカニズムを、連鎖的な障害拡大を防ぐという観点から検討します。
システムの拡張に伴い、アーキテクチャ上の関心はマルチテナント分離、デプロイの柔軟性、そして信頼性保証へと移っていきます。SaaS とオンプレミスの両環境をサポートすることは、異なる運用上の制約をもたらし、設定可能性、自動化、環境前提に関する明確な意思決定を迫ります。メッセージングの信頼性モデル、そして理論上の保証と運用上の現実性との間にある妥協も、中心的なテーマとなります。
後半の記事では、初期設計が不十分であることが判明した際に中核コンポーネントを書き直した経験、過度な抽象化に陥らずに拡張可能な API を設計する方法、そして基盤システムを不安定化させることなく、レイテンシに敏感なリアルタイムパイプラインへ AI 機能を統合する際の課題について振り返ります。
各記事は、表面的な機能紹介ではなく、エンジニアリング上の思考に焦点を当てます。必要に応じて、トレードオフ、採用しなかった代替案、そしてアーキテクチャ上の妥協についても率直に述べます。なぜなら、現実のシステムは理想よりも制約によって形づくられることの方が多いからです。アーキテクチャ図は明快さを示しているように見えますが、その明快さは多くの場合、改訂を経て初めて得られるものです。
本シリーズは、製品が「何ができるか」を軸に構成されているのではありません。システムが成長、負荷、そして時間に耐えながらどのように生き残るのか、そのプロセスを軸に構成されています。
予定しているトピックは以下のとおりです。
- なぜカスタマーサポートシステムをゼロから構築するのか
- MVP から本番アーキテクチャへの移行
- .NET におけるリアルタイムメッセージングコアの設計
- 高並行 WebSocket 接続への対応
- リアルタイムシステムにおけるメモリ最適化と GC 圧力
- メッセージングパイプラインにおけるバックプレッシャーと安定性
- マルチテナントアーキテクチャ:論理分離と物理分離
- オンプレミス導入を前提とした設計
- メッセージの信頼性と配信保証
- コアコンポーネントを書き直して得た教訓
- 拡張性を考慮した Open API 設計
- リアルタイムサポートシステムへの AI 統合
各記事では、表面的な機能紹介ではなく、エンジニアリング上の思考に焦点を当てます。
必要に応じて、トレードオフ、採用しなかった代替案、そしてアーキテクチャ上の妥協についても取り上げます。なぜなら、現実のシステムは理想よりも制約によって形づくられることの方が多いからです。
範囲と前提
本シリーズは、本番環境でリアルタイムシステムを構築し、進化させてきた経験から得られたアーキテクチャ、設計思考、そしてエンジニアリング上の教訓に焦点を当てます。重視するのは「思考のプロセス」です。なぜその意思決定がなされたのか、どのような制約がそれを形づくったのか、そしてその判断が時間の経過とともにどのように評価されたのかを明らかにします。
一方で、機密性の高いセキュリティ情報、顧客データ、特定のデプロイ構成、あるいは内部の独自実装の詳細を公開することはありません。実際のシステムは特定の運用コンテキストの中で動作しており、それらは公開できないし、公開すべきでもありません。安定性とセキュリティは理論上の問題ではなく、実務上の責任です。
例を挙げる場合も、内部構造を明らかにするのではなく、設計パターンを示すために抽象化された形で提示します。ベンチマークについても、実運用の正確な数値ではなく、傾向や方向性として言及します。アーキテクチャ図を掲載する場合も、実際のインフラ構成をそのまま示すのではなく、概念モデルとして表現します。
目的は、運用上のリスクをさらすことではなく、思考を共有することにあります。エンジニアリングの透明性とは、すべてのコードやデプロイ詳細を公開することではありません。システムを形づくる意思決定について明確に語り、それに伴うトレードオフについて誠実に説明することです。
境界を設けることは議論の制限ではなく、責任あるエンジニアリング実践の一部なのです。
本シリーズの対象読者
本シリーズは、レイテンシ、並行性、信頼性が抽象的な概念ではなく、日々のエンジニアリング上の制約として存在するリアルタイムシステムを構築している方にとって有益かもしれません。SaaS インフラを設計する中で、アーキテクチャ上の意思決定が決して孤立したままでは済まず、デプロイモデル、テナント分離、運用の複雑さ、長期的な保守性へと波及していくことを実感しているなら、共感できる内容になるはずです。
理論図を超えたマルチテナントアーキテクチャを模索している方や、WebSocket が多用されるワークロードを扱い、接続ライフサイクル管理やメモリ挙動がシステム安定性に直結する環境で取り組んでいる方にも、本シリーズで扱うテーマは重なる部分があるでしょう。また、数週間の開発ではなく、何年にもわたる反復の後でも理解可能で適応可能なシステムを設計したいと考えている方にも、その視点を前提として書いています。
これは、短時間で読み切れることを目的に最適化されたコンテンツではありません。絶対解ではなくトレードオフで考える姿勢、即効的な解決策ではなく進化のプロセスで捉える姿勢を前提としています。焦点は機能比較ではなく、構造的な思考にあります。一度動けばよいものを作るのではなく、複雑さが増してもなお動き続けるものをどう設計するかがテーマです。
もし「10分でチャットアプリを作る方法」といった手軽なガイドを探しているのであれば、本シリーズはそれではありません。迅速なプロトタイピングに関する優れた資料は数多く存在します。本シリーズが扱うのは、プロトタイプが現実のトラフィック、現実の制約、そして現実の運用責任に直面し始めた後に何が起こるのか、という問いです。
長期的なエンジニアリング記録
ソフトウェアシステムは進化します。
機能が追加され、エッジケースが顕在化するにつれて、複雑さは蓄積していきます。初期設計では見えなかったボトルネックに直面することもあります。かつては完全に妥当だと思えた前提を、改めて問い直さざるを得なくなることもあります。アーキテクチャ図の上では整然として見えるものも、本番環境では多層化され、調整と妥協の積み重ねとなるのが現実です。
どんなシステムも、最初のバージョンのままではあり続けません。並行モデルは調整され、データ構造は置き換えられ、メッセージパイプラインは書き直されます。デプロイのワークフローは簡素化されることもあれば、新たな要件によって再び複雑化することもあります。やがて課題は「動かすこと」そのものではなく、積み重なった意思決定に押し潰されることなく、動き続けさせることへと変わります。
本シリーズは、その過程を記録しようとする試みです。完成された成功物語としてではなく、現在進行形のエンジニアリングプロセスとしてまとめます。そこには改訂や構造的な修正、そして初期の確信がより深い理解へと置き換わった瞬間も含まれます。安定性は単一の正しい判断によって得られるものではなく、多くの場合、反復、制約、そして洗練の結果として形成されます。
インフラレベルの SaaS システムにおいて、進化は選択肢ではありません。実際のトラフィックは隠れた弱点を露呈させます。実際の顧客はアーキテクチャを再構築せざるを得ない要件を持ち込みます。実際の運用責任は、トレードオフの評価基準そのものを変えていきます。時間が経つにつれ、エンジニアリングの焦点は単なる機能追加から、変化に適応しながら明確さを保つことへと移っていきます。
リアルタイムアーキテクチャ、分散設計におけるトレードオフ、あるいは継続的に稼働し続けなければならないシステムの構築と維持という現実に関心があるなら、この記録を追うことに価値を見いだせるかもしれません。
終わりに
本シリーズの議論が有益だと感じていただけたなら、ぜひご意見やご指摘、異なる視点をお寄せください。エンジニアリングは対話によって磨かれますし、建設的な異論はアーキテクチャの明確さを一層鋭くします。本シリーズを継続して読み、真剣に向き合ってくださる皆さまに、あらかじめ感謝申し上げます。
ShenDesk は概念上のプロジェクトではありません。実際の本番環境で、実際のユーザーに利用されている実在の製品です。ここで述べている内容は理論モデルではなく、実装と運用の現場に根ざしています。製品そのものにご関心があれば、以下のサイトをご覧ください。
システム設計に関する議論は、ひとつの記事で完結するものではありません。類似のインフラを構築している方、同様のトレードオフに直面している方、あるいはリアルタイムアーキテクチャについてより深く議論したい方がいらっしゃれば、ぜひつながり、意見を交換できればと思います。
たとえ単独の開発者が構築したシステムであっても、エンジニアリングは本質的に孤独な営みではありません。

