Use the Go SDK
Applies to: the SDK supplied with your SMKV build; examples of outcome classification and cluster retries describe the current development SDK. Requires Go 1.22 or newer.
Add the local module
The SDK is currently a local module named go-smkv, not a publicly published Go module. Obtain the matching SDK source through your release/support channel. In your application module, adjust the replacement path:
go mod edit [email protected]
go mod edit -replace=go-smkv=../smkv/go-smkv
Connect and write
Save this as main.go, start a standalone node, then run go run .:
package main
import (
"context"
"errors"
"fmt"
"log"
"time"
smkv "go-smkv"
)
func main() {
client, err := smkv.NewClient("127.0.0.1:7379", smkv.Options{
PoolSize: 4,
Timeout: 5 * time.Second,
})
if err != nil { log.Fatal(err) }
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
key := []byte("greeting")
if err := client.Put(ctx, key, []byte("hello"), smkv.WithTTL(time.Minute), smkv.WithSync()); err != nil {
log.Fatal(err)
}
value, err := client.Get(ctx, key)
if errors.Is(err, smkv.ErrNotFound) { fmt.Println("cache miss"); return }
if err != nil { log.Fatal(err) }
fmt.Println(string(value))
}
Clients are safe for concurrent goroutines and use a bounded connection pool. Reuse clients, close them during shutdown, and do not mutate input slices until a call returns. Connections are lazy; constructing a direct client does not verify a live server. Empty byte values are valid.
Route across a cluster
Use management seeds, not data addresses. With an existing ctx:
cluster, err := smkv.NewClusterClient(ctx,
[]string{"10.0.0.11:7381", "10.0.0.12:7381"},
smkv.Options{PoolSize: 4, Timeout: 5 * time.Second})
if err != nil { return err }
defer cluster.Close()
err = cluster.Put(ctx, []byte("key"), []byte("value"))
This fragment belongs inside a function returning error. The SDK discovers the map and routes to the primary owner. Use GetReplica explicitly when stale replica reads are acceptable; primary reads do not silently fall back to replicas. All advertised data and management addresses must be reachable from the application.
Set deadlines and durability
The earlier of the context deadline and configured timeout bounds the entire call, including pool waits and connection setup. Await a write before issuing a dependent read. Concurrent calls across connections have no ordering guarantee.
Omit WithTTL for no expiration. WithTTL(0) expires immediately. WithSync() waits for local sync, not replica confirmation. Current quorum RF2 builds additionally support WithReplicaAck() and WithDurableReplicaAck(). Keep the client timeout above the server's confirmation budget, with transport headroom.
Handle outcomes deliberately
| Error or condition | Application response |
|---|---|
ErrNotFound |
Treat as absent or expired; choose your cache-miss path |
ErrBusy |
Request rejected before admission; back off within your deadline |
ErrCapacity |
Inspect capacity; repeated immediate retries cannot create headroom |
ErrOutcomeUnknown |
Mutation may have executed; reconcile before retrying |
ErrUnauthorized |
Repair identity, role, or credential configuration |
| Context cancellation/deadline | Stop waiting; still check mutation outcome classification |
Use errors.Is, not message matching. Check ErrOutcomeUnknown before deciding a failed mutation is safe to retry. Direct clients never retry automatically. The current cluster client can retry bounded transient reads and explicit wrong-owner rejections; it never blindly replays uncertain mutations. SDK behavior has evolved, so deploy clients and servers from compatible releases.
For typed data, continue with tables and columns.