SDKs

Go SDK

The Go SDK uses syscall.Mmap (via an internal mmap package) to map the compiled binary read-only into the process address space. Lookups traverse the MPHF routing table and blob entirely through pointer arithmetic; unsafe.String returns a zero-copy string aliasing the mapped region.

Installation

Requires Go 1.22+. No CGo, no libc dependency.

bash
go get github.com/Attos-Labs/attos-go@latest

Configuration & Initialisation

OpenConfig controls a single map open. Synchronizer options are supplied as variadic Option funcs to NewSynchronizer.

Field / OptionDefaultEffect
OpenConfig.ValidateChecksumfalseVerifies CRC32-IEEE trailer on open. Zero latency impact post-open.
OpenConfig.PinToMemoryfalseCalls mlock(2). Eliminates page-fault jitter on first access. Non-fatal if mlock fails.
OpenConfig.Loggerlog.Default()Receives mlock warnings and hot-swap events. Inject a structured logger to unify output.
WithSyncInterval(d)0 (disabled)Enables background polling goroutine. Zero means manual Sync() only.
WithBaseURL(url)localhost:8080Control-plane endpoint for dataset sync.
WithAPIKey(key)""Sent as X-API-Key header on every sync request.
WithCacheDir(dir)./.attos/cacheLocal path where downloaded .nh binaries are persisted.
WithLogger(l)log.Default()Injects a *log.Logger into the Synchronizer for sync and telemetry messages.
main.go
import (
    "context"
    "log"
    "os"
    "time"

    attos "github.com/Attos-Labs/attos-go"
)

appLogger := log.New(os.Stderr, "[myapp] ", log.LstdFlags)

sync, err := attos.NewSynchronizer(
    context.Background(),
    "my-dataset",
    attos.WithBaseURL("https://api.attoscloud.com"),
    attos.WithAPIKey(os.Getenv("ATTOS_API_KEY")),
    attos.WithCacheDir("/var/lib/attos"),
    attos.WithSyncInterval(30 * time.Second),
    attos.WithLogger(appLogger),
)
if err != nil {
    log.Fatalf("attos init: %v", err)
}
defer sync.Close()

For local file access without the control plane, use attos.OpenWithConfig(path, cfg) directly.

go
cfg := attos.OpenConfig{
    ValidateChecksum: true,
    PinToMemory:      true,
}
m, err := attos.OpenWithConfig("data.attos", cfg)
if err != nil {
    500">// err is one of the typed sentinels defined in errors.go
    log.Fatal(err)
}
defer m.Close()

Core API — Lookups

Both methods perform a single xxHash64 hash, two array reads (routing table + values table), and a key-match verification. Allocation profile: 0 B/op, 0 allocs/op (verified by BenchmarkLookupLatency).

GetString

go
500">// Zero-copy: returned string aliases the mmap region.
500">// Valid until m.Close() or the next hot-swap.
value, err := m.GetString("user-42")
if errors.Is(err, attos.ErrKeyNotFound) {
    500">// absent key — not an error condition in most applications
}
fmt.Println(value) 500">// e.g. "mock_data_42_rev_1234567890"

Get ([]byte)

go
500">// Returns a slice into the mmap region — no copy.
payload, err := m.Get([]byte("user-42"))
if err == nil {
    500">// payload is valid until m.Close()
}

Scan (full iteration)

go
m.Scan(func(key, value []byte) bool {
    fmt.Printf("%s → %s
", key, value)
    return true 500">// return false to halt early
})

Hot-Swap Lifecycle

The Synchronizer's background goroutine calls Sync(ctx) on the configured interval. On each successful sync:

  1. The new .nh binary is downloaded and written to the cache directory.
  2. Open(blobPath) maps it via syscall.Mmap.
  3. atomic.Pointer[Map].Swap(newMap) publishes the new pointer — readers see the new map immediately.
  4. oldMap.Close() is called only after the swap — preventing any window where no map is available.

Synchronizer.Close() cancels the polling context, stops the ticker, calls wg.Wait() to drain the goroutine, and only then unmaps the final blob.

Error Handling

All methods return typed sentinel errors. Use errors.Is — never string matching.

SentinelTrigger conditionRecommended action
ErrKeyNotFoundHash slot found but key bytes do not matchNormal; return a 404 or default value
ErrMapClosedGet called after Close()Stop using this instance; log and terminate
ErrInvalidMagicFirst 4 bytes ≠ "ATTO"Reject file; alert on corrupted deployment
ErrInvalidVersionVersion field ≠ 1Reject file; check SDK / builder version parity
ErrChecksumMismatchCRC32 trailer mismatch (ValidateChecksum=true)Reject file; re-download from control plane
ErrCorruptedBinaryFile too small, offset OOB, bad keyLenReject file; treat as ErrChecksumMismatch
ErrMlockFailedmlock(2) syscall failedLog warning; continue — lookups still functional

Plan limits

When a sync hits a plan limit (for example more than 3 active nodes or 10 GB of bandwidth a month on Free), the control plane answers 402 (or 429) with a JSON body whose code says which limit. The SDK keeps serving the data it already has, pauses syncing for 5 minutes (or the Retry-After seconds, capped at 1 hour) and then retries; lookups are never affected. See Plans, limits & error codes for every code and how to fix it.

How nodes are counted
  • A node is one running SDK, identified by its node ID. It counts as active while it has synced in the last 5 minutes (SDKs check for updates every 30 seconds), so a stopped server drops out within 5 minutes.
  • The node ID defaults to the server's hostname, so a restarted process is still one node. Set ATTOS_NODE_ID (or the SDK's node ID option) to choose it yourself; processes sharing a hostname count as one node.
  • Paid plans bill extra nodes on the monthly average: the node count is recorded every hour and the month's average (rounded) is compared with the included nodes (50 on Pro, 10 on Starter) when the month closes, at $2 per extra node on Pro ($3 on Starter, where node overage is off unless you switch it on). The peak is shown for information only.
  • Example: a rolling deploy of 50 pods briefly shows about 100 nodes while old and new pods overlap, but only for a few minutes, so the month still averages about 50 and nothing extra is billed.
  • Past the included nodes: with overage on, extra nodes keep syncing and are billed; with it off (and on Free, capped at 3), a new node gets 402 and keeps serving whatever data it already has, while nodes already syncing are unaffected.

Benchmarks

Measured on linux/amd64, 11th Gen Intel Core i5-1155G7 @ 2.50 GHz.

bash
$ cd attos-go && go test -bench=BenchmarkLookupLatency -benchmem -run=400">'^$' ./...

BenchmarkLookupLatency-8    58958785    21.29 ns/op    0 B/op    0 allocs/op