• Stars
    star
    867
  • Rank 52,618 (Top 2 %)
  • Language
    Go
  • License
    BSD 3-Clause "New...
  • Created over 3 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Protocol Buffers compiler that generates optimized marshaling & unmarshaling Go code for ProtoBuf APIv2

vtprotobuf, the Vitess Protocol Buffers compiler

This repository provides the protoc-gen-go-vtproto plug-in for protoc, which is used by Vitess to generate optimized marshall & unmarshal code.

The code generated by this compiler is based on the optimized code generated by gogo/protobuf, although this package is not a fork of the original gogo compiler, as it has been implemented to support the new ProtoBuf APIv2 packages.

Available features

vtprotobuf is implemented as a helper plug-in that must be run alongside the upstream protoc-gen-go generator, as it generates fully-compatible auxiliary code to speed up (de)serialization of Protocol Buffer messages.

The following features can be generated:

  • size: generates a func (p *YourProto) SizeVT() int helper that behaves identically to calling proto.Size(p) on the message, except the size calculation is fully unrolled and does not use reflection. This helper function can be used directly, and it'll also be used by the marshal codegen to ensure the destination buffer is properly sized before ProtoBuf objects are marshalled to it.

  • equal: generates the following helper methods

    • func (this *YourProto) EqualVT(that *YourProto) bool: this function behaves almost identically to calling proto.Equal(this, that) on messages, except the equality calculation is fully unrolled and does not use reflection. This helper function can be used directly.

    • func (this *YourProto) EqualMessageVT(thatMsg proto.Message) bool: this function behaves like the above this.EqualVT(that), but allows comparing against arbitrary proto messages. If thatMsg is not of type *YourProto, false is returned. The uniform signature provided by this method allows accessing this method via type assertions even if the message type is not known at compile time. This allows implementing a generic func EqualVT(proto.Message, proto.Message) bool without reflection.

  • marshal: generates the following helper methods

    • func (p *YourProto) MarshalVT() ([]byte, error): this function behaves identically to calling proto.Marshal(p), except the actual marshalling has been fully unrolled and does not use reflection or allocate memory. This function simply allocates a properly sized buffer by calling SizeVT on the message and then uses MarshalToSizedBufferVT to marshal to it.

    • func (p *YourProto) MarshalToVT(data []byte) (int, error): this function can be used to marshal a message to an existing buffer. The buffer must be large enough to hold the marshalled message, otherwise this function will panic. It returns the number of bytes marshalled. This function is useful e.g. when using memory pooling to re-use serialization buffers.

    • func (p *YourProto) MarshalToSizedBufferVT(data []byte) (int, error): this function behaves like MarshalTo but expects that the input buffer has the exact size required to hold the message, otherwise it will panic.

  • marshal_strict: generates the following helper methods

    • func (p *YourProto) MarshalVTStrict() ([]byte, error): this function behaves like MarshalVT, except fields are marshalled in a strict order by field's numbers they were declared in .proto file.

    • func (p *YourProto) MarshalToVTStrict(data []byte) (int, error): this function behaves like MarshalToVT, except fields are marshalled in a strict order by field's numbers they were declared in .proto file.

    • func (p *YourProto) MarshalToSizedBufferVTStrict(data []byte) (int, error): this function behaves like MarshalToSizedBufferVT, except fields are marshalled in a strict order by field's numbers they were declared in .proto file.

  • unmarshal: generates a func (p *YourProto) UnmarshalVT(data []byte) that behaves similarly to calling proto.Unmarshal(data, p) on the message, except the unmarshalling is performed by unrolled codegen without using reflection and allocating as little memory as possible. If the receiver p is not fully zeroed-out, the unmarshal call will actually behave like proto.Merge(data, p). This is because the proto.Unmarshal in the ProtoBuf API is implemented by resetting the destionation message and then calling proto.Merge on it. To ensure proper Unmarshal semantics, ensure you've called proto.Reset on your message before calling UnmarshalVT, or that your message has been newly allocated.

  • pool: generates the following helper methods

    • func (p *YourProto) ResetVT(): this function behaves similarly to proto.Reset(p), except it keeps as much memory as possible available on the message, so that further calls to UnmarshalVT on the same message will need to allocate less memory. This an API meant to be used with memory pools and does not need to be used directly.

    • func (p *YourProto) ReturnToVTPool(): this function returns message p to a local memory pool so it can be reused later. It clears the object properly with ResetVT before storing it on the pool. This method should only be used on messages that were obtained from a memory pool by calling YourProtoFromVTPool. Using p after calling this method will lead to undefined behavior.

    • func YourProtoFromVTPool() *YourProto: this function returns a YourProto message from a local memory pool, or allocates a new one if the pool is currently empty. The returned message is always empty and ready to be used (e.g. by calling UnmarshalVT on it). Once the message has been processed, it must be returned to the memory pool by calling ReturnToVTPool() on it. Returning the message to the pool is not mandatory (it does not leak memory), but if you don't return it, that defeats the whole point of memory pooling.

  • clone: generates the following helper methods

    • func (p *YourProto) CloneVT() *YourProto: this function behaves similarly to calling proto.Clone(p) on the message, except the cloning is performed by unrolled codegen without using reflection. If the receiver p is nil a typed nil is returned.

    • func (p *YourProto) CloneMessageVT() proto.Message: this function behaves like the above p.CloneVT(), but provides a uniform signature in order to be accessible via type assertions even if the type is not known at compile time. This allows implementing a generic func CloneVT(proto.Message) without reflection. If the receiver p is nil, a typed nil pointer of the message type will be returned inside a proto.Message interface.

Usage

  1. Install protoc-gen-go-vtproto:
go install github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto@latest
  1. Ensure your project is already using the ProtoBuf v2 API (i.e. google.golang.org/protobuf). The vtprotobuf compiler is not compatible with APIv1 generated code.

  2. Update your protoc generator to use the new plug-in. Example from Vitess:

for name in $(PROTO_SRC_NAMES); do \
    $(VTROOT)/bin/protoc \
    --go_out=. --plugin protoc-gen-go="${GOBIN}/protoc-gen-go" \
    --go-grpc_out=. --plugin protoc-gen-go-grpc="${GOBIN}/protoc-gen-go-grpc" \
    --go-vtproto_out=. --plugin protoc-gen-go-vtproto="${GOBIN}/protoc-gen-go-vtproto" \
    --go-vtproto_opt=features=marshal+unmarshal+size \
    proto/$${name}.proto; \
done

Note that the vtproto compiler runs like an auxiliary plug-in to the protoc-gen-go in APIv2, just like the new GRPC compiler plug-in, protoc-gen-go-grpc. You need to run it alongside the upstream generator, not as a replacement.

  1. (Optional) Pass the features that you want to generate as --go-vtproto_opt. If no features are given, all the codegen steps will be performed.

  2. Compile the .proto files in your project. You should see _vtproto.pb.go files next to the .pb.go and _grpc.pb.go files that were already being generated.

  3. (Optional) Switch your RPC framework to use the optimized helpers (see following sections)

Using the optimized code with RPC frameworks

The protoc-gen-go-vtproto compiler does not overwrite any of the default marshalling or unmarshalling code for your ProtoBuf objects. Instead, it generates helper methods that can be called explicitly to opt-in to faster (de)serialization.

vtprotobuf with GRPC

To use vtprotobuf with the new versions of GRPC, you need to register the codec provided by the github.com/planetscale/vtprotobuf/codec/grpc package.

package servenv

import (
	"github.com/planetscale/vtprotobuf/codec/grpc"
	"google.golang.org/grpc/encoding"
	_ "google.golang.org/grpc/encoding/proto"
)

func init() {
	encoding.RegisterCodec(grpc.Codec{})
}

Note that we perform a blank import _ "google.golang.org/grpc/encoding/proto" of the default proto coded that ships with GRPC to ensure it's being replaced by us afterwards. The provided Codec will serialize & deserialize all ProtoBuf messages using the optimized codegen.

Mixing ProtoBuf implementations with GRPC

If you're running a complex GRPC service, you may need to support serializing ProtoBuf messages from different sources, including from external packages that will not have optimized vtprotobuf marshalling code. This is perfectly doable by implementing a custom codec in your own project that serializes messages based on their type. The Vitess project implements a custom codec to support ProtoBuf messages from Vitess itself and those generated by the etcd API -- you can use it as a reference.

Twirp

Twirp does not support customizing the Marshalling/Unmarshalling codec by default. In order to support vtprotobuf, you'll need to perform a search & replace on the generated Twirp files after running protoc. Here's an example:

for twirp in $${dir}/*.twirp.go; \
do \
  echo 'Updating' $${twirp}; \
  sed -i '' -e 's/respBytes, err := proto.Marshal(respContent)/respBytes, err := respContent.MarshalVT()/g' $${twirp}; \
  sed -i '' -e 's/if err = proto.Unmarshal(buf, reqContent); err != nil {/if err = reqContent.UnmarshalVT(buf); err != nil {/g' $${twirp}; \
done; \

DRPC

To use vtprotobuf as a DRPC encoding, simply pass github.com/planetscale/vtprotobuf/codec/drpc as the protolib flag in your protoc-gen-go-drpc invocation.

Example:

protoc --go_out=. --go-vtproto_out=. --go-drpc_out=. --go-drpc_opt=protolib=github.com/planetscale/vtprotobuf/codec/drpc

Connect

To use vtprotobuf with Connect simply pass in connect.WithCodec(grpc.Codec{}) as a connect option to the client and handler constructors.

package main

import (
	"net/http"

	"github.com/bufbuild/connect-go"
	"github.com/foo/bar/pingv1connect"
	"github.com/planetscale/vtprotobuf/codec/grpc"
)

func main() {
	mux := http.NewServeMux()
	mux.Handle(pingv1connect.NewPingServiceHandler(
		&PingServer{},
		connect.WithCodec(grpc.Codec{}), // Add connect option to handler.
	))
	// handler serving ...

	client := pingv1connect.NewPingServiceClient(
		http.DefaultClient,
		"http://localhost:8080",
		connect.WithCodec(grpc.Codec{}), // Add connect option to client.
	)
	/// client code here ...
}

More Repositories

1

beam

A simple message board for your organization or project
TypeScript
2,052
star
2

database-js

A Fetch API-compatible PlanetScale database driver
TypeScript
1,166
star
3

cli

The CLI for PlanetScale Database
Go
603
star
4

fast_page

Blazing fast pagination for ActiveRecord with deferred joins ⚡️
Ruby
308
star
5

vitess-operator

Kubernetes Operator for Vitess
Go
303
star
6

nextjs-planetscale-starter

A Next.js starter app with NextAuth.js (Auth), Prisma (ORM), and PlanetScale (database), ready to be deployed on Netlify
TypeScript
257
star
7

nextjs-starter

Next.js starter application using Prisma to connect to PlanetScale
JavaScript
136
star
8

f1-championship-stats

🏎
TypeScript
130
star
9

planetscale-go

Go client library to access the PlanetScale API
Go
125
star
10

docs

PlanetScale documentation
Shell
93
star
11

schemadiff

Declarative schema diffing, normalization, validation and manipulation via command line
Go
91
star
12

discussion

78
star
13

issues-calendar-for-github-projects

A tool to view GitHub Project Issues in a calendar view.
TypeScript
77
star
14

planetscale_rails

Make Rails schema migrations easy with PlanetScale
Ruby
62
star
15

planetscale-node

PlanetScale SQL Proxy NPM Module
TypeScript
55
star
16

sql-proxy

SQL Proxy for PlanetScale DB
Go
53
star
17

connection-examples

A collection of PlanetScale connection examples
Elixir
51
star
18

activerecord-sql_commenter

Rails query logging compatible with sqlcommenter
Ruby
43
star
19

nextjs-conf-2021

The code from "Databases as Code with PlanetScale and Prisma" talk at Next.js Conf 2021
JavaScript
34
star
20

vitess-releases

Vitess releases
Shell
30
star
21

ghcommit-action

GitHub Action to commit files to a git branch using the ghcommit utility
Shell
30
star
22

planetscale-ruby

Ruby client for PlanetScale databases
Ruby
30
star
23

ghcommit

Use GitHub's GraphQL API to commit files to a GitHub repository.
Go
28
star
24

express-example

Example Express.js app connecting to PlanetScale
JavaScript
28
star
25

setup-pscale-action

Setup the PlanetScale CLI for GitHub Actions
TypeScript
20
star
26

boost-beta

Welcome to the PlanetScale Boost Private Beta
17
star
27

create-branch-action

A GitHub Action that creates a new branch of your PlanetScale database
Shell
12
star
28

planetpets

A demo application that uses PlanetScale OAuth v2 to access users' databases and organizations.
TypeScript
11
star
29

planetscale-nestjs

Starter application demonstrating how to connect a NestJS API to a PlanetScale MySQL database
TypeScript
11
star
30

database-js-starter

A sample Node.js application that uses the database-js package.
JavaScript
11
star
31

figma-diagram-generator

Code to go along with the figma DB diagram creator video
TypeScript
11
star
32

airbyte-source

Airbyte source for PlanetScale databases
Go
9
star
33

django_psdb_engine

Disable foreign key constraints in Django
Python
9
star
34

deploy-deploy-request-action

A GitHub Action that deploys an existing deploy request of your PlanetScale database
Shell
8
star
35

laravel-example

Sample application showing how to integrate Laravel with PlanetScale
PHP
7
star
36

cloudflare-workers-quickstart

A sample repository demonstrating how to use PlanetScale with CloudFlare Workers.
JavaScript
7
star
37

laravel-crud-mysql

JavaScript
6
star
38

vitess-framework-testing

Validating Vitess across clients and frameworks.
Ruby
6
star
39

singer-tap

Singer.io tap for extracting PlanetScale data
Go
6
star
40

mysql-dds

Loadable MySQL functions for working with DDSketches
C++
6
star
41

terraform-provider-planetscale

Terraform provider for PlanetScale
Go
6
star
42

django-example

Connect a sample Django application to PlanetScale
Python
6
star
43

sysbench

A fork of sysbench to add Vitess specific Modifications
C
6
star
44

10-minute-app

Set up a stateful app fast
JavaScript
5
star
45

vercel-integration-example

JavaScript
5
star
46

go-planetscale-products

Go
5
star
47

create-deploy-request-action

A GitHub Action that creates a new deploy request for your PlanetScale database
Dockerfile
5
star
48

psdbproxy

MySQL proxy for local connections to a PlanetScale database over HTTP/2
Go
5
star
49

golang-example

Go
4
star
50

integrations

planetscale integration examples
JavaScript
4
star
51

fivetran-source

PlanetScale Source connector for FiveTran
Go
4
star
52

scoop-bucket

Scoop bucket for PlanetScale CLI binaries.
4
star
53

create-branch-password-action

A GitHub Action that creates a new password for your PlanetScale database branch
Shell
4
star
54

pscale-workflow-helper-scripts

Shell
4
star
55

vault-gcp-creds-buildkite-plugin

Retrieve time-limited oauth2 access token for an impersonated account from a Hashicorp Vault GCP Secrets Backend
Shell
4
star
56

heroku-buildpack-planetscale

A Heroku Buildpack for adding Planetscale CLI to your project
Ruby
3
star
57

golang-example-gin

Go
3
star
58

symfony-example

Connect a sample Symfony app to PlanetScale
PHP
3
star
59

log

🪵 📓 PlanetScale's opinionated structured logging library
Go
3
star
60

homebrew-tap

Homebrew repository for the pscale CLI
Ruby
3
star
61

php-example

Connect a sample PHP project to a PlanetScale database.
PHP
3
star
62

mysql-for-python-developers

Python
3
star
63

ps-prechecks

Shell
3
star
64

kubeconform-buildkite-plugin

Run kubeconform against your Kubernetes configurations
Shell
2
star
65

laravel-caching

Sample repo for Laravel caching blog post
PHP
2
star
66

k8s-demo

A simple demo of Vitess on Kubernetes
Python
2
star
67

psdb

Go
2
star
68

transmission

Writes data to MySQL. Quickly.
Rust
2
star
69

cloudranger

Go library for mapping IP address ranges to cloud provider regions (currently: AWS and GCP)
Go
2
star
70

vitess-types

Protobuf types extracted from Vitess
Go
2
star
71

go-bookings-api

An API written in Go simulating a travel booking agency.
Go
1
star
72

django-react-demo

JavaScript
1
star
73

go-logkeycheck

Ensure zap log field names are consistent
Go
1
star
74

vault-oidc-auth-buildkite-plugin

Authenticate to Hashicorp Vault with Buildkite OIDC (JWT) tokens.
Shell
1
star
75

core-prettier

Core Design System shared prettier config
JavaScript
1
star
76

vault-aws-creds-buildkite-plugin

Retrieve time-limited AWS credentials from a Hashicorp Vault AWS Secrets Backend
Shell
1
star
77

aws-connection-strings-example

JavaScript
1
star
78

pargzip

Fork of https://github.com/golang/build/tree/master/pargzip
Go
1
star