DSE GraphおよびGraph Analytics

DSE Graphでは、Sparkを使用してOLAPクエリーを実行することができます。

トランザクション負荷が高い際に、多数のローカル・グラフ探索をリアルタイムで実行できます。グラフの密度が高すぎる場合や、分岐要素(グラフの各レベルの接続ノード数)が多すぎる場合、OLTPクエリーに回答するメモリーおよび計算の要件は通常のアプリケーションのワークロードで許容可能な範囲を超えます。このような種類のクエリーは、詳細なクエリーと呼ばれます。

スキャン・クエリーは、グラフ全体またはグラフの大部分にタッチするクエリーです。このようなクエリーは通常、多数の頂点とエッジを探索します。たとえば、友達の友達を検索するソーシャル・ネットワーク・グラフに対するクエリーは、スキャン・クエリーです。

詳細なクエリーやスキャン・クエリーを使用するアプリケーションでは、OLAPクエリーを使用すると、パフォーマンスが向上します。

DSE Graphを使用したOLAPクエリーの実行

DSE Graphで作成されたすべてのグラフには、gremlin-consoleおよびDataStax Studioで使用可能なOLAP探索ソースaが含まれています。この探索ソースでは、SparkGraphComputerを使用して、クエリーを分析し、基盤となるDSE Analyticsノードに対してクエリーを実行します。ノードは、OLAP探索ソースへのアクセスが可能なGraphおよびSparkで起動される必要があります。1回限りまたは単一セッションOLAPクエリーの場合、database.agという別名にして、クエリーを作成します。たとえば、Gremlin Consoleの場合は、次のようになります。

gremlin> :remote config alias g database.a 
gremlin> g.V().count()

グラフの他の部分に対して複数のクエリーを実行する場合は、graph.snapshot()を使用して、グラフの各部分についてOLAP探索ソースを返します。たとえば、Gremlin Consoleの場合は、次のようになります。

gremlin> categories = graph.snapshot().vertices('category').create()

create()を呼び出す前にスナップショットに対してconf()メソッドを使用し、TinkerPopのSparkGraphComputer構成オプションを設定します。たとえば、スナップショットのストレージ・レベルを明示的にMEMORY_ONLYに設定する場合は、次のようになります。

graph.snapshot().vertices("vertexlabel_alice", "vertexlabel_bob").edges("edgelabel_carol").conf("gremlin.spark.persistStorageLevel", "MEMORY_ONLY").create()

GremlinからのSparkプロパティの設定

グラフ上でgraph.configuration.setPropertyメソッドを使用して、GremlinからSparkプロパティを設定することができます。

gremlin> :remote config alias g database.a
gremlin> g.graph.configuration.setProperty("property name", value)
==> null

デフォルトでは、Sparkアプリケーションはノードで使用可能なすべてのリソースを使用するため、他のSparkアプリケーションを実行することはできません。探索で使用する最大コア数とメモリー容量を設定して、OLAP探索を実行する前にアプリケーションのリソースを制限します。これは、コアの数やメモリーの容量が非常に多いサーバーでは特に重要です。

たとえば、この要求では、1つのコアと4 GBのメモリーをそれぞれ備えた10個のエグゼキューターが設定されます。

gremlin> :remote config alias g example_graph.a
==>g=example_graph.a
gremlin> g.graph.configuration.setProperty("spark.cores.max", 10)
==>null
gremlin> g.graph.configuration.setProperty("spark.executor.memory", "4g")
==>null
gremlin> g.graph.configuration.setProperty("spark.executor.cores", "1")
==>null

spark.cores.maxプロパティは、Sparkで使用される最大コア数を設定します。このプロパティをコアの総数よりも低く設定すると、クエリーが実行されるノードの数が制限されます。spark.executor.memoryプロパティは、各エグゼキューターで使用されるメモリー容量を設定します。spark.executor.coresプロパティは、各エグゼキューターで使用されるコアの数を設定します。

GremlinからSparkプロパティを構成する前に、現在実行中のSparkコンテキストをSpark Web UIから強制終了します。これにより、現在実行中のすべてのGremlin OLAPクエリーが強制終了されます。Spark Web UIで、「Apache TinkerPop's Spark-Gremlin」というアプリケーションを見つけ、アプリケーションIDの横にあるkillをクリックします。

OLAP探索では、実行中に多くの中間オブジェクトが作成されます。これらのオブジェクトはJVMのガーベージコレクションによって回収されるため、エグゼキューターを減らしてメモリーとCPUリソースを増やすとパフォーマンスが向上するグラフ以外のSparkジョブと比べて、メモリーとCPUリソースをそれぞれ減らした、大規模なエグゼキューターのプールを構成することをお勧めします。

ガーベージ・コレクションによる一時停止を減らし、OLAP探索のパフォーマンスを高めるために、コアが8個未満のエグゼキューターを割り当てることをお勧めします(ほとんどの場合、1個で機能します)。Sparkで使用可能なメモリーは、コア間で均等に分散させる必要があります。たとえば、3つのノードがあり、それぞれのノードで24個のコアと96 GBがSpark専用に割り当てられている場合、コアの総数は24 * 3 = 72個、メモリーの合計は96 GB * 3 = 188 GBになります。すべてのリソースを割り当てるには、72個の単一コアのエグゼキューターと4 GBのメモリーをそれぞれ要求する必要があります。

gremlin> :remote config alias g example_graph.a
==>g=example_graph.a
gremlin> g.graph.configuration.setProperty("spark.cores.max", 72)
==>null
gremlin> g.graph.configuration.setProperty("spark.executor.memory", "4g")
==>null
gremlin> g.graph.configuration.setProperty("spark.executor.cores", "1")
==>null

分析OLAPクエリーを使用する場合

大規模なグラフでは、通常、詳細なクエリーを実行する場合は、OLAPクエリーの方がパフォーマンスに優れています。ただし、OLTP読み込みの一環として詳細なクエリーを実行する意味があるのは、このクエリーがあまり実行されない場合に限られます。たとえば、オンライン支払いプロバイダーは、支払いを素早く処理するためにOLTPクエリーを好みますが、取引に不正がないか確かめるために詳細なクエリーの実行が必要になる場合があります。詳細なクエリーはOLTPワークロードとして非常に長い時間がかかる可能性がありますが、全体的に見て、アプリケーションのパフォーマンスはアプリケーションをOLTPクエリーとOLAPクエリーに分割するよりも速くなります。

グラフ全体を分析するレコメンデーション・エンジンや検索エンジンなどの長時間の実行されるプロセスや周期的なプロセスでは、OLAPクエリーが理想的なユース・ケースとなります。ただし、詳細なクエリーを呼び出したり、データベース全体をスキャンしたりする1回限りのデータ分析操作でも、OLAPクエリーとして実行する利点がある場合があります。OLTPクエリーとOLAPクエリーのパフォーマンスの違いについては、「DSE Graph、OLTP、およびOLAP」を参照してください。

多数のエッジと頂点を削除する場合のベスト・プラクティス

グラフから多数のエッジや頂点を削除する際、自動で削除される前に非常に多くのトゥームストーンがデータベースに残っていることが原因で、それ以降のクエリーでエラー・メッセージが表示されることがあります。

そのようなエラーのログ・エントリーは以下のようになります。

ERROR [ReadStage-32] 2017-05-18 11:18:29,289  StorageProxy.java:2068 - Scanned over 100001 tombstones during query 'SELECT * FROM t33215.PhoneNumber_p WHERE token(community_id) > -7331398285705078207 AND token(community_id) <= -6858404847917653807 LIMIT 1000' (last scanned row partion key was ((216134144), 1250272)); query aborted

このようなエラーを回避するには、spark.cassandra.input.split.size_in_mbプロパティをデフォルトの64 MBよりも小さいサイズに設定して、要求あたりのトゥームストーンの数を減らします。spark.cassandra.input.split.size_in_mbプロパティは、CQLクエリーのたびにSpark Cassandra Connectorが要求するデータ・サイズの概算を設定します。

以下の例は、spark.cassandra.input.split.size_in_mbプロパティを1 MBに設定し、グラフから電話番号の頂点をすべて削除する方法を示しています。

:remote config alias g example_graph.a
g.graph.configuration.setProperty("spark.cassandra.input.split.size_in_mb", "1")
g.V().hasLabel("PhoneNumber").drop().iterate()