シェアードナッシング
各ノードが独立したリソースを持ち、共有状態を排除するアーキテクチャ
シェアードナッシングとは
シェアードナッシング (Shared-Nothing Architecture) は、各ノードが独立した CPU、メモリ、ストレージを持ち、ノード間で状態を共有しないアーキテクチャである。用語としては Michael Stonebraker が 1986 年の論文「The Case for Shared Nothing」(IEEE Database Engineering Bulletin 9 巻 1 号 4-9 ページ) で提唱した。
台数を増やすほど処理量が伸びるのは、ノードが増えても取り合う対象が増えないためである。共有された資源が 1 つでもあれば、そこへのアクセスは台数に比例して増え、いずれ資源そのものが上限になる。共有をやめれば、この上限が構造として消える。代わりに、どのデータをどのノードへ置くかという分割の判断と、ノードをまたぐ処理の調整が設計側に残る。1 台の障害が他ノードへ直接波及しない点も利点だが、リクエストを振り分ける層やノードの所在を管理する層は共有部分として残るため、系全体に単一障害点が無いとは限らない。
共有メモリ型・共有ディスク型との違い
対比の軸は「何を共有しているか」である。共有するものが多いほど 1 台あたりの実装は単純になり、台数を増やしたときに先に上限へ当たる箇所が変わる。
| 観点 | 共有メモリ型 | 共有ディスク型 | シェアードナッシング |
|---|---|---|---|
| 共有するもの | メモリとストレージ | ストレージ | 何も共有しない |
| 台数を増やしたとき | メモリへの経路が先に上限になる | ストレージへの入出力が先に上限になる | 取り合う資源が無く台数に比例して伸びやすい |
| 1 ノード障害の波及 | 全体に及ぶ | 共有ストレージが健全なら残りのノードで継続できる | そのノードが担当するデータ範囲に限られる |
| 実装の難所 | 排他制御 | ノード間でのキャッシュの整合 | データの分割とノードをまたぐ結合 |
| 例 | 1 筐体内の複数 CPU | Oracle RAC、共有ストレージ層を持つ構成 | Lambda、DynamoDB |
共有ディスク型が今も使われるのは、ストレージを共有していればどのノードからも同じデータが読めるため、データの置き場所を分割せずに済むからである。シェアードナッシングはその手間を引き受ける代わりに、共有資源による上限を外している。
Lambda はシェアードナッシング
Lambda では、同時に届いたリクエストの数だけ実行環境が作られる。どのリクエストがどの環境に割り当てられるかは、呼び出す側から指定できない。
リクエスト 1 → Lambda インスタンス A (独立したメモリ)
リクエスト 2 → Lambda インスタンス B (独立したメモリ)
リクエスト 3 → Lambda インスタンス C (独立したメモリ)
各実行環境は独立したメモリを持ち、環境をまたいでメモリを共有しない。そのため、リクエストをまたいで残す必要のある状態は DynamoDB や S3 へ外部化する。
事故になりやすいのは、ハンドラーの外側 (グローバル領域) に置いた値である。実行環境が再利用されると前回の値が残っているため、手元で数回呼ぶだけのテストでは動いてしまう。しかし新しい環境に当たった呼び出しにはその値が無く、同時に走る別環境の呼び出しも互いの値を見られない。ハンドラーの外側は「次の呼び出しで残っている保証がないキャッシュ」として扱い、処理の正しさをそこに依存させない。
DynamoDB のパーティション
DynamoDB は、テーブルを内部で複数のパーティションへ分けて保持する。
DynamoDB テーブル:
パーティション A (独立したストレージ + コンピュート)
パーティション B (独立したストレージ + コンピュート)
パーティション C (独立したストレージ + コンピュート)
各パーティションが独立してリクエストを処理するため、パーティションが増えれば同時に処理できる量も増える。ただしこれは、アクセスがパーティションへ均等に分かれている場合の話である。1 つのパーティションが出せる量には上限があるため、パーティションキーの値が偏ると、そのパーティションだけが先に上限へ達し、テーブル全体に割り当てた容量を使い切れなくなる (ホットパーティション)。共有をやめた構成では、偏りがそのまま処理量の上限になる。
設計原則
アプリケーションに状態を持たないステートレス設計が基本で、状態は DB、キャッシュ、S3 に外部化する。同じリクエストを何度処理しても同じ結果になる冪等性を確保し、ノード間の依存を排除して独立性を保つ。
シェアードナッシングが難しいケース
難しくなるのは、1 回のリクエストの中で終わらない状態を扱う場合である。「同じ利用者の次のリクエストが別のノードへ届いても成立するか」を問うと、作り直しが必要な箇所を見分けられる。
| ケース | 理由 | 対策 |
|---|---|---|
| セッション管理 | ユーザーの状態を保持 | DynamoDB or ElastiCache に外部化 |
| ファイルアップロード | ローカルディスクに保存 | S3 に直接アップロード |
| WebSocket | 接続状態を保持 | API Gateway WebSocket + DynamoDB |
Twelve-Factor App との関係
Twelve-Factor App の第 6 原則は、アプリケーションを 1 つまたは複数のステートレスなプロセスとして実行し、永続化が必要なデータはすべてバックエンドサービス (典型的にはデータベース) へ格納するよう求めている。原文はこのプロセスの性質をステートレスかつシェアードナッシングであると述べている。
一方で、プロセスのメモリやファイルシステムを 1 つの処理の中のキャッシュとして使うことは認めている。禁じているのは、そこへ置いたものが将来のリクエストやジョブでも読める前提を置くことである。線引きは Lambda のハンドラー外側と同じで、キャッシュとして使うか、処理の正しさの前提にするかの差になる。
つまずきやすい点
- 分類は現代のマネージドサービスにそのまま当てはまらない。計算層とストレージ層を分け、ストレージ側は共有するという構成が広く使われており、1 つのサービスがどれか 1 分類に収まるとは限らない。分類は名札ではなく、「何を共有しているために、台数を増やしたとき何が先に上限になるのか」を読み取る道具として使う。
- 状態を外部化しても、外部化した先が共有資源になる。セッションを 1 つのキャッシュへ寄せればアプリケーション層は台数を増やせるが、上限はその先へ移るだけである。移した先自体が分割できる作りかどうかまで見る。
- ノードをまたぐ処理のコストは消えない。複数ノードにまたがるデータを突き合わせる処理では、ノード間でデータを配り直す通信が発生する。台数を増やしても、この通信が支配的な処理は速くならない (水平スケーリング)。
- ノードの増減でどのデータがどこへ移るかが運用上の焦点になる。移動量を抑える手法として コンシステントハッシュ が使われる。
この記事は役に立ちましたか?
関連用語
関連する記事
技術書の読書ノート術 - 付箋 / マーカー / デジタルの使い分け
技術書を読むときのノートの取り方を比較します。付箋派、マーカー派、デジタルノート派、それぞれの長所と短所を実体験をもとに紹介します。
README を書くように本を読む - エンジニアのための構造化読書法
エンジニアが日常的に書く README のフォーマットを読書に応用する方法を紹介します。目的 / 使い方 / 注意点の 3 点で本の内容を整理すると、半年後に読み返しても実務で引き直せる記録として残ります。
技術書の読む順番戦略 - 複数冊を組み合わせて理解を加速させる
技術書を 1 冊ずつ読むのではなく、複数冊を戦略的に組み合わせることで、1 冊では届かない理解の深さと広さに達する方法を解説します。