simple magicDATA

SMKV / Build applications

Tables, columns, and scans

Applies to: typed rows are in the baseline product; scan commands and the latest mutation options require current development builds and SDKs. Upgrade all servers before introducing a record format they cannot replay.

Model a row

A row is identified by table name and key. Tables are schemaless: rows can contain different columns and types. Supported values are bytes, UTF-8 strings, signed 64-bit integers, floats, and booleans. There is no null type; remove a column to represent its absence.

Table and column names are 1–64 UTF-8 bytes. A row has at most 64 columns. Encoded names, keys, and metadata count against record-size limits. Raw values and table rows occupy distinct key domains.

Write and project columns

Inside a Go function with client and ctx already configured:

users := client.Table("users")
err := users.Put(ctx, []byte("user-123"), smkv.Columns{
    "country": "US",
    "visits": int64(12),
    "active": true,
}, smkv.WithTTL(time.Hour))
if err != nil { return err }
row, err := users.Get(ctx, []byte("user-123"), "country", "visits")
if err != nil { return err }
fmt.Println(row)

Put replaces the whole row; omitted TTL means no expiration. Get without a projection returns every column. Missing projected columns are omitted; missing rows return ErrNotFound. Integers decode as int64.

Update atomically within a row

err = users.Update(ctx, []byte("user-123"),
    smkv.Columns{"visits": int64(13)}, []string{"active"})
if err != nil { return err }

This sets visits and removes active in one row mutation, preserving other columns. It is assignment, not a server-side increment. An update requires an existing unexpired row. Omitted TTL preserves its absolute expiration; a supplied TTL resets it. To clear expiration, replace the row with Put and no TTL.

Projection reduces network response size but still reads the full stored row. Updates rewrite and replicate the full row. There are no cross-row transactions.

Scan a table

Current development builds provide bounded scans through smkv-ctl, the Go SDK, and the dashboard Tables view:

smkv-ctl --seed 10.0.0.11:7381 scan --table users \
  --columns country,visits --limit 50 \
  --filters '[{"column":"visits","op":"ge","value":{"type":"int","value":"10"}}]'

Pass the returned cursor verbatim with --cursor for the next page, keeping the same query. An empty row list can still have a continuation cursor. Stop only when the cursor is absent/empty. The CLI processes a bounded page, not an unlimited export.

Filters support typed comparisons and existence; they are not secondary-index lookups. Scans have no global ordering or transactional snapshot. Concurrent writes can cause omissions or duplicates; restart, compaction, or topology changes may invalidate the cursor. Restart explicitly and reconcile earlier results when that happens.

Keep scans behind application traffic

Reads, writes, and replication take precedence. Scan pages yield between records, are capped at 100 rows and a bounded response size, and are admitted at most ten times per second per node. Continuous foreground load can starve a scan or make it time out.

For Busy, retry the same page after at least 100 ms within an overall deadline. Stop and investigate other failures. Use backup export, rather than scans, when you need a verified recovery archive.