Orientation
This is the on-ramp. If you’re an experienced engineer new to Go, start here: we’ll learn Go by reading a real production codebase rather than working through toy examples. The running example throughout this guide is multigres, a distributed-systems project written in Go, and this page covers what it is, how its code is laid out, and — most importantly — how to read unfamiliar Go fast enough to find your way around a large codebase.
The “how to read Go” material here is language-general; it pays off in any Go project you ever touch.
What multigres is
Section titled “What multigres is”Multigres is Vitess for Postgres: a set of small Go services that sit in front of real PostgreSQL servers and add horizontal scaling, connection pooling, and automated failover. An application speaks the ordinary PostgreSQL wire protocol; it never knows it is talking to a proxy. Behind the proxy, the system decides which Postgres to run a query on, reuses connections, and — when a Postgres dies — elects a new leader and reconnects, all without the client noticing.
It is a single Go module, github.com/multigres/multigres, on Go 1.25. All Go
code lives under go/. The project is early-stage; the wire protocol and gRPC
contracts are its stable spine.
The mental model: service topology and the request path
Section titled “The mental model: service topology and the request path”There are five long-running services (implementations under go/services/)
plus operator and test binaries. The latency-sensitive path is just two hops,
and only the first hop is gRPC — the second is a real pooled SQL connection:
flowchart LR Client["psql / app"] -->|"PostgreSQL wire protocol"| GW["multigateway<br/>(stateless proxy, PG wire in)"] GW -->|"gRPC: MultiPoolerService"| PL["multipooler<br/>(connection pool, serves queries)"] PL -->|"pooled SQL"| PG["PostgreSQL<br/>(real PG process)"]
Everything else is off the query path:
| Service | Role | On the query path? |
|---|---|---|
| multigateway | Stateless proxy: speaks PG wire + gRPC, routes queries | Yes (hop 1) |
| multipooler | Connection pooling; serves queries; talks to pgctld via gRPC | Yes (hop 2) |
| pgctld | PostgreSQL control daemon — lifecycle only (start/stop/restart) | No (control plane) |
| multiorch | Consensus + failover orchestration | No (control plane) |
| multiadmin | Admin service, HTTP + gRPC endpoints | No (admin) |
Two binaries under go/cmd/ are not services: multigres is the operator
CLI, and portpoolserver is a cross-process port allocator used only by
integration tests.
The cluster is organized into cells (availability zones), with metadata in a topology store (etcd in production). For the full cell-aware topology, the leader/primary terminology, and the exact query trace, see the deep dive: Architecture & Request Flow.
Repo map
Section titled “Repo map”The repo root holds the contracts and config; everything Go lives under go/.
| Path | What lives here |
|---|---|
proto/ | gRPC/protobuf contracts (.proto). Source of truth for service surfaces; generates into go/pb/. Key files: multipoolerservice.proto, pgctldservice.proto, multiorchservice.proto, multigatewayservice.proto, clustermetadata.proto, query.proto. |
config/ | Per-component YAMLs (e.g. multigateway.yaml, multipooler.yaml, pgctld.yaml, multiorch.yaml) — small files holding things like http-port and log-level. They are one source in a precedence chain (flags > env > config file > defaults); cluster-wide metadata lives in the topology store, not here. |
Makefile | Self-documenting build/dev driver (make help). |
go/ | All Go source — see below. |
Inside go/:
| Path | What lives here | Dependency rule |
|---|---|---|
go/cmd/ | 7 binaries (the 5 services + multigres CLI + portpoolserver). Each is a tiny main.go. | Can depend on anything. |
go/services/ | The 5 long-running services’ implementations (multiadmin, multigateway, multiorch, multipooler, pgctld). | Cannot depend on cmd/ or other services. |
go/common/ | Shared code: mterrors, pgprotocol, sqltypes, parser, queryservice, topoclient, consensus, constants, etc. | Cannot depend on cmd/ or services/. |
go/tools/ | Generic, project-agnostic helpers (timers, retry, …). | Cannot depend on any repo code outside tools/. |
go/pb/ | Generated protobuf Go (// Code generated). Read-only. | — |
go/observability/ | Metric catalog. | — |
go/provisioner/ | Cluster provisioning (local + provisioner.go). | — |
go/test/ | End-to-end tests and test utilities (endtoend, utils). | — |
The dependency direction is strict and worth internalizing — it tells you which way imports may point:
flowchart TB cmd["cmd/<br/>(can depend on anything)"] services["services/"] common["common/"] tools["tools/"] cmd --> services cmd --> common cmd --> tools services --> common services --> tools common --> tools
In words: cmd/ may depend on anything; services/ may not depend on cmd/
or on other services; common/ may not depend on cmd/ or services/; and
tools/ may not depend on anything outside tools/.
How to read unfamiliar Go, fast
Section titled “How to read unfamiliar Go, fast”Go has almost no metaprogramming, so the code says what it does. The leverage is in knowing where to start and how the pieces are wired.
Import path == directory
Section titled “Import path == directory”github.com/multigres/multigres/go/services/multigateway is the directory
go/services/multigateway. Strip the module prefix
(github.com/multigres/multigres) and you have a path relative to the repo
root. This mapping is exact and always holds.
Start at the entry points
Section titled “Start at the entry points”Every binary is a tiny go/cmd/<svc>/main.go that delegates immediately. There
are two shapes.
Service binaries build a cobra
command, then run an Init / RunDefault pair. From
go/cmd/multigateway/main.go:
func run(ctx context.Context, mg *multigateway.MultiGateway) error { if err := mg.Init(ctx); err != nil { return err } return mg.RunDefault()}The package doc-comment and the cobra Short string are the project’s own
one-line descriptions — read them first. (multigateway’s Short: “Multigateway
is a stateless proxy responsible for accepting requests from applications and
routing them to the appropriate multipooler server(s) for query execution. It
speaks both the PostgreSQL Protocol and a gRPC protocol.”)
The operator CLI has a different shape — go/cmd/multigres/main.go just
dispatches to subcommands (which live under go/cmd/multigres/command/):
func main() { root := command.GetRootCommand() if err := root.Execute(); err != nil { os.Exit(1) }}Follow an interface seam
Section titled “Follow an interface seam”The request path is glued together by interfaces, not concrete types. When you
hit one — engine.IExecute, queryservice.QueryService — grep for its name
to find the definition and the concrete implementation(s):
grep -rn 'IExecute' go/This is the single most useful habit for reading Go service code: an interface defines the seam, and the implementation lives in some other package you find by grepping the name. See Interfaces & composition for why Go is structured this way.
Read doc-comments without opening files
Section titled “Read doc-comments without opening files”These are general Go techniques:
go doc <import-path>prints a package’s doc-comment and exported symbols.go doc <import-path>.Symboldrills into one type or function. Faster than opening the file when you just want the contract.goplsis the Go language server (jump-to-definition, find-references). If your editor has Go support, it is running gopls.
How to use this guide
Section titled “How to use this guide”The guide is organized into five tracks. The language and project tracks are the spine; the tooling and reference tracks support them.
The language track presupposes nothing but general programming experience; the project track presupposes the whole language track. Within each track, read in order — the numbering encodes a real dependency chain, not just a sequence.
A few workflow facts worth knowing before you build anything:
- Build with
make build; regenerate code withmake proto/make parser; do both plus binaries withmake build-all. - This codebase runs its tests through a dev wrapper rather than calling
go testdirectly — integration tests auto-build first. See the testing workflow.
See also
Section titled “See also”- Architecture & Request Flow — the full topology, cells, and the query trace this page summarizes.
- cmd & cobra — how each
main.gobuilds its command and runsInit/RunDefault. - Interfaces & composition — the interface seams (
engine.IExecute,queryservice.QueryService) you follow when reading the request path. - Build & Make and Testing workflow — the
maketargets and the test wrapper. - Glossary — cell, shard, pooler-type, leader/primary terms.