☃️

GraphDBの「Dgraph」の話 - Goで叩く

22 min read

こんにちは。
もう、Webの開発を何年もやってきたのになと思うのに、まだまだ理解できてなかったことがあるんだなぁと日々痛感させられてるmasamikiです。

GraphDB

こないだ GoのORM「ent」の話 を書かせてもらったのですが、GraphQLを使う仕事も結構でてきたなと。

さてさて、同じGraphの名を冠するGraphDBとはなんでしょうか。

Graph自体はentでMySQLに対して表現していたように、DBによらず、そのデータ構造のモデルを作ることは難しくありません。

この記事の言葉をかりると Graph DBとは、index-freeな隣接する頂点を取得できるストレージシステムなら、それをGraphDBと呼べるそうです。

もうちょっと細かい定義としては、(といっても翻訳してるだけですが)

  • すべての要素(つまり、頂点またはエッジ)には、隣接する要素への直接のポインタがある。
  • どの頂点が他のどの頂点に隣接しているかを判断するために、O(logn)インデックスルックアップを行わない。(Bツリー等のインデックスを用いた走査が行われない)
  • グラフが接続されている場合、グラフ全体は単一の原子データ構造になる

つまり、ポインタが入ってしまっている構造であれば、こういう隣接点の取り方をできるのですが、

Bツリーなどのインデクシングを使ったDBでのGraph表現はこうなってしまうので、

MySQLでentを使って、Graphな表現ができても、MySQLはGraphDBではないと言える訳ですね。

O(logn)インデックスが、データ量が多くても、それ程遅くならない仕組みであったとしても、データ構造がGraphになることが分かっているなら、隣接点のポインタが直接入ってしまっているGraphDBを使う方がスピード的にも確かに有利そうですね。(記事の55ページ)

どんなのがあるの?

「Neo4j」、「Janus Graph」、「sones」、「OrientDB」、「Info Grid」、「Infinite Graph」といったDBがあり、Neo4jが一番使い易く、広く使われている印象です。

Dgraph

GraphQLを最速で構築するアプリ(←Webサイトの記述を翻訳)だそうです。
まず、デモ遊びたい方はここで遊べます。
(といっても、クエリのこと分からなかったら分かんないっすよね)

get startedの説明だとこんな感じ。

Dgraph is an open-source, scalable, distributed, highly available and fast graph database, designed from the ground up to be run in production.
(Dgraphとは、オープンソースでスケーラブルな分散型の高可用性で高速なグラフデータベースであり、本番環境で実行できるようにゼロから設計されています。)

そして、githubの説明はこんな感じ。

Dgraph is a horizontally scalable and distributed GraphQL database with a graph backend. 
(Dgraphは、グラフバックエンドを備えた水平方向にスケーラブルで分散型のGraphQLデータベースです。)

あれ?GraphDBと書かず、よく見るとGraphQL databaseと書いてある……

グラフバックエンドと書いてあるように、確かにGraphDBではあるものは間違いなさそうなものの、
クエリとして使える言語がGraphQL(正確にはGraphQLを基にした言語)だというのをどうやら押したいのではないのかなぁと。

押すのも、そう、GraphQLをネイティブにサポートしてるデータベースって、これがどうやら最初らしんですよね。

ちなみに、↓はgithubにあった、他のDBとの比較表です。(Neo4jでGraphQLを使うためには、Extensionが必要だったりします。)

Features Dgraph Neo4j Janus Graph
Architecture Sharded and Distributed Single server (+ replicas in enterprise) Layer on top of other distributed DBs
Replication Consistent None in community edition (only available in enterprise) Via underlying DB
Data movement for shard rebalancing Automatic Not applicable (all data lies on each server) Via underlying DB
Language GraphQL inspired Cypher, Gremlin Gremlin
Protocols Grpc / HTTP + JSON / RDF Bolt + Cypher Websocket / HTTP
Transactions Distributed ACID transactions Single server ACID transactions Not typically ACID
Full Text Search Native support Native support Via External Indexing System
Regular Expressions Native support Native support Via External Indexing System
Geo Search Native support External support only Via External Indexing System
License Apache 2.0 GPL v3 Apache 2.0

GraphQL(っぽいクエリ)を使えるむっちゃ速いGraphDB、それが「Dgraph」という感じですかね。

Dgraph、シャーディングして分散できるようにもなっているのですが、これまでのグラフデータベースはパフォーマンスが高いものの分散されるような設計ではなかったこともあり、そこもDgraphの特徴のようです。

準備

Docker Composeを使ってDgraphを用意するとしたら、こんな感じになります。
これを実行すると、
Dgraph Alpha、Dgraph Zero、Ratelが起動します。

  • Dgraph Zero: Dgraphクラスターを制御し、サーバーをグループに割り当て、サーバーグループ間でデータのバランスを取り直します。
  • Dgraph Alpha: predicateとindexをホストします。predicateは、ノードに関連付けられたプロパティ、または2つのノード間の関係を示します。indexは、適切な関数を使用してフィルタリングを有効にするためにpredicateに関連付けることができるトークナイザーです。
  • Ratel: クエリ、ミューテーション、スキーマの変更を実行するためのUIを提供します。
version: "3.2"
services:
  dgraph:
    image: dgraph/standalone:v20.03.0
    ports:
      - 8080:8080
      - 8000:8000
      - 9080:9080
    volumes:
      - ./dgraph:/dgraph

ただし、このstandaloneイメージは、クイックスタート用なので、本番環境での利用は推奨されていません。

本番用にはこんな形で、それぞれのコンテナを立ち上げるような形になります。

version: "3.2"
services:
  zero:
    image: dgraph/dgraph:latest
    volumes:
      - type: volume
        source: dgraph
        target: /dgraph
        volume:
          nocopy: true
    ports:
      - 5080:5080
      - 6080:6080
    restart: on-failure
    command: dgraph zero --my=zero:5080
  
  alpha:
    image: dgraph/dgraph:latest
    volumes:
      - type: volume
        source: dgraph
        target: /dgraph
        volume:
          nocopy: true
    ports:
      - 8080:8080
      - 9080:9080
    restart: on-failure
    command: dgraph alpha --my=alpha:7080 --lru_mb=2048 --zero=zero:5080 --whitelist 172.18.0.1/8
  ratel:
    image: dgraph/dgraph:latest
    volumes:
      - type: volume
        source: dgraph
        target: /dgraph
        volume:
          nocopy: true
    ports:
      - 8000:8000
    command: dgraph-ratel

volumes:
  dgraph:

リードレプリカを作りたい時は、nginxを使ってこんな感じ

version: "3.5"
services:
  nginx:
    image: nginx:1.17.7
    depends_on:
      # Hostnames referenced in nginx.conf need to be available
      # before Nginx starts
      - zero1
      - zero2
      - zero3
      - alpha1
      - alpha2
      - alpha3
    ports:
      - 80:80
      - 8080:8080
      - 9080:9080
    volumes:
      - type: bind
        source: ./nginx.conf
        target: /etc/nginx/conf.d/dgraph.conf
        read_only: true
  alpha1:
    image: dgraph/dgraph:latest
    working_dir: /data/alpha1
    hostname: alpha1
    ports:
    - 8080
    - 9080
    volumes:
      - type: volume
        source: dgraph
        target: /dgraph
        volume:
          nocopy: true
    command: dgraph alpha --my=alpha1:7080 --lru_mb=1024 --zero=zero1:5080 --logtostderr
      -v=2 --idx=1 --whitelist 172.19.0.9/8 
  alpha2:
    image: dgraph/dgraph:latest
    working_dir: /data/alpha2
    hostname: alpha2
    depends_on:
    - alpha1
    labels:
      cluster: test
    ports:
    - 8080
    - 9080
    volumes:
      - type: volume
        source: dgraph
        target: /dgraph
        volume:
          nocopy: true
    command: dgraph alpha --my=alpha2:7080 --lru_mb=1024 --zero=zero1:5080 --logtostderr
      -v=2 --idx=2 --whitelist 172.19.0.9/8 
  alpha3:
    image: dgraph/dgraph:latest
    working_dir: /data/alpha3
    hostname: alpha3
    ports:
    - 8080
    - 9080
    volumes:
      - type: volume
        source: dgraph
        target: /dgraph
        volume:
          nocopy: true
    command: dgraph alpha --my=alpha3:7080 --lru_mb=1024 --zero=zero1:5080 --logtostderr
      -v=2 --idx=3 --whitelist 172.19.0.9/8 
  zero1:
    image: dgraph/dgraph:latest
    working_dir: /data/zero1
    hostname: zero1
    ports:
    - 5080
    - 6080
    volumes:
      - type: volume
        source: dgraph
        target: /dgraph
        volume:
          nocopy: true
    command: dgraph zero --idx=1 --my=zero1:5080 --replicas=3 --logtostderr -v=2
      --bindall
  zero2:
    image: dgraph/dgraph:latest
    working_dir: /data/zero2
    hostname: zero2
    ports:
    - 5080
    - 6080
    volumes:
      - type: volume
        source: dgraph
        target: /dgraph
        volume:
          nocopy: true
    command: dgraph zero --idx=2 --my=zero2:5080 --replicas=3 --logtostderr -v=2
      --peer=zero1:5080
  zero3:
    image: dgraph/dgraph:latest
    working_dir: /data/zero3
    hostname: zero3
    ports:
    - 5080
    - 6080
    volumes:
      - type: volume
        source: dgraph
        target: /dgraph
        volume:
          nocopy: true
    command: dgraph zero --idx=3 --my=zero3:5080 --replicas=3 --logtostderr -v=2
      --peer=zero1:5080
  ratel:
    image: dgraph/dgraph:latest
    working_dir: /data/ratel
    ports:
      - 8000:8000
    volumes:
      - type: volume
        source: dgraph
        target: /dgraph
        volume:
          nocopy: true
    command: dgraph-ratel
volumes:
  dgraph:

↓nginx

upstream alpha_http {
  server alpha1:8080;
  server alpha2:8080;
  server alpha3:8080;
}

# $upstream_addr is the ip:port of the Dgraph Alpha defined in the upstream
# Example: 172.25.0.2, 172.25.0.7, 172.25.0.5 are the IP addresses of alpha1, alpha2, and alpha3
# /var/log/nginx/access.log will contain these logs showing "localhost to <upstream address>"
# for the different backends. By default, Nginx load balancing is round robin.

# [15/Jan/2020:00:28:11 +0000] 172.25.0.1 - - -  localhost to: 172.25.0.2:9080: POST /api.Dgraph/Query HTTP/2.0 200 upstream_response_time 0.028 msec 1579048091.865 request_time 0.027
# [15/Jan/2020:00:28:11 +0000] 172.25.0.1 - - -  localhost to: 172.25.0.7:9080: POST /api.Dgraph/Query HTTP/2.0 200 upstream_response_time 0.032 msec 1579048091.897 request_time 0.031
# [15/Jan/2020:00:28:11 +0000] 172.25.0.1 - - -  localhost to: 172.25.0.5:9080: POST /api.Dgraph/Query HTTP/2.0 200 upstream_response_time 0.028 msec 1579048091.926 request_time 0.028

log_format upstreamlog '[$time_local] $remote_addr - $remote_user - $server_name $host to: $upstream_addr: $request $status upstream_response_time $upstream_response_time msec $msec request_time $request_time';

server {
  listen 9080 http2;
  access_log /var/log/nginx/access.log upstreamlog;
  location / {
    grpc_pass grpc://alpha_grpc;
  }
}

server {
  listen 8080;
  access_log /var/log/nginx/access.log upstreamlog;
  location / {
    proxy_pass http://alpha_http;
  }
}

あとはdocker-compose upで立ち上げるだけ。

そして、リードレプリカを使う時は、NginxのgRPCに対するコネクションを増やすためにこんなコマンドを実行。
docker-compose exec alpha1 dgraph increment --alpha nginx:9080 --num=10

lru_mbでalphaが使うRAMの設定ができるのですが、これは利用できるRAMの3分の1程度をしてして上げるのいいとのこと。(なんかJVMみたい)

ちなみに、alphaに対してportが2つ(8080、9080)空けられているのは、8080がhttp用、9080がgrpc用です。

Dockerを立ち上げてはみましたが、Dgraphを搭載したフルマネージドGraphQLバックエンドサービスであるSlashGraphQLというのがあるので、自分で立てなくても大丈夫です。そっちも使えます。

触ってみよう

http://localhost:8000/ でRatelにアクセスすることができます。
デモと内容は一緒ですね)

Launch Latest(でなくてもいいですが)をクリックすると、Ratelの画面が表示されます。

叩き方

GraphQL押しのGraphDBですが、Dgraphにはいくつかの叩き方があります。

  1. GraphQL
  2. RDF(Resource Description Framework): ミューテーションで使えます。
  3. DQL(Dgraph Query Language):クエリで使えます。

GraphQLは言わずもがなですが、DQLは、名前の通り、Dgraph専用のクエリで、上で説明していたUIのRatelでもDQLを叩いて、取得したりミューテーションしたりします。

Schemaの作成

GraphQLがー、と言っている通り、Schemaが重要。
まず、Schemaを作ります。

ただし、Dgraphは、構造やスキーマを強制せず、すぐにデータの入力を開始し、必要に応じて制約を追加できるので、スキーマ作りは必須ではないです。

ではでは、このグラフのスキーマを作ってみましょか。

GraphQLで作る

type Tweet {
    id: ID!
    title: String!
    content: String! @search(by: [fulltext])
    user: User!
}

type User {
    name: String! @id
    age: Int @search
    tweets: [Tweet] @hasInverse(field: user)
}

GraphQLのスキーマをもっと知りたい方はこちらにて

@idや@hasInverseというのはdgraph側で予約されてるdirectiveです。
index of Directiveにもあるように
@idがUNIQUEですよというDirectiveで、@hasInverseが一方向のEdgeだけでなく、双方向のEdgeにするという意味のDirectiveです。

これをschema.grapqlというファイルを作って、その中に書き込み、curlなら

curl -X POST localhost:8080/admin/schema --data-binary '@schema.graphql'

こんな形でデータを投げてやればSchemaがつくられます。

こんなレスポンスが返ってきてるのではないでしょうか?

{"data":{"code":"Success","message":"Done"}}

Dgraphは/graphqlのパスでGraphQL APIを実行し、/adminで管理インターフェースを実行します。 /adminインターフェースを使用すると、/graphqlで提供されるGraphQLスキーマを追加および更新できます。

GraphQLを使わず作る(RDF?)

定義の書き方はこんな感じです。
まず、Predicatesを記載して、それをtypeに使ってあげるという定義の仕方です。

Tweet.content: string @index(fulltext) .
Tweet.title: string .
Tweet.user: uid .
User.age: int @index(int) .
User.name: string @index(hash) @upsert .
User.tweets: [uid] .
type Tweet {
	Tweet.title
	Tweet.content
	Tweet.user
}
type User {
	User.name
	User.age
	User.tweets
}

また、DirectiveもGraphQLのモノとはことなっており、@indexはインデックスを貼るのに使い、@upsertは、トランザクションをコミットする際に、インデックスキーのコンフリクトをチェックし、同時アップサートを実行時のユニーク制約を適用させるのに使います。

この定義をこんな形でリクエストすれば、定義が行われます。

curl "localhost:8080/alter" -XPOST -d $'
    Tweet.content: string @index(fulltext) .
    Tweet.title: string .
    Tweet.user: uid .
    User.age: int @index(int) .
    User.name: string @index(hash) @upsert .
    User.tweets: [uid] .
    type Tweet {
	Tweet.title
	Tweet.content
	Tweet.user
    }
    type User {
	User.name
	User.age
	User.tweets
    }'

こんなレスポンスが返ってきてるのではないでしょうか。

{"data":{"code":"Success","message":"Done"}}

cliではなく、Ratelのguiでもできます。

RatelのSchemaページを開くと、Dgraphに登録してるSchemaを見ることができます。

右上のBuik Editをクリックするとこんなページがでてきます。

defaultで入っているSchemaに追加して、上のSchemaを入れて上げましょう。

こんな感じでSchemaが入ったのがみれるかと思います。

(あ、今日12月24日だからか、メニューの一番上、サンタの帽子被ってますね笑)

Goでやってみる

GoでDgraphを扱うためのpackageがありまして、名をdgoと言います。

import (
	"github.com/dgraph-io/dgo/v200"
	"github.com/dgraph-io/dgo/v200/protos/api"
	"google.golang.org/grpc"
)

最低限必要なpackageはこんな感じです。
grpcを使うので、grpcのpackageを含めておく必要があります。

まず、DgraphのClientを準備しましょう。
自分はfuncで切り出してこんな感じにしてます。

func getDgraphClient() (*dgo.Dgraph, CancelFunc) {
	conn, err := grpc.Dial("localhost:9080", grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}

	dc := dgo.NewDgraphClient(api.NewDgraphClient(conn))

	return dc, func() {
		if err := conn.Close(); err != nil {
			log.Printf("Error while closing connection:%v", err)
		}
	}
}

GraphQLで投げたい。とは思っているものの、どうやらdgoはDQLで投げるようで。
Schemaはこんな感じで記載してやります。

op := &api.Operation{Schema: `
	Tweet.content: string @index(fulltext) .
	Tweet.title: string .
	Tweet.user: uid .
	User.age: int @index(int) .
	User.name: string @index(hash) @upsert .
	User.tweets: [uid] .
	type Tweet {
		Tweet.title
		Tweet.content
		Tweet.user
	}
	type User {
		User.name
		User.age
		User.tweets
	}
`,}

ここまでをまとめるとこんな感じです。

package main

import (
	"context"
	"log"

	"github.com/dgraph-io/dgo/v200"
	"github.com/dgraph-io/dgo/v200/protos/api"
	"google.golang.org/grpc"
)

type CancelFunc func()

func main() {
	dc, cancel := getDgraphClient()
	defer cancel()

	ctx := context.Background()
	op := &api.Operation{Schema: `
		Tweet.content: string .
	Tweet.content: string @index(fulltext) .
	Tweet.title: string .
	Tweet.user: uid .
	User.age: int @index(int) .
	User.name: string @index(hash) @upsert .
	User.tweets: [uid] .
	type Tweet {
		Tweet.title
		Tweet.content
		Tweet.user
	}
	type User {
		User.name
		User.age
		User.tweets
	}`,}
	err := dc.Alter(ctx, op)
	if err != nil {
		log.Fatal(err)
	}
}

func getDgraphClient() (*dgo.Dgraph, CancelFunc) {
	conn, err := grpc.Dial("localhost:9080", grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}

	dc := dgo.NewDgraphClient(api.NewDgraphClient(conn))

	return dc, func() {
		if err := conn.Close(); err != nil {
			log.Printf("Error while closing connection:%v", err)
		}
	}
}

これを実行して、Schemaを作ります。

Mutation(Add)

GraphQLで追加する

Dgraphは、Schema作成時にミューテーションのための、inputとreturnの型をSchemaに自動で追加してくれます。

なので、今回の場合だとこんなmutationを書いてあげればデータの作成ができます。

mutation MyMutation {
  addUser(input: [
    { name: "masamiki", age: 99, tweets: [{ content: "Dgraph", title: "GraphQL" }]}
  ]) {
    numUids
    user {
      name
    }
  }

これをcurlでリクエストすると

curl -X POST 'localhost:8080/graphql' -H 'Content-Type: application/graphql' -d 'mutation MyMutation {
  addUser(input: [
    { name: "masamiki", age: 99, tweets: [{ content: "Dgraph", title: "GraphQL" }]}
  ]) {
    numUids
    user {
      name
    }
  }
}'

userの名前が載ったレスポンスが返ってきてるのではないでしょうか。

{"data":{"addUser":{"numUids":2,"user":[{"name":"masamiki"}]}},"extensions":{"touched_uids":5,"tracing":{"version":1,"startTime":"2020-12-24T05:43:26.9452586Z","endTime":"2020-12-24T05:43:26.9797827Z","duration":34519200,"execution":{"resolvers":[{"path":["addUser"],"parentType":"Mutation","fieldName":"addUser","returnType":"AddUserPayload","startOffset":243200,"duration":34227000,"dgraph":[{"label":"mutation","startOffset":426600,"duration":23382300},{"label":"query","startOffset":28743900,"duration":5642100}]}]}}}}

RDFで追加する

RDFだとこんな感でTripleというものを使って表現します。
構造としては<subject> <predicate> <object> .という感じで、subjectは、有向性Edgeのpredicateを持つobjectにリンクされることを意味しています。

{
   set {
      _:t <Tweet.content> "Dgraph" .
      _:t <Tweettitle> "GraphQL" .
      _:t <dgraph.type> "Tweet" .
      _:m <User.name> "masamiki" .
      _:m <User.age> "99" .
      _:m <User.tweets> _:t .
      _:m <dgraph.type> "User" .
   }
}

ここではSchemaのどのタイプなのかを<dgraph.type>という形で表現します。

これをcurlで

curl -H "Content-Type: application/rdf" "localhost:8080/mutate?commitNow=true" -XPOST -d $'
{
   set {
      _:t <Tweet.content> "Dgraph" .
      _:t <Tweet.title> "GraphQL" .
      _:t <dgraph.type> "Tweet" .
      _:m <User.name> "masamiki" .
      _:m <User.age> "99" .
      _:m <User.tweets> _:t .
      _:m <dgraph.type> "User" .
   }
}'

そうするとこん..(ry

{"data":{"code":"Success","message":"Done","queries":null,"uids":{"m":"0x2b","t":"0x2a"}},"extensions":{"server_latency":{"parsing_ns":661700,"processing_ns":8785500,"assign_timestamp_ns":1486000,"total_ns":10474300},"txn":{"start_ts":1975,"commit_ts":1976,"preds":["1-Tweet.content","1-Tweet.title","1-User.age","1-User.name","1-User.tweets","1-dgraph.type"]}}}

Goでやってみる

まず、structの定義です。

type Tweet struct {
	Uid     string     	`json:"uid,omitempty"`
	Title	string     	`json:"Tweet.title,omitempty"`
	Content	string     	`json:"Tweet.content,omitempty"`
	User 	User		`json:"Tweet.user,omitempty"`
	DType   []string   `json:"dgraph.type,omitempty"`
}

type User struct {
	Uid     string     	`json:"User.uid,omitempty"`
	Name    string     	`json:"User.name,omitempty"`
	Age     int        	`json:"User.age,omitempty"`
	Tweets 	[]Tweet 	`json:"User.tweets,omitempty"`
	DType   []string   	`json:"dgraph.type,omitempty"`
}

投げる時は、JSONで投げるので、JSONにマーシャルするためのタグを付けておきます。

structを初期化、JSONにマーシャル、transactionを貼ってミューテーション。
これが、一連の流れです。

package main

import (
	"context"
	"encoding/json"
	"log"

	"github.com/dgraph-io/dgo/v200"
	"github.com/dgraph-io/dgo/v200/protos/api"
	"google.golang.org/grpc"
)

type CancelFunc func()

type Tweet struct {
	Uid     string     	`json:"uid,omitempty"`
	Title	string     	`json:"Tweet.title,omitempty"`
	Content	string     	`json:"Tweet.content,omitempty"`
	User 	User		`json:"Tweet.user,omitempty"`
	DType   []string   `json:"dgraph.type,omitempty"`
}

type User struct {
	Uid     string     	`json:"User.uid,omitempty"`
	Name    string     	`json:"User.name,omitempty"`
	Age     int        	`json:"User.age,omitempty"`
	Tweets 	[]Tweet 	`json:"User.tweets,omitempty"`
	DType   []string   	`json:"dgraph.type,omitempty"`
}

func main() {
	dc, cancel := getDgraphClient()
	defer cancel()

	ctx := context.Background()
	u := User{
		Uid:     "_:m",
		Name:    "masamiki",
		Age:     99,
		DType:   []string{"User"},
		Tweets: []Tweet{{
			Title: "GraphQL",
			Content: "Dgraph",
			DType:   []string{"Tweet"},
		}},
	}

	mu := &api.Mutation{
		CommitNow: true,
	}
	pb, err := json.Marshal(u)
	if err != nil {
		log.Fatal(err)
	}

	mu.SetJson = pb
	response, err := dc.NewTxn().Mutate(ctx, mu)
	if err != nil {
		log.Fatal(err)
	}
	log.Println(response)
}

func getDgraphClient() (*dgo.Dgraph, CancelFunc) {
	conn, err := grpc.Dial("localhost:9080", grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}

	dc := dgo.NewDgraphClient(api.NewDgraphClient(conn))

	return dc, func() {
		if err := conn.Close(); err != nil {
			log.Printf("Error while closing connection:%v", err)
		}
	}
}

Query

Queryの話、書こうと思ったのですが、長くなりそうなので、後日、別で書きます。

とりあえず……

DQLで取ってみる

DQLを使って、今回入れたデータをさっとRatelで見てみるだけは、やりましょうか。
とりあえず、userとそれに紐付くtweetを取りたいならこんな感じ。

{
  users(func: has(<dgraph.type>)) @filter(eq(<dgraph.type>, "User")) {
    expand(_all_) {
      expand(_all_) 
    }
  }
} 

expand(_all_)を使うと、すべてのpredicateを表示することができます。
一旦全部取得するって時は、これを使うのがいいですかね。

そうすると、こんな関係性をもったノードが表示されているのではないでしょうか?

以上。
また、時間作って、次の記事書きますmm。