スキーマの何がありがたいかと言えば、意図しないデータの混入を防ぐことができるという点である。スキーマを定義する作業はめんどくさいものだけれども、あとあと自分を守ってくれる。
JanusGraphにはSchema Constraints(スキーマ制約)という機能があり、予め設定したラベル/プロパティしか登録できないという制約を設けることができる。
設定
Schema Constraintsに関する設定は以下の2つ。
変数 | 説明 | デフォルト値 |
---|---|---|
schema.constraints | グラフによって使用されるスキーマ制約を設定する。値がtrue でかつschema.default がnone に設定されている場合、スキーマ違反が発生した場合にIllegalArgumentException がthrowされる。値が true でかつschema.default がnone でない場合は、schema.default の設定に従ってスキーマ制約が自動的に生成される。値が false (デフォルト値)の場合はスキーマ制約は適用されない。 |
false |
schema.default | グラフによって作用されるDefaultSchemaMaker の挙動を設定する。- default : データ挿入時に自動的にスキーマを生成する- none : 自動的なスキーマ生成は行わない- logging : default とほぼ同様だが、スキーマ生成時にメッセージを出すその他、 DefaultSchemeMaker のインターフェースを実装したクラス名(+パッケージ名)を指定することもできる |
default |
この2つの変数の組み合わせによって挙動が変わる。制約を有効にするには
- schema.constraints=true
- schema.default=none
とすればよい。
サーバー設定方法
ダウンロードしたアーカイブを使うか、Dockerを使うかで設定方法が異なる。
アーカイブを使う場合
自分で設定ファイルを作成する。conf/gremlin-server
ディレクトリにmyserver.yaml
とmyserver.properties
の2つのファイルを用意する。
host: 0.0.0.0
port: 8182
scriptEvaluationTimeout: 30000
channelizer: org.apache.tinkerpop.gremlin.server.channel.WebSocketChannelizer
graphs: {
graph: conf/gremlin-server/myserver.properties}
scriptEngines: {
gremlin-groovy: {
plugins: { org.janusgraph.graphdb.tinkerpop.plugin.JanusGraphGremlinPlugin: {},
org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample.groovy]}}}}
serializers:
- { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
- { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0, config: { serializeResultToString: true }}
- { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
# Older serialization versions for backwards compatibility:
- { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
- { className: org.apache.tinkerpop.gremlin.driver.ser.GryoLiteMessageSerializerV1d0, config: {ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
- { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0, config: { serializeResultToString: true }}
- { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerGremlinV2d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }}
- { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerGremlinV1d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistryV1d0] }}
- { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistryV1d0] }}
processors:
- { className: org.apache.tinkerpop.gremlin.server.op.session.SessionOpProcessor, config: { sessionTimeout: 28800000 }}
- { className: org.apache.tinkerpop.gremlin.server.op.traversal.TraversalOpProcessor, config: { cacheExpirationTime: 600000, cacheMaxSize: 1000 }}
metrics: {
consoleReporter: {enabled: true, interval: 180000},
csvReporter: {enabled: true, interval: 180000, fileName: /tmp/gremlin-server-metrics.csv},
jmxReporter: {enabled: true},
slf4jReporter: {enabled: true, interval: 180000},
gangliaReporter: {enabled: false, interval: 180000, addressingMode: MULTICAST},
graphiteReporter: {enabled: false, interval: 180000}}
maxInitialLineLength: 4096
maxHeaderSize: 8192
maxChunkSize: 8192
maxContentLength: 65536
maxAccumulationBufferComponents: 1024
resultIterationBatchSize: 64
writeBufferLowWaterMark: 32768
writeBufferHighWaterMark: 65536
長いが、もともとあったファイルをコピペして、graph(6行目)のところだけ自前のファイルパスに書き換えただけである。
gremlin.graph=org.janusgraph.core.JanusGraphFactory
storage.backend=berkeleyje
storage.directory=db/berkeley
schema.constraints=true
schema.default=none
そしてサーバーを起動(Windowsの人はgremlin-server.batを使用のこと)
$ bin/gremlin-server.sh conf/gremlin-server/myserver.yaml
Dockerの場合
コンテナに環境変数を追加する。Docker-Composeの設定ファイルを以下に示す。
version: '3'
services:
schemajanus:
image: janusgraph/janusgraph
ports:
- 8182:8182
environment:
- janusgraph.schema.default=none
- janusgraph.schema.constraints=true
volumes:
- janusgraph-schema-test:/var/lib/janusgraph
volumes:
janusgraph-schema-test:
立ち上げる場合は
$ docker-compose up -d
停止する場合は
$ docker-compose down
データを抹消したい(リセットしたい)場合は
$ docker-compose down -v
動作確認
Vertex Label - Property Constraints
スキーマを作ってみて、どのような挙動をするのか確認してみる。まず、スキーマを定義する。いつものようにGremlinコンソールからリモート接続してコマンドを打ち込む(公式ドキュメントそのまま)。
gremlin> :remote connect tinkerpop.server conf/remote.yaml session
==>Configured localhost/127.0.0.1:8182-[4f6e7645-3836-4108-bcc2-e44c685e6147]
gremlin> :> m = graph.openManagement()
==>org.janusgraph.graphdb.database.management.ManagementSystem@47f525aa
gremlin> :> person = m.makeVertexLabel('person').make()
==>person
gremlin> :> name = m.makePropertyKey('name').dataType(String.class).cardinality(org.janusgraph.core.Cardinality.SET).make()
==>name
gremlin> :> birthDate = m.makePropertyKey('birthDate').dataType(Long.class).cardinality(org.janusgraph.core.Cardinality.SINGLE).make()
==>birthDate
gremlin> :> m.addProperties(person, name, birthDate)
==>person
gremlin> :> m.commit()
==>null
制約を作っているのは、addProperties
。ここに指定したラベルとプロパティが関連付けられる模様。
まず、頂点とプロパティを挿入してみる。
gremlin> :> g.addV("person").property("name", "bob").property("birthDate", 31)
==>v[4296]
登録済みのラベルとプロパティは当然OK。
gremlin> :> g.addV("animal")
Vertex Label with given name does not exist: animal
Type ':help' or ':h' for help.
Display stack trace? [yN]
登録していないラベルはダメ。よし。
gremlin> :> g.addV("person").property("country", "japan")
Property Key with given name does not exist: country
Type ':help' or ':h' for help.
Display stack trace? [yN]
登録していないプロパティもダメ。よしよし。
gremlin> :> g.addV("person")
==>v[4128]
むむむ。プロパティなしでも登録できてしまう。調べてみたが今のところ、定義していないプロパティを使うのはNGだが、定義したプロパティを使わないのはOKらしい。プロパティ毎になくてもよい
か必須
かを指定できるといいのだけれども……。
もう1つラベルとプロパティを追加して、以下のような組み合わせにしてみる。
- ラベルperson - プロパティname - プロパティbirthDate
- ラべルlang - プロパティname - プロパティtype
gremlin> :> lang = m.makeVertexLabel("lang").make()
==>lang
gremlin> :> type = m.makePropertyKey("type").dataType(String.class).cardinality(org.janusgraph.core.Cardinality.SINGLE).make()
==>type
gremlin> :> name = m.getPropertyKey("name")
==>type
gremlin> :> m.addProperties(lang, name, type)
==>lang
gremlin> :> m.commit()
gremlin> :> g.addV("lang").property("name", "java").property("type", "procedual")
==>v[8208]
gremlin> :> g.addV("lang").property("birthDate", 30)
Property Key constraint does not exist for given Vertex Label [lang] and property key [birthDate].
Type ':help' or ':h' for help.
Display stack trace? [yN]
gremlin> :> g.addV("person").property("type", "lazy")
Property Key constraint does not exist for given Vertex Label [person] and property key [type].
Type ':help' or ':h' for help.
Display stack trace? [yN]
それぞれのラベルについて、addProperties
で関連付けていないプロパティを付けようとするとエラーによる。グッド!
Edge Label - Property Constraints
エッジのラベルについても確認してみる。
gremlin> :> m = graph.openManagement()
==>org.janusgraph.graphdb.database.management.ManagementSystem@3baddb94
gremlin> :> follow = m.makeEdgeLabel("follow").multiplicity(org.janusgraph.core.Multiplicity.MULTI).make()
==>follow
gremlin> :> name = m.getPropertyKey("name")
==>name
gremlin> :> m.addProperties(follow, name)
An Edge [follow] can not have a property [name] with the cardinality [SET].
Type ':help' or ':h' for help.
Display stack trace? [yN]
公式ドキュメントの通りにやると怒られるんですが……。エッジに対してカーディナリティがSET
のプロパティは付けられない、と。SINGLE
のプロパティを新しく作ってやり直します。
gremlin> :> date_from = m.makePropertyKey("date_from").dataType(String.class).cardinality(org.janusgraph.core.Cardinality.SINGLE).make()
==>date_from
gremlin> :> m.addProperties(follow, date_from)
==>follow
gremlin> :> m.commit()
==>null
それで、適当に頂点を2つ用意して、エッジを追加、と。
gremlin> :> alice = g.addV("person").property("name", "alice").next()
==>v[4240]
gremlin> :> ellie = g.addV("person").property("name", "ellie").next()
==>v[8336]
gremlin> :> g.addE("follow").from(alice).to(ellie)
Connection constraint does not exist for given Edge Label [follow], outgoing Vertex Label [person] and incoming Vertex Label [person]
Type ':help' or ':h' for help.
Display stack trace? [yN]
今度はConnection Constraintを先に定義しろと怒られた。さもありなん。先にそちらの対応を行う。
Connection Constraints
頂点とエッジのつながりのルールを定義する。
ここまでの例で作成した頂点ラベルperson
とエッジラベルfollow
を接続する。
- 1.(person) -> 2.[follow] -> 3.(person)
gremlin> :> m = graph.openManagement()
==>org.janusgraph.graphdb.database.management.ManagementSystem@ab1b7ec
gremlin> :> person = m.getVertexLabel("person")
==>person
gremlin> :> follow = m.getEdgeLabel("follow")
==>follow
// エッジとその両端の頂点の関係を定義する。引数は2.エッジ, 1.元の頂点, 3.先の頂点の順番
gremlin> :> m.addConnection(follow, person, person)
==>follow
gremlin> :> m.commit()
==>null
と、ここで問題があり、このままエッジを追加してもConnection constraint does not exist
とエラーが発生した。サーバーを再起動することで解消した(参考)。
gremlin> :> alice = g.V().has("name", "alice").next()
==>v[4240]
gremlin> :> ellie = g.V().has("name", "ellie").next()
==>v[8336]
gremlin> :> g.addE("follow").from(alice).to(ellie)
==>e[odxcc-38g-27th-oe09c][4192-follow->8336]
ようやくエッジが追加できるようになった。1つ手前に戻って、プロパティの追加を確認してみる。
//関連付け済みのプロパティdate_fromを試す
gremlin> :> g.addE("follow").from(alice).to(ellie).property("date_from", "2020/04/01")
==>e[odxqk-38g-27th-oe09c][4192-follow->40964304] // OK
//関連付けしていないプロパティbirthDateを試す
gremlin> :> g.addE("follow").from(alice).to(ellie).property("birthDate", 30)
Property Key constraint does not exist for given Edge Label [follow] and property key [birthDate].
Type ':help' or ':h' for help.
Display stack trace? [yN]
ちゃんと機能している。
自動化
ここまで大分ごちゃごちゃといろいろ作業してしまったため、整理を兼ねて、自動化のスクリプトを組んだ(使い方はこちらを参照)。
:remote connect tinkerpop.server conf/remote.yaml session
:> m = graph.openManagement()
// Vertex Labels
:> person = m.makeVertexLabel("person").make()
:> lang = m.makeVertexLabel("lang").make()
// Edge Labels
:> follow = m.makeEdgeLabel("follow").multiplicity(org.janusgraph.core.Multiplicity.SIMPLE).make()
:> use = m.makeEdgeLabel("use").multiplicity(org.janusgraph.core.Multiplicity.SIMPLE).make()
// Properties
:> name = m.makePropertyKey("name").dataType(String.class).cardinality(org.janusgraph.core.Cardinality.SET).make()
:> date_from = m.makePropertyKey("date_from").dataType(String.class).cardinality(org.janusgraph.core.Cardinality.SINGLE).make()
:> birth_month = m.makePropertyKey("birth_month").dataType(Byte.class).cardinality(org.janusgraph.core.Cardinality.SINGLE).make()
:> birth_day = m.makePropertyKey("birth_day").dataType(Byte.class).cardinality(org.janusgraph.core.Cardinality.SINGLE).make()
:> lang_type = m.makePropertyKey("lang_type").dataType(String.class).cardinality(org.janusgraph.core.Cardinality.SINGLE).make()
// Vertex Label - Property Constraints
:> m.addProperties(person, name, birth_month, birth_day)
:> m.addProperties(lang, name, lang_type)
// Edge Label - Property Constraints
:> m.addProperties(follow, date_from)
// Connection Constraints
:> m.addConnection(follow, person, person)
:> m.addConnection(use, person, lang)
:> m.commit()
:remote close
カスタマイズして使って欲しい。データを追加するスクリプトも用意した。
:remote connect tinkerpop.server conf/remote.yaml session
// Add Vertices
:> bob = g.addV("person").property("name", "bob").property("birth_month", 2).property("birth_day", 11).next()
:> james = g.addV("person").property("name", "james").property("birth_month", 5).property("birth_day", 24).next()
:> alice = g.addV("person").property("name", "alice").property("birth_month", 8).property("birth_day", 31).next()
:> ellie = g.addV("person").property("name", "ellie").property("birth_month",12).property("birth_day", 25).next()
:> java = g.addV("lang").property("name", "Java").property("lang_type", "procedual").next()
:> haskell = g.addV("lang").property("name", "Haskell").property("lang_type", "functional").next()
// Add Edges
:> g.addE("follow").from(bob).to(alice).property("date_from", "2015/11/07")
:> g.addE("follow").from(alice).to(ellie).property("date_from", "2019/12/12")
:> g.addE("use").from(james).to(java)
:> g.addE("use").from(james).to(haskell)
// Commit Transation
:> graph.tx().commit()
:remote close
あとがき
今回でスキーマ関連はおしまい。次はトランザクションについての理解を深める。