Skip to content

Native Go    Java Wire Compatible

FoundationDB for Go

A Go ecosystem for FoundationDB, essentials first: a native, pure-Go client and a wire-compatible Record Layer, with a SQL engine on top. No cgo, and 2-4x faster reads than libfdb_c.

import "fdb.dev/pkg/fdbgo/fdb" // pure Go. CGO_ENABLED=0. no libfdb_c.

fdb.MustAPIVersion(730)
db, _ := fdb.OpenDatabase("/etc/foundationdb/fdb.cluster")

db.Transact(func(tx fdb.WritableTransaction) (any, error) {
	tx.Set(fdb.Key("greeting"), []byte("hello"))
	return tx.Get(fdb.Key("greeting")).MustGet(), nil
})

Pre-1.0. The wire format is the part to trust first, and it’s conformance- and differential-tested against Java. Pin a commit and run the suites before you ship. Maturity →

No cgo. Static binary. Faster reads.

Most FDB tooling links Apple’s C library through cgo. That means no static binaries, painful cross-compilation, and a glibc dependency. The pure-Go client speaks the FoundationDB wire protocol directly. It’s the default backend and produces byte-identical reads and writes. The read speedup comes from skipping the C client’s network-thread hop and multi-version-client shim. Writes go through the same commit path, so they run at parity.

pure-Go vs libfdb_c · bar length = speedup
Get 100 B
60 vs 218 µs
3.6x
Get 1 KB
61 vs 209 µs
3.4x
GetRange 100 keys
92 vs 363 µs
3.9x
Read @ 10 ms RTT
5.3 vs 12.6 ms
2.4x
Set + Commit
1.01 vs 1.00 ms
1.0x

The 10 ms-RTT row is the one that matters: localhost microbenchmarks are syscall-bound, so they flatter the pure-Go client. Under real network latency the read advantage holds (2.4x at 10 ms) and converges to parity at high RTT, where both clients are waiting on the network. Writes run at parity throughout. The numbers are reproducible from TestBenchmarkSanity, and the method is in PERFORMANCE.md. Want Apple's C client instead? One build tag (-tags libfdbc) swaps it in, same bytes on the wire.

Quickstart

Install the driver, then open a database, create a schema, and read and write, all from Go. The pure-Go client is the default backend, so you only need a cluster file.

No cluster handy? frl fdb up starts a single-node FoundationDB in Docker (the only prerequisite) and points the frl CLI at it. Remove it with frl fdb down.

Install

go get fdb.dev/pkg/relational/sqldriver

Open a database, create a schema, write and read, in Go

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "fdb.dev/pkg/relational/sqldriver"
)

func main() {
	db, err := sql.Open("fdbsql",
		"fdbsql:///myapp?cluster_file=/etc/foundationdb/fdb.cluster&schema=main")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Create the database, a schema template, and a schema.
	db.Exec(`CREATE DATABASE /myapp`)
	db.Exec(`CREATE SCHEMA TEMPLATE app
	    CREATE TABLE users (id BIGINT, name STRING, email STRING, PRIMARY KEY (id))
	    CREATE INDEX by_email ON users (email)`)
	db.Exec(`CREATE SCHEMA /myapp/main WITH TEMPLATE app`)

	// Write a row, then read it back.
	db.Exec(`INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com')`)

	var name string
	if err := db.QueryRow(
		`SELECT name FROM users WHERE email = ?`, "alice@example.com",
	).Scan(&name); err != nil {
		log.Fatal(err)
	}
	fmt.Println(name) // Alice
}

Or query the same data from the CLI

$ frl sql --database /myapp --schema main
fdb> SELECT name, email FROM users WHERE email = 'alice@example.com';
NAME  │ EMAIL
──────┼───────────────────
Alice │ alice@example.com
(1 row)

The Cascades planner uses the by_email index for that query, so it isn't a full scan. The same store is reachable from Go's typed record API if you'd rather store protobuf records directly. See the record-layer guide.

A client, and layers on top.

FoundationDB is an ordered, transactional key-value store with strict-serializable ACID, and it’s what Snowflake and Apple’s CloudKit run on. Higher-level data models are built as layers on top of it. fdb.dev is the Go client plus a growing set of those layers.

Share a cluster with Java.

Wire compatibility is the whole point of the project. Record, index, version, continuation, and split-record formats are byte-identical to Java Record Layer 4.12.11.0, and the client speaks the FoundationDB 7.3 wire protocol (validated against 7.3.77; 8.0 is future work). CI enforces all of this against real FoundationDB with a Java conformance suite, a cross-backend differential, and a binding-stress tester. No mocks.

Get started →