< back

Neo4j Bolt Driver for Go: Code Reading - Routing Table

本記事では、Neo4j Driver として公式サポートされている、neo4j-go-driver における Routing Table の実装について見ていきます。

本記事では、v4.3 branch の最新である 5a14c7024ca commit hash のソースコードを前提にしています。

Routing Table

Routing Table の概要については、以下の記事を以前書きましたので、そちらをぜひご覧ください。

"Neo4j Bolt Protocol - Routing Table"

ROUTE Message Type Support

データベースインスタンスに対して Routing Table の情報をリクエストするのに ROUTE リクエストメッセージが策定されたのは以前の記事でも述べました。この ROUTE リクエストメッセージを利用したルーティングの実装は、以下の commit で実装されています。

fd5844fcf0f730ad3d4550de63e02709189f384b

ROUTE Message Request

Bolt Protocol の仕様 にある通り、ROUTE メッセージであることを示すフラグは、66 が利用されています。

const (
    //...
    msgRoute      byte = 0x66 // > 4.2
)

ROUTE メッセージのフィールドとしては、以下の三つでした。

routing::Dictionary,
bookmarks::List<String>,
db:String

この仕様に沿ったバイナリのエンコーディングは、outgoing.go において実装されています。routing/bookmarks/db それぞれのフィールドを順番にバイナリに変換していることが分かります。

// neo4j/internal/bolt/outgoing.go
func (o *outgoing) appendRoute(context map[string]string, bookmarks []string, database string) {
    //...
    o.begin()
    // signature
    o.packer.StructHeader(byte(msgRoute), 3)
    // routing
    o.packer.MapHeader(len(context))
    for k, v := range context {
        o.packer.String(k)
        o.packer.String(v)
    }
    // bookmarks
    o.packer.ArrayHeader(len(bookmarks))
    for _, bookmark := range bookmarks {
        o.packer.String(bookmark)
    }
    // database
    if database == db.DefaultDatabase {
        o.packer.Nil()
    } else {
        o.packer.String(database)
    }
    o.end()
}

ここで多用されている (p *Packer) String() は、PackStream で策定されている String 型のエンコーディング方式です。

Marker Size Maximum number of bytes
D0 8-bit big-endian unsigned integer 255 bytes
// neo4j/internal/packstream/packer.go
func (p *Packer) String(s string) {
    p.listHeader(len(s), 0x80, 0xd0)
    p.buf = append(p.buf, []byte(s)...)
}

ROUTE Message Response

hydrator.go の以下の箇所において、ROUTE リクエストメッセージを送った結果返却されたバイナリを、db.RoutingTable にデコードしています。

func (h *hydrator) routingTable() *db.RoutingTable {
    rt := db.RoutingTable{}
    nkeys := h.unp.Len()
    for ; nkeys > 0; nkeys-- {
        h.unp.Next()
        key := h.unp.String()
        h.unp.Next()
        switch key {
        case "ttl":
            rt.TimeToLive = int(h.unp.Int())
        case "servers":
            nservers := h.unp.Len()
            for ; nservers > 0; nservers-- {
                h.routingTableRole(&rt)
            }
        //...
        }
    }
    return &rt
}

func (h *hydrator) routingTableRole(rt *db.RoutingTable) {
    //...
}

db.RoutingTable は、TTL およびロール別のサーバーのアドレスを格納する slice をフィールドに持つ以下の構造体です。

type RoutingTable struct {
    TimeToLive int
    Routers    []string
    Readers    []string
    Writers    []string
}

Read Routing Table

Routing Table を読む実装自体は、readtable.go に実装されています。コネクションプールから利用できるコネクションを借りながら、ROUTE リクエストメッセージを送信し、データベースインスタンスから Routing Table 情報を取得しています。

// neo4j/internal/router/readtable.go

// Tries to read routing table from any of the specified routers using new or existing connection
// from the supplied pool.
func readTable(ctx context.Context, pool Pool, routers []string, routerContext map[string]string, bookmarks []string,
    database string, boltLogger log.BoltLogger) (*db.RoutingTable, error) {
    //...
}

最後に

以上、neo4j-go-driver の Routing Table の実装についての紹介を行いました。

November 09, 2021

Recommended

  1. Neo4j を VectorDB として使う

    Neo4j は v5.13 で Vector search index を実装した。使い方は他の VectorDB と同様で、ベクタライズしたデータを Vector Index に格納する。Cypher クエリを通じて近似解を導出する。 この記事では、Vector Index の使い方について紹介する。具体例では、ノ…
  2. Neo4j Causal Clustering 紹介

    本記事では、Neo4j における Causal Clustering (因果クラスタリング) について説明します。 クラスタリングの目的 データベースを運用する場合、単一のインスタンスを稼働させるか、Causal Clustering を用いてクラスター構成を組むか(クラスタリング)のいずれかを選択することになるでし…
  3. Neo4j Bolt Handshake Protocol Introduced

    Bolt Protocol において、サーバーとクライアントが接続を開始するためのプロトコルとして、Bolt Handshake Protocol Specification の仕様が策定されています。 本記事では、Bolt Handshake Protocol の概要について説明します。 Handshake Wor…