YugabyteDB Go RPC client

automate all the database things

The YugabyteDB RPC API isn’t an official API, and that’s what makes it interesting. The whole distributed aspect of YugabyteDB is based on that API. Digging through it is a perfect way to understand the internals of the database. The RPC API can also be used to automate various aspects of the database. I’ll come back to this subject in near future.

Another reason for investigating the RPC API was to have a lightweight version of yb-admin tools. I run everything in containers. When I want to connect to a cluster, I start another container with a client and connect with it to the cluster. The YugabyteDB Docker image isn’t tiny, in fact, 2.11.2.0-b89 is 1.77GB.

One point seventy seven gigabyte!

I’m kinda not a fan of pulling in an image of such size just to execute list_all_masters. Neither I am a fan of doing docker exec in a production setting. I wanted to have a tool comparable to yb-admin but of a smaller footprint. And I wanted a library, an embeddable library to help automate the hell out of it.

And that’s why some months ago I’ve written the very first version of the YugabyteDB client library for Go. Today, I’ve published the 0.0.2-beta.1 release. Here’s a brief overview of what it can do:

  • given a list of master addresses, find the leader master and use that for further communication with the cluster
  • handle leader change, auto-discovery of a new leader, handles redelivery
  • auto-reconnect on connection loss, handles redelivery
  • wraps API error types and exposes them as Go errors
  • utilities to deal with various ID types and hybrid time
  • automatically discovers available messages and services, generic execution API is completely type-driven
  • client discovers the service and operation based on the request type
  • configurable logging sink, via hclog.InterceptLogger
  • configurable metrics sink

The only feature unsupported feature are side cars but they will come.

I’ve written more about side cars in my previous article.

What’s the best way to sharpen your appetite? Code, obviously! So here’s how to start with this client:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

import (
  "encoding/json"
  "fmt"

  "github.com/hashicorp/go-hclog"
  "github.com/radekg/yugabyte-db-go-client/client"
  "github.com/radekg/yugabyte-db-go-client/configs"

  clientErrors "github.com/radekg/yugabyte-db-go-client/errors"
  ybApi "github.com/radekg/yugabyte-db-go-proto/v2/yb/api"
)

func main() {

  logger := hclog.Default()
  
  clientConfig := &configs.YBClientConfig{
      MasterHostPort: []string{"127.0.0.1:7100", "127.0.0.2:7100", "127.0.0.3:7100"},
  }

  ybdbClient := client.NewYBClient(clientConfig).WithLogger(logger)
  if err := ybdbClient.Connect(); err != nil {
    logger.Error("could not initialize api client", "reason", err)
    panic(err)
  }

  defer ybdbClient.Close()

  request := &ybApi.ListMastersRequestPB{}
  response := &ybApi.ListMastersResponsePB{}

  if err := ybdbClient.Execute(request, response); err != nil {
    if terr, ok := err.(*clientErrors.ServiceRPCError); ok {
      logger.Error("service RPC error", "reason", terr.Error())
      panic(err)
    }
    logger.Error("could not execute RPC call", "reason", err.Error())
    panic(err)
  }

  if err := clientErrors.NewMasterError(response.Error); err != nil {
    logger.Error("YugabyteDB master error", "reason", err.Error())
    panic(err)
  }

  jsonBytes, jsonErr := json.MarshalIndent(response, "", "  ")
  if jsonErr != nil {
    logger.Error("failed serializing response to JSON", "reason", jsonErr.Error())
    panic(jsonErr)
  }

  logger.Info("YugabyteDB masters")
  fmt.Println(string(jsonBytes))

}

Maybe you need to adjust the master addresses list in line 20.

but wait, there’s more

The client comes with github.com/radekg/yugabyte-db-go-client/testutils/master and github.com/radekg/yugabyte-db-go-client/testutils/tserver packages. Those contain a bunch of dockertest based utilities for writing tests using containers. Simply write your integration test and have a cluster right there, when running go test (use some higher timeout, these need some time for the cluster to spin up).

For reference:

That’ll get you going.

§there’s … more

Being able to execute calls is one thing, knowing what to execute is a totally different beast.

There’s this little repository here: radekg/yugabyte-db-go-client-api. It contains a naive implementation of some of the yb-admin commands. That code is currently written against v0.0.1-beta.X version of the client (so 2.11.0.0 and 2.11.1.0 YugabyteDB) but it might be a good source of inspiration.

There’s a rewrite planned for that repository so best is not to invest too much time integrating that as a dependency in your own code.

§protobuf

The client depends on the radekg/yugabyte-db-go-proto library. That’s where the extracted *.proto files and generated Go code live. Have a look at the readme in that repository to find out more on how to generate your own code from YugabyteDB sources.

The radekg/yugabyte-db-go-proto library versioning follows the YugabyteDB versioning.

§a couple of closing notes

Client versions:

  • 0.0.1-beta.4 is the last version for YugabyteDB 2.11.0.0 and 2.11.1.0
  • 0.0.2-beta.1 is targeted for YugabyteDB 2.11.2+, there are some protobuf changes which might not be exactly compatible with previous database versions, I don’t really know yet, haven’t tried it myself, but I doubt it’ll work

Would I recommend using this in production? Yeah, why not. It’s used in production. But please keep in mind, there’s no warranty!

If you find a bug, feel free to submit a pull request.

§commercial plug

If your company is looking for someone who can help you migrate your database to the cloud and/or your company needs a solid partner who knows a thing or two about YugabyteDB, feel free to reach out to me at radek.gruchalski@klarrio.com.