シタデルアーキテクチャ(Citadel Architecture)は、Ruby on Railsの作者DHH(David Heinemeier Hansson)が2020年4月に名付けた構成で、大きなモノリスを中心に据え、性質の違う一部の処理だけを小さな独立サービス(アウトポスト)へ出します。マイクロサービスへ分割せずにモノリスの限界をしのぐための考え方です。この記事では、原文の定義、実例3件と連携方式、マイクロサービス・モジュラモノリスとの違い、採用すべきでない場面を、DHHとAppSignalの原文に沿って整理します。
まとめ:シタデルアーキテクチャの要点
- 定義:Majestic Monolith(意図して設計したモノリス)を中心に置き、アプリの責務の小さな一部だけを切り出したアウトポスト(Outpost)を周りに配置する構成です。
- 名付け親:DHHが2020年4月7日のツイートで呼び名を出し、翌8日にSignal v. Noiseで解説しました。同じ日にAppSignalが自社事例を公開しています。
- 切り出す理由は、組織・性能・実装の3つのいずれかです。リクエストの大半を占める軽い処理や、大量データの取り込みが典型例です。
- 前提:DHHは、まずモノリスを改善し、それでも足りないときに初めてアウトポストを検討するよう書いています。最初から選ぶ構成ではありません。
- セキュリティの多層防御を指す言葉ではありません。城塞(citadel)は比喩で、本丸がモノリス、周辺の前哨基地がアウトポストです。
シタデルアーキテクチャの定義:Majestic Monolithとアウトポスト
DHHが2020年4月8日に示した原文の定義
出典はDHHのブログ記事「The Majestic Monolith can become The Citadel」(Signal v. Noise、2020年4月8日)です。定義の文は次のとおりです。
「That next step is The Citadel, which keeps the Majestic Monolith at the center, but supports it with a set of Outposts, each extracting a small subset of application responsibilities.」(次の段階がシタデルである。Majestic Monolithを中心に保ったまま、アプリの責務の小さな一部をそれぞれ切り出したアウトポスト群でそれを支える)
アウトポストの役割については「either for organizational or performance or implementation reasons」と書かれています。組織上・性能上・実装上の理由で、モノリスとは性質の違う一部の振る舞い(divergent behavior)を引き受けるのがアウトポストです。前日のツイートでは「a few auxiliary outpost apps for highly specialized and divergent needs」と表現しており、数は「少数(a few)」が前提です。
citadel(シタデル)という語の意味と比喩
英語の citadel は「城塞」「砦」を意味します。Cambridge Dictionaryは「a castle that is part of a city, where people can shelter from danger if the city is attacked」(都市の一部をなす城で、攻撃を受けたとき人々が避難する場所)と説明しています。DHHは比喩の意味を説明していませんが、都市の中の本丸がモノリス、その外に点在する前哨基地(outpost)が切り出したサービス、という位置関係として読めます。重要な機能が中心にまとまり、周辺は小さく少ない。この絵がそのまま構成を表しています。
前提となるMajestic Monolith(2016年)
シタデルの中心にあるMajestic Monolithは、DHHが2016年2月29日に同じブログの「The Majestic Monolith」で書いた考え方です。不要な概念モデルや抽象をできる限り削った統合システムで、偶然ではなく意図して作るモノリスを指します(2020年の記事では「アプリに必要なことをすべて行う単一のコードベース」と言い直しています)。事例として挙げられたBasecamp 3は、コントローラ200個・メソッド900、モデル190クラス・メソッド1,473という規模を、プログラマー約12人で6つのプラットフォーム(Web・iOS・Android・Mac・Windows・メール)向けに提供していました。
2020年の記事はこの続編で、「大半のWebアプリは生涯Majestic Monolithで足りる。その限界は多くの人が想像するよりずっと高い」と述べたうえで、それでも限界に当たったときの次の一手としてシタデルを示しています。
「多層防御のセキュリティ設計」という説明の誤り
城塞という語から連想して、シタデルアーキテクチャを「中核の機密データを同心円状の防御層で守るセキュリティ設計パターン」と受け取られることがあります(本記事の旧版もこの説明をしていました)。DHHの原文にこの意味は含まれていません。記事本文には security や defense に当たる語が一度も出てこず、論点はモノリスの規模・性能の限界と、マイクロサービスへの分割に代わる選択肢です。
この取り違えは実務で判断を誤らせます。多層防御(defense in depth)や最小権限は、ネットワークの区画分けや権限設計として検討するもので、アウトポストを切り出す理由にはなりません。「中核を守りたいからシタデルにする」と考えると、セキュリティの課題に対してサービス数だけが増え、運用の負担が上がります。シタデルという言葉は、性能や組織の理由で処理を1つ2つ外に出すかどうかを議論するときに使うものです。
アウトポストの実例3件と連携方式
原文と公開事例から確認できるアウトポストは次の3件です。モノリスとのつなぎ方がそれぞれ違います。
| 事例 | 切り出した処理 | 主な理由 | モノリスとの連携 |
|---|---|---|---|
| Basecamp(Campfire) | 新着メッセージのポーリング | 性能 | モノリスのDBを直接読む |
| AppSignal | 監視データの取り込み・処理 | 性能(DBのロック) | Kafka経由 |
| HEY | メールの受信 | 原文に記載なし | モノリスへHTTP POST |
CampfireとAppSignalは、切り出したアウトポストが1つだけだと明言しています。3件とも、業務ロジックの本体はモノリスに残っています。この点がマイクロサービスとの分かれ目です。
Campfireのポーリング:全リクエストの99%を占めた20行の処理
DHHが原文で挙げた例は、2005年に作られたチャット製品Campfireです。常時接続ではなくポーリングで新着を確認する方式で、接続中のクライアントが3秒ごとに「新しいメッセージはあるか」と問い合わせていました。答えのほとんどは「無い」でしたが、それでも認証とDBへの問い合わせが毎回走ります。この処理はリクエスト全体の99%ほどを占め、DHHの記憶ではRubyで20行ほどの単純なものでした。
性能特性がアプリの他の部分とまったく違い、しかも小さい。DHHはこれを「アウトポストにうってつけ」とし、実際にC・C++・Go・Erlangなどで何度も書き直しています。一方でアプリの残りはRailsのモノリスのままでした。コメント欄でDHHは、このポーラーはモノリスのDBから直接読んでいたと答えています。
AppSignalのデータ取り込み:Rust+Kafkaへ移した理由
監視サービスのAppSignalは、DHHの記事と同じ2020年4月8日に「The Citadel Architecture at AppSignal」を公開し、自社の構成をシタデルと位置づけました。顧客が使う画面はReactを一部に含むRailsのモノリスで、バックエンドはすべてRubyです。
当初は、エージェントから届くデータをサブドメインのSinatraアプリで受け、Sidekiqのジョブとしてモノリスが処理していました。事業が伸び、監視するリクエストが数十億件単位になると、越えられない限界に当たりました。AppSignalは、主な制約はRubyの遅さではなく、データベースのロックが多すぎる設計にあったと書いています。そこで取り込みと処理だけをRust製のサービスへ書き直し、キュー兼ストレージとしてKafkaを使いました。移したのはこの部分だけで、モノリスは引き続きほとんどのロジックを担い、Kafkaと頻繁にやり取りしています。モノリスからアウトポストと通信しやすくするため、Kafka用のgemを自作しました。
この事例から読み取れるのは、言語を変えたこと自体より、ボトルネックの所在(DBのロック)を突き止めてから境界を引いた点です。KafkaのProducer・Consumer側の設計はKafka Java APIの実装設計で扱っています。
HEYのメール受信:postfixからモノリスへPOST
原文のコメント欄でDHHは、メールサービスHEYの受信系も例に挙げています。postfixがメールを受け取り、アプリが止まっているときはディスクにバッファし、シェルスクリプトから生のメールをモノリスのコントローラへPOSTします。受け取ったメールを記録するか拒否するかはRailsのAction Mailboxが判断し、その処理はすべてモノリスの中にあります。アウトポスト側は受け口だけで、判断ロジックを持ちません。
連携方式の選び分け:DB直読み・HTTP・メッセージング
DHHはDB経由とHTTP POSTの両方について「どちらでも機能する(both works)」と答えています。そのうえで、3件の連携方式にはそれぞれ向く条件があります。DB直読みは最も手軽ですが、アウトポストがモノリスのスキーマに依存するので、読み取り専用で対象テーブルが少ない処理に限るのが安全です。HTTPでモノリスへ渡す方式は、判断ロジックをモノリスに残せるため、アウトポストを受け口に徹させたい場合に合います。Kafkaのようなメッセージングは、AppSignalのように流量が大きく、受け取りと処理の速度差を吸収したい場合の選択肢です。どの方式でも、業務ルールをアウトポストへ複製しないことが、シタデルをシタデルのまま保つ条件になります。
マイクロサービス・モジュラモノリスとの違い
| 観点 | シタデル | マイクロサービス | モジュラモノリス |
|---|---|---|---|
| デプロイ単位 | モノリス+少数のアウトポスト | 多数のサービス | 1つ |
| 業務ロジックの所在 | モノリス | 各サービスに分散 | モノリス内の各モジュール |
| 分割の動機 | 性質の違う一部の処理 | チーム・ドメインごとの独立 | コードの境界の整理 |
| 新規開発の主な場所 | モノリス | 新しいサービス | 各モジュール |
3つは排他的ではありません。モジュール境界を整えたモノリスの周りにアウトポストを置くこともできます。違いが出るのは、分割をどこまで進めるつもりかという方針の部分です。
マイクロサービスとの違い:新規開発がどこで起きるか
見た目が近いのは、Martin Fowlerが「Monolith First」(2015年6月3日)で紹介した移行の型です。Fowlerは、モノリスから始めて縁(edges)からマイクロサービスを少しずつ剥がしていく方法が一般的だと書き、その結果「新規開発のほとんどがマイクロサービスで行われ、モノリスは比較的静かになる」形になりうると説明しています。
シタデルはこの逆です。モノリスが開発の中心であり続け、アウトポストは例外として増やしません。周辺にサービスがある構成でも、新しい機能がどちらに書かれているかを見れば区別できます。マイクロサービス全体の利点・欠点はマイクロサービスとは?モノリスとの違い・メリット・デメリットで整理しています。
モジュラモノリスとの違い:コード内の境界か、プロセス外への切り出しか
モジュラモノリスは、1つのデプロイ単位のままコードをモジュールに分け、依存の向きを検査して境界を守る設計です。分割はコードの中で完結します。シタデルはプロセスの外にアウトポストを出す判断で、性能特性や実装手段が違うために同じプロセスに置けない処理が対象です。チームが大きくなって互いの変更がぶつかるという悩みには、先にモジュラモノリスで境界を整えるほうが合います。DHHも原文で、大規模な組織の多くがモノレポで運用している点を添えています。境界の作り方はモジュラモノリスの設計と依存検証、リポジトリ構成はモノレポ(Monorepo)の解説を参照してください。
ストラングラーフィグとの違い:移行の手順か、目指す形か
ストラングラーフィグは、Martin Fowlerが2004年6月29日にStrangler Fig Applicationとして名付けた、旧システムを新システムへ少しずつ置き換え、最終的に旧側を取り除く移行の手順です。シタデルは置き換えを目的にせず、モノリスを残すことを前提にした「目指す形」です。アウトポストを切り出す作業そのものに、ルーティングを段階的に切り替えるストラングラーフィグの手順を使うことはできます。具体的な切り替え方はストラングラーフィグパターンの実装手順で解説しています。
シタデルを採用すべきでない場面と判断手順
DHHは「最初にすべきは、対処できるまでMajestic Monolithを改善することだ」と書き、それに失敗して初めて次の段階としてシタデルを挙げています。したがって、次のような場面ではアウトポストを作るべきではありません。
- ボトルネックの原因を測っていない。AppSignalは取り込みを早くからSinatraとSidekiqのジョブに分けていましたが、それでもDBのロックで限界に当たり、そこで初めてアウトポストを作りました。インデックス・キャッシュ・ジョブ化で解消するうちは、モノリス内で済みます。
- 切り出したい処理が、モノリスの業務ルールやモデルの大半を必要とする。ロジックを複製することになり、分割の効果より二重管理の負担が大きくなります。
- 新しい言語やフレームワークを試したいことが動機になっている。切り出す理由は性質の違い(divergent behavior)であるべきです。Campfireでも切り出しの動機は性能で、多言語での書き直しはDHH自身が「趣味(hobby)」と呼ぶ後日談でした。
- チーム間の衝突だけが悩みである。まずコードの境界とリポジトリ運用(モジュラモノリスやモノレポ)で対処します。組織上の理由でアウトポストを検討するのは、それでも解けない場合です。
アウトポストを切り出すかどうかの判断手順
- 問題を計測で特定する(どの処理が、どの資源で、どれだけの割合を占めているか)。
- モノリスの中で改善を試す(クエリ・インデックス・キャッシュ・バックグラウンドジョブ・スケールアップ)。
- 残った問題が、小さく性質の違う一部の処理に集中しているかを確かめる。Campfireのポーラーは全リクエストの99%・20行程度でした。
- 連携方式を決める(読み取りだけならDB直読み、判断をモノリスに残すならHTTP、流量が大きければメッセージング)。
- アウトポストは1つずつ増やし、業務ルールがモノリスに残っているかを定期的に確認する。
手順3で処理が広く散らばっていると分かった場合、課題はシタデルの範囲を超えています。その場合はモジュール境界の見直しか、マイクロサービスの検討に進みます。どちらに向くかの判断軸はマイクロサービスの向き不向きと判断基準が参考になります。
アウトポストの寿命:Campfireポーラーの引退とAction Cable
アウトポストは、製品や周辺技術とともに役目を終えることがあります。DHHはコメント欄で、Campfireと古いポーラーはすでに引退させたこと、Basecamp 3のチャット機能はモノリスの一部としてAction Cableを使い、スケールのためのアウトポストを必要としていないことを答えています。WebSocketの常時接続をフレームワークが標準で扱えるようになり、ポーリングを外に出す理由がなくなったと読めます。周辺技術が変われば、後継の実装ではアウトポストを作らずに済むこともあります。Action Cableの現行の使い方はAction Cableとは?Rails 8.1のWebSocket実装で扱っています。
国内でも、タイミーのテックリードが2024年12月の記事「Majestic Monolith, そして Citadel」で、意図せずこの構成になっているプロダクトは多いのではないかと述べ、自社もその1つだと書いています。名前が付いたことで、別物と感じていた他プロダクトの構成にも共通点を見出せるようになったという評価です。
よくある質問
シタデルアーキテクチャとは何ですか?
大きなモノリス(Majestic Monolith)を中心に置き、性質の違う一部の処理だけを少数の独立サービス(アウトポスト)へ切り出す構成です。DHHが2020年4月に名付けました。
シタデル(citadel)はどういう意味ですか?
英語で城塞・砦を意味し、都市の中にあって攻撃時に人々が避難する城を指します。アーキテクチャ名では、本丸をモノリス、周辺の前哨基地(outpost)を切り出したサービスにたとえています。
アウトポストとは何ですか?
モノリスから組織・性能・実装のいずれかの理由で切り出した小さなサービスです。例として、Campfireのポーリング処理、AppSignalのRust+Kafkaによるデータ取り込み、HEYのメール受信があります。
マイクロサービスとはどう違いますか?
シタデルは業務ロジックと新規開発の中心がモノリスに残り、切り出すのは少数の例外的な処理だけです。マイクロサービスはサービス群に責務を分散させ、新しい機能も新しいサービスで作るのが一般的です。
Ruby on Rails以外でも使えますか?
使えます。公開事例でも、アウトポスト側はC・C++・Go・Erlang(Campfire)やRust(AppSignal)で書かれています。モノリス側の事例がRails系に多いだけで、モノリスの限界に対して部分的な切り出しで対処するという判断は、どの技術スタックにも当てはまります。