lightweight gRPC replacement である dRPC を試してみる。

こんにちは k-jun です。今回は gRPC から諸々の必要ないものを削ぎ落とした結果、高速化された dRPC を試してみます。

https://github.com/storj/drpc

誕生の経緯などはこちらの post が詳しそうです。 gRPC には使っていない機能、Option なども多く これら必要ないものを落として Network Storage のサービスを高速化させたとかなんとか。(ざっと雰囲気で読んでいます)

https://www.storj.io/blog/introducing-drpc-our-replacement-for-grpc

post によると、gRPC とすぐに取り替えっこ出来ると書いてあるのでやって見ます。 ちょうどよく、前に gRPC 試した際の repository が残っているので、これを dRPC 化してみます。

https://github.com/k-jun/playground-grpc.git

syntax = "proto3";

option go_package = "github.com/k-jun/grpc";

service PlaygroundDrpc {
  rpc CreateTodo(TodoData) returns (TodoData) {}
  rpc ReadTodo(TodoData) returns (TodoData) {}
  rpc UpdateTodo(TodoData) returns (TodoData) {}
  rpc DeleteTodo(TodoData) returns (TodoData) {}
}

message TodoData {
  string id = 1;
  string title = 2;
  string body = 3;
}
$ protoc --proto_path=proto --go_out=drpc --go-drpc_out=drpc --go_opt=paths=source_relative --go-drpc_opt=paths=source_relative proto/playground-grpc.proto

ディレクトリ構成は以下な感じ。

$ tree
.
├── Makefile
├── README.md
├── drpc
│   ├── playground-grpc.pb.go
│   └── playground-grpc_drpc.pb.go
├── go.mod
├── go.sum
├── grpc
│   └── playground-grpc.pb.go
├── main.go
├── proto
│   └── playground-grpc.proto
└── server
    └── server.go

Client 側のコードも書いてみる。

package main

import (
    "context"
    "fmt"
    "net"

    pb "playground-grpc/drpc"

    "storj.io/drpc/drpcconn"
)

func main() {
    rawconn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        panic(err)
    }
    conn := drpcconn.New(rawconn)
    defer conn.Close()

    client := pb.NewDRPCPlaygroundDrpcClient(conn)
    ctx := context.Background()
    creatTodo, err := client.CreateTodo(ctx, &pb.TodoData{
        Id:    "1",
        Title: "homework",
        Body:  "math",
    })
    if err != nil {
        panic(err)
    }
    fmt.Println(creatTodo)
    readTodo, err := client.ReadTodo(ctx, &pb.TodoData{Id: "1"})
    if err != nil {
        panic(err)
    }
    fmt.Println(readTodo)
    updateTodo, err := client.UpdateTodo(ctx, &pb.TodoData{Title: "updated title", Body: "updated body"})
    if err != nil {
        panic(err)
    }
    fmt.Println(updateTodo)
    deleteTodo, err := client.DeleteTodo(ctx, &pb.TodoData{Id: "1"})
    if err != nil {
        panic(err)
    }
    fmt.Println(deleteTodo)
}
$ go run cmd/client.go
id:"1"  title:"homework"  body:"math"
id:"1"  title:"homework"  body:"math"
title:"updated title"  body:"updated body"
id:"1"

おおお凄い!実際に動いた!確かに gRPC から移行するのであれば、Server の Register の箇所をちょこっと変えるだけで実装できた。 変更差分を PR にして、まとめると。

https://github.com/k-jun/playground-grpc/pull/1/files

せっかく Golang なので、簡単に Benchmark も取ってみる。

func BenchmarkDRPC(b *testing.B) {
    rawconn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        panic(err)
    }
    conn := drpcconn.New(rawconn)
    defer conn.Close()

    client := pb.NewDRPCPlaygroundDrpcClient(conn)
    ctx := context.Background()

    for i := 0; i < b.N; i++ {
        _, err := client.CreateTodo(ctx, &pb.TodoData{
            Id:    "1",
            Title: "homework",
            Body:  "math",
        })
        if err != nil {
            panic(err)
        }
        _, err = client.ReadTodo(ctx, &pb.TodoData{Id: "1"})
        if err != nil {
            panic(err)
        }
        _, err = client.UpdateTodo(ctx, &pb.TodoData{Title: "updated title", Body: "updated body"})
        if err != nil {
            panic(err)
        }
        _, err = client.DeleteTodo(ctx, &pb.TodoData{Id: "1"})
        if err != nil {
            panic(err)
        }
    }
}
$ go test -bench . ./...
?       playground-grpc [no test files]
goos: darwin
goarch: amd64
pkg: playground-grpc/cmd
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkDRPC-12            5433            216052 ns/op
PASS
ok      playground-grpc/cmd     2.607s

gRPC に戻して以下のコードで Benchmark を再び回す。

func BenchmarkGRPC(b *testing.B) {
    conn, err := grpc.Dial("localhost:8080", []grpc.DialOption{grpc.WithInsecure()}...)
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    client := pb.NewPlaygroundGrpcClient(conn)
    ctx := context.Background()

    for i := 0; i < b.N; i++ {
        _, err := client.CreateTodo(ctx, &pb.TodoData{
            Id:    "1",
            Title: "homework",
            Body:  "math",
        })
        if err != nil {
            panic(err)
        }
        _, err = client.ReadTodo(ctx, &pb.TodoData{Id: "1"})
        if err != nil {
            panic(err)
        }
        _, err = client.UpdateTodo(ctx, &pb.TodoData{Title: "updated title", Body: "updated body"})
        if err != nil {
            panic(err)
        }
        _, err = client.DeleteTodo(ctx, &pb.TodoData{Id: "1"})
        if err != nil {
            panic(err)
        }
    }
}
$ go test -bench . ./...
?       playground-grpc [no test files]
goos: darwin
goarch: amd64
pkg: playground-grpc/cmd
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkGRPC-12            3003            398791 ns/op

だめじゃん 笑。半分ぐらいの性能しか出ていない。もとがこんだけ違うと Build しても対して差分は変わらなそう。

それでは今回はこのへんで。