Saxy/Tellstone

Tellstone is a high-performance in-memory database and drop-in replacement for Redis; Join https://discord.gg/kEf78DUFX

Go

231

103 commits

updated Oct 19, 2026

See the code

README

Tellstone logo

CI Go Version License

Tellstone is an ultra‑high‑performance, cloud‑native in‑memory key/value store written entirely in Go. It speaks two protocols over TCP — a compact custom binary protocol and a Redis‑compatible (RESP2) protocol — on top of a shared-nothing (SN) storage engine with optional TTL eviction, at‑rest encryption, and write-ahead log persistence.

       +---------------------------------------------+
       |             Your K8s Cluster                |
       |                                             |
       |  [App Pod] --( binary :9988 / RESP :6379 )->|
       |                                             |
       |     +---------------------------------+     |
       |     |        TELLSTONE CORE           |     |
       |     |  (N Shards, each a goroutine +  |     |
       |     |   sync.RWMutex + map[string]Item)|    |
       |     |  FNV-1a hash → O(1) dispatch    |     |
       |     +---------------------------------+     |
       +---------------------------------------------+

Why Tellstone?

Many managed databases (PostgreSQL, MySQL, …) become bottlenecks under high‑frequency workloads. Tellstone offers a lean, modern, memory‑efficient buffer that:

  • Zero‑Copy Binary Protocol – Direct binary messages avoid text parsing / Protobuf overhead.
  • Redis‑Compatible – An optional RESP2 listener lets you drive Tellstone with redis-cli, redis-benchmark, memtier_benchmark, and existing Redis client libraries (GET/SET/PING/DEL).
  • Shared-Nothing Engine – N independent shards, each containing one map[string]Item plus a sync.RWMutex. Keys are pinned to a shard via FNV-1a hashing so the lock is almost never contended. No cross-shard coordination, no channel round-trips, no per-request allocations.
  • Configurable TTL Eviction – An active timing‑wheel (chronometer) evicts expired keys in O(1); lazy eviction on read backs it up.
  • Optional At‑Rest Encryption – ChaCha20‑Poly1305, off by default.
  • Write-Ahead Log Persistence – Per-shard append-only WAL for crash recovery. SET and DEL operations are persisted (deletes as tombstones) and replayed on restart. Zero-allocation on the hot path (Write = 0 allocs/op). Disabled by default.
  • Metrics, Tracing & Audit Logging – Built‑in Prometheus exporter, optional OpenTelemetry tracing, and an opt-in structured audit trail for security events.

Core Architecture

For a detailed description of the package structure, request flow, and design decisions, see ARCHITECTURE.md.

LayerPackageNotes
Binary protocolinternal/networkMsgRequest/MsgResponse frames (GET/SET/DEL, TTL, key, value)
RESP2 protocolinternal/respRedis‑compatible listener reusing the same engine
Request routerinternal/routerFNV‑1a hash → O(1) shard dispatch
Shard runnerinternal/shardShared‑nothing shard: synchronous Execute(), per‑shard sync.RWMutex
Storage engineinternal/storageSingle‑map engine, TTL eviction via timing wheel
Persistenceinternal/persistencePer‑shard append‑only WAL, zero‑alloc write path
Cryptointernal/cryptoOptional ChaCha20‑Poly1305
Auditinternal/auditStructured JSON security events (connect, disconnect, auth_*, acl_deny, command); rotating file writer, optional encryption
Metrics / tracinginternal/metrics, internal/tracePrometheus text exporter, OTLP/gRPC tracing

Getting Started

Install

Debian / Ubuntu (APT)

curl -fsSL https://saxy.github.io/tellstone-apt/saxy-keyring.gpg \
  | sudo gpg --dearmor -o /usr/share/keyrings/saxy-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/saxy-keyring.gpg] \
  https://saxy.github.io/tellstone-apt stable main" \
  | sudo tee /etc/apt/sources.list.d/saxy-tellstone.list > /dev/null

sudo apt update && sudo apt install tellstone

macOS (Homebrew)

brew tap Saxy/tellstone-tap
brew install --cask Saxy/tellstone-tap/tellstone

Binary Downloads

Pre-built binaries are available on the Releases page for Linux, macOS, and Windows (amd64 and arm64).

Build from Source

Requires Go 1.26+ and optionally task (go‑task):

task build          # → ./bin/tellstone   (or: go build -o bin/tellstone ./cmd/tellstone)

Run

task run            # binary protocol on 127.0.0.1:9988
task run:resp       # binary on :9988  +  Redis-compatible RESP on :6379

Or run the binary directly with flags / environment variables:

./bin/tellstone --addr 127.0.0.1:9988 --enable-resp --resp-addr 127.0.0.1:6379
TSD_ADDR=127.0.0.1:9988 TSD_ENABLE_RESP=true ./bin/tellstone

If a previous run got killed uncleanly and left a server stuck on a port (address already in use), find and stop it with:

task kill                          # checks :19988, :6379, :6060 and any bin/tellstone process
task kill PORTS="9988" NAME=myapp  # override the ports/name to search for

Works on Linux and macOS (lsof/pgrep/pkill, no OS-specific tooling).

Configuration

Every option is available as a flag and an environment variable.

FlagEnvDefaultDescription
--addrTSD_ADDR127.0.0.1:9988Binary‑protocol listen address
--enable-respTSD_ENABLE_RESPfalseEnable the Redis‑compatible RESP listener
--resp-addrTSD_RESP_ADDR127.0.0.1:6379RESP listen address
--resp-starttlsTSD_RESP_STARTTLSfalseAllow RESP plaintext connections to upgrade with TLS
--shardsTSD_NUM_SHARDS0 (auto = CPU)Number of shared-nothing shards
--max-msg-sizeTSD_MAX_MSG_SIZE16MiBPer‑message size limit
--max-mem-bytesTSD_MAX_MEM_BYTES0 (unlimited)Total engine memory ceiling
--evict-intervalTSD_EVICT_INTERVAL1sChronometer tick interval (0 disables active eviction)
--evict-slotsTSD_EVICT_SLOTS256Timing‑wheel slot count
--enable-encryptionTSD_ENABLE_ENCRYPTIONfalseEnable ChaCha20‑Poly1305 at‑rest encryption
--encryption-keyTSD_ENCRYPTION_KEY(none)Base‑64 encoded 32‑byte key (one key source is required when encryption is on)
--encryption-key-fileTSD_ENCRYPTION_KEY_FILE(none)Path to a file holding the raw 32‑byte key; mutually exclusive with --encryption-key
--enable-metricsTSD_ENABLE_METRICSfalseEnable the Prometheus exporter
--metrics-addrTSD_METRICS_ADDR:9100Prometheus exporter address (/metrics)
--trace-ratioTSD_TRACE_RATIO0.0OpenTelemetry sample ratio (0 disables)
--enable-persistenceTSD_ENABLE_PERSISTENCEfalseEnable write-ahead log persistence for crash recovery
--persistence-dirTSD_PERSISTENCE_DIR(platform)Directory for WAL data files
--snapshot-intervalTSD_SNAPSHOT_INTERVAL0 (disabled)Time between periodic snapshots (e.g. 5m); requires --enable-persistence
--snapshot-bytesTSD_SNAPSHOT_BYTES64MiBWAL size threshold that triggers a snapshot; requires --enable-persistence
--tls-certTSD_TLS_CERT(none)TLS certificate path; watched for automatic rotation
--tls-keyTSD_TLS_KEY(none)TLS private key path; watched for automatic rotation
--tls-caTSD_TLS_CA(none)Client CA path for mTLS; watched for automatic rotation
--require-passTSD_REQUIRE_PASS(none)Single password required via AUTH; empty disables it
--rbac-configTSD_RBAC_CONFIG(none)YAML/JSON RBAC policy file (roles, users, default role); hot-reloaded on SIGHUP
--oauth-providerTSD_OAUTH_PROVIDER(none)OAuth preset (google|stackit); empty + --oauth-issuer → generic OIDC
--oauth-issuerTSD_OAUTH_ISSUER(none)OIDC issuer / discovery base URL of the identity provider
--oauth-client-idTSD_OAUTH_CLIENT_ID(none)OAuth2 client ID used as the expected token audience
--enable-auditTSD_ENABLE_AUDITfalseEnable structured audit logging
--audit-log-pathTSD_AUDIT_LOG_PATHstdoutAudit destination: a directory (rotated 0600 files) or stdout
--audit-eventsTSD_AUDIT_EVENTSauth,aclComma-separated event types: auth, acl, connect, disconnect, command, or all
--shutdown-timeoutTSD_SHUTDOWN_TIMEOUT10sMax wait for graceful shutdown on SIGINT/SIGTERM

Runtime tuning (environment only): TSD_GC_PERCENT (default -1, GC off for a zero‑GC hot path), TSD_MEM_LIMIT_BYTES (soft heap ceiling), TSD_ENABLE_PROFILING (serves pprof on 127.0.0.1:6060).

At-rest encryption takes its 32-byte key from exactly one source. --encryption-key carries it base64-encoded, because process arguments and environment variables are NUL-terminated and roughly 1 in 8 random 32-byte keys contains a 0x00 byte that cannot survive them:

tellstone --enable-encryption --encryption-key "$(openssl rand -base64 32)"

--encryption-key-file carries the same key as raw, unencoded bytes, which suits a mounted Kubernetes Secret or a vault-agent-rendered file. Every byte of the file is significant, so it must be exactly 32 bytes with no trailing newline:

# umask 077 in a subshell so the file is created 0600; a later chmod would leave
# the key world-readable in between.
(umask 077; head -c 32 /dev/urandom > /etc/tellstone/key)
tellstone --enable-encryption --encryption-key-file /etc/tellstone/key

The key is read once at startup; rotating it requires a restart. Enabling encryption without either source is refused at startup rather than silently falling back to plaintext.

Deprecated: --encryption-key previously used the value as raw text, so a literal 32-character key such as 0123456789abcdef0123456789abcdef was accepted. That form still works and logs a warning at startup, but it is insecure and will be removed in the next major release. Re-encode an existing key with base64 < <keyfile>, or move it to --encryption-key-file. The two forms are never ambiguous: a base64 key is 44 characters (43 unpadded), while a 32-character value can only decode to 24 bytes.

When TLS is enabled, Tellstone watches the parent directories of the certificate, key, and optional client CA. Valid replacements are applied after a 500 ms debounce; existing TLS connections continue uninterrupted. Directory watching supports atomic file renames and Kubernetes projected Secret updates.

By default, both listeners require TLS from the first byte. Setting --resp-starttls keeps only the RESP listener plaintext until a client sends STARTTLS; Tellstone replies +OK in plaintext and then requires an immediate TLS 1.3 handshake on the same socket. STARTTLS is allowed before AUTH so credentials need not cross plaintext. The command must not be pipelined with any other plaintext bytes. The binary listener always retains implicit TLS, and each RESP upgrade loads the latest rotated certificate configuration. Once a RESP connection is handed to the TLS state machine — on accept for implicit TLS, or after a STARTTLS acceptance — it must complete the handshake within 10 seconds; the listener closes it at the deadline even if the client sends nothing further, so stalled connections cannot pile up.


Using Tellstone

Redis‑compatible (RESP) — easiest

Start with task run:resp, then use any Redis client:

redis-cli -p 6379 PING            # PONG
redis-cli -p 6379 SET foo bar     # OK
redis-cli -p 6379 GET foo         # "bar"
redis-cli -p 6379 SET k v EX 60   # OK (60s TTL)
redis-cli -p 6379 DEL foo         # (integer) 1

Supported commands today: PING, GET, SET (with EX/PX), DEL, AUTH, COMMAND, ROLE (CREATE/SETUSER/DELUSER/DELETE/LIST/GETUSER), ACL (SETUSER/DELUSER/LIST/LOG). Unknown commands return a -ERR reply without dropping the connection. STARTTLS is additionally available when --resp-starttls is enabled.

Authentication & RBAC

Start with --require-pass for a single shared password, or --rbac-config for per-user authentication with role-based access control (supersedes --require-pass):

# policy.yaml — loaded at startup and hot-reloaded on SIGHUP.
# Passwords are bcrypt hashes, e.g. of "adminsecret" / "alicepw"; a password
# is required unless the user is explicitly marked nopass.
roles:
  - name: admin
    rules: ["+@all", "~*"]
  - name: readonly
    rules: ["+get", "~*"]
users:
  - name: admin
    role: admin
    password: "$2a$10$pcaKkTfRy.KSdNUgKszYYedE7L32P9fSEG3x1phq0EbjeYkn5WpEi"
  - name: alice
    role: readonly
    password: "$2a$10$sslrTYVwaIaA7O1lhokY2OgnojP5bB8YJ/o2MXaFP1v49lG8fqJYK"
default_role: readonly    # least privilege: fallback for users without an explicit role

Generate a password hash with htpasswd -nbBC 10 "" PASSWORD | tr -d ':\n' or mkpasswd -m bcrypt PASSWORD (bcrypt, cost 10, $2a$10$...). Do not use openssl passwd — it emits SHA-512-crypt, not bcrypt. ROLE SETUSER accepts a raw >password and bcrypt-hashes it server-side, so runtime-created users need no tooling.

./bin/tellstone --rbac-config policy.yaml --enable-resp
redis-cli AUTH admin adminsecret                 # +OK
redis-cli ROLE CREATE operator +get '~users:*'   # +OK (runtime roles)
redis-cli ROLE SETUSER bob operator '>bobpw'     # +OK
redis-cli ROLE GETUSER bob                       # bob / operator / 1

Roles are user-defined; a role's rules are Redis-style tokens: +cmd / -cmd grant or revoke one command, +@cat / -@cat a whole category, and ~prefix whitelists a key namespace (an empty list or ~* allows every key). - rules override + rules. The built-in categories:

CategoryGrants
loginAUTH, PING, COMMAND
readGET, INFO
writeSET, DEL
readwriteread + write + login
operatorreadwrite + FLUSH
maintenanceFLUSH, SHUTDOWN, CONFIG, DEBUG, MONITOR
adminAUTH, ROLE, ACL, USER, GRANT, REVOKE
all / noneevery registered command / nothing

A ready-to-run policy file ships with the role example at cmd/example/role/policy.yaml.

Unauthenticated data commands return -NOAUTH; commands a user's role does not grant return -NOPERM. The native binary client offers the same via client.AuthUser and RoleCreate / RoleSetUser (see cmd/example/role). The ACL command family (ACL SETUSER / ACL DELUSER / ACL LIST / ACL LOG) manages the same policy store through a Redis-flavored alias, driven end-to-end in cmd/example/acl.

Federated auth (OAuth / OIDC)

Connections can present an OIDC id_token (a signed JWT) as their credential instead of a password. The token's claims map to a role through the policy file's oauth.rules (claims + match pattern + role, first match wins), and that role is pinned to the connection like any other. Requires --rbac-config — a token can only map to a role through the policy. The server needs no client secret: verification is signature + issuer + audience against the provider's public JWKS.

./bin/tellstone --rbac-config policy.yaml --oauth-provider google --oauth-client-id 1234.apps.googleusercontent.com
redis-cli AUTH <id_token>              # +OK — claims map to a role via oauth.rules

Supported presets: google, stackit; set --oauth-issuer for any other OIDC provider. How the provider pipeline, claim matching, and fail-closed semantics work is documented in internal/oauth/README.md. A runnable client example lives in cmd/example/oauth.

Audit logging

Opt-in structured audit trail via --enable-audit. Each selected audit event is written as one JSON line carrying "level": "AUDIT", so log aggregators can separate it from operational INFO/WARN/ERROR output without custom parsing:

./bin/tellstone --enable-audit --audit-log-path /var/log/tellstone --audit-events all

--audit-log-path is stdout (the default) or a directory. In directory mode the server creates rotating files named <unix-timestamp>_<dir-fingerprint>_tsd.log with 0600 permissions, rotating to a fresh file once a file reaches 50 MiB (history is never truncated or renamed). When --enable-encryption is set, every record is sealed with the crypto engine before it is flushed. The engine is a no-op when audit logging is disabled, so the hot path pays nothing.

--audit-events selects which event types are logged (default auth,acl; all enables every type). connect, disconnect, and command are high-volume and must be opted into explicitly:

EventFires onFields
connect / disconnectTCP connection open/close, both protocolsremote_addr, protocol, shard_id
auth_successsuccessful AUTHuser, remote_addr, protocol
auth_failurerejected AUTH (bad password, unknown user, malformed request)user, reason, remote_addr, protocol
acl_denycommand blocked by RBAC (-NOPERM)user, command, key, remote_addr, protocol
commandevery dispatched data/admin commandcommand, key, user, remote_addr, protocol
{"event":"acl_deny","level":"AUDIT","msg":"command denied by rbac policy","protocol":"binary","remote_addr":"127.0.0.1:51642","command":"SET","key":"config:key","user":"reader","time":"2026-08-05T10:40:32.908569649+02:00"}

Native binary protocol (Go client)

The native protocol is the fastest path. Use the bundled client in internal/network:

import "github.com/Saxy/Tellstone/internal/network"

c, _ := network.Dial("127.0.0.1:9988", 2*time.Second)
defer c.Close()

scratch := make([]byte, 4096)              // reusable response buffer (zero-alloc)
c.Set([]byte("hello"), []byte("world"), 0, scratch)   // ttlMs=0 → no expiry
val, _ := c.Get([]byte("hello"), scratch)             // val == "world"

A runnable example lives in cmd/example/client.


Benchmarks

Methodology matters. A naive local benchmark runs the load generator on the same cores as the server's event loops, so the two contend for CPU and the latency tail balloons. All tasks below pin the server and the load generator to disjoint core sets (taskset) so the numbers reflect the server, not scheduler contention. For absolute comparisons, run the load generator on a separate host.

Native binary protocol

task bench:native       # pinned: server cpu0-15, generator cpu16-31

Redis‑compatible (RESP) via memtier

task bench:resp                 # latency run (pipeline=1)
task bench:resp:pipeline        # throughput ceiling (pipeline=16)
task bench:resp:hits            # preload then read-heavy (realistic ~100% hit rate)
task bench:resp:correctness     # preload then read back — proves GET returns what SET stored

Override workload knobs on the command line, e.g.:

task bench:resp PIPELINE=16 DURATION=30 CONNS=50 RATIO=1:4 KEYSPACE=1000000

You can point memtier_benchmark/redis-benchmark at :6379 directly and run the identical command against Redis, Dragonfly, Valkey (or --protocol=memcache_text against memcached) for an apples‑to‑apples comparison.

Reference results

memtier_benchmark — 100k requests, 256-byte values, --ratio=1:10 (1:10 read:write), pipeline 10, uniform random keys, preloaded key set. All four systems tested with identical parameters on the same hardware.

In-memory database benchmarks are highly sensitive to the underlying network infrastructure. To provide an honest and comprehensive view of Tellstone's performance, we categorize our results into two distinct scenarios: Cloud SDN Constraints (simulating a standard production microservice mesh) and Raw Engine Capabilities (eliminating the network stack to test core architectural limits).

Methodology: memtier_benchmark executed from a separate client VM against a remote target VM via a virtual Software-Defined Network. 100k requests, 256-byte values, --ratio=1:10, pipeline=10. At higher concurrency levels (16_64 and 60_128), all engines run directly into the physical Packets Per Second (PPS) ceiling imposed by the cloud provider's virtual switches, capping out at roughly 850K ops/s. Tellstone successfully saturates the cloud network infrastructure, operating at the absolute limit of the hardware while maintaining lower or equivalent tail latencies compared to Redis, Valkey, and Dragonfly.

Benchmark Results

Small (4 threads, 16 clients)

SystemThroughputvs Redisavgp50p99p99.9
Tellstone664K ops/s1.04x0.96ms0.97ms1.35ms1.54ms
Redis636K ops/s1.0x0.99ms0.98ms1.45ms1.70ms
Valkey607K ops/s0.96x1.05ms1.04ms1.50ms1.72ms
Dragonfly552K ops/s0.87x1.18ms1.10ms2.35ms2.85ms

Medium (16 threads, 64 clients)

SystemThroughputvs Redisavgp50p99p99.9
Tellstone870K ops/s1.05x11.76ms11.78ms12.67ms17.02ms
Redis831K ops/s1.0x12.32ms11.65ms19.97ms22.27ms
Valkey786K ops/s0.95x13.02ms12.99ms19.58ms20.48ms
Dragonfly864K ops/s1.04x11.85ms10.05ms40.96ms62.98ms

Large (60 threads, 128 clients)

SystemThroughputvs Redisavgp50p99p99.9
Tellstone856K ops/s1.01x89.63ms20.22ms913.41ms1867.78ms
Redis849K ops/s1.0x90.34ms23.17ms712.70ms1818.62ms
Valkey723K ops/s0.85x106.19ms112.13ms178.18ms415.74ms
Dragonfly843K ops/s0.99x90.86ms64.51ms481.28ms1073.15ms

Tellstone leads throughput across all three environments (up to 10.5% faster than Redis, 20% faster than Valkey, 20% faster than Dragonfly at small scale). Its p50 latency is consistently the lowest or near-lowest at every concurrency level.

Native binary protocol

Throughput with the native binary protocol (no pipelining, read-heavy):

ConnectionsThroughputp50
32940K RPS99us
200940K RPS99us
10001.47M RPS470us
20001.35M RPS1.2ms

Numbers are environment-specific; reproduce with task bench:resp and the benchmark/benchmark.sh script.

Bare‑metal benchmarks (localhost, no network overhead)

Same memtier_benchmark parameters (256 B values, --ratio=1:10, pipeline 10, uniform random keys) but executed on a dedicated bare‑metal server (Intel Xeon Platinum 8580, 56 cores, 118 GB RAM, Debian). The load generator and server share the same machine, with taskset pining the server process to the requested core set. This isolates raw engine throughput and latency from any cloud‑SDN constraints.

500K requests per client, 4 clients per memtier thread. Server taskset -c 0-N, memtier runs unpinned.

Bare-metal Benchmark Results

Small (4 CPUs)

SystemTotal Ops/svs Redisavgp50p99p99.9
Tellstone2,448K2.06x0.06ms0.06ms0.23ms0.41ms
Dragonfly1,404K1.18x0.11ms0.11ms0.17ms0.22ms
Redis1,186K1.0x0.13ms0.12ms0.21ms0.25ms
Valkey1,036K0.87x0.15ms0.14ms0.23ms0.26ms

Medium (16 CPUs)

SystemTotal Ops/svs Redisavgp50p99p99.9
Tellstone6,806K5.98x0.09ms0.07ms0.41ms0.70ms
Dragonfly4,122K3.62x0.15ms0.15ms0.26ms0.32ms
Redis1,139K1.0x0.56ms0.54ms1.06ms1.09ms
Valkey1,016K0.89x0.63ms0.60ms1.19ms1.22ms

Large (32 CPUs)

SystemTotal Ops/svs Redisavgp50p99p99.9
Tellstone12,738K11.53x0.10ms0.08ms0.44ms0.78ms
Dragonfly7,286K6.59x0.18ms0.17ms0.61ms1.29ms
Redis1,105K1.0x1.16ms1.15ms2.33ms2.38ms
Valkey996K0.90x1.29ms1.27ms2.56ms2.61ms

On bare metal Tellstone scales nearly linearly with available cores — 11.5x Redis at 32 CPUs — while Redis and Valkey flatline around 1 M ops/s regardless of core count. Dragonfly scales well (6.6x) but Tellstone maintains a ~1.75x lead at every level, with the lowest p50 latency across the board (0.06 – 0.08 ms).


Development

task test           # go test ./...
task test:race      # go test -race ./...
task vet            # go vet ./...
task check          # vet + race tests (run before committing)
task fmt            # format

Continuous Integration

Pull requests and pushes to main trigger the CI workflow:

  • Build — go build ./...
  • Vet — go vet ./...
  • Test — go test ./...
  • Race tests — go test -race ./...

Benchmarks are not run automatically on every push due to resource constraints. Run them locally with task bench:native or task bench:resp:precise.

Observability

  • Metrics: task run:resp with --enable-metrics exposes Prometheus text at http://<metrics-addr>/metrics (default :9100).
  • Audit logging: --enable-audit writes structured security events (see Audit logging); run with --audit-events all to capture every event type.

Profiling

Two independent workflows, both built on the stock Go toolchain (pprof / trace). Neither assumes a specific core count, OS, or machine — every variable below is overridable on the CLI, so the same commands work on a laptop, a CI runner, or a dedicated benchmarking host.

1) Profile a package's benchmarks directly — no server involved, good for isolating one function (e.g. the storage engine or the RESP parser):

task profile:pkg                                          # ./internal/storage/..., all benchmarks
task profile:pkg PKG=./internal/resp/... BENCH=BenchmarkParseGet
task profile:view FILE=tmp/profile/cpu.out                # opens the CPU profile in the browser
task profile:view FILE=tmp/profile/mem.out ARGS=-alloc_space

2) Profile the running server under real load, generated from a second terminal:

task run:profiling                    # foreground server, RESP + live pprof on :6060
# in a second terminal, generate load, e.g.:
task bench:resp:pipeline
# or: ./bin/benchmark -addr 127.0.0.1:19988 -c 32 -n 1000000 -read-ratio 0.95 -skew 1.5
# while load is running, pull a profile and open it in the browser:
task profile:live                     # CPU, 30s sample (default)
task profile:live KIND=heap
task profile:live KIND=mutex
task profile:live KIND=block
task profile:live:trace               # execution trace, opened via `go tool trace`

go tool pprof -http starts a local web server and opens your default browser automatically. On a headless/remote host, set PORT=<port> and open http://<host>:<port> yourself (e.g. via an SSH tunnel), or browse the raw index at http://127.0.0.1:6060/debug/pprof/ directly.


Contributing

Contributions are welcome — especially around networking, replication, persistence, and RESP command coverage. See CONTRIBUTING.md for the full guide (DCO sign-off required, core principles, workflow).


“A contest of focus. Keep yours made of steel.” — Tellstone

databases
db
memory
memory-database
memory-db

Saxy/Tellstone

Tellstone is a high-performance in-memory database and drop-in replacement for Redis; Join https://discord.gg/kEf78DUFX

Go

231

103 commits

updated Oct 19, 2026

See the code

README

Tellstone logo

CI Go Version License

Tellstone is an ultra‑high‑performance, cloud‑native in‑memory key/value store written entirely in Go. It speaks two protocols over TCP — a compact custom binary protocol and a Redis‑compatible (RESP2) protocol — on top of a shared-nothing (SN) storage engine with optional TTL eviction, at‑rest encryption, and write-ahead log persistence.

       +---------------------------------------------+
       |             Your K8s Cluster                |
       |                                             |
       |  [App Pod] --( binary :9988 / RESP :6379 )->|
       |                                             |
       |     +---------------------------------+     |
       |     |        TELLSTONE CORE           |     |
       |     |  (N Shards, each a goroutine +  |     |
       |     |   sync.RWMutex + map[string]Item)|    |
       |     |  FNV-1a hash → O(1) dispatch    |     |
       |     +---------------------------------+     |
       +---------------------------------------------+

Why Tellstone?

Many managed databases (PostgreSQL, MySQL, …) become bottlenecks under high‑frequency workloads. Tellstone offers a lean, modern, memory‑efficient buffer that:

  • Zero‑Copy Binary Protocol – Direct binary messages avoid text parsing / Protobuf overhead.
  • Redis‑Compatible – An optional RESP2 listener lets you drive Tellstone with redis-cli, redis-benchmark, memtier_benchmark, and existing Redis client libraries (GET/SET/PING/DEL).
  • Shared-Nothing Engine – N independent shards, each containing one map[string]Item plus a sync.RWMutex. Keys are pinned to a shard via FNV-1a hashing so the lock is almost never contended. No cross-shard coordination, no channel round-trips, no per-request allocations.
  • Configurable TTL Eviction – An active timing‑wheel (chronometer) evicts expired keys in O(1); lazy eviction on read backs it up.
  • Optional At‑Rest Encryption – ChaCha20‑Poly1305, off by default.
  • Write-Ahead Log Persistence – Per-shard append-only WAL for crash recovery. SET and DEL operations are persisted (deletes as tombstones) and replayed on restart. Zero-allocation on the hot path (Write = 0 allocs/op). Disabled by default.
  • Metrics, Tracing & Audit Logging – Built‑in Prometheus exporter, optional OpenTelemetry tracing, and an opt-in structured audit trail for security events.

Core Architecture

For a detailed description of the package structure, request flow, and design decisions, see ARCHITECTURE.md.

LayerPackageNotes
Binary protocolinternal/networkMsgRequest/MsgResponse frames (GET/SET/DEL, TTL, key, value)
RESP2 protocolinternal/respRedis‑compatible listener reusing the same engine
Request routerinternal/routerFNV‑1a hash → O(1) shard dispatch
Shard runnerinternal/shardShared‑nothing shard: synchronous Execute(), per‑shard sync.RWMutex
Storage engineinternal/storageSingle‑map engine, TTL eviction via timing wheel
Persistenceinternal/persistencePer‑shard append‑only WAL, zero‑alloc write path
Cryptointernal/cryptoOptional ChaCha20‑Poly1305
Auditinternal/auditStructured JSON security events (connect, disconnect, auth_*, acl_deny, command); rotating file writer, optional encryption
Metrics / tracinginternal/metrics, internal/tracePrometheus text exporter, OTLP/gRPC tracing

Getting Started

Install

Debian / Ubuntu (APT)

curl -fsSL https://saxy.github.io/tellstone-apt/saxy-keyring.gpg \
  | sudo gpg --dearmor -o /usr/share/keyrings/saxy-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/saxy-keyring.gpg] \
  https://saxy.github.io/tellstone-apt stable main" \
  | sudo tee /etc/apt/sources.list.d/saxy-tellstone.list > /dev/null

sudo apt update && sudo apt install tellstone

macOS (Homebrew)

brew tap Saxy/tellstone-tap
brew install --cask Saxy/tellstone-tap/tellstone

Binary Downloads

Pre-built binaries are available on the Releases page for Linux, macOS, and Windows (amd64 and arm64).

Build from Source

Requires Go 1.26+ and optionally task (go‑task):

task build          # → ./bin/tellstone   (or: go build -o bin/tellstone ./cmd/tellstone)

Run

task run            # binary protocol on 127.0.0.1:9988
task run:resp       # binary on :9988  +  Redis-compatible RESP on :6379

Or run the binary directly with flags / environment variables:

./bin/tellstone --addr 127.0.0.1:9988 --enable-resp --resp-addr 127.0.0.1:6379
TSD_ADDR=127.0.0.1:9988 TSD_ENABLE_RESP=true ./bin/tellstone

If a previous run got killed uncleanly and left a server stuck on a port (address already in use), find and stop it with:

task kill                          # checks :19988, :6379, :6060 and any bin/tellstone process
task kill PORTS="9988" NAME=myapp  # override the ports/name to search for

Works on Linux and macOS (lsof/pgrep/pkill, no OS-specific tooling).

Configuration

Every option is available as a flag and an environment variable.

FlagEnvDefaultDescription
--addrTSD_ADDR127.0.0.1:9988Binary‑protocol listen address
--enable-respTSD_ENABLE_RESPfalseEnable the Redis‑compatible RESP listener
--resp-addrTSD_RESP_ADDR127.0.0.1:6379RESP listen address
--resp-starttlsTSD_RESP_STARTTLSfalseAllow RESP plaintext connections to upgrade with TLS
--shardsTSD_NUM_SHARDS0 (auto = CPU)Number of shared-nothing shards
--max-msg-sizeTSD_MAX_MSG_SIZE16MiBPer‑message size limit
--max-mem-bytesTSD_MAX_MEM_BYTES0 (unlimited)Total engine memory ceiling
--evict-intervalTSD_EVICT_INTERVAL1sChronometer tick interval (0 disables active eviction)
--evict-slotsTSD_EVICT_SLOTS256Timing‑wheel slot count
--enable-encryptionTSD_ENABLE_ENCRYPTIONfalseEnable ChaCha20‑Poly1305 at‑rest encryption
--encryption-keyTSD_ENCRYPTION_KEY(none)Base‑64 encoded 32‑byte key (one key source is required when encryption is on)
--encryption-key-fileTSD_ENCRYPTION_KEY_FILE(none)Path to a file holding the raw 32‑byte key; mutually exclusive with --encryption-key
--enable-metricsTSD_ENABLE_METRICSfalseEnable the Prometheus exporter
--metrics-addrTSD_METRICS_ADDR:9100Prometheus exporter address (/metrics)
--trace-ratioTSD_TRACE_RATIO0.0OpenTelemetry sample ratio (0 disables)
--enable-persistenceTSD_ENABLE_PERSISTENCEfalseEnable write-ahead log persistence for crash recovery
--persistence-dirTSD_PERSISTENCE_DIR(platform)Directory for WAL data files
--snapshot-intervalTSD_SNAPSHOT_INTERVAL0 (disabled)Time between periodic snapshots (e.g. 5m); requires --enable-persistence
--snapshot-bytesTSD_SNAPSHOT_BYTES64MiBWAL size threshold that triggers a snapshot; requires --enable-persistence
--tls-certTSD_TLS_CERT(none)TLS certificate path; watched for automatic rotation
--tls-keyTSD_TLS_KEY(none)TLS private key path; watched for automatic rotation
--tls-caTSD_TLS_CA(none)Client CA path for mTLS; watched for automatic rotation
--require-passTSD_REQUIRE_PASS(none)Single password required via AUTH; empty disables it
--rbac-configTSD_RBAC_CONFIG(none)YAML/JSON RBAC policy file (roles, users, default role); hot-reloaded on SIGHUP
--oauth-providerTSD_OAUTH_PROVIDER(none)OAuth preset (google|stackit); empty + --oauth-issuer → generic OIDC
--oauth-issuerTSD_OAUTH_ISSUER(none)OIDC issuer / discovery base URL of the identity provider
--oauth-client-idTSD_OAUTH_CLIENT_ID(none)OAuth2 client ID used as the expected token audience
--enable-auditTSD_ENABLE_AUDITfalseEnable structured audit logging
--audit-log-pathTSD_AUDIT_LOG_PATHstdoutAudit destination: a directory (rotated 0600 files) or stdout
--audit-eventsTSD_AUDIT_EVENTSauth,aclComma-separated event types: auth, acl, connect, disconnect, command, or all
--shutdown-timeoutTSD_SHUTDOWN_TIMEOUT10sMax wait for graceful shutdown on SIGINT/SIGTERM

Runtime tuning (environment only): TSD_GC_PERCENT (default -1, GC off for a zero‑GC hot path), TSD_MEM_LIMIT_BYTES (soft heap ceiling), TSD_ENABLE_PROFILING (serves pprof on 127.0.0.1:6060).

At-rest encryption takes its 32-byte key from exactly one source. --encryption-key carries it base64-encoded, because process arguments and environment variables are NUL-terminated and roughly 1 in 8 random 32-byte keys contains a 0x00 byte that cannot survive them:

tellstone --enable-encryption --encryption-key "$(openssl rand -base64 32)"

--encryption-key-file carries the same key as raw, unencoded bytes, which suits a mounted Kubernetes Secret or a vault-agent-rendered file. Every byte of the file is significant, so it must be exactly 32 bytes with no trailing newline:

# umask 077 in a subshell so the file is created 0600; a later chmod would leave
# the key world-readable in between.
(umask 077; head -c 32 /dev/urandom > /etc/tellstone/key)
tellstone --enable-encryption --encryption-key-file /etc/tellstone/key

The key is read once at startup; rotating it requires a restart. Enabling encryption without either source is refused at startup rather than silently falling back to plaintext.

Deprecated: --encryption-key previously used the value as raw text, so a literal 32-character key such as 0123456789abcdef0123456789abcdef was accepted. That form still works and logs a warning at startup, but it is insecure and will be removed in the next major release. Re-encode an existing key with base64 < <keyfile>, or move it to --encryption-key-file. The two forms are never ambiguous: a base64 key is 44 characters (43 unpadded), while a 32-character value can only decode to 24 bytes.

When TLS is enabled, Tellstone watches the parent directories of the certificate, key, and optional client CA. Valid replacements are applied after a 500 ms debounce; existing TLS connections continue uninterrupted. Directory watching supports atomic file renames and Kubernetes projected Secret updates.

By default, both listeners require TLS from the first byte. Setting --resp-starttls keeps only the RESP listener plaintext until a client sends STARTTLS; Tellstone replies +OK in plaintext and then requires an immediate TLS 1.3 handshake on the same socket. STARTTLS is allowed before AUTH so credentials need not cross plaintext. The command must not be pipelined with any other plaintext bytes. The binary listener always retains implicit TLS, and each RESP upgrade loads the latest rotated certificate configuration. Once a RESP connection is handed to the TLS state machine — on accept for implicit TLS, or after a STARTTLS acceptance — it must complete the handshake within 10 seconds; the listener closes it at the deadline even if the client sends nothing further, so stalled connections cannot pile up.


Using Tellstone

Redis‑compatible (RESP) — easiest

Start with task run:resp, then use any Redis client:

redis-cli -p 6379 PING            # PONG
redis-cli -p 6379 SET foo bar     # OK
redis-cli -p 6379 GET foo         # "bar"
redis-cli -p 6379 SET k v EX 60   # OK (60s TTL)
redis-cli -p 6379 DEL foo         # (integer) 1

Supported commands today: PING, GET, SET (with EX/PX), DEL, AUTH, COMMAND, ROLE (CREATE/SETUSER/DELUSER/DELETE/LIST/GETUSER), ACL (SETUSER/DELUSER/LIST/LOG). Unknown commands return a -ERR reply without dropping the connection. STARTTLS is additionally available when --resp-starttls is enabled.

Authentication & RBAC

Start with --require-pass for a single shared password, or --rbac-config for per-user authentication with role-based access control (supersedes --require-pass):

# policy.yaml — loaded at startup and hot-reloaded on SIGHUP.
# Passwords are bcrypt hashes, e.g. of "adminsecret" / "alicepw"; a password
# is required unless the user is explicitly marked nopass.
roles:
  - name: admin
    rules: ["+@all", "~*"]
  - name: readonly
    rules: ["+get", "~*"]
users:
  - name: admin
    role: admin
    password: "$2a$10$pcaKkTfRy.KSdNUgKszYYedE7L32P9fSEG3x1phq0EbjeYkn5WpEi"
  - name: alice
    role: readonly
    password: "$2a$10$sslrTYVwaIaA7O1lhokY2OgnojP5bB8YJ/o2MXaFP1v49lG8fqJYK"
default_role: readonly    # least privilege: fallback for users without an explicit role

Generate a password hash with htpasswd -nbBC 10 "" PASSWORD | tr -d ':\n' or mkpasswd -m bcrypt PASSWORD (bcrypt, cost 10, $2a$10$...). Do not use openssl passwd — it emits SHA-512-crypt, not bcrypt. ROLE SETUSER accepts a raw >password and bcrypt-hashes it server-side, so runtime-created users need no tooling.

./bin/tellstone --rbac-config policy.yaml --enable-resp
redis-cli AUTH admin adminsecret                 # +OK
redis-cli ROLE CREATE operator +get '~users:*'   # +OK (runtime roles)
redis-cli ROLE SETUSER bob operator '>bobpw'     # +OK
redis-cli ROLE GETUSER bob                       # bob / operator / 1

Roles are user-defined; a role's rules are Redis-style tokens: +cmd / -cmd grant or revoke one command, +@cat / -@cat a whole category, and ~prefix whitelists a key namespace (an empty list or ~* allows every key). - rules override + rules. The built-in categories:

CategoryGrants
loginAUTH, PING, COMMAND
readGET, INFO
writeSET, DEL
readwriteread + write + login
operatorreadwrite + FLUSH
maintenanceFLUSH, SHUTDOWN, CONFIG, DEBUG, MONITOR
adminAUTH, ROLE, ACL, USER, GRANT, REVOKE
all / noneevery registered command / nothing

A ready-to-run policy file ships with the role example at cmd/example/role/policy.yaml.

Unauthenticated data commands return -NOAUTH; commands a user's role does not grant return -NOPERM. The native binary client offers the same via client.AuthUser and RoleCreate / RoleSetUser (see cmd/example/role). The ACL command family (ACL SETUSER / ACL DELUSER / ACL LIST / ACL LOG) manages the same policy store through a Redis-flavored alias, driven end-to-end in cmd/example/acl.

Federated auth (OAuth / OIDC)

Connections can present an OIDC id_token (a signed JWT) as their credential instead of a password. The token's claims map to a role through the policy file's oauth.rules (claims + match pattern + role, first match wins), and that role is pinned to the connection like any other. Requires --rbac-config — a token can only map to a role through the policy. The server needs no client secret: verification is signature + issuer + audience against the provider's public JWKS.

./bin/tellstone --rbac-config policy.yaml --oauth-provider google --oauth-client-id 1234.apps.googleusercontent.com
redis-cli AUTH <id_token>              # +OK — claims map to a role via oauth.rules

Supported presets: google, stackit; set --oauth-issuer for any other OIDC provider. How the provider pipeline, claim matching, and fail-closed semantics work is documented in internal/oauth/README.md. A runnable client example lives in cmd/example/oauth.

Audit logging

Opt-in structured audit trail via --enable-audit. Each selected audit event is written as one JSON line carrying "level": "AUDIT", so log aggregators can separate it from operational INFO/WARN/ERROR output without custom parsing:

./bin/tellstone --enable-audit --audit-log-path /var/log/tellstone --audit-events all

--audit-log-path is stdout (the default) or a directory. In directory mode the server creates rotating files named <unix-timestamp>_<dir-fingerprint>_tsd.log with 0600 permissions, rotating to a fresh file once a file reaches 50 MiB (history is never truncated or renamed). When --enable-encryption is set, every record is sealed with the crypto engine before it is flushed. The engine is a no-op when audit logging is disabled, so the hot path pays nothing.

--audit-events selects which event types are logged (default auth,acl; all enables every type). connect, disconnect, and command are high-volume and must be opted into explicitly:

EventFires onFields
connect / disconnectTCP connection open/close, both protocolsremote_addr, protocol, shard_id
auth_successsuccessful AUTHuser, remote_addr, protocol
auth_failurerejected AUTH (bad password, unknown user, malformed request)user, reason, remote_addr, protocol
acl_denycommand blocked by RBAC (-NOPERM)user, command, key, remote_addr, protocol
commandevery dispatched data/admin commandcommand, key, user, remote_addr, protocol
{"event":"acl_deny","level":"AUDIT","msg":"command denied by rbac policy","protocol":"binary","remote_addr":"127.0.0.1:51642","command":"SET","key":"config:key","user":"reader","time":"2026-08-05T10:40:32.908569649+02:00"}

Native binary protocol (Go client)

The native protocol is the fastest path. Use the bundled client in internal/network:

import "github.com/Saxy/Tellstone/internal/network"

c, _ := network.Dial("127.0.0.1:9988", 2*time.Second)
defer c.Close()

scratch := make([]byte, 4096)              // reusable response buffer (zero-alloc)
c.Set([]byte("hello"), []byte("world"), 0, scratch)   // ttlMs=0 → no expiry
val, _ := c.Get([]byte("hello"), scratch)             // val == "world"

A runnable example lives in cmd/example/client.


Benchmarks

Methodology matters. A naive local benchmark runs the load generator on the same cores as the server's event loops, so the two contend for CPU and the latency tail balloons. All tasks below pin the server and the load generator to disjoint core sets (taskset) so the numbers reflect the server, not scheduler contention. For absolute comparisons, run the load generator on a separate host.

Native binary protocol

task bench:native       # pinned: server cpu0-15, generator cpu16-31

Redis‑compatible (RESP) via memtier

task bench:resp                 # latency run (pipeline=1)
task bench:resp:pipeline        # throughput ceiling (pipeline=16)
task bench:resp:hits            # preload then read-heavy (realistic ~100% hit rate)
task bench:resp:correctness     # preload then read back — proves GET returns what SET stored

Override workload knobs on the command line, e.g.:

task bench:resp PIPELINE=16 DURATION=30 CONNS=50 RATIO=1:4 KEYSPACE=1000000

You can point memtier_benchmark/redis-benchmark at :6379 directly and run the identical command against Redis, Dragonfly, Valkey (or --protocol=memcache_text against memcached) for an apples‑to‑apples comparison.

Reference results

memtier_benchmark — 100k requests, 256-byte values, --ratio=1:10 (1:10 read:write), pipeline 10, uniform random keys, preloaded key set. All four systems tested with identical parameters on the same hardware.

In-memory database benchmarks are highly sensitive to the underlying network infrastructure. To provide an honest and comprehensive view of Tellstone's performance, we categorize our results into two distinct scenarios: Cloud SDN Constraints (simulating a standard production microservice mesh) and Raw Engine Capabilities (eliminating the network stack to test core architectural limits).

Methodology: memtier_benchmark executed from a separate client VM against a remote target VM via a virtual Software-Defined Network. 100k requests, 256-byte values, --ratio=1:10, pipeline=10. At higher concurrency levels (16_64 and 60_128), all engines run directly into the physical Packets Per Second (PPS) ceiling imposed by the cloud provider's virtual switches, capping out at roughly 850K ops/s. Tellstone successfully saturates the cloud network infrastructure, operating at the absolute limit of the hardware while maintaining lower or equivalent tail latencies compared to Redis, Valkey, and Dragonfly.

Benchmark Results

Small (4 threads, 16 clients)

SystemThroughputvs Redisavgp50p99p99.9
Tellstone664K ops/s1.04x0.96ms0.97ms1.35ms1.54ms
Redis636K ops/s1.0x0.99ms0.98ms1.45ms1.70ms
Valkey607K ops/s0.96x1.05ms1.04ms1.50ms1.72ms
Dragonfly552K ops/s0.87x1.18ms1.10ms2.35ms2.85ms

Medium (16 threads, 64 clients)

SystemThroughputvs Redisavgp50p99p99.9
Tellstone870K ops/s1.05x11.76ms11.78ms12.67ms17.02ms
Redis831K ops/s1.0x12.32ms11.65ms19.97ms22.27ms
Valkey786K ops/s0.95x13.02ms12.99ms19.58ms20.48ms
Dragonfly864K ops/s1.04x11.85ms10.05ms40.96ms62.98ms

Large (60 threads, 128 clients)

SystemThroughputvs Redisavgp50p99p99.9
Tellstone856K ops/s1.01x89.63ms20.22ms913.41ms1867.78ms
Redis849K ops/s1.0x90.34ms23.17ms712.70ms1818.62ms
Valkey723K ops/s0.85x106.19ms112.13ms178.18ms415.74ms
Dragonfly843K ops/s0.99x90.86ms64.51ms481.28ms1073.15ms

Tellstone leads throughput across all three environments (up to 10.5% faster than Redis, 20% faster than Valkey, 20% faster than Dragonfly at small scale). Its p50 latency is consistently the lowest or near-lowest at every concurrency level.

Native binary protocol

Throughput with the native binary protocol (no pipelining, read-heavy):

ConnectionsThroughputp50
32940K RPS99us
200940K RPS99us
10001.47M RPS470us
20001.35M RPS1.2ms

Numbers are environment-specific; reproduce with task bench:resp and the benchmark/benchmark.sh script.

Bare‑metal benchmarks (localhost, no network overhead)

Same memtier_benchmark parameters (256 B values, --ratio=1:10, pipeline 10, uniform random keys) but executed on a dedicated bare‑metal server (Intel Xeon Platinum 8580, 56 cores, 118 GB RAM, Debian). The load generator and server share the same machine, with taskset pining the server process to the requested core set. This isolates raw engine throughput and latency from any cloud‑SDN constraints.

500K requests per client, 4 clients per memtier thread. Server taskset -c 0-N, memtier runs unpinned.

Bare-metal Benchmark Results

Small (4 CPUs)

SystemTotal Ops/svs Redisavgp50p99p99.9
Tellstone2,448K2.06x0.06ms0.06ms0.23ms0.41ms
Dragonfly1,404K1.18x0.11ms0.11ms0.17ms0.22ms
Redis1,186K1.0x0.13ms0.12ms0.21ms0.25ms
Valkey1,036K0.87x0.15ms0.14ms0.23ms0.26ms

Medium (16 CPUs)

SystemTotal Ops/svs Redisavgp50p99p99.9
Tellstone6,806K5.98x0.09ms0.07ms0.41ms0.70ms
Dragonfly4,122K3.62x0.15ms0.15ms0.26ms0.32ms
Redis1,139K1.0x0.56ms0.54ms1.06ms1.09ms
Valkey1,016K0.89x0.63ms0.60ms1.19ms1.22ms

Large (32 CPUs)

SystemTotal Ops/svs Redisavgp50p99p99.9
Tellstone12,738K11.53x0.10ms0.08ms0.44ms0.78ms
Dragonfly7,286K6.59x0.18ms0.17ms0.61ms1.29ms
Redis1,105K1.0x1.16ms1.15ms2.33ms2.38ms
Valkey996K0.90x1.29ms1.27ms2.56ms2.61ms

On bare metal Tellstone scales nearly linearly with available cores — 11.5x Redis at 32 CPUs — while Redis and Valkey flatline around 1 M ops/s regardless of core count. Dragonfly scales well (6.6x) but Tellstone maintains a ~1.75x lead at every level, with the lowest p50 latency across the board (0.06 – 0.08 ms).


Development

task test           # go test ./...
task test:race      # go test -race ./...
task vet            # go vet ./...
task check          # vet + race tests (run before committing)
task fmt            # format

Continuous Integration

Pull requests and pushes to main trigger the CI workflow:

  • Build — go build ./...
  • Vet — go vet ./...
  • Test — go test ./...
  • Race tests — go test -race ./...

Benchmarks are not run automatically on every push due to resource constraints. Run them locally with task bench:native or task bench:resp:precise.

Observability

  • Metrics: task run:resp with --enable-metrics exposes Prometheus text at http://<metrics-addr>/metrics (default :9100).
  • Audit logging: --enable-audit writes structured security events (see Audit logging); run with --audit-events all to capture every event type.

Profiling

Two independent workflows, both built on the stock Go toolchain (pprof / trace). Neither assumes a specific core count, OS, or machine — every variable below is overridable on the CLI, so the same commands work on a laptop, a CI runner, or a dedicated benchmarking host.

1) Profile a package's benchmarks directly — no server involved, good for isolating one function (e.g. the storage engine or the RESP parser):

task profile:pkg                                          # ./internal/storage/..., all benchmarks
task profile:pkg PKG=./internal/resp/... BENCH=BenchmarkParseGet
task profile:view FILE=tmp/profile/cpu.out                # opens the CPU profile in the browser
task profile:view FILE=tmp/profile/mem.out ARGS=-alloc_space

2) Profile the running server under real load, generated from a second terminal:

task run:profiling                    # foreground server, RESP + live pprof on :6060
# in a second terminal, generate load, e.g.:
task bench:resp:pipeline
# or: ./bin/benchmark -addr 127.0.0.1:19988 -c 32 -n 1000000 -read-ratio 0.95 -skew 1.5
# while load is running, pull a profile and open it in the browser:
task profile:live                     # CPU, 30s sample (default)
task profile:live KIND=heap
task profile:live KIND=mutex
task profile:live KIND=block
task profile:live:trace               # execution trace, opened via `go tool trace`

go tool pprof -http starts a local web server and opens your default browser automatically. On a headless/remote host, set PORT=<port> and open http://<host>:<port> yourself (e.g. via an SSH tunnel), or browse the raw index at http://127.0.0.1:6060/debug/pprof/ directly.


Contributing

Contributions are welcome — especially around networking, replication, persistence, and RESP command coverage. See CONTRIBUTING.md for the full guide (DCO sign-off required, core principles, workflow).


“A contest of focus. Keep yours made of steel.” — Tellstone

databases
db
memory
memory-database
memory-db

Project health

Latest release

v1.3.0 · Aug 20, 2026

Releases this year

4

Significant stargazers

0xflotus

499 followers · starred Jul 2026

Manuel Rüger

228 followers · starred Jul 2026

Herman Slatman

1,067 followers · starred Jul 2026

Christian Winther

299 followers · starred Jul 2026