データを一致させるビューを書くことは自由ですが、様々なデザインドキュメントやビュー定義を作成したり、まとめるのに必要な性能やストレージとの関連については考慮しておくべきです。
ビューを開発し、配備するときは次について考慮すべきです。
デザインドキュメントあたりのビューの量
同じデザインドキュメントの各ビューにある各map/reduceの組み合わせで利用されるインデックスが、同時に更新されるので、同じデザインドキュメントであまりにも多くのビューを宣言するのは避けてください。たとえば、5つの異なるビューのあるデザインドキュメントがある場合、ビューのひとつのみにアクセスされたときでさえ、5つすべてのビューがほぼ同時に更新されます。
これは、特に頻繁にアクセスされるビューでは、ビューのインデックス生成時間の増加につながります。代わりに、頻繁に使用されるビューを個別のデザインドキュメントとして切り出してください。
デザインドキュメントあたりのビューの正確な数は、含まれるビューとビュー定義のグルーピングの更新頻度の必要条件の組み合わせから決められるべきです。たとえば、高い頻度で更新しなければならない(たとえば、ブログ投稿へのコメント)ビューと、それより低い頻度で更新しなければならない(たとえば、人気のブログ投稿)ビューがある場合、それらのビューを2つのデザインドキュメントに分けてください。するとコメントビューは頻繁に、そしてもうひとつのビューに依然せず更新されます。
stale
パラメータを使用することによって、ビューの更新をいつでも設定することができます。(「インデックス更新とstale
パラメータ」を参照。)また、個々のデザインドキュメントのための様々な自動ビュー更新時間をいつでも設定できます。より詳細な情報については、「インデックスの自動更新」を参照してください。
既存ビューを変更すること
既存のビュー定義を変更したり、開発ビューで完全なビルドを実行している場合、ビュー全体が再作成される必要があります。加えて、同じデザインドキュメント内で定義されているすべてのビューも再作成されます。
ひとつのデザインドキュメント内ですべてのビューを再構築することは、各ドキュメントを各map()
とreduce()
関数がパースする必要があり、ディスク上に結果のインデックスを格納するというように、I/OそしてCPUの負荷の高い操作です。
この再構築のプロセスは、クラスタ内のすべてのノード間で発生し、ビューが再作成されるまで全体的なディスクI/OとCPUの使用率が増加します。このプロセスは、最新に保つ必要があるプロダクション用のデザインドキュメントとビューでも同じように動作します。
ドキュメントIDが含まれていないこと
ドキュメントIDはビューがアクセスされたときにビューシステムによって自動的に出力されます。reduceが有効でないビューにアクセスするときは、行を生成するドキュメントのドキュメントIDをいつでも取得することができます。keyもしくはvalueデータに(meta.id
からの)ドキュメントIDを含めるべきではありません。
ドキュメントフィールドをチェックすること
map()
もしくはreduce()
関数のもとになるドキュメント内のフィールドや属性は、その値を参照したり比較したりする前にチェックすべきです。これはデザインドキュメントのビュー定義が同時に処理されるためです。デザインドキュメント内のひとつのビューで実行時エラーが発生すると、同一デザインドキュメント内の他のビューが生成されなくなります。ビューでの実行時エラーの一般的な原因は、参照されたフィールドや属性が存在しないか、無効であるためです。
最も一般的な問題は、アクセスされたnullオブジェクト内のフィールドです。これはデザインドキュメント内のすべてのビューで実行されたとき失敗の原因となる実行時エラーを生成します。この問題を解決するには、使用されたり、またはコンテンツの値がチェックされる前に、指定されたオブジェクトが存在するかどうかをチェックするべきです。たとえば、doc.ingredient
オブジェクトが存在しない場合、nullオブジェクトのlength
属性へのアクセスが失敗するので、次のビューは失敗します:
function(doc, meta) { emit(doc.ingredient.ingredtext, null); }
emit()
を呼び出す前に親オブジェクトへのチェックを追加することで、もとのドキュメントでフィールドが存在していなければ関数は呼び出されないことが保証されます。
function(doc, meta) { if (doc.ingredient) { emit(doc.ingredient.ingredtext, null); } }
if
文内で値を比較するときも、同じチェックを行うべきです。
このテストは、属性または子の値をチェックしているすべてのオブジェクト(例えば、配列のインデックス)で実行されるべきです。
ビューのサイズ、ディスクストレージ、そしてI/O
map関数内でemit()
ステートメントにより宣言された情報は、ビューのインデックスデータに含まれており、ディスクに格納されます。この情報を出力すると、インデックスに次のような影響があるでしょう:
ディスク上のインデックスサイズの増加—生成されたビュー内のより詳細で複雑なkey/valueの組み合わせは、ディスクへより多くの情報を格納します。
ディスクI/Oの増加—ディスク上のデータを処理し、格納して、そしてビューを問い合わせるときにデータを取得するために、ビューでのより大きく複雑なkey/value定義は、バックグラウンドでのデータの更新と読み取りの両方に必要となる全体的なディスクI/Oを増加させます。
その結果、インデックスが非常に大きくなる可能性があります。複数のビューを作成したり、ビューの出力としてドキュメントの大部分、あるいは全体を含める場合などは、オリジナルのドキュメントデータのサイズを大きく超えるサイズになることがあります。
たとえば、各ビューがvalueとしてドキュメント全体を含み、10のビューが定義されている場合、インデックスファイルのサイズはビューが生成されたもとのデータのサイズの10倍以上にもなります。500バイトのドキュメントが100万件あると、ソースデータが500MBしかないのにビューのインデックスは約5GBにもなります。
ビューにvalueデータを含むこと
ビューはemit()
により出力されるキーと値の両方を格納します。最高のパフォーマンスを確保するために、ビューは情報を検索して選択するために必要となる最小限のキーデータのみを出力するべきです。emit()
によって出力される値は、reduce()
内で使用されるデータで必要となるときにのみ使用されるべきです。
個々のドキュメント、もしくはバルクでのドキュメントを取得する主要なCouchbaseAPIを使用してドキュメントの値を取得することができます。いくつかのSDKが自動的にこの操作を実行できます。Couchbase SDKsを参照してください。
このモデルを使用すると、出力されるビューのデータがドキュメントの状態と一致しないところやビューがドキュメント自体にもはや格納されていないドキュメントからの値のデータを出力するようなところでの問題を防ぎます。
reduceを使用する予定のないビューでは、null値を出力すべきです:
function(doc, meta) { emit(doc.experience, null); }
これは必要な情報のみを含み、ビュー更新時の高い性能と低いディスク使用率を確保する最適化されたビューを作成します。
ビューの出力にドキュメント全部を含んでいないこと
ビューのインデックスは基本的な情報を提供し、もとのドキュメントを指し、暗黙的に返されるドキュメントIDを介するように設計されるべきです。ビューの出力内にすべてのドキュメントを含むことは悪い習慣です。
クライアントライブラリを介してあとで個々のドキュメントデータへリクエストすることでいつでもすべてのドキュメントデータにアクセスすることができます。通常はこうすることでビューインデックスにすべてのドキュメントデータを含むより非常に早くなり、そしてすべてのドキュメントデータをロードしなくてもインデックス性能を最適化することができます。
たとえば、次に悪いビューの例を示します:
function(doc, meta) { emit(doc.experience, doc); }
上記のビューには、性能やインデックスサイズに重大な影響を与えることがあります。
これは、インデックス内のすべてのドキュメントの内容を含みます。
代わりに、ビューは次のように定義されるべきです:
function(doc, meta) { emit(doc.experience, null); }
クライアントライブラリを通じてドキュメントデータに個々にアクセスするか、ビルトインクライアントライブラリのオプションを使用してドキュメントデータを別々に習得することができます。
ドキュメントタイプを使用すること
(格納されたJSON内のフィールドを使用することにより、ドキュメントの構造を示すため)ドキュメントタイプを使用する場合、大きなデータベースではビュー関数が更新もインデックスの追加もされないのにドキュメントタイプのインデックスを更新するために呼び出されるということを意味しうることを認識してください。
たとえば、基本的なオブジェクトリストのゲームのobjectと、そのobjectに関連するplayerを保存するデータベース内で、’object’か'player'かを示すためにJSONでのフィールドを使用します。情報を出力するビューで、ドキュメントがobjectのとき:
function(doc, meta) { emit(doc.experience, null); }
playerがバケットに追加されただけの場合、新しいobjectがデータベースに追加されていないにも関わらずこのビューを更新するmap/reduce関数はビューが更新されたときに実行されます。時間が経つにつれて、これはビューの構築プロセスに重大なオーバーヘッドを追加することにつながります。
このようなデータベースの構成では、アプリケーションの観点からobjectとplayerに別のバケットを使用し、それゆえ、ビューインデックスの更新が完全に分割され、処理の間ドキュメントタイプをチェックする必要がないような構造にすることがより簡単な解決策になるでしょう。
ビルトインreduce関数を使用すること
可能なところでは、提供されているビルトインreduce関数、_sum
、_count
、_stats
を使用してください。
これらの機能は、高度に最適化されています。カスタムreduce関数を使用するには、追加の処理を必要とし、インデックスの生産にさらなるビルド時間を費やすでしょう。