web-dev-qa-db-ja.com

MongoDB / NoSQL:ドキュメントの変更履歴を保持する

データベースアプリケーションのかなり一般的な要件は、データベース内の1つ以上の特定のエンティティへの変更を追跡することです。行バージョン管理、ログテーブル、または履歴テーブルと呼ばれるこれを聞いたことがあります(他の名前があると確信しています)。 RDBMSでアプローチする方法はいくつかあります。すべてのソーステーブルからのすべての変更を1つのテーブル(ログの詳細)に書き込むか、各ソーステーブルに個別の履歴テーブルを作成できます。また、アプリケーションコードのログインを管理するか、データベーストリガーを介して管理するかを選択できます。

同じ問題の解決策がNoSQL /ドキュメントデータベース(具体的にはMongoDB)でどのように見えるか、そしてそれが統一された方法でどのように解決されるかを考えています。ドキュメントのバージョン番号を作成し、それらを上書きすることはありませんか? 「実際の」ドキュメントと「ログに記録された」ドキュメントに対して別々のコレクションを作成しますか?これはクエリとパフォーマンスにどのように影響しますか?

とにかく、これはNoSQLデータベースの一般的なシナリオですか?その場合、一般的なソリューションはありますか?

114
Phil Sandler

いい質問です、私もこれを自分で調べていました。

変更ごとに新しいバージョンを作成する

RubyのMongoidドライバーの バージョン管理モジュール に出会いました。自分では使用していませんが、 見つけることができるもの から、各ドキュメントにバージョン番号を追加します。古いバージョンはドキュメント自体に埋め込まれています。主な欠点は、ドキュメント全体が各変更で複製されることです。そのため、大規模なコンテンツを扱う場合、多くの重複コンテンツが保存されます。ドキュメント。ただし、小さなサイズのドキュメントを処理する場合やドキュメントを頻繁に更新しない場合は、この方法で問題ありません。

変更を新しいバージョンにのみ保存する

別のアプローチは、変更されたフィールドのみを新しいバージョンに保存することです。その後、履歴を「フラット化」して、ドキュメントの任意のバージョンを再構築できます。ただし、モデルの変更を追跡し、アプリケーションが最新のドキュメントを再構築できるように更新と削除を保存する必要があるため、これはかなり複雑です。フラットなSQLテーブルではなく、構造化されたドキュメントを扱うため、これは難しいかもしれません。

ドキュメント内に変更を保存する

各フィールドには、個別の履歴を含めることもできます。この方法では、ドキュメントを特定のバージョンに再構築するのがはるかに簡単です。アプリケーションでは、変更を明示的に追跡する必要はありませんが、値を変更するときにプロパティの新しいバージョンを作成するだけです。ドキュメントは次のようになります。

{
  _id: "4c6b9456f61f000000007ba6"
  title: [
    { version: 1, value: "Hello world" },
    { version: 6, value: "Foo" }
  ],
  body: [
    { version: 1, value: "Is this thing on?" },
    { version: 2, value: "What should I write?" },
    { version: 6, value: "This is the new body" }
  ],
  tags: [
    { version: 1, value: [ "test", "trivial" ] },
    { version: 6, value: [ "foo", "test" ] }
  ],
  comments: [
    {
      author: "joe", // Unversioned field
      body: [
        { version: 3, value: "Something cool" }
      ]
    },
    {
      author: "xxx",
      body: [
        { version: 4, value: "Spam" },
        { version: 5, deleted: true }
      ]
    },
    {
      author: "jim",
      body: [
        { version: 7, value: "Not bad" },
        { version: 8, value: "Not bad at all" }
      ]
    }
  ]
}

ただし、バージョンでドキュメントの一部を削除済みとしてマークするのは、やや厄介です。アプリケーションから削除/復元できるパーツのstateフィールドを導入できます。

{
  author: "xxx",
  body: [
    { version: 4, value: "Spam" }
  ],
  state: [
    { version: 4, deleted: false },
    { version: 5, deleted: true }
  ]
}

これらの各アプローチを使用すると、最新のフラット化されたバージョンを1つのコレクションに保存し、履歴データを別のコレクションに保存できます。文書の最新バージョンのみに関心がある場合、これによりクエリ時間が改善されます。ただし、最新バージョンと履歴データの両方が必要な場合は、1つではなく2つのクエリを実行する必要があります。そのため、単一のコレクションを使用するか、2つの別個のコレクションを使用するかの選択は、アプリケーションが履歴バージョンを必要とする頻度に依存する必要があります

この答えのほとんどは私の考えの頭脳であり、実際にこれを試したことはありません。振り返ってみると、重複データのオーバーヘッドがアプリケーションにとって非常に重要でない限り、最初のオプションがおそらく最も簡単で最良のソリューションです。 2番目のオプションは非常に複雑であり、おそらく努力する価値はありません。 3番目のオプションは、基本的にオプション2の最適化であり、実装がより簡単になりますが、オプション1を使用できない場合を除き、実装する価値はおそらくないでしょう。

これに関するフィードバック、および問題に対する他の人々の解決策を楽しみにしています:)

92

これをサイトに部分的に実装し、「別のドキュメントにリビジョンを保存」(および別のデータベース)を使用します。差分を返すカスタム関数を作成し、保存します。それほど難しくなく、自動回復を可能にします。

7
Amala

ドキュメント内に変更を保存するのバリエーションがないのはなぜですか?

各キーペアに対してバージョンを保存する代わりに、ドキュメント内の現在のキーペアは常に最新の状態を表し、変更の「ログ」は履歴配列内に保存されます。作成後に変更されたキーのみがログにエントリを持ちます。

{
  _id: "4c6b9456f61f000000007ba6"
  title: "Bar",
  body: "Is this thing on?",
  tags: [ "test", "trivial" ],
  comments: [
    { key: 1, author: "joe", body: "Something cool" },
    { key: 2, author: "xxx", body: "Spam", deleted: true },
    { key: 3, author: "jim", body: "Not bad at all" }
  ],
  history: [
    { 
      who: "joe",
      when: 20160101,
      what: { title: "Foo", body: "What should I write?" }
    },
    { 
      who: "jim",
      when: 20160105,
      what: { tags: ["test", "test2"], comments: { key: 3, body: "Not baaad at all" }
    }
  ]
}
4
Paul Taylor

現在のNoSQLデータベースと履歴NoSQLデータベースを使用できます。毎日実行される夜間のETLがあります。このETLはすべての値をタイムスタンプ付きで記録するため、値の代わりに常にタプル(バージョン管理されたフィールド)になります。現在の値に変更が加えられた場合にのみ新しい値を記録し、プロセスのスペースを節約します。たとえば、この歴史的なNoSQLデータベースjsonファイルは次のようになります。

{
  _id: "4c6b9456f61f000000007ba6"
  title: [
    { date: 20160101, value: "Hello world" },
    { date: 20160202, value: "Foo" }
  ],
  body: [
    { date: 20160101, value: "Is this thing on?" },
    { date: 20160102, value: "What should I write?" },
    { date: 20160202, value: "This is the new body" }
  ],
  tags: [
    { date: 20160101, value: [ "test", "trivial" ] },
    { date: 20160102, value: [ "foo", "test" ] }
  ],
  comments: [
    {
      author: "joe", // Unversioned field
      body: [
        { date: 20160301, value: "Something cool" }
      ]
    },
    {
      author: "xxx",
      body: [
        { date: 20160101, value: "Spam" },
        { date: 20160102, deleted: true }
      ]
    },
    {
      author: "jim",
      body: [
        { date: 20160101, value: "Not bad" },
        { date: 20160102, value: "Not bad at all" }
      ]
    }
  ]
}
2
Paul Kar.