Graphのアンチパターン

DSE Graphでよくある間違いを検証します。

DSE Graphでは、よくある間違いがいくつかあります。ベスト・プラクティスを検討することで、学習のハードルを下げるとともに、グラフ・アプリケーションのパフォーマンスを改善することができます。

インデックス作成を使用しない

インデックス作成は、分散データベースでのクエリーのレイテンシーを減らす際の主要機能です。DSE Graphは、インデックス作成を利用して複雑なグラフ探索のOLTP読み取りレイテンシーを短縮します。理解しておくべきことは、DSE Graphでのグローバルなインデックス作成には、頂点ラベルとプロパティ・キーの両方が関わるということです。頂点ラベルは、基盤となるDSEデータストアの検索をパーティション単位に絞り込み、さらに、クラスター内の1つまたは少数のDSEデータベース・ノードにまで絞り込みます。複数の頂点ラベルに使用するプロパティ・キーについてインデックスを作成しても、クエリーでこの頂点ラベルを指定しないと、クラスターがほぼ完全にスキャンされます。したがって、以下のようなクエリーを使用する場合は、
g.V().has('name','James Beard')...
プロパティ・キーnameを使用しているすべての頂点をチェックするように探索で指定する必要があります。このクエリーを次のように変更すると、
g.V().has('author', 'name', 'James Beard')...
クエリーで著者レコードのすべての名前について作成可能なインデックスを参照し、頂点を1つだけ取得して探索を開始できます。インデックスはスキーマ作成時に追加されます。
schema.vertexLabel('author').index('byName').secondary().by('name').add()

つまり、探索でこのような変更を行った場合、クエリーはOLAPクエリーからOLTPクエリーへと変更されます。

プロパティ・キーの作成

プロパティ・キーの作成は、DSE Graphのパフォーマンスに影響を及ぼす可能性があります。一意のプロパティ・キー名を使用することが最初は有益のように見えますが、異なる頂点ラベルにプロパティ・キーを再利用すると、グラフのプロパティ・キーのストレージ効率が向上します。たとえば、次の例を見てみましょう。
schema.propertyKey('recipeCreationDate').Timestamp().create()
schema.propertyKey('mealCreationDate').Timestamp().create()
schema.propertyKey('reviewCreationDate').Timestamp().create()
このプロパティ・キー名だとコードが読みやすく、グラフ探索での追跡が簡単になりますが、保存されている追加のプロパティ・キーごとにリソースが必要です。代わりに、次のようなプロパティ・キーを1つ使用すると、
schema.propertyKey('timestamp').Timestamp().create()
オーバーヘッドを減らすことができます。グラフ探索では、プロパティ・キーは主に頂点ラベルと一緒に使用されるため、timestampは、頂点ラベルとプロパティ・キーの組み合わせによって一意に識別されます。

頂点ラベルの作成

頂点ラベルの作成は、DSE Graphのパフォーマンスに影響を及ぼす可能性があります。一意の頂点ラベルを多く使用すると便利なように思えますが、プロパティ・キーのように、作成する頂点ラベルをできるだけ少なくすると、ストレージ要件を改善することができます。たとえば、次の例を見てみましょう。
schema.vertexLabel('recipeAuthor').create()
schema.vertexLabel('bookAuthor').create()
schema.vertexLabel('mealAuthor').create()
schema.vertexLabel('reviewAuthor').create()
前述したように、このような頂点ラベルは読みやすいという利点がありますが、頂点ラベルに対して一意にクエリーを行わないのであれば、この機能を単一の頂点ラベルにまとめた方が得策です。たとえば、上記のコードでは、「recipes」、「meals」、「books」の作成者が同じ可能性がある一方、「reviews」にはさまざまな著者とクエリーのタイプが存在する可能性があります。次のように、頂点ラベルは4つではなく2つ使用してください。
schema.vertexLabel('author').create()
schema.vertexLabel('reviewer').create()
実際のところ、この場合は、作成者およびレビュー担当者の重複が十分に大きければ、頂点ラベルpersonを1つだけ使用する方が適しているかもしれません。場合によっては、personが作成者かレビュー担当者かを識別するプロパティ・キーが有効な選択肢となります。
schema.propertyKey('type').Text().create()
schema.vertexLabel('person').create()
graph.addVertex(label, 'person', 'type', 'author', 'name', 'Jamie Oliver')

スキーマ作成または構成設定と探索クエリーの混在

次の文を見てみましょう。最初の文は、読み取り整合性のためのグラフ設定を行います。2つ目の文は、すべての頂点について、値がread vertexnameフィールドに対してカウントを実行します。
schema.config().option('graph.tx_groups.default.read_consistency').set('ALL');
g.V().has('name', 'read vertex').count()
Gremlin Serverでは、1つのトランザクションで両方の文が実行されます。このトランザクションの実行中に行われた変更は、両方のアクションが正常にコミットされた場合に適用されます。読み取り整合性の変更は、トランザクションが終了されるまで実際には適用されません。したがって、この変更は、次のトランザクションで初めて有効になります。これらの文は、個別のリクエストとして順次処理されることはありません。

処理中にこのようなエラーが発生しないようにするには、アプリケーション内で、スキーマ作成または構成設定と探索クエリーを混在させないでください。グラフ探索でグラフ・データベースに対してクエリーを実行する前に、スキーマを作成して構成を行っておくことがベスト・プラクティスです。

OLTPクエリーの実行時間が長すぎることを示すInterruptedException

一般的に、この例外がログに記録されていた場合は、OLTPクエリーの実行時間が長すぎることを意味します。典型的な原因としては、グラフ探索クエリーで使用する要素のインデックスが作成されていないことが挙げられます。インデックスを作成してクエリーを再試行してください。

g.V().count()およびg.E().count()は長い遅延を引き起こす可能性がある

大規模なグラフでカウントを実行すると、重大な問題が発生することがあります。基本的に、コマンドはすべての頂点で繰り返す必要があるため、グラフが大きい場合は何時間もかかります。どのテーブル・スキャン(すべての頂点の繰り返し)もOLTPプロセスではありません。エッジで同じプロセスを実行することも実質的に同じです。つまり、すべてのテーブルがスキャンされます。現時点で、これらのカウントを得るには、Sparkコマンドを使用する方法が推奨されます。

graph_name_systemに対して低すぎるレプリケーション係数を設定する

作成された各グラフは、graph_name、graph_name_system、およびgraph_name_pvtの3つのDSEデータベース・キースペースを作成します。graph_name_systemはグラフ・スキーマを格納しており、このデータを喪失するとグラフ全体が動作不能になります。クラスター構成に基づいて適切なレプリケーション係数を設定してください。

パラメーター化されたクエリーではなく文字列連結をアプリケーションで使用する

グラフ・アプリケーションでの文字列連結は、パフォーマンスを大幅に低下させます。一意な各クエリー文字列はノードでキャッシュされるオブジェクトを作成し、ノードのリソースを使い果たします。リソース割り当てに起因する問題を防ぐには、パラメーター化されたクエリー(DSE JavaドライバーDSE PythonドライバーDSE RubyドライバーDSE Node.jsドライバーDSE C#ドライバーDSE C/C++ドライバー)を使用してください。