JanusGraphのスキーマ① - Edge Label Multiplicity

本当はスキーマ全体についての記事を書こうと思ったが、最初のEdge Label Multiplicityだけで文量が嵩みそうだったので、ひとまずこれだけを重点的に解説してみる。

例によって、英語が読めるなら公式のドキュメントに目を通すのをおススメする。
[Schema and Data Modeling](https://docs.janusgraph.org/basics/schema/)

## Edge Label Multiplicity

エッジ(辺)のラベルについては、Multiplicity(多重度？)という設定を行うことができる。Multiplicityは5種類の中から1つ選択することができ、それぞれ制限の内容が異なる。制限に反する場合、エッジ登録時にエラーが発生して失敗する。

|Multiplicity|説明|
|---|---|
|MULTI|**[デフォルト]**一切の制限なし。あらゆる2頂点間について、方向、エッジ数ともに好きなだけエッジを張ることができる|
|SIMPLE|2頂点間のエッジについてはどちらの方向についても最大1本まで|
|MANY2ONE|頂点に入ってくる方向(incoming)については制限なし。出ていく方向(outgoing)については全ての頂点に対して合計1本まで|
|ONE2MANY|頂点から出ていく方向(outgoing)については制限なし。入ってくる方向(incoming)については全ての頂点に対して合計1本まで|
|ONE2ONE|頂点から出ていく方向は最大1本まで、頂点に入ってくる方向も最大1本まで|

簡単な操作で試してみる。新規のデータベースを立ち上げた状態でGremlinコンソールからリモート接続する([参考](https://qiita.com/chromia/items/8c70ca78df62c47dcae2#%E5%AE%9F%E9%A8%93%E7%94%A8%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%92%E6%96%B9%E3%83%AA%E3%82%BB%E3%83%83%E3%83%88%E6%96%B9%E6%B3%95))

```groovy
gremlin> :remote connect tinkerpop.server conf/remote.yaml session
==>Configured localhost/127.0.0.1:8182-[f3e6dcfb-60ab-44d7-aaeb-0f26b01135cc]
gremlin> :> m = graph.openManagement()
==>org.janusgraph.graphdb.database.management.ManagementSystem@146ecc1c
gremlin> :> m.makeEdgeLabel("multi").multiplicity(Multiplicity.MULTI).make()
==>multi
gremlin> :> m.makeEdgeLabel("simple").multiplicity(Multiplicity.SIMPLE).make()
==>simple
gremlin> :> m.makeEdgeLabel("many2one").multiplicity(Multiplicity.MANY2ONE).make()
==>many2one
gremlin> :> m.makeEdgeLabel("one2many").multiplicity(Multiplicity.ONE2MANY).make()
==>one2many
gremlin> :> m.makeEdgeLabel("one2one").multiplicity(Multiplicity.ONE2ONE).make()
==>one2one
gremlin> :> m.commit()
==>null
```

以上のようにして、5種類それぞれのEdge Labelを登録する。`printSchema`を実行すると、以下のような状態になっている。

```groovy
gremlin> :> m = graph.openManagement()
==>org.janusgraph.graphdb.database.management.ManagementSystem@552c511a
gremlin> :> m.printSchema()
==>------------------------------------------------------------------------------------------------
Edge Label Name                | Directed    | Unidirected | Multiplicity                         |
---------------------------------------------------------------------------------------------------
multi                          | true        | false       | MULTI                                |
simple                         | true        | false       | SIMPLE                               |
many2one                       | true        | false       | MANY2ONE                             |
one2many                       | true        | false       | ONE2MANY                             |
one2one                        | true        | false       | ONE2ONE                              |
---------------------------------------------------------------------------------------------------
```

さらに、グラフに4つほど確認用の頂点を追加する。

```groovy
:> bob = g.addV("person").property("name", "bob").next()
==>v[4136]
:> james = g.addV("person").property("name", "james").next()
==>v[4184]
:> alice = g.addV("person").property("name", "alice").next()
==>v[8232]
:> ellie = g.addV("person").property("name", "ellie").next()
==>v[4152]
```

これらに対して、エッジを作成してみる。

### MULTI

![janusgraph_schema1_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235258/f68e8e6b-6c8a-efd9-8d28-663596e0fdc7.png)


```groovy
gremlin> :> g.addE("multi").from(bob).to(alice)
==>e[2rp-36w-6c5-6co][4136-follow->8232]
gremlin> :> g.addE("multi").from(bob).to(alice)
==>e[35x-36w-6c5-6co][4136-follow->8232]
```

自由にエッジを張ることができる。当然だが、同じ2頂点間に複数エッジを張っても問題ない。

### SIMPLE

![janusgraph_schema1_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235258/3a080499-c4de-ba22-d34a-b3dcee151311.png)


```groovy
gremlin> :> g.addE("simple").from(bob).to(alice)
==>e[4qt-36w-1lh-6co][4136-simple->8232]
gremlin> :> g.addE("simple").from(bob).to(alice)
An edge with the given label already exists between the pair of vertices and the label [simple] is simple
Type ':help' or ':h' for help.
Display stack trace? [yN]
```

同じ2頂点間に複数張ろうとするとエラーになった。`Already Exists`と怒られた。

```groovy
gremlin> :> g.addE("simple").from(alice).to(bob)
==>e[551-6co-1lh-36w][8232-simple->4136]
gremlin> :> g.addE("simple").from(alice).to(bob)
An edge with the given label already exists between the pair of vertices and the label [simple] is simple
Type ':help' or ':h' for help.
Display stack trace? [yN]
```

向きを逆にして(aliceとbobを入れ替えて)も同じようにエラーとなる。

### MANY2ONE

![janusgraph_schema1_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235258/1b43fcbc-11f6-73c7-c882-c88163c011ff.png)

`MANY2ONE`は`SIMPLE`の制限を含みつつ、さらに厳しい。

```groovy
gremlin> :> g.addE("many2one").from(bob).to(alice)
==>e[5j9-36w-2dx-6co][4136-many2one->8232]
gremlin> :> g.addE("many2one").from(bob).to(ellie)
An edge with the given label already exists on the out-vertex and the label [many2one] is out-unique
Type ':help' or ':h' for help.
Display stack trace? [yN]
gremlin> :> g.addE("many2one").from(james).to(alice)
==>e[1l7-388-2dx-6co][4184-many2one->8232]
gremlin> :> g.addE("many2one").from(james).to(ellie)
An edge with the given label already exists on the out-vertex and the label [many2one] is out-unique
Type ':help' or ':h' for help.
Display stack trace? [yN]
```

1本目は問題ないが、2本目は違う頂点に対して張ろうとしてもエラーになる。エラーメッセージの`out-unique`が言うとおりに、頂点から出る矢印は1本に限られる。

### ONE2MANY

![janusgraph_schema1_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235258/25986bdd-1c19-ef5b-6668-b956f0e15a5a.png)

`MANY2ONE`もまた、`SIMPLE`の制限を含みつつ、さらに厳しい。

```groovy
gremlin> :> g.addE("one2many").from(alice).to(bob)
==>e[5xh-6co-36d-36w][8232-one2many->4136]
gremlin> :> g.addE("one2many").from(ellie).to(bob)
An edge with the given label already exists on the in-vertex and the label [one2many] is in-unique
Type ':help' or ':h' for help.
Display stack trace? [yN]
gremlin> :> g.addE("one2many").from(alice).to(james)
==>e[6bp-6co-36d-388][8232-one2many->4184]
gremlin> :> g.addE("one2many").from(ellie).to(james)
An edge with the given label already exists on the in-vertex and the label [one2many] is in-unique
Type ':help' or ':h' for help.
Display stack trace? [yN]
```

ある頂点から、それぞれ違う頂点に対して、何本張っても問題はない。ただし、同じ頂点に対して2本目を張ることは出来ない。エラーメッセージの`in-unique`が言うとおりに、頂点に入ってくる矢印は1本に限られる。

### ONE2ONE

![janusgraph_schema1_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235258/042fdafd-63f7-436e-091a-4bd52ac75809.png)

`ONE2ONE`は最も重い制限である。`MANY2ONE`と`ONE2MANY`両方の条件を課される。

```groovy
gremlin> :> g.addE("one2one").from(bob).to(alice)
==>e[6px-36w-3yt-6co][4136-one2one->8232]
gremlin> :> g.addE("one2one").from(alice).to(james)
==>e[745-6co-3yt-388][8232-one2one->4184]
gremlin> :> g.addE("one2one").from(james).to(ellie)
==>e[1zf-388-3yt-37c][4184-one2one->4152]
gremlin> :> g.addE("one2one").from(ellie).to(alice)
An edge with the given label already exists on the in-vertex and the label [one2one] is in-unique
Type ':help' or ':h' for help.
Display stack trace? [yN]
```

入ってくるのも1本まで、出ていくのも1本までという、シンプルな形のグラフとなる(線形リストとか、環状ループとか)。

### 選択方針

不都合がない範囲で、なるべく厳しい制限を課すように設定するのが良い。データ入力時に誤ったエッジを入力してしまうミスを少しだけでも減らすことができる。