VeltrixDB Docs
v1.1.0 GitHub FAQ Quickstart

Go Client

The Go client is a thread-safe, connection-pooled driver for VeltrixDB with the broadest API surface of any SDK — single-key ops, batches, namespaces, hash fields, atomic ops (CAS/INCR/DECR/SETNX), range scans, optimistic transactions, secondary indexes, and vector search.

Install

bash
go get github.com/VeltrixDB/veltrixdb-client-go

Requires Go 1.19+. No external dependencies — the client is built entirely on the standard library (net, crypto/tls, encoding/binary).

Quick start

This example is drawn directly from the SDK's bundled go/example/main.go: connect, ping, put/get/delete, run a batch, and fan out concurrent writes across the pool.

go
package main import ( "context" "errors" "fmt" "log" "time" vdb "github.com/VeltrixDB/veltrixdb-client-go" ) func main() { // ── Connect ────────────────────────────────────────────────────── cfg := vdb.DefaultConfig() cfg.PoolSize = 16 // 16 persistent connections client, err := vdb.New("127.0.0.1:9000", cfg) if err != nil { log.Fatalf("connect: %v", err) } defer client.Close() ctx := context.Background() // ── Ping ───────────────────────────────────────────────────────── if err := client.Ping(ctx); err != nil { log.Fatalf("ping: %v", err) } // ── Put / Get / Delete ─────────────────────────────────────────── if err := client.Put(ctx, "hello", []byte("world")); err != nil { log.Fatalf("put: %v", err) } val, err := client.Get(ctx, "hello") if err != nil { log.Fatalf("get: %v", err) } fmt.Printf("GET hello = %s\n", val) if err := client.Delete(ctx, "hello"); err != nil { log.Fatalf("delete: %v", err) } // ErrNotFound is a known sentinel, not a hard error. _, err = client.Get(ctx, "hello") if errors.Is(err, vdb.ErrNotFound) { fmt.Println("GET hello → not found (expected after delete)") } }

Config

Config is passed to New; DefaultConfig() returns production-sane defaults that you can override selectively.

go
type Config struct { PoolSize int // connections to maintain (default 8) DialTimeout time.Duration // TCP/TLS handshake timeout (default 5s) IOTimeout time.Duration // per-operation deadline (default 10s); 0 = no deadline TLS *tls.Config // nil = plain TCP }

The client pre-dials all PoolSize connections inside New, so construction fails fast if the server is unreachable. Close() drains and closes every pooled connection; after Close, all methods return ErrClosed.

API surface

Every method takes a context.Context as its first argument, so cancellation and per-call deadlines compose naturally with the pool's own IOTimeout.

Core key-value

MethodDescription
New(addr, cfg)Create client and pre-dial all pool connections
Put(ctx, key, value)Write a key
Get(ctx, key)Read a key. Returns nil, ErrNotFound if missing
Delete(ctx, key)Delete a key (no error if already absent)
Ping(ctx)Check connection health
Info(ctx)Get server stats string
Auth(ctx, user, pass)Authenticate (only needed if the server has auth enabled)
Close()Close all pool connections

Batch operations

MethodDescription
MultiPut(ctx, entries)Write multiple keys in one round trip
MultiGet(ctx, keys)Read multiple keys in one round trip
go
entries := []vdb.Entry{ {Key: "user:1", Value: []byte(`{"name":"Alice","age":30}`)}, {Key: "user:2", Value: []byte(`{"name":"Bob","age":25}`)}, // With a 60-second TTL: {Key: "session:abc", Value: []byte("token-xyz"), TTLSeconds: 60}, } mputResults, err := client.MultiPut(ctx, entries) if err != nil { log.Fatalf("multiput: %v", err) } for i, r := range mputResults { if r.Err != nil { log.Printf("entry %d failed: %v", i, r.Err) } } getResults, err := client.MultiGet(ctx, []string{"user:1", "user:2", "user:99"}) for _, r := range getResults { if r.Found { fmt.Printf("GET %s = %s\n", r.Key, r.Value) } else { fmt.Printf("GET %s → not found\n", r.Key) } }

Results are returned in the same order as the input. Only individual entries fail on partial errors — the batch itself only errors on connection or protocol-level failure.

Namespaces

MethodDescription
PutNS(ctx, ns, key, value, ttl)Write into a namespace
GetNS(ctx, ns, key)Read from a namespace
DeleteNS(ctx, ns, key)Delete from a namespace
ScanNamespace(ctx, ns, prefix, limit)Scan keys in a namespace
ListNamespaces(ctx)List all namespaces and key counts
DropNamespace(ctx, ns)Delete all keys in a namespace

Hash fields (per-field TTL)

MethodDescription
HSet(ctx, key, field, value, ttl)Set a field with its own TTL
HGet(ctx, key, field)Get a field value
HDel(ctx, key, field)Delete one field
HGetAll(ctx, key)Get all live fields and values
HKeys(ctx, key)Get all live field names
HLen(ctx, key)Count live fields
HExpire(ctx, key, field, ttl)Update TTL of an existing field
HTTL(ctx, key, field)Remaining TTL (-1=forever, -2=not found)

Atomic operations

New in v1.1.0 — typed atomic ops (previously Rust/C++ only):

MethodDescription
CAS(ctx, key, expected, newValue) (bool, error)Compare-and-swap. (true, nil) = swapped, (false, nil) = current value didn't match expected, (false, ErrNotFound) = key doesn't exist
Incr(ctx, key, delta) (int64, error)Atomically add delta (int64) and return the new value; a missing key starts from 0. Errors if the existing value is not an integer
Decr(ctx, key, delta) (int64, error)Atomically subtract delta and return the new value; a missing key starts from 0
SetNX(ctx, key, value) (bool, error)Set only if absent. (true, nil) = created, (false, nil) = key already existed
go
// Counter — missing key starts from 0 n, err := client.Incr(ctx, "counter", 5) // n = 5 n, err = client.Decr(ctx, "counter", 2) // n = 3 // Compare-and-swap swapped, err := client.CAS(ctx, "hello", []byte("world"), []byte("earth")) if err != nil { if errors.Is(err, vdb.ErrNotFound) { // key doesn't exist } } else if !swapped { // current value didn't match "world" } // Distributed lock-style set-if-absent created, err := client.SetNX(ctx, "lock", []byte("owner-A")) // created == false → someone else holds the key

Range scans, transactions, indexes, and vectors

Beyond the wire-protocol operations documented in the client README, the Go client exposes a further set of methods (RangeScan, ScanCursor, Txn, KeyVersion, IdxCreate/IdxDrop/IdxQuery, VSet/VSearch, Query) implemented directly in client.go:

MethodDescription
RangeScan(ctx, start, end, limit, reverse)Ordered key-range scan; requires the server's ordered index
ScanCursor(ctx, cursor, limit)Cursor-paginated full-keyspace scan; pass "" to start
Txn(ctx, ops)Atomic optimistic transaction (SET/SETIF/DEL ops); returns ErrTxnConflict on a failed version guard
KeyVersion(ctx, key)Optimistic version token for a key (0 = absent) — feeds TxnOp.ExpectedVersion
IdxCreate(ctx, name, field) / IdxDrop(ctx, name)Manage a persistent secondary index on a JSON field
IdxQuery(ctx, name, value, limit)Look up primary keys by indexed field value
VSet(ctx, key, vec) / VSearch(ctx, k, query)Store/search vectors by cosine similarity
Query(ctx, ns, field, op, value, limit)Field-predicate query over a namespace (= != > < >= <= contains)

Txn semantics are read-committed optimistic CAS — atomic within the batch, but not serializable. Re-read versions with KeyVersion and retry on ErrTxnConflict.

TLS

go
import "crypto/tls" // Server verification only tlsCfg := &tls.Config{RootCAs: loadCACert("ca.crt")} client, _ := veltrix.New("veltrix.example.com:9443", veltrix.Config{TLS: tlsCfg}) // mTLS (client certificate + server certificate) tlsCfg := &tls.Config{ Certificates: []tls.Certificate{loadCert("client.crt", "client.key")}, RootCAs: loadCACert("ca.crt"), }

Auth

go
client, _ := veltrix.New("127.0.0.1:9000", veltrix.Config{PoolSize: 4}) client.Auth(ctx, "alice", "secret") client.Put(ctx, "key", []byte("value"))

Error handling

The client defines three sentinel errors you should check with errors.Is: ErrNotFound, ErrClosed, and ErrTxnConflict.

go
val, err := client.Get(ctx, "missing-key") if errors.Is(err, veltrix.ErrNotFound) { // key doesn't exist } if errors.Is(err, context.DeadlineExceeded) { // operation timed out }

Internally, when an operation fails with anything other than ErrNotFound or ErrTxnConflict, the pool assumes the connection is broken: it discards it and dials a fresh replacement before returning the error to the caller.

Concurrent usage

The Client is safe for concurrent use from multiple goroutines — each call borrows a connection from the pool independently:

go
const workers = 50 done := make(chan struct{}, workers) for i := 0; i < workers; i++ { go func(id int) { key := fmt.Sprintf("concurrent:%d", id) val := []byte(fmt.Sprintf("value-%d", id)) if err := client.Put(ctx, key, val); err != nil { log.Printf("worker %d put: %v", id, err) } done <- struct{}{} }(i) } for i := 0; i < workers; i++ { <-done }
Match pool size to concurrency If you run 16 concurrent goroutines against the client, set PoolSize: 16. A larger pool doesn't help throughput and just wastes idle connections.

Cluster-aware variant

This page covers github.com/VeltrixDB/veltrixdb-client-go — a single-node, connection-pooled client. The VeltrixDB server repository also bundles a separate cluster-aware client under github.com/VeltrixDB/veltrixdb/client (client.NewClient), which discovers cluster topology, builds a consistent-hash ring matching the server's, and follows Raft MOVED redirects automatically. Reach for that package specifically when talking to a raft or replicated mode deployment — see Deployment Modes.