GraphDBの「TigerGraph」の話

20 min read読了の目安(約18100字

はじめに

先日、こんな記事がありました → Netflixの屋台骨 「AIレコメンド」技術最前線

Neo4jの横にに並ぶ、TigerGraph。
ちなみに、ちょっと前に大型の調達もありました。 → エンタープライズ・グラフ・データベースのTigerGraphが111.3億円を調達

前回、Dgraphの記事を書きましたが「GraphDBの「Dgraph」の話 - Goで叩く」、時代はTigerGraphな雰囲気なのでしょうか。

ランキングもちょっと前まで、下にいたのに、

もはや、Amazon Neptuneを抜く勢いです。(本日2021/5/29)

参考: DB Engines

ベンチマークでも、TigerGraphの方がDgraphより圧倒的に早いもよう…
Comparing TigerGraph and Dgraph Using the LDBC SNB Benchmark

さて。
そうか。
これはやらねば。
と、公式をやっってみただけですが、記事を書いていきます。

準備

公式ではdockerのコマンドがありますが、Docker Composeで準備します。

Docker Compose

ymlはこんな感じ。portを三つ使います。

tigergraph:
    image: docker.tigergraph.com/tigergraph:latest
    ports:
      - "14022:22"
      - "9000:9000"
      - "14240:14240"
    ulimits:
      nofile:
        soft: 1000000
        hard: 1000000
    volumes:
      - tigergraph

volumes:
  tigergraph:

実行。

むっちゃイメージのダウンロード遅い……。
イメージが遠くのサーバーにあるのを体感できます。

TigarGraphをスタートする

22のポートを14022にForwardするようにしているので、14022ポートを指定して接続します。
ちなみに、パスワードも、tigergraph

ssh -p 14022 tigergraph@localhost   

接続後、これでスタートさせます。

gadmin start all

mysql.server start的な感じですかね。

なるほど。
tigergraphをuserとしてsshで接続して、サービスを起こさないといけない…と言うことは、docker-composeのcommandで素直にスタートできない系ですね。起動に何ステップか踏まないといけないのは面倒…

いろいろやってみる

GSQL Shellを実行は、このコマンド。
sshで接続したコンテナ内で実行します。

gsql

GSQL Shell内でlsを実行すると、verticesやedgesなどの一覧が見られます。

GSQL > ls
---- Global vertices, edges, and all graphs
Vertex Types: 
Edge Types: 

Graphs: 
Jobs: 


JSON API version: v2
Syntax version: v1

drop allで、全部削除することができます。(まだ、何もないですが)

GSQL > drop all
Dropping all, about 1 minute ...
Abort all active loading jobs
Resetting GPE...
Successfully reset GPE
Stopping GPE GSE
Successfully stopped GPE GSE in 0.706 seconds
Clearing graph store...
Successfully cleared graph store
Starting GPE GSE RESTPP
Successfully started GPE GSE RESTPP in 0.101 seconds
Everything is dropped.

定義する

vertex

vertexの定義のためのクエリは、こんな感じ。
SQLのテーブルの定義と似てるので、理解しやすいですね。

CREATE VERTEX person (
	PRIMARY_ID name STRING,
	name STRING,
	age INT,
	gender STRING,
	state STRING
)

attributeの名前とデータのタイプで定義します。

基本的なタイプはこんな感じ.

Type Default value
INT 0
UINT 0
FLOAT 0
DOUBLE 0
BOOL false
STRING ""
DATETIME 1970-01-01 00:00:00
VERTEX "Unknown"
EDGE No edge: {}
JSONOBJECT An empty object: {}
JSONARRAY An empty array: []

vertexのデータには、ユニークな識別子が必要になります。
なので、PRIMARY_IDを指定し、それ以降に、optionalなattributeを記載していきます。

name、2回書くのですね…

実行。

GSQL > CREATE VERTEX person (PRIMARY_ID name STRING,name STRING,age INT,gender STRING,state STRING)
The vertex type person is created.

edge

edgeの定義のクエリは、こんな感じ。
personからpersonへのedgeを作ります。

UNDIRECTEDというインディケーターが付いてます。
これは、edgeが双方向性のあるedgeですよー、という意味です。
一方通行で良いときは、DIRECTEDを使います。

connectd_dayは、optionalなattributeです。
こんな形で、edgeに対して、attributeを定義することができます。

CREATE UNDIRECTED EDGE friendship (
	FROM person,
	TO person,
	connect_day DATETIME
)

これで、Fromのprimary_idとToのprimary_idが繋がるようになります。

実行。

GSQL > CREATE UNDIRECTED EDGE friendship (FROM person, TO person, connect_day DATETIME)
The edge type friendship is created.

graph

グラフを定義します。

このクエリで、socailというグラフが作成されます。

CREATE GRAPH social (person, friendship)

グラフというのは、そうですね……抜き出した点と線のグループとでも言いましょうか…立ち位置的にはRDBのdatabaseと似たような立ち位置になるのかなー、と思います。
(後ほどUSE GRAPH hogeというようなクエリもでてきますので)

実行。

GSQL > CREATE GRAPH social (person, friendship)
Stopping GPE GSE RESTPP
Successfully stopped GPE GSE RESTPP in 15.393 seconds
Starting GPE GSE RESTPP
Successfully started GPE GSE RESTPP in 0.089 seconds
The graph social is created.

以上で、グラフの定義ができました。

lsで、これまでの定義が確認できます。

GSQL > ls
---- Graph social
Vertex Types: 
  - VERTEX person(PRIMARY_ID name STRING, name STRING, age INT, gender STRING, state STRING) WITH STATS="OUTDEGREE_BY_EDGETYPE"
Edge Types: 
  - UNDIRECTED EDGE friendship(FROM person, TO person, connect_day DATETIME)

Graphs: 
  - Graph social(person:v, friendship:e)
Jobs: 
Queries: 

作る:Load Job

2つのCSVをコンテナに置いて、これをロードしてみます。

Jobの作成

CSVは、こんな感じ

/home/tigergraph/person.csv

name,gender,age,state
Tom,male,40,ca
Dan,male,34,ny
Jenny,female,25,tx
Kevin,male,28,az
Amily,female,22,ca
Nancy,female,20,ky
Jack,male,26,fl

/home/tigergraph/person.csv

person1,person2,date
Tom,Dan,2017-06-03
Tom,Jenny,2015-01-01
Dan,Jenny,2016-08-03
Jenny,Amily,2015-06-08
Dan,Nancy,2016-01-03
Nancy,Jack,2017-03-02
Dan,Kevin,2015-12-30

コマンドはこんな感じ。
これも、SQLにあるLOAD DATAとそっくりですね。

USE GRAPH social
BEGIN
CREATE LOADING JOB load_social FOR GRAPH social {
   DEFINE FILENAME file1="/home/tigergraph/person.csv";
   DEFINE FILENAME file2="/home/tigergraph/friendship.csv";

   LOAD file1 TO VERTEX person VALUES ($"name", $"name", $"age", $"gender", $"state") USING header="true", separator=",";
   LOAD file2 TO EDGE friendship VALUES ($0, $1, $2) USING header="true", separator=",";
}
END
  • USE GRAPH social どのグラフか指定します。
  • BEGIN ... END 複数行のコマンドを実行する時に囲んで、1ステートメントさせます。

実行。

GSQL > USE GRAPH social
Using graph 'social'
GSQL > BEGIN
GSQL > CREATE LOADING JOB load_social FOR GRAPH social {
GSQL >    DEFINE FILENAME file1="/home/tigergraph/person.csv";
GSQL >    DEFINE FILENAME file2="/home/tigergraph/friendship.csv";
GSQL > 
GSQL >    LOAD file1 TO VERTEX person VALUES ($"name", $"name", $"age", $"gender", $"state") USING header="true", separator=",";
GSQL >    LOAD file2 TO EDGE friendship VALUES ($0, $1, $2) USING header="true", separator=",";
GSQL > }
GSQL > END
The job load_social is created.
GSQL > 

Jobの実行

Job実行のクエリはこんな感じ。

RUN LOADING JOB load_social

実行。

GSQL > RUN LOADING JOB load_social
[Tip: Use "CTRL + C" to stop displaying the loading status update, then use "SHOW LOADING STATUS jobid" to track the loading progress again]
[Tip: Manage loading jobs with "ABORT/RESUME LOADING JOB jobid"]
Starting the following job, i.e.
  JobName: load_social, jobid: social.load_social.file.m1.1622273212213
  Loading log: '/home/tigergraph/tigergraph/log/restpp/restpp_loader_logs/social/social.load_social.file.m1.1622273212213.log'

Job "social.load_social.file.m1.1622273212213" loading status
[FINISHED] m1 ( Finished: 2 / Total: 2 )
  [LOADED]
  +---------------------------------------------------------------------------+
  |                       FILENAME |   LOADED LINES |   AVG SPEED |   DURATION|
  |/home/tigergraph/friendship.csv |              8 |       7 l/s |     1.00 s|
  |    /home/tigergraph/person.csv |              8 |       7 l/s |     1.00 s|
  +---------------------------------------------------------------------------+

ロードされました……みたいです。

見る:GSQL

QUERYの作成

以下のようなクエリで、クエリを作成できます。
ややこしいので、独自に定義するクエリを「QUERY」と書くことにしましょう。

USE GRAPH social
CREATE QUERY hello(VERTEX<person> p) {
  Start = {p};
  Result = SELECT tgt
           FROM Start:s-(friendship:e) ->person:tgt;
  PRINT Result;
}

helloはクエリ名です。
Start は、QUERYの実行時に呼び出されるパラメータpで代入されたpersonの識別子が入ります。
FROMで、取得するvertex、edgeを指定します。

実行。

GSQL > USE GRAPH social
Using graph 'social'
GSQL > CREATE QUERY hello(VERTEX<person> p) { Start = {p}; Result = SELECT tgt FROM Start:s-(friendship:e) ->person:tgt; PRINT Result; }
The query hello has been added!

lsで確認すると、hello Queryが確認できますね。

GSQL > ls
---- Graph social
Vertex Types: 
  - VERTEX person(PRIMARY_ID name STRING, name STRING, age INT, gender STRING, state STRING) WITH STATS="OUTDEGREE_BY_EDGETYPE"
Edge Types: 
  - UNDIRECTED EDGE friendship(FROM person, TO person, connect_day DATETIME)

Graphs: 
  - Graph social(person:v, friendship:e)
Jobs: 
  - CREATE LOADING JOB load_social FOR GRAPH social {
      DEFINE FILENAME file2 = "/home/tigergraph/friendship.csv";
      DEFINE FILENAME file1 = "/home/tigergraph/person.csv";
      LOAD file1 TO VERTEX person VALUES($"name", $"name", $"age", $"gender", $"state") USING SEPARATOR=",", HEADER="true", EOL="\n";
      LOAD file2 TO EDGE friendship VALUES($0, $1, $2) USING SEPARATOR=",", HEADER="true", EOL="\n";
    }

Queries: 
  - hello(vertex<person> p) 

QUERYのインストール

この時点では、QUERYの定義はされているものの、まだインストールがされておらず、使うことができません。

インストールを行います!

クエリはこんな感じ。

INSTALL QUERY hello

実行。

GSQL > INSTALL QUERY hello
Start installing queries, about 1 minute ...
hello query: curl -X GET 'http://127.0.0.1:9000/query/social/hello?p=VALUE'. Add -H "Authorization: Bearer TOKEN" if authentication is enabled.

[========================================================================================================] 100% (1/1) 

QUERYの実行

QUERYの実行のクエリはこんな感じ。
personをパラメータとするクエリなので、personのprimary_idを指定してあげます。

RUN QUERY hello("Tom")

実行。

GSQL > RUN QUERY hello("Tom")
{
  "error": false,
  "message": "",
  "version": {
    "schema": 0,
    "edition": "enterprise",
    "api": "v2"
  },
  "results": [{"Result": [
    {
      "v_id": "Dan",
      "attributes": {
        "gender": "male",
        "name": "Dan",
        "state": "ny",
        "age": 34
      },
      "v_type": "person"
    },
    {
      "v_id": "Jenny",
      "attributes": {
        "gender": "female",
        "name": "Jenny",
        "state": "tx",
        "age": 25
      },
      "v_type": "person"
    }
  ]}]
}

Tomさんの友達が出力されました。

でもこれ、いちいちQUERY定義つくるのでしょうか…?
サービス上でどう使えばいいんだろう……migrationする時とかに、QUERYも定義すればいいのですかね?

もうちょっと複雑なQUERY

このQUERYは、2ホップ隣のpersonを取得し且つその平均年齢を取ってくるQUERYです。

CREATE QUERY hello2 (VERTEX<person> p) {
  OrAccum  @visited = false;
  AvgAccum @@avgAge;
  Start = {p};

  FirstNeighbors = SELECT tgt
                   FROM Start:s -(friendship:e)-> person:tgt
                   ACCUM tgt.@visited += true, s.@visited += true;

  SecondNeighbors = SELECT tgt
                    FROM FirstNeighbors -(:e)-> :tgt
                    WHERE tgt.@visited == false
                    POST_ACCUM @@avgAge += tgt.age;

  PRINT SecondNeighbors;
  PRINT @@avgAge;
}

@でlocalのaccumulatorを定義できます。(variableではない)
or条件のaccumulatorを定義するため、OrAccumというtypeを指定しています。
今回、初期値にfalseを与えています。

@@でglobalなaccumulatorを定義できます。
averageのaccumulatorを定義するため、AvgAccumというtypeを指定しています。

1つめのselect文で、1ホップ隣のperson vertexを取得しているのですが、その時に@visitedにtrueを入れていき、既出のpersonかどうかを判定できるようにしています。

+=でTom.@visited <== (initial value: false) OR (true) OR (true)こんなふうに値が蓄積(accumulate)されていきます。

2つめのselect文で、2ホップ隣のperson vertexを取得しているのですが、whereで、1ホップ隣のpersonを除くようにして、2ホップ隣のpersonだけを取得しています。

なるほどぉ。

見る:curl(Built-in Queries)

curlでBiult-inのクエリを実行してみます。
docker composeで空けていた9000のポートが、httpを受け付けているポートです。

count

これは、stat_vertex_numberというクエリで、vetexの数を返してくれるクエリです。
typeは、vertexの名前を入れるようですが、ワイルドカードも使えるようです。

curl -X POST 'http://localhost:9000/builtins/social' -d  '{"function":"stat_vertex_number","type":"*"}'  | jq .

実行。

curl -X POST 'http://localhost:9000/builtins/social' -d  '{"function":"stat_vertex_number","type":"*"}'  | jq .
{
  "version": {
    "edition": "enterprise",
    "api": "v2",
    "schema": 0
  },
  "error": false,
  "message": "",
  "results": [
    {
      "v_type": "person",
      "count": 7
    }
  ]
}

select

vertexの詳細を取得するパスはこちら。"http://localhost:9000/graph/{graph_name}/vertices/{vertex_type}/{vertex_id}"

実行。

curl -X GET "http://localhost:9000/graph/social/vertices/person/Tom" | jq .
{
  "version": {
    "edition": "enterprise",
    "api": "v2",
    "schema": 0
  },
  "error": false,
  "message": "",
  "results": [
    {
      "v_id": "Tom",
      "v_type": "person",
      "attributes": {
        "name": "Tom",
        "age": 40,
        "gender": "male",
        "state": "ca"
      }
    }
  ]
}

Tomさんの情報が取得できました。

edge取得のパスは、http://localhost:9000/graph/edges/{source_vertex_type}/{source_vertex_id}/{edge_type}/ですが、edgeの先のvertexのidで絞り込む時は、http://localhost:9000/graph/edges/{source_vertex_type}/{source_vertex_id}/{edge_type}/{target_vertex_type}/{target_vertex_id}です。

実行。

curl -X GET "http://localhost:9000/graph/social/edges/person/Tom/friendship/" | jq .
{
  "version": {
    "edition": "enterprise",
    "api": "v2",
    "schema": 0
  },
  "error": false,
  "message": "",
  "results": [
    {
      "e_type": "friendship",
      "directed": false,
      "from_id": "Tom",
      "from_type": "person",
      "to_id": "Dan",
      "to_type": "person",
      "attributes": {
        "connect_day": "2017-06-03 00:00:00"
      }
    },
    {
      "e_type": "friendship",
      "directed": false,
      "from_id": "Tom",
      "from_type": "person",
      "to_id": "Jenny",
      "to_type": "person",
      "attributes": {
        "connect_day": "2015-01-01 00:00:00"
      }
    }
  ]
}

見る:curl(独自クエリ)

先ほどhello QUERYを投げてみましょう。

パスは、http://localhost:9000/query/{graph_name}/{query_name}?p=Tom

実行。

curl -X GET 'http://localhost:9000/query/social/hello?p=Tom' | jq
{
  "version": {
    "edition": "enterprise",
    "api": "v2",
    "schema": 0
  },
  "error": false,
  "message": "",
  "results": [
    {
      "Result": [
        {
          "v_id": "Dan",
          "v_type": "person",
          "attributes": {
            "name": "Dan",
            "age": 34,
            "gender": "male",
            "state": "ny"
          }
        },
        {
          "v_id": "Jenny",
          "v_type": "person",
          "attributes": {
            "name": "Jenny",
            "age": 25,
            "gender": "female",
            "state": "tx"
          }
        }
      ]
    }
  ]
}

Tomさんの友達が取得できました。

作る:GSQL

CSVからperson作る方法を書きましたが、SQLのようにINSERT文でも書けます。
ただし、直接INSERTを流せるわけではなく、QUERYとしてインストールして、流す仕様です。
(なので、QUERY作成の後に書きました。)

INSERTのクエリはこんな感じ。

INSERT INTO vertex_or_edge_type VALUES (full_list_of_parameter_values)

すべてのパラメータを使わない場合は、こんな感じ。

INSERT INTO vertex_type (PRIMARY_ID, specified_attributes) VALUES (ID, values_for_specified_attributes)

クエリの作成

CREATE QUERY insertPerson(STRING name, INT age, STRING gender, STRING state) FOR GRAPH social {
	INSERT INTO person VALUES (name, name, age, gender, state);
}

実行。

GSQL > CREATE QUERY insertPerson(STRING name, INT age, STRING gender, STRING state) FOR GRAPH social { INSERT INTO person VALUES (name, name, age, gender, state);}
The query insertPerson has been added!

クエリの実行

まずは、インストール。

このクエリを、

INSTALL QUERY insertPerson

実行。

GSQL > INSTALL QUERY insertPerson
Start installing queries, about 1 minute ...
insertPerson query: curl -X GET 'http://127.0.0.1:9000/query/social/insertPerson?name=VALUE&age=VALUE&gender=VALUE&state=VALUE'. Add -H "Authorization: Bearer TOKEN" if authentication is enabled.

[========================================================================================================] 100% (1/1) 

そしてQUERYの実行のクエリ

RUN QUERY insertPerson("Masamiki",32,"male","ca")

の実行。

GSQL > RUN QUERY insertPerson("Masamiki",32,"male","ca")
{
  "error": false,
  "message": "",
  "version": {
    "schema": 0,
    "edition": "enterprise",
    "api": "v2"
  },
  "results": []
}

curlで確認すると、ちゃんとMasamikiの詳細が返ってきました。

 curl -X GET "http://localhost:9000/graph/social/vertices/person/Masamiki" | jq .                                                                 
{
  "version": {
    "edition": "enterprise",
    "api": "v2",
    "schema": 0
  },
  "error": false,
  "message": "",
  "results": [
    {
      "v_id": "Masamiki",
      "v_type": "person",
      "attributes": {
        "name": "Masamiki",
        "age": 32,
        "gender": "male",
        "state": "ca"
      }
    }
  ]
}

GraphStudio

gadmin start allの後、http://localhost:14240をブラウザで表示するとGraphStudioというGUIを開くことができます。

右上の「admin」をクリックすると、ダッシュボードページに遷移できます。

どうやら、ここでinfraの監視が行えるようです。

トップページの「DesingSchema」をクリックすると、定義したvertexとedgeが見られます。

マウスオーバーでattributeも確認できます。

クリックするとプロパティが表示されて、編集も行えるようです。

アイコンも設定できるみたいです。
おもしろいUIですね。

クエリを使わなくても、vertexとedgeはここで定義していくことができるようになっているようです。

exploreを使ってみようと、クリックするもgraphを選択してくださいとの警告。

おっと、どこで選択するんだ…あぁ、ここか。

グラフを選択して、各メニューがアクティベートされました。

さて、exploreですが…まず、ALLでピック!

ちゃんと登録したpersonが出てきてますね。

ちなみに、どんなクエリ投げてるかも見えるので、こっちで最初は勉強できそう。

おわりに

Dgraphを最近は使っていたのですが、TigerGraphの方が素直で分かり易かったです。
SQLに似たクエリが使えるので、大半のエンジニアがそうだと思いますが、SQLに慣れ親しんだ人が多いので、嫌悪感を湧かさずに触れます。

基本的に定義を作る時とかで時間がかかっていた印象ですが、クエリの実行がTigerGraphの方が速いのであれば、ほぼ文句ないですね…

懸念点としては、最小のサイズで(フリーを除く)1インスタンスで1時間120円程度、つまり年間最低100万円程度…。

サービスとしてうまくいってからでないと、利用するのが難しい値段だなという。

いや、Dgraphもマネージドはそれ以上にかかるか。

よし、移行しよう。