SQL Server のような従来のデータベースで実際に何が起こるか考えてみてください。
- 項目がテーブルから作成、更新、または削除されると、テーブルに関連付けられたインデックスも更新する必要があります。
- テーブルのインデックスが多いほど、書き込み操作は遅くなります。
- 既存のテーブルに新しいインデックスを作成すると、完全に構築されるまでまったく使用されません。クエリに応答できるインデックスが他にない場合は、低速のテーブル スキャンが発生します。
- 変更中に他の人が既存のインデックスからクエリを実行しようとすると、変更が完了するまでリーダーがブロック
C
されますA
。
- これにより、多くの場合、読み取りが遅くなり、タイムアウトになり、デッドロックが発生する可能性があります。
「結果整合性」という NoSQL の概念は、これらの問題を軽減するように設計されています。A
一貫性よりも有効性を優先することにより、読み取りが最適化されますC
。RavenDB はこの点でユニークではありませんが、一貫性を維持する機能を備えているという点で多少特殊です。注文の確認やエンド ユーザーのプロファイルの表示など、単一のドキュメントを取得する場合、これらの操作は ACID に準拠しており、「結果整合性」設計の影響を受けません。
「結果整合性」を理解するには、Web サイトで製品のリストを見ている一般的なユーザーについて考えてみてください。同時に、あなたの会社の営業スタッフは、カタログの修正、新製品の追加、価格の変更などを行っています。リストがこれらの変更と完全に一致していることは、おそらくさほど重要ではないと主張する人もいるでしょう。結局のところ、数秒前にサイトにアクセスしたユーザーは、とにかく変更されていないデータを受け取っていたでしょう. 最も重要なことは、製品の結果を迅速に提供することです。書き込みが進行中であるという理由でクエリをブロックすると、顧客への応答時間が遅くなり、Web サイトでのエクスペリエンスが低下し、売り上げが失われる可能性があります。
したがって、RavenDB では次のようになります。
- ドキュメント ストアに対して書き込みが行われます。
- 単一
Load
の操作は直接ドキュメント ストアに移動します。
- クエリはインデックス ストアに対して発生します
- ドキュメントが書き込まれると、既に定義されているインデックスについて、データがドキュメント ストアからインデックス ストアにコピーされます。
- インデックスにクエリを実行すると、バックグラウンドで実行中のコピーの状態に関係なく、そのインデックスに既に含まれているものを取得できます。これが、インデックスが「古い」場合がある理由です。
- インデックスを指定せずにクエリを実行し、Raven がクエリに応答するために新しいインデックスを必要とする場合、Raven はオンザフライでインデックスの構築を開始し、それらの結果の一部をすぐに返します。結果の 1 ページを提供するのに十分な長さのブロックのみです。その後、バックグラウンドでインデックスの作成を続行するため、次回クエリを実行するときに、より多くのデータを利用できるようになります。
それでは、このアプローチのマイナス面を示す例を挙げましょう。
- 営業担当者は、アルファベット順に並べられた「商品リスト」ページに移動します。
- 最初のページでは、「りんご」が現在販売されていないことがわかります。
- 「製品を追加」をクリックして、「りんご」と入力する新しいページに移動します。
- その後、「製品リスト」ページに戻りますが、インデックスが古いため、リンゴは表示されません。WTF-そうですか?
この問題に対処するには、データのすべての閲覧者が同等と見なされるべきではないことを理解する必要があります。その特定の営業担当者は、新しく追加された製品を見たいと要求するかもしれませんが、顧客は同じレベルの緊急性でそれを知ったり気にかけたりすることはありません.
そのため、営業担当者が表示している「商品リスト」ページで、次のような操作を行うことができます。
var results = session.Query<Product>()
.Customize(x => x.WaitForNonStaleResultsAsOfLastWrite())
.OrderBy(x=> x.Name)
.Skip((pageNumber-1) * pageSize).Take(pageSize);
カタログの顧客のビューでは、そのカスタマイズ行を追加したくないでしょう。
非常に正確にしたい場合は、もう少し最適化された戦略を使用できます。
- 「製品の追加」ページから「製品のリスト」ページに戻るときに、追加したばかりの ProductID を渡します。
そのページでクエリを実行する直前に、ProductID が渡された場合は、クエリ コードを次のように変更します。
var product = session.Load(productId);
var etag = session.Advanced.GetEtagFor(product);
var results = session.Query<Product>()
.Customize(x => x.WaitForNonStaleResultsAsOf(etag))
.OrderBy(x=> x.Name)
.Skip((pageNumber-1) * pageSize).Take(pageSize);
これにより、インデックスからの他の結果と共に結果リストに含まれる 1 つの製品の変更だけを取得するために絶対に必要な時間だけ待機することが保証されます。
ProductId の代わりに etag を返すことでこれをわずかに最適化できますが、アプリケーションの他の場所からの再利用性が低下する可能性があります。
ただし、リストがアルファベット順に並べ替えられていて、「りんご」の代わりに「プラム」を追加した場合、これらの結果がすぐに表示されない可能性があることに注意してください. ユーザーがその商品を含むページにスキップするまでに、その商品はすでにそこにある可能性があります。