2つのデータベースアーキテクチャ:運用と歴史


8

私は珍しいデータベース構造について考え、誰かが以前にそれを使用しているのを見たことがあるのではないかと思いました。基本的に2つのデータベースを使用しています。

  • 最初のデータベースは、現在有効なデータのみを保持します
  • 2番目のデータベースは、最初のデータベースで入力、更新、または削除されたすべての履歴を保持します

シナリオ

私は、発生するすべてをログに記録する必要があり、データが頻繁に変更されるプロジェクトに取り組んでいます。

(実際のものではない)

サッカーリーグのデータベース設計を行う必要があります。このリーグには選手とチームがあります。プレイヤーはしばしばチームを切り替えます。

  • 最初の要件:データベースは、次の試合をプレイするために必要な情報を保持する必要があります。これは、すべてのプレーヤー、チーム、および各プレーヤーが現在所属しているチームのリストを意味します。
  • 2番目の要件:データベースは、統計の生成に使用する履歴値を保持する必要があります。これは、チームに所属していたすべてのプレーヤーのリスト、またはプレーヤーが参加していたすべてのチームのリストを意味します。

問題

これらの2つの要件は、互いに正反対です。同じデータベースですべてを実行しようとしましたが、意味がありません。最初の要件は「次の試合のプレー」のみを対象とし、2番目の要件は「統計の生成」のみを対象とします。

同じデータベースですべてを行うために、明らかなソフト削除を使用して情報を削除/更新する一種の「挿入のみ」のデータベースを使用しました...

最初は簡単な作業のように見えましたが、プレーヤー、チーム、および各プレーヤーの現在のチームのリストを保持することは、突然、かなり難しくなります。次の一致を再生するために必要なアプリケーションロジックはすでに十分に複雑ですが、データベースは非常に役に立たない設計になり、アプリケーションは次の一致を再生するためにすべてのクエリに「削除済み」チェックを追加する必要があります

「チームのすべてのプレーヤーが私に来てください」と叫ぶコーチになりたいですか?それから2000人のプレーヤーがあなたのところに来ます。その時点で、おそらく、「このチームで削除されていないすべてのプレイヤー私のところにやって来ます」と叫ぶでしょう(この愚かなデザインについては誓います)。

私の結論

なぜすべてを同じデータベースに入れる必要があるのか​​不思議に思いました。多くの列(time_created、who_created_it、time_deleted、who_deleted_it)を追加しない限り、ソフト削除はすべてをログに記録するのに不十分なだけでなく、すべてを複雑にします。データベースの設計が複雑になり、アプリケーションの設計も複雑になります。

また、これら2つの要件は、分割できない1つのアプリケーションの一部として受け取りますが、これは2つの完全に異なるアプリケーションであると考え続けています。なぜ一緒にすべてをしようとしているのですか?

そのとき、データベースを2つに分割することを考えました。次の試合をプレイするためにのみ使用され、現在有効な情報のみが含まれる運用データベースと、作成、削除、および誰が行ったかについて、これまで存在していたすべての情報を保持する履歴データベース。

目標は、2番目のデータベース(履歴)にできるだけ多くの情報を保持しながら、最初のデータベース(運用)とアプリケーションをできるだけシンプルに保つことです。

ご質問

  • 以前にそのデザインを見たことがありますか?名前はありますか?
  • 私が見逃している明らかな落とし穴はありますか?



編集2015-03-16

現在のアーキテクチャ

基本的に、アーキテクチャ全体を2ステップのプロセスと考えることができます。

ステップ1 :

  • アプリケーションが実行中で、ユーザーがいくつかのアクションを実行しています
  • イベントが発生するたびに、イベントテーブルに自動的に記録されます(監査ソリューション)。
  • 次に、運用データベースの正しい行が更新されます

ステップ2 :

  • ジョブがイベントテーブルの最新の挿入を読み取り、この新しいデータを履歴データベースに挿入します。
  • ユーザーは履歴データベースを照会して、必要な情報を取得します。

イベントテーブルから、いつでも情報を再構築できます。問題は、このイベントテーブルが簡単にクエリできないことです。これは、履歴データベースが機能する場所です。必要なものを正確に取得しやすい方法でデータを提示する。

すべてを同じテーブルに入れるときの追加の問題

各クエリで「削除されている」チェックの複雑さが増すことについては、すでに懸念を表明しています。しかし、別の問題があります:整合性

外部キーと制約多用して、データベースのデータがいつでも有効であることを確認しています。

例を見てみましょう:

制約:チームごとにゴールキーパーは1人だけです。

チームごとにゴールキーパーが1人だけかどうかをチェックする一意のインデックスを追加するのは簡単です。しかし、ゴールキーパーを変更するとどうなりますか。以前の1つについての情報を保持する必要がありますが、同じチームに2つのゴールキーパーがいます。1つはアクティブで、もう1つは非アクティブであり、制約と矛盾します。

確かに制約にチェックを追加するのは簡単ですが、管理および検討する必要があるもう1つのことです。


いくつかの標準的なアプローチについてゆっくりと変化する次元を見てみましょう
Turch

1
価値のあるチェック:イベントソーシング。テーブルの1つはイベントのログであり、完全な監査証跡を提供します。その一部は後でアーカイブできます。別の表は、すべてのイベントからの変更を適用することによって作成された現在の要約状態です。イベントストリームの一部からのみ復元できるように、以前のチェックポイントを含めることができます。
9000年

回答:


7

履歴は頻繁に発生しますが、履歴(監査レコードとも呼ばれます)は、同じテーブルまたは同じデータベースの別のテーブルに保持されます。

たとえば、テーブルの更新が挿入として実装され、古い「現在の」レコードに履歴レコードであることを示すフラグが設定され、更新されたときのタイムスタンプがあるシステムを使用していた列に書き込まれます。

今日私はすべての変更が専用の監査テーブルに書き込まれ、更新がテーブルで行われるシステムで作業しています。

後者はよりスケーラブルですが、一般的な方法で実装するのは簡単ではありません。

クエリをシンプルにし、「is current」フラグを追加する必要がないという目標を達成する最も簡単な方法は、ビューまたはストアドプロシージャを介したクエリの読み取りのみを許可することです。次に、「get all player」と言う呼び出しを行うと、ストアドプロシージャは現在のプレーヤーのみを返します(2番目の手順を実装して、どのプレーヤーを返すかをより細かく制御してプレーヤーを返すことができます)。これは書き込みにも適しています。プレーヤーを更新するためのストアドプロシージャは、クライアントが履歴メカニズムを知らなくても、必要な履歴の詳細を記述してプレーヤーを更新できます。このため、ストアドプロシージャは、現在のプレーヤーのみを返すビューよりも優れています。これにより、DBアクセスメカニズム全体が読み取りと書き込みで同じに保たれるため、すべてがsprocを通過します。


現在、私は後者を好みます。ストアドプロシージャまたはビューを介してすべてのクエリを実行することは大したことではないように思われるかもしれませんが、それらすべてを維持することはかなり複雑になります。ビューとストアドプロシージャにも多くの制限があります。blog.sqlauthority.com/2010/10/03/... stackoverflow.com/questions/921190/...
Gudradain

1
「オブジェクトのローカル変数はグローバルの使用を制限する」と言っているような制限があります。データアクセスサーフェスを制限すると、データアクセスサーフェスがより安全になり、設計を改善する必要があります。これらは良いことです。リンク:ビューは制限されており、それらは置換テーブルとして設計されていません。参加できないsprocは、別のsprocが必要であることを意味します。SQLをsprocsで作成することは、クライアントサイドコードで作成するよりも悪くはありません。これを完全に適切に維持することで、sprocコードを簡単に維持できます。
gbjbaanb 2015年

1
@Gudradain gbjbaanbと一緒です。1つのRDBMSに関する1つの不十分に書かれたブログ投稿と、誰かがストアドプロシージャで不自然なことを行おうとしていてできないスタックオーバーフローに関する質問への無関係なリンクに基づいて、優れたクライアントサーバープラクティスの憎悪に基づいています。基本的には、ビューを読み取り、ストアドプロシージャを介して書き込みます。はい、バージョン管理とリリースに関するプロセスが必要ですが、とにかくそれが必要です。
mcottle 2015年

@mcottle ORMとデータアクセスレイヤーをすでに使用しているプロジェクトがある場合、ビューとsprocをパススルーするのは実際的ではないようです。また、すべてをデータベースに入れると、プログラムロジックのカプセル化が不十分になります。多くの場合、同じデータベースを使用する複数のアプリケーションがあります。同じ場所に多くの異なるアプリケーションに関連するすべてのストアドプロシージャを配置しても意味がありません。どこにでも「削除される」を追加するこのアプローチをとらなければならない場合は、(アプリケーションの)データアクセス層で行います。
Gudradain 2015年

@Gudradainはい、「多くの異なるアプリケーション」が同じテーブルとデータを喜んで使用しているためです。sprocは、DBレイヤーに貼り付けられたクライアントロジックではなく、別の形式のテーブルと考えてください。ORMを介してそれらを呼び出すこともできます。実際に多くの異なるアプリケーションでそれらを使用したい場合は、スキーマを使用してそれらを分離します-DBアプリケーション固有にします。他のアプリケーションに影響を与えないデータAPI)。Sproc ベストプラクティスであり、クライアントコードは「is deleted」を追加するのを忘れる可能性があります。
gbjbaanb 2015年

4

データベースを2つのデータベースに分割すると、リレーショナル参照と参照整合性チェックのすべての利点が失われます。私はそのようなことを試したことがありませんが、それは大きな悪夢になると思います。

特定のシステムを記述するデータセット全体が単一のデータベースに属していると思います。データへのアクセスの利便性の問題は、データ編成を決定するための正当な理由になることはほとんどありません。

データへのアクセスの利便性の問題は、RDBMSが提供する便利な機能を利用して処理する必要があります。

そのため、「現在の」データベースと「履歴」データベースを使用する代わりに、データベースは1つだけにし、データベース内のすべてのテーブルに「履歴」というプレフィックスを付ける必要があります。次に、「現在」として表示するテーブルごとに1つずつ、一連​​のビューを作成し、表示したくない履歴行を各ビューで除外し、現在のものだけを通過させる必要があります。

これはRDBMSの便利な機能を利用してプログラマーの便利な問題に対処し、データベースの設計はそのままにしておくため、問題の適切な解決策です。

発生する可能性が高い問題の例(コメントが長すぎる)

チームに関する現在の情報を表示している画面(たとえば、team.id = 10、team.name = "Manchester United")を見ていて、「履歴を表示」というボタンをクリックするとします。その時点で、同じチームの履歴情報を表示する画面に切り替える必要があります。したがって、「現在の」データベースでは「マンチェスターユナイテッド」の略であることを知っている10のIDを取得し、希望する必要があります。このID番号10も、履歴データベースでは「マンチェスターユナイテッド」を表しています。idが両方のデータベースでまったく同じエンティティを参照することを強制する参照整合性ルールはないため、基本的に、暗黙的に接続された2つの完全にばらばらなデータセットがあり、暗黙の接続のみが認識し、守られ、維持されることが約束されます。データベース外のコードで。

そしてもちろん、これは「チーム」テーブルなどの主要なテーブルだけでなく、「プレーヤーの位置:フォワード、ミッドフィールダー、ゴールキーパーなど」のようにサイドに配置する最も小さなテーブルにも当てはまります。

同じデータベース内で歴史性を実現する

歴史性を維持するにはさまざまな方法があり、それらはこの質問の範囲を超えていますが、これは基本的にあなたのこの特定のアイデアが持つかもしれない落とし穴ですが、ここにアイデアがあります:

これまでにデータベースに加えられた変更ごとに1つのエントリを含むログテーブルを維持できます。このようにして、すべての「現在の」テーブルからデータを完全に消去し、ログに記録された変更を再生することで完全に再構築できます。もちろん、最初から現在までの変更を再生して「現在の」テーブルを再構築できる場合は、一時的なテーブルのセットを構築して、再生することによって特定の時間座標でデータベースのビューを取得することもできます。時間の始まりからその特定の時間座標までの変化。

これは「イベントソーシング」として知られています(Martin Fowlerによる記事)。


2つのデータベース間または1つのデータベース内のリレーショナル参照と参照整合性を意味しますか?これらの制約を尊重するデータのみをデータベースに挿入する必要があるため、外部キーと制約を適切な場所で使用しています。
グドラデイン2015年

まあ、私が知る限り、単一のデータベース内でのみリレーションを持つことができます。データベース間の関係を許可するRDBMSが存在する可能性がありますが、それは他の製品で期待されていることではなく、控えめな意見ではありません。
Mike Nakis、2015年

したがって、データベースを2つのデータベースに分割することで、各データベース内で別々にリレーションを持つことができますが、2つのデータベース間ではできません。災害を引き起こす可能性があります。
Mike Nakis、2015年

1
履歴データベースのデータは、運用データベースからのみ取得され、そのデータベースの更新/削除はありません。私の観点から見ると、このデータベースは関係や制約さえも必要としません。ある時点で運用データベースにあったデータを保持するだけです。そのデータについて確認する操作や何かはありません。重要なのは、他のデータベースに存在しているかどうか(はい/いいえ)だけです。私はそれの問題が何であるかをちょっと
見落とし

これはコメントには長すぎるので、私の回答の修正として追加しました。
Mike Nakis、2015年

2

まず最初に、コードベースは、ビジネスロジック(次の試合でプレーするプレーヤーを選ぶ)がデータベースアクセスロジック(データベースに接続し、データ構造をマップするレイヤー)と区別されるという懸念を明確に分離していますか?データベース行に、またはその逆)?これに対する答えは、なぜこれに対処しているのかを説明するのに大いに役立ちます。

データベースの設計が複雑になり、アプリケーションの設計も複雑になります。

さて...

次の一致を再生するために必要なアプリケーションロジックはすでに十分に複雑ですが、データベースは非常に役に立たない設計になり、アプリケーションは次の一致を再生するためにすべてのクエリに「削除済み」チェックを追加する必要があります。

あなたはRDBMSについて話していると仮定すると、あなたはまだ持っていることができbitemporalデータベースキャプチャすべての有効なデータの過去、現在、おそらく将来、ということ、その後はあなたのためのデータベースクエリのロジックを処理するために強力な、十分なデータベースアクセスライブラリ/ ORMフレームワークを使用します。データベースビューを使用して、選択を支援することもできます。次に、コードのビジネスロジック部分は、基礎となる一時的なフィールドを知る必要がないはずです。これにより、上記の問題が解消されます。

たとえば、アプリケーションでSQLクエリをハードコーディングする必要はありません。

SELECT * FROM team_players WHERE team_id = ? AND valid_from >= ? AND valid_to <= ? AND ...

?パラメーターバインディングとして使用)

架空のデータベースアクセスライブラリでは、(疑似コード)としてクエリを実行できます。

dbConnection.select(Table.TEAM_PLAYERS).match(Field.TEAM_ID, <value>).during(Season.NOW)

または、データベースビューのアプローチを使用します。

-- Using MySQL dialect for illustration
CREATE VIEW seasonal_players AS SELECT * FROM team_players 
    WHERE valid_from >= ... AND valid_to <= ... AND ...

次に、その期間中のプレーヤーを次のようにクエリできます。

SELECT * FROM seasonal_players WHERE team_id = ?

提案されたデータベースモデルに戻り、運用データベースからの履歴データの削除をどのように処理しますか?INSERT運用データベースと履歴データベースのそれぞれに2つの類似した行を単にDELETE置き、履歴データを削除するユーザーアクションがある場合はいつでも、運用データベースでを実行しますか?


大きく考える

「データベース」がスケールアウトされた分散データベースクラスタリング/ストリーム処理ソリューションである大規模なデータ処理について話している場合、アプローチはLambdaと漠然と(おそらく定義された用語のいくつかだけで)聞こえますアーキテクチャあなたの「歴史」(すなわち過去-リアルタイム)データのバッチは、あなたが探している統計情報の種類を実行するために別々に処理し、あなたの「操作」されているが、データ(リアルタイムストリーミングすること)のために、クエリ可能なままバッチ処理がそれらを保持する前に事前定義された制限。しかし、このアプローチの基礎は利点によって、より駆動されると、自分のアプリケーションロジックの単なる簡略化するよりも、現在のビッグデータの実装の限界。


編集(OPの編集後)

私は以前にこれに答えるべきだったが、とにかく:

また、これら2つの要件は、分割できない1つのアプリケーションの一部として受け取りますが、これは2つの完全に異なるアプリケーションであると考え続けています。なぜ一緒にすべてをしようとしているのですか?

これは一般に、エンドユーザーが必要なデータベースの数ではなく、機能の観点から考える傾向があるためです

また、次のように述べています。

ステップ1:

  • イベントが発生するたびに、イベントテーブルに自動的に記録されます(監査ソリューション)。
  • 次に、運用データベースの正しい行が更新されます。

ステップ2:

  • ジョブはイベントテーブルの最新の挿入を読み取り、この新しいデータを履歴データベースに挿入します。

すごい!これで、イベントテーブルができました。これは、他の回答で言及されているイベントソーシングの概念です。ただし、イベントテーブルを読み取り、運用データベースの正しい行を更新するのは何ですか。これは、最後のイベントを読み取って履歴データベースに挿入するジョブと同じですか?

制約の例に関するもう1つのポイント:

外部キーと制約を多用して、データベースのデータがいつでも有効であることを確認しています。

例を見てみましょう:

制約:チームごとにゴールキーパーは1人だけです。(試合中にピッチでアクティブに、ただの付記

任意の時点」とは、有効時間またはトランザクション時間を指しますか?

3人目の新しいゴールキーパーがいるとどうなりますか?2つの「古い」ゴールキーパーのデータを有効に保つために、履歴データベースの一時フィールドに一意の制約を作成しますか?


何かを挿入/更新/削除する前に、この時点でデータを再構築するために必要なすべての情報を保持するイベントを作成し、運用データベースを更新します。その後、イベント情報を履歴データベースに追加します。プロセスはほとんど自動で、維持する必要のあるものはほとんどありません。
グドラデイン2015年

1
イベントは、トリガーによってテーブルに直接作成されます。イベントが保存され、行が作成/更新/削除されるか、何も起こりません。したがって、それは同時に起こります。このようにして、イベントが作成され、イベントが運用データベースの関係と制約によって指定された有効なことを実行しようとしていることを確認します。
グドラダイン2015年

1

これは実際には、データベーストランザクションが一般的に実装されている方法に似ていますが、履歴データは通常、運用データベースに書き込まれた後に破棄されます。私が考えることができる最も近いプログラミングパターンは、イベントソーシングです。

これら2つのデータベースを分割することは正しいことだと思います。より具体的には、履歴データはいつでも運用データベースを再構築するのに十分であるため、「運用」データベースをキャッシュと見なします。アプリケーションの性質とパフォーマンス要件によっては、プログラムを起動するたびにメモリ内の履歴データから現在の状態を再構築することが妥当であれば、このキャッシュを個別のデータベースとして維持する必要がない場合があります。

落とし穴に関する限り、遭遇する可能性がある主な問題は、(同じプログラム内で、または複数のクライアントが同時にデータベースを使用することによって)何らかの種類の同時実行性が必要な場合です。その場合は、履歴データベースと運用データベースへの変更がアトミックに行われるようにする必要があります。同じプログラム内で同時実行が発生する場合、最善の策はおそらく何らかのロックメカニズムです。同じデータベースと対話する複数のクライアントの場合、最も簡単な方法は、両方を同じデータベース内のテーブルとして保持し、トランザクションを使用してデータベースの一貫性を保つことです。


1
イベントソーシングは実際、私が現在持っているものにかなり近いです。監査証跡は、イベントのリストと比較できる各挿入/更新/削除から自動的に生成されます。
グドラデイン2015年

0

データベースでのデータのバージョン管理のサポートは確立されたトピックであり、一部のDBMSはこの機能をサポートしています。MariaDBがデータのバージョニング(https://mariadb.com/resources/blog/automatic-data-versioning-in-mariadb-server-10-3/)をサポートし、クイック検索でOrpheusDB(https:/ /medium.com/data-people/painless-data-versioning-for-collaborative-data-science-90cf3a2e279d

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.