Sekiban の分散志向DCB vs Pure DCB:順序管理とスケーラビリティの設計の比較
株式会社ジェイテックジャパン CTOの高丘 @tomohisaです。最近は久しぶりにフレームワークの開発から、フレームワークを使ったビジネスドメインの開発を行なっていて、どちらも面白いなと感じています。
イベント駆動システムで整合性をどう扱うかは常に難しい課題です。最近注目されている Dynamic Consistency Boundary (DCB) モデルでは、条件付き追記(append condition) や グローバルな順序 を使って、サーガやプロセスマネージャーによる非同期オーケストレーションを簡略化できます。一方でスケーラビリティや可用性の観点で懸念もあります。
本記事では、Sekiban のアクターモデルにタグ毎直列化を組み込んだ「分散志向 DCB」の実装と、Pure DCB(dcb.events の仕様どおり条件付き追記を重視する方式)の長所・短所を、順序管理・一貫性・実用上のトレードオフの観点から比較します。
DCB とは、
DCB に関しては、弊社のエンジニアで、Developer Advocate for Sekiban も行なってくださっている川江さんが、 #イベントソーシング勉強会で2回にわたって発表してくださっています。
登壇資料はこちらです。
ここでいう、集約をなくすというのは、イベントソーシングで一般的に用いられている、集約というデータの一貫性単位毎に、イベントストアのデータベースにおいてパーティションを設定することにより、その集約においてのデータの一貫性を向上させるための『集約』のことを言っています。
DCBにおいては、データベースに『集約毎のパーティションを置かない』ことによって『Kill the Aggregate(集約を排除する)』と理解できます。
イベントに関しては、以下のYoutubeをご覧ください。
第4回イベントソーシング勉強会
第3回イベントソーシング勉強会は以下の通りです。
Pure DCB の仕組み
dcb.events の仕様では、イベントストアは以下を満たす必要があります:
- Append Condition:新しいイベントを追記する前に、「Decision Model を構築した時点以降に、同じクエリに一致するイベントが他から追加されていないこと」を確認する仕組み。さもなければ append は失敗する。(参考: DCB Events - Specification)
- グローバルなシーケンス位置(Sequence Position)を持ち、イベント追記や読み込みにおいて順序性を保証。(参考: DCB Events - Specification)
- プロジェクションを使って関連するイベントのみを読み取り、Decision Model を構築。(参考: DCB Events - Projections)
コード例(Pure DCB/擬似コード)
// Pure DCB の典型的なフロー
const events = eventStore.read(query, { upTo: lastKnownPosition });
const state = project(events);
if (!stateConstraintViolated) {
const appendResult = eventStore.append(newEvents, {
failIfEventsMatch: query,
after: lastKnownPosition
});
if (!appendResult.success) {
// 衝突/失敗時のリトライ or エラー処理
}
} else {
// Constraint 違反 → エラー
}
Sekiban の分散志向 DCB
Sekiban ではアクターモデルを導入し、タグ単位で予約(reservation)→書き込み → 確定(confirm)という直列化制御をアプリケーション層で行います。タグが異なれば並行できるように設計されています。
コード例(Sekiban/擬似コード)
こちらは実際のSekibanのコードではないですが、分散での予約、書き込み、確定のフローを擬似的に表したものです。
// 擬似 C# コード:タグごとの予約 → 書き込み → 確定フロー
var reservations = new List<Reservation>();
foreach (var tag in tagsNeedingConsistency)
{
var res = await TagConsistentActor.MakeReservationAsync(tag, lastSortableUniqueId);
if (!res.Success)
{
foreach (var r in reservations)
{
await TagConsistentActor.CancelReservationAsync(r);
}
return Error("Reservation failed for tag: " + tag);
}
reservations.Add(res);
}
var writeResult = await EventStore.WriteEventsAsync(eventsWithTags);
if (!writeResult.Success)
{
foreach (var r in reservations)
{
await TagConsistentActor.CancelReservationAsync(r);
}
return Error("WriteEvents failed");
}
foreach (var r in reservations)
{
await TagConsistentActor.ConfirmReservationAsync(r);
}
return Success();
比較:Pros & Cons
項目 | Sekiban 型(分散志向 DCB) | Pure DCB 型(仕様遵守) |
---|---|---|
順序保証 | タグ毎に Actor が直列化。タグ間の競合をタグ設計で制御可能。 | グローバルな順序位置 + Append Condition による保証。ストアによる約束に依存。 |
スケーリング | タグを多数分割/Actor を複数ノードに分散配置すれば高並行書き込み可能。ホットタグ設計が鍵。 | 書き込みはすべて順序付け/条件付き append を通るため、グローバル順序がボトルネックになる可能性あり。パーティションが難しい。 |
整合性の強さ | アプリ層で制御が多く、ストアが弱くても順序保証可能。 | ストア機能(条件付き append、単調増加の Sequence Position)が十分であれば非常に強い。 |
実装複雑さ | 予約・有効期限・再同期・失敗時のキャンセルなどのロジックが必要。運用も注意が必要。 | 分かりやすい仕様。アプリは条件付き append とリトライが中心。 |
可用性/障害耐性 | Actor が障害時にどうなるかを設計する必要あり。決定ログや再送が不十分な場合、予約中の状態が残るなどがリスク。 | ストア側が堅牢なら可用性が高いが、分散ストアでの順序保証や遅延が障害になる可能性あり。 |
読み取り遅延とスループット | 読み取り時にキャッチアップが必要で、タグ確認フェーズが書き込み遅延要因となることあり。 | 条件付き append 以外に影響がなければ読み取りはシンプル。ただし after チェックや Query 処理が激しくなると遅延。 |
スケーラビリティの懸念と緩和策
Pure DCB 型には以下のようなスケール障害の典型があります。その緩和策として、Sekiban 型やハイブリッドな設計が有用です。
- 懸念
- グローバル順序位置の取得/管理が集中点(ボトルネック/SPOF)になる。
- 条件付き append の
failIfEventsMatch
が重いクエリになること。 - 書き込みが多いタグで遅延や競合が頻発。
- 緩和策
- タグを細かく分け、ホットタグを避ける。
- 予約や順序取得のキャッシュ/ローカルキャッシュ活用。
- 非同期確認や期限切れシステムを整備。
- バッファリング/バッチ処理で書き込みをまとめる。
- 分散ノードで Actor を配置し、並行書き込みを可能にする設計。
dcb.events の仕様で読み取れるスケーラビリティ制約
- FAQ で「順序を確保するために Events must be ordered…」「パーティションが容易ではない」といった制約が言及されている。
- Specification で
append()
における Append Condition の要件が定義され、失敗時のクエリチェックなどが記載されている。(参考: DCB Events - Specification)
結論
Sekiban の実装は「純粋な DCB モデル」ではなく、「分散を意識した DCB の亜種」であり、スケール性を得るための設計が多数含まれています。Pure DCB のシンプルさと強整合性の利点を取りつつ、ホットタグ問題・順序集中のボトルネックを避けたいようなドメインには Sekiban 型が有力な選択肢です。
どちらを選ぶかは、ドメインの特性(書き込み頻度・タグ設計可能性・整合性強度の要求)と、ストア/インフラの性能仕様を見極めたうえでの判断が重要です。
Discussion