JanusGraphの2つの使い方 - EmbeddedとServer

JanusGraphを使ってグラフDBを構築するには、大きく2つの方法がある。EmbeddedとGremlin Serverである。

## Embedded
[TinkerPop - Embedded](https://tinkerpop.apache.org/docs/current/reference/#connecting-embedded)
その名の通り、実行するJVM(Java Virtual Machine)上に組み込まれるグラフDBであり、最小構成ではスタンドアロンで運用できる。シンプルなスタイル。

この方法の手順は概ね以下の通りになる。

1. `Graph`のインスタンスを生成する
2. `GraphTraversalSource`のインターフェースを取得する
3. クエリを発行し、結果を受け取る

簡単なソースコードで表すと以下のようになる(言語はgroovy)。

```groovy:groovy
Graph graph = TinkerGraph.open();  // 空のグラフを生成
GraphTraversalSource g = graph.traversal();
g.addV("person").iterate();
```

グラフの生成の方法は様々ある。TinkerPopが用意するグラフ用のクラスは[TinkerGraph](https://qiita.com/chromia/items/b93ef0b3a14eb875e7cc#tinkergraph)や[TinkerFactory](https://qiita.com/chromia/items/b93ef0b3a14eb875e7cc#tinkerfactory)があるが、これらはデータの永続化やトランザクション処理が出来ない。JanusGraphは標準の方法として[JanusGraphFactory](https://docs.janusgraph.org/getting-started/basic-usage/)を提供しており、これなら永続化もトランザクションも出来る。

以下はGremlinコンソール上でJanusGraphFactoryを使ってみるサンプルである(Windows10上で[JanusGraph0.5.1](https://github.com/JanusGraph/janusgraph/releases/tag/v0.5.1)を使用)。

```groovy:Console
gremlin> graph = JanusGraphFactory.open("conf/janusgraph-berkeleyje-lucene.properties")
==>standardjanusgraph[berkeleyje:C:\******\janusgraph-0.5.1\conf\../db/berkeley]
gremlin> g = graph.traversal()
==>graphtraversalsource[standardjanusgraph[berkeleyje:C:\******\janusgraph-0.5.1\conf\../db/berkeley], standard]
gremlin> g.addV("person").property("name", "bob")
==>v[4112]
```

設定ファイルには`conf/janusgraph-berkeleyje-lucene.properties`を使用した。これはストレージ・バックエンドに`Berkeley DB`、インデックス・バックエンドに`Lucene`ライブラリを使用する設定である。ストレージは`db/berkeley`に生成・保存される。

JanusGraphではドキュメント中のコードではEmbeddedグラフを使ったコードを記載しているので、まずはこちらに親しむのが得策と思われる。

## Gremlin Server
サーバーについては既に[JanusGraphによるグラフDB入門](https://qiita.com/chromia/items/049c587c3ee0e388be5b#%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%AE%E7%AB%8B%E3%81%A1%E4%B8%8A%E3%81%92with-berkeley-db)という記事の中で少し説明しているので、そちらも参照のこと。

こちらはEmbeddedとは違い、専用のサーバープロセスを立ち上げ、ポートを通じてクライアントと接続する。こちらの方法ではJanusGraphFactoryなどは使用できない。その代わりに以下の2種類の接続方法が使える。

- `DriverRemoteConnection`と`AnonymousTraversalSource`を使った接続
- `Cluster`と`Client`を使った接続

### DriverRemoteConnection&AnonymousTraversalSource
[TinkerPopドキュメント - Connecting Gremlin Server](https://tinkerpop.apache.org/docs/current/reference/#connecting-gremlin-server)

こちらはEmbeddedの場合と似通っていて、分かりやすい手法である。

1. `DriverRemoteConnection`を使ってサーバーとの接続を確立する
2. `AnonymousTraversal`を使って`GraphTraversalSource`(g)を獲得する
3. クエリを送信し、結果を取得する
4. 終わったら接続を閉じる

```groovy:groovy
connection = DriverRemoteConnection.using('localhost', 8182, 'g')
g = AnonymousTraversalSource.traversal().withRemote(connection)
vertices = g.V().id().toList()
connection.close()
```

この方法の場合の特徴は2つある
- `GraphTraversalSource`(g)は取得できるが、`Graph`(graph)は取得できないので、`ManagementSystem`(スキーマやインデックスの定義インターフェース)や、`AddVertex`や`addEdge`といった"structure API"へのアクセスが制限される。
- 毎回のクエリ実行は1つのトランザクションでラッピングされて実行される。自分で複数のクエリを1つのトランザクションとして実行するような制御は出来ない。

[TinkerPop javadoc - DriverRemoteConnection](http://tinkerpop.apache.org/javadocs/current/full/org/apache/tinkerpop/gremlin/driver/remote/DriverRemoteConnection.html)
[TinkerPop javadoc - AnonymousTraversalSource](https://tinkerpop.apache.org/javadocs/current/full/org/apache/tinkerpop/gremlin/process/traversal/AnonymousTraversalSource.html)

### Cluster&Client
[TinkerPopドキュメント - Connecting Via Drivers](https://tinkerpop.apache.org/docs/current/reference/#connecting-via-drivers)

こちらは比較的泥臭いスタイルとなる。

1. `Cluster`を使ってサーバーとの接続を確立する(言語によっては不要)
2. `Client`を使って、クエリメッセージを送信できる状態にする
3. クエリを送信し、結果を受け取る
4. Clientを使い終わったら、`Client.close`で処分
5. Clusterを使い終わったら、`Cluster.close`で処分

```groovy:groovy
cluster = Cluster.open()  // 引数省略時はlocalhost:8182に接続
client = cluster.connect()
result = client.submit("g.V().count()")  // 直接文字列で送信
print(result[0].object)
client.close()
cluster.close()
```

この場合、クエリは文字列で指定する。なおGremlinの場合もインジェクション攻撃が存在し、`Gremlin Injection`と呼ばれている。対策については大体SQLの場合と同じ([Parameterized Scripts](http://tinkerpop.apache.org/docs/current/reference/#parameterized-scripts)を参照)

`Cluster.connect`は2引数のオーバーライド`connect(sessionID, manageTransactions)`を持つ。第二引数の`manageTransactions`が`false`の場合、自動でのトランザクション管理は行われず、ユーザーが自分でトランザクションを管理することができる(ただし正しく扱う責任が生じる)。`true`の場合はsubmitを行うたびに自動的にトランザクションが開始され、送信したクエリの最後にコミットまたはロールバックが行われる。引数なしの`connect`を実行した場合は`manageTransactions=true`がデフォルトである。

```groovy:groovy
cluster = Cluster.open()
client = cluster.connect("mysession", true)  // managed
client.submit("g.addV('person').property('name', 'joseph').iterate()")  // 登録される(自動コミット)
client.submit("g.tx().rollback()")  // コミット済みなので取り消せない
client.close()

client = cluster.connect("mysession", false)  // not managed
client.submit("g.addV('person').property('name', 'joseph').iterate()")  // まだ登録されない
client.submit("g.tx().rollback()") // 取り消せる
client.close()
cluster.close()
```

Gremlinコンソールではリモート接続した際、ユーザーが打ち込んだコマンドをそのままClient.submitで送信している([コード](https://github.com/apache/tinkerpop/blob/b73d2f3c0187dbdabc683260ace5cee17a4342aa/gremlin-console/src/main/java/org/apache/tinkerpop/gremlin/console/jsr223/DriverRemoteAcceptor.java#L216))。つまりGremlinコンソール上のリモート送信(:>)で実行できることは全てこの方法で実行可能である。

[TinkerPop Javadoc - Cluster](http://tinkerpop.apache.org/javadocs/current/core/org/apache/tinkerpop/gremlin/driver/Cluster.html)
[TinkerPop Javadoc - Client](https://tinkerpop.apache.org/javadocs/current/full/org/apache/tinkerpop/gremlin/driver/Client.html)

### サーバーの接続方法はどちらを使うべき？
どちらにも一長一短があるため、状況に応じて両方を併用するのが良いと思われる。
単純なクエリを扱うなら前者を、複雑またはトランザクションが必要なクエリなら後者を使うのが良さそうだ。

## EmbeddedとServerはどちらがいいの？
練習用、あるいは単一プロセスで済むようなアプリケーションならEmbeddedで良さそう。
一方、複数のプロセスが並列にデータアクセスするような状況ではサーバーを立てるべきだと考えられる。

## まとめ

|方式|DB接続方法|リモート接続|手動トランザクション制御|備考|
|---|---|---|---|---|
|Embedded|JanusGraphFactory|×|〇||
|Server|DriverRemoteConnection&<br>AnonymousGraphTraversal|〇|×|Graphインターフェースに対するアクセスができない|
|Server|Cluster&Client|〇|〇|クエリは文字列で送信(インジェクションに注意)|