[Today I Learn][21-03-2020] Modeling Tweeter Post sử dụng Dgraph

Lý do có series này

Đơn giản là muốn nhờ Viblo note lại những kiến thức học được trong một ngày thôi. Đây không phải là các bài blog chia sẻ hoàn chỉnh nên mọi người có thể không cần để ý đến nó

Modeling Tweeter với Dgraph

Phân tích chút

Đầu tiên phải xem một Tweeter message thì có format như thế nào. Nó trông như thế này

Ta thấy để tweet trên có các thành phần, có thể chia thành các entities:

  • Author: chính là user có tên Karthic Rao tên đăng nhập @hackintoshrao
  • Body chính là nội dung của tweet đó
  • Hash tags là các tags liên quan đến tweet đó như bên trên là #GraphDB#GraphQL
  • Mention một tweet có thể mention đến một tweeter user khác như trong hình trên là mention đến thằng @dgraphlabs và thằng @francesc

Thiết kế database

Dựng các nodes

Dễ dàng nhận thấy có thể xây dựng database cho ứng dụng trên bao gồm 3 nodes:

  • User node biểu diễn thông tin của User cũng chính là biểu diễn Author và các thằng được mention
  • Tweet node biểu diễn thông tin của một tweet
  • Hashtag node biểu diễn thông tin của một tag Dựng sẵn 3 node lên trước sau đó ta sẽ vẽ relation giữa* chúng

Dựng các relation

Xây dựng các relation tức là đi tìm các cạnh liên kết giữa các node với nhau. Dễ thấy

  • TweetUser có quan hệ hai chiều tức là một User sẽ là tác giả của một Tweet (authored) và một Tweet sẽ mention đến nhiều User (mentioned)

  • TweetHashtag node cũng sẽ có quan hệ. Một Tweet sẽ được tag với các Hashtag với relationship tagged_with

Đây là biểu đồ mô hình giống như tweet phía trên

Tạo thử database

Tạo thử thôi

{
  "set": [
    {
      "user_handle": "hackintoshrao",
      "user_name": "Karthic Rao",
      "uid": "_:hackintoshrao",
      "authored": [
        {
          "tweet": "Test tweet for the fifth episode of getting started series with @dgraphlabs. Wait for the video of the fourth one by @francesc the coming Wednesday!\n#GraphDB #GraphQL",
          "tagged_with": [
            {
              "uid": "_:graphql",
              "hashtag": "GraphQL"
            },
            {
              "uid": "_:graphdb",
              "hashtag": "GraphDB"
            }
          ],
          "mentioned": [
            {
              "uid": "_:francesc"
            },
            {
              "uid": "_:dgraphlabs"
            }
          ]
        }
      ]
    },
    {
      "user_handle": "francesc",
      "user_name": "Francesc Campoy",
      "uid": "_:francesc",
      "authored": [
        {
          "tweet": "So many good talks at #graphqlconf, next year I'll make sure to be *at least* in the audience!\nAlso huge thanks to the live tweeting by @dgraphlabs for alleviating the FOMO😊\n#GraphDB ♥️ #GraphQL",
          "tagged_with": [
            {
              "uid": "_:graphql"
            },
            {
              "uid": "_:graphdb"
            },
            {
              "hashtag": "graphqlconf"
            }
          ],
          "mentioned": [
            {
              "uid": "_:dgraphlabs"
            }
          ]
        }
      ]
    },
    {
      "user_handle": "dgraphlabs",
      "user_name": "Dgraph Labs",
      "uid": "_:dgraphlabs",
      "authored": [
        {
          "tweet": "Let's Go and catch @francesc at @Gopherpalooza today, as he scans into Go source code by building its Graph in Dgraph!\nBe there, as he Goes through analyzing Go source code, using a Go program, that stores data in the GraphDB built in Go!\n#golang #GraphDB #Databases #Dgraph ",
          "tagged_with": [
            {
              "hashtag": "golang"
            },
            {
              "uid": "_:graphdb"
            },
            {
              "hashtag": "Databases"
            },
            {
              "hashtag": "Dgraph"
            }
          ],
          "mentioned": [
            {
              "uid": "_:francesc"
            },
            {
              "uid": "_:dgraphlabs"
            }
          ]
        },
        {
          "uid": "_:gopherpalooza",
          "user_handle": "gopherpalooza",
          "user_name": "Gopherpalooza"
        }
      ]
    }
  ]
}

Đây là đồ thị vừa mới xây dựng

Trong đó nút xanh dương là user node nốt xanh lá cây là tweet node còn node màu tìm là hashtag node

Query thử lấy ra danh sách những user

{
  tweet_graph(func: has(user_handle)) {
     user_handle
  }
}

Query thêm cả các tweet và hashtag tương ứng

{
  tweet_graph(func: has(user_handle)) {
  	user_handle
    authored {
      tweet
      tagged_with {
        hashtag
      }
    }
  }
}

thu được kết quả

Đánh index

Hash index

Bây giờ muốn search với các user_handle nhất định thì cần phải đánh index. Ở đây search chính xác nên cần phải đánh index hash

Sau đó thêm vào câu query eq(user_handle, "hackintoshrao")

{
  tweet_graph(func: eq(user_handle, "hackintoshrao")) {
  	user_handle
    authored {
      tweet
      tagged_with {
        hashtag
      }
    }
  }
}

Thu được kết quả

Sau đó muốn query thêm cả các thằng mentioned nữa thì thêm vào query

{
  tweet_graph(func: eq(user_handle, "hackintoshrao")) {
     user_name
     authored {
      tweet
      tagged_with {
        hashtag
      }
      mentioned {
        user_name
      }
    }
  }
}

thu được kết quả

Exact index

Hash index chỉ hỗ trợ query eq trong khi muốn query gt, ge, lt, le thì cần phải dùng đến index dạng exact. Chuyển lại user_handle sang exact ta có

Lưu ý: Mặc dù Dgraph cho phép thay đổi index nhưng hạn chế việc thay đổi vì khi thay đổi thì sẽ mất thời gian để đánh lại index cũng như tất cả các xử lý mutations (thêm, sửa, xoá) sẽ bị dừng lại trong quá trình đánh lại index

Sau khi đánh lại index thì thu được kết quả như sau

Theo thứ tự alphabet thì các node có user_handle lớn hơn dgraphlabsfrancesc, gopherpaloozahackintoshrao

Term index

term index cho phép chúng ta search nội dung cần tìm kiếm theo 1 hoặc nhiều keywords. Đầu tiên thử đánh index term cho predicate tweet

Dgraph cung cấp hai tuỳ chọn để query với term index đó là alloftermsanyofterms. Ngoài hai hàm trên ra thì term index chỉ support thêm operator là eq

Find thử

{
  find_tweets(func: anyofterms(tweet, "Go Graph")) {
    tweet
  }
}

câu truy vấn tìm tất cả các tweet có chứa từ Go hoặc Graph Kết quả thu được

{
        "tweet": "Let's Go and catch @francesc at @Gopherpalooza today, as he scans into Go source code by building its Graph in Dgraph!\nBe there, as he Goes through analyzing Go source code, using a Go program, that stores data in the GraphDB built in Go!\n#golang #GraphDB #Databases #Dgraph "
}

Ngược lại với anyofterms thì allofterms sẽ tìm tất cả các bài tweet mà chứa tất cả các keywords trong truy vấn

{
  find_tweets(func: allofterms(tweet, "Go GraphQL")) {
    tweet
  }
}

Kết quả không có tweet nào

Thay thử từ khoá Go trong câu truy vấn bằng graphdb thì ta thu được

Chú ý: query với term index bằng alloftermsanyofterm không phân biệt chữ hoa, chữ thường và được loại bỏ các kí tự đặc biệt ở đầu và cuối mỗi keywords

Tìm kiếm nâng cao

Full-text search

Muốn chuyển sang mode fulltext thì thực hiện giống như các lần đánh index trước

Sau đó hãy thử query

{
  search_tweet(func: alloftext(tweet, "graph data and analyze it in graphdb")) {
    tweet
  }
}

ta thấy thu được kết quả

Tương ứng với đoạn tweet sau

Có thể nhận thấy không phải tất cả các từ trong search string đều có mặt trong tweet matched. Đây là tác dụng của full text search

Tìm kiếm với regex

Ngoài việc hỗ trợ tìm kiếm với các thể loại trên thì thằng Dgraph này còn hỗ trợ tìm kiếm với regex. Đầu tiên list tất cả các hashtag

{
  hash_tags(func: has(hashtag)) {
    hashtag
  }
}

Chúng ta thấy có tổng cộng 4 hash tag có chứa substring graphDgraph, GraphQL, graphqlconf, graphDB chúng ta có thể sử dụng built-in regexp để tìm kiếm các hashtag dạng này với cấu trúc sử dụng là regexp(predicate, /regular-expression/). Cùng query thử

{
  reg_search(func: regexp(hashtag, /^.*graph.*$/)) {
    hashtag
  }
}

Có vẻ như chúng ta đã quên mất đánh index trigram cho hashtag .

Sau khi đánh index trigram xong thì query lại thu được kết quả

Tổng kết và nhận xét

  • Mô hình hoá một vấn đề khá đơn giản với graph
  • Query trực quan, thuận tiện cho việc phát triển backend.
  • Hỗ trợ sẵn các kiểu index cho bài toán tìm kiếm string khá tiện, với mục đích tìm kiếm cơ bản thì chức năng cũng giống như một số third partty khác như Elasticsearch tuy nhiên chưa tìm hiểu được cách custom tokenizer cho đa ngôn ngữ hoặc config các thông số cho full text search
  • Nhìn chung thấy chưa có nhiều điểm đặc sắc nên hi vọng sẽ có những khám phá mới mẻ hơn trong những ngày tới