イベントソースストレージとしてのRDBMSの使用


119

RDBMS(SQL Serverなど)を使用してイベントソースデータを格納している場合、スキーマはどのようになるでしょうか?

抽象的な意味で話されているいくつかのバリエーションを見ましたが、具体的なものはありません。

たとえば、「製品」エンティティがあり、その製品への変更は、価格、コスト、説明の形式で発生するとします。私がするかどうかについて私は混乱しています:

  1. 製品のすべてのフィールドを含む「ProductEvent」テーブルを作成します。各変更は、そのテーブルの新しいレコードを意味し、必要に応じて「誰、何、どこ、なぜ、いつ、どのように」(WWWWWH)を意味します。コスト、価格、または説明が変更されると、製品を表すまったく新しい行が追加されます。
  2. 製品のコスト、価格、および説明を、外部キー関係を使用してProductテーブルに結合された個別のテーブルに格納します。これらのプロパティに変更が発生した場合は、必要に応じてWWWWWHを使用して新しい行を書き込みます。
  3. WWWWWHに加えて、イベントを表すシリアル化されたオブジェクトを「ProductEvent」テーブルに格納します。つまり、特定の製品のアプリケーション状態を再構築するために、イベント自体をアプリケーションコードでロード、シリアル化解除、および再生する必要があります。 。

特に上記のオプション2について心配しています。極端に言うと、製品テーブルはプロパティごとにほぼ1テーブルであり、特定の製品のアプリケーション状態をロードするには、各製品のイベントテーブルからその製品のすべてのイベントをロードする必要があります。このテーブル爆発は私に悪臭を放ちます。

「依存している」と私は確信しており、単一の「正解」はありませんが、何が受け入れ可能で何がまったく受け入れられないかを感じ取ろうとしています。また、NoSQLがここで役立つことも知っています。集約ルートに対してイベントを格納できます。つまり、オブジェクトを再構築するためのイベントを取得するためのデータベースへの単一の要求のみですが、ここではNoSQL dbを使用していません。瞬間なので、私は代替案を探しています。


2
最も単純な形式:[Event] {AggregateId、AggregateVersion、EventPayload}。集計タイプは必要ありませんが、オプションで格納できます。イベントタイプは必要ありませんが、オプションで保存できます。これは、起こったことの長いリストであり、それ以外のものは単に最適化です。
Yves Reynhout、2011

7
間違いなく#1と#2から離れてください。すべてをblobにシリアル化し、そのように保存します。
ジョナサンオリバー

回答:


109

イベントストアは、イベントの特定のフィールドまたはプロパティについて知っている必要はありません。そうしないと、モデルを変更するたびに、データベースを移行する必要があります(古き良き状態ベースの永続性と同じように)。したがって、オプション1と2はまったくお勧めしません。

以下はNcqrsで使用されるスキーマです。ご覧のとおり、「イベント」テーブルには関連データがCLOB(つまり、JSONまたはXML)として保存されています。これはオプション3に対応します(必要なのは1つの一般的な「イベント」テーブルだけなので、「ProductEvents」テーブルがないことです。Ncqrsでは、集約ルートへのマッピングは「EventSources」テーブルを介して行われ、各EventSourceは実際の集計ルート。)

Table Events:
    Id [uniqueidentifier] NOT NULL,
    TimeStamp [datetime] NOT NULL,

    Name [varchar](max) NOT NULL,
    Version [varchar](max) NOT NULL,

    EventSourceId [uniqueidentifier] NOT NULL,
    Sequence [bigint], 

    Data [nvarchar](max) NOT NULL

Table EventSources:
    Id [uniqueidentifier] NOT NULL, 
    Type [nvarchar](255) NOT NULL, 
    Version [int] NOT NULL

Jonathan Oliverのイベントストア実装のSQL永続化メカニズムは、基本的に、BLOBフィールドが「ペイロード」である「コミット」と呼ばれる1つのテーブルで構成れています。これはNcqrsの場合とほとんど同じですが、イベントのプロパティをバイナリ形式でシリアル化するだけです(たとえば、暗号化のサポートを追加します)。

Greg Youngは、GregのWebサイトで詳細に文書化されているように、同様のアプローチを推奨しています

彼の典型的な「イベント」テーブルのスキーマは次のとおりです。

Table Events
    AggregateId [Guid],
    Data [Blob],
    SequenceNumber [Long],
    Version [Int]

9
素敵な答え!EventSourcingを使用するために読み続けている主な引数の1つは、履歴をクエリする機能です。興味深いデータがすべてXMLまたはJSONとしてシリアル化されている場合に、クエリを効率的に行うレポートツールを作成するにはどうすればよいですか?テーブルベースのソリューションを探している興味深い記事はありますか?
Marijn Huizendveld 2012

11
@MarijnHuizendveldは、おそらくイベントストア自体に対してクエリを実行したくないでしょう。最も一般的なソリューションは、レポートまたはBIデータベースにイベントを投影するいくつかのイベントハンドラーをフックすることです。これらのハンドラーに対してイベント履歴を再生します。
Dennis Traub、2012

1
@Denis Traub回答ありがとうございます。イベントストア自体に対してクエリを実行しませんか?新しいBIケースを考案するたびに完全な履歴を再生しなければならない場合、それはかなり厄介な/強烈なものになると思いますか?
Marijn Huizendveld、2012

1
ある時点で、モデルからのデータを最新の状態で格納するために、イベントストアの他にテーブルも必要になると思いましたか?そして、モデルを読み取りモデルと書き込みモデルに分割します。書き込みモデルはイベントストアに適合し、イベントストアの格闘技は読み取りモデルを更新します。読み取りモデルには、システム内のエンティティを表すテーブルが含まれているため、読み取りモデルを使用してレポートや表示を行うことができます。何か誤解したに違いない。
theBoringCoder 2013年

10
@theBoringCoderイベントソーシングとCQRSが混乱しているか、少なくとも頭の中でつぶされているようです。彼らは頻繁に一緒に発見されますが、同じものではありません。CQRSでは読み取りモデルと書き込みモデルを分離しますが、Event Sourcingでは、アプリケーションの真の単一のソースとしてイベントストリームを使用します。
ブライアンアンダーソン

7

GitHubプロジェクトCQRS.NETには、いくつかの異なるテクノロジーでEventStoreを実行する方法の具体例がいくつかあります。執筆時点では、Linq2SQLとそれに伴うSQLスキーマ使用したSQLの実装があり、MongoDB用、DocumentDB(Azureの場合はCosmosDB)用、および(上記のように)EventStore を使用する実装があります。Azureには、フラットファイルストレージと非常によく似たテーブルストレージやブロブストレージなどの機能があります。

ここでの主なポイントは、それらがすべて同じプリンシパル/契約に準拠していることです。それらはすべて単一の場所/コンテナ/テーブルに情報を格納し、メタデータを使用して別のイベントからイベントを識別し、イベント全体を「そのまま」格納します。したがって、ドキュメントデータベース、リレーショナルデータベース、フラットファイルのいずれを選択するかに応じて、イベントストアの同じ意図にすべて到達するためのいくつかの異なる方法があります(いつでも気が変わって、移行またはサポートする必要があるとわかった場合に役立ちます)複数のストレージテクノロジー)。

プロジェクトの開発者として、私が行ったいくつかの選択についていくつかの洞察を共有できます。

まず、多くの理由で(整数ではなく一意のUUID / GUIDを使用しても)発見したため、戦略的な理由で順次IDが発生するため、IDを持つだけではキーとして十分に一意ではなかったため、メインのIDキー列をデータ/本当に(アプリケーションの意味で)一意のキーとなるものを作成するオブジェクトタイプ。保存する必要がないと言う人もいますが、それはグリーンフィールドであるか、既存のシステムと共存する必要があるかによって異なります。

保守性の理由から、コンテナー/テーブル/コレクションを1つに固執しましたが、エンティティ/オブジェクトごとに個別のテーブルをいじっていました。実際には、アプリケーションに "CREATE"権限が必要(通常​​、これは良い考えではありません...通常、例外/除外があります)、または新しいエンティティ/オブジェクトが存在するか、展開されるたびに、新しい作成する必要のある保管コンテナ/テーブル/コレクション。これは、ローカル開発にとっては非常に遅く、本番環境への展開には問題があることがわかりました。そうではないかもしれませんが、それは私たちの実際の経験でした。

もう1つ覚えておかなければならないのは、アクションXを実行するように要求すると、さまざまなイベントが発生する可能性があるため、コマンド/イベント/生成されたすべてのイベントが役立つことです。たとえば、ショッピングカートで「購入」を押すと、アカウントおよび倉庫保管イベントがトリガーされる可能性があります。消費するアプリケーションはこれらすべてを知りたいので、CorrelationIdを追加しました。これは、コンシューマーがリクエストの結果として発生したすべてのイベントを要求できることを意味しました。スキーマで確認できます

特にSQLでは、インデックスとパーティションが適切に使用されていないと、パフォーマンスが本当にボトルネックになることがわかりました。スナップショットを使用している場合は、イベントを逆の順序でストリーミングする必要があることに注意してください。いくつかの異なるインデックスを試したところ、実際には、本番環境の実際のアプリケーションをデバッグするためにいくつかの追加のインデックスが必要であることがわかりました。もう一度、スキーマでそれを確認します

他の運用中のメタデータは、運用ベースの調査中に役立ちました。タイムスタンプは、イベントが永続化されたのか発生したのかについての洞察を与えてくれました。これにより、非常に大量のイベントを発生させる、特にイベント駆動型のシステムに関する支援が得られ、ネットワークのパフォーマンスやネットワーク全体でのシステムの分散に関する情報が得られました。


ありがとうございます。たまたま、この質問を書いてからずっと、私は自分自身をgithub上のInforigami.Regaloライブラリの一部として構築しました。RavenDB、SQL Server、およびEventStoreの実装。笑って、ファイルベースのものをやろうと思った。:)
Neil Barnwell 2017

1
乾杯。私は主に、最近に出くわし、結果だけでなく、学んだ教訓のいくつかを共有する他の人のために回答を追加しました。
cdmdotnet 2017

3

さて、あなたはDatomicを見てみたいかもしれません。

Datomicは、柔軟な時間ベースのファクトのデータベースであり、柔軟なスケーラビリティとACIDトランザクションを備えたクエリと結合をサポートしています。

ここに詳しい答えを書いた

Datomicのデザインを説明するStuart Hallowayのトークはこちらでご覧いただけます

Datomicはファクトを時間内に格納するため、イベントソーシングのユースケースなどに使用できます。


2

ドメインモデルが進化するにつれて、ソリューション(1と2)は非常に早く問題になると思います。新しいフィールドが作成され、一部は意味が変わり、一部は使用できなくなります。結局、テーブルには何十ものnull許容フィールドがあり、イベントのロードは混乱します。

また、イベントストアは書き込みにのみ使用する必要があることを覚えておいてください。クエリを実行するだけで、集計のプロパティではなく、イベントをロードします。それらは別のものです(それがCQRSの本質です)。

ソリューション3人々が通常行うことは、それを達成する多くの方法があります。

例として、SQL Server でEventFlow CQRSを使用すると、次のスキーマでテーブルが作成されます。

CREATE TABLE [dbo].[EventFlow](
    [GlobalSequenceNumber] [bigint] IDENTITY(1,1) NOT NULL,
    [BatchId] [uniqueidentifier] NOT NULL,
    [AggregateId] [nvarchar](255) NOT NULL,
    [AggregateName] [nvarchar](255) NOT NULL,
    [Data] [nvarchar](max) NOT NULL,
    [Metadata] [nvarchar](max) NOT NULL,
    [AggregateSequenceNumber] [int] NOT NULL,
 CONSTRAINT [PK_EventFlow] PRIMARY KEY CLUSTERED 
(
    [GlobalSequenceNumber] ASC
)

どこ:

  • GlobalSequenceNumber:単純なグローバル識別。プロジェクション(readmodel)を作成するときに、欠落しているイベントの順序付けまたは識別に使用できます。
  • BatchId:アトミックに挿入されたイベントのグループの識別(TBH、これがなぜ役立つかわからない)
  • AggregateId集計の識別
  • データ:シリアル化されたイベント
  • メタデータ:イベントからのその他の有用な情報(たとえば、デシリアライズに使用されるイベントタイプ、タイムスタンプ、コマンドからのオリジネーターIDなど)
  • AggregateSequenceNumber:同じ集計内のシーケンス番号(書き込みが順不同で発生することがない場合に便利です。このフィールドを使用して楽観的同時実行性を実現します)

ただし、ゼロから作成する場合は、YAGNIの原則に従い、ユースケースに必要な最小限のフィールドで作成することをお勧めします。


BatchIdがCorrelationIdおよびCausationIdに関連している可能性があると私は主張します。イベントの原因を特定し、必要に応じてそれらをつなぎ合わせるために使用されます。
ダニエルパーク

かもしれない。これはそうですが、それをカスタマイズする方法を提供することは理にかなっています(たとえば、リクエストのIDとして設定する)が、フレームワークはそれを行いません。
Fabio Marreco

1

考えられるヒントは、設計の後に「緩やかに変化する次元」(type = 2)が続くことです。

  • 発生するイベントの順序(代理キーを使用)
  • 各状態の持続性(有効な開始-有効な終了)

左折りたたみ関数も実装できますが、将来のクエリの複雑さを考慮する必要があります。


1

これは遅い答えだと思いますが、スループット要件が高くなければ、RDBMSをイベントソースストレージとして使用することは完全に可能であることを指摘したいと思います。私が説明するために作成したイベントソーシング元帳の例をお見せします。

https://github.com/andrewkkchan/client-ledger-service 上記は、イベントソーシング元帳Webサービスです。 https://github.com/andrewkkchan/client-ledger-core-db また、上記ではRDBMSを使用して状態を計算しているため、トランザクションサポートなどのRDBMSに伴うすべての利点を享受できます。 https://github.com/andrewkkchan/client-ledger-core-memory そして、バーストを処理するためにメモリで処理する別のコンシューマがいます。

上記の実際のイベントストアはKafkaにまだ存在していると主張します。RDBMSは、挿入が常に追加である場合は特に、挿入が遅いためです。

このコードが、この質問に対して既に提供されている非常に優れた理論的な回答とは別に、説明に役立つことを願っています。


ありがとう。私はSQLベースの実装を構築してからずっと経ちます。クラスター化されたキーのどこかを非効率的に選択しない限り、RDBMSが挿入に時間がかかる理由がわかりません。追加のみで問題ありません。
Neil Barnwell、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.