Skip to content

Architecture

Tellstone is organized into a small set of packages, each with one job:

LayerPackageNotes
Binary protocolclient (public), internal/network (impl)MsgRequest/MsgResponse frames (GET/SET/DEL, TTL, key, value)
RESP2 protocolinternal/respRedis-compatible listener reusing the same engine; optional STARTTLS upgrade
Storage engineinternal/storageGOMAXPROCS sharded buckets (--num-shards), per-shard RWMutex, timing-wheel eviction
Persistenceinternal/persistencePer-shard append-only WAL for crash recovery (--enable-persistence)
Cryptointernal/cryptoOptional ChaCha20-Poly1305 for at-rest encryption
Transport securityinternal/tlsOptional TLS 1.3 / mTLS with automatic certificate rotation
RBACinternal/rbacOptional per-user auth + role-based command gating; atomic policy snapshots
Metrics / tracinginternal/metrics, internal/tracePrometheus text exporter, OTLP/gRPC tracing for observability

Both listeners — the native binary protocol and the RESP2 listener — sit in front of the same sharded storage engine. Nothing about the engine assumes which protocol a given connection is using; RESP support is additive, not a separate code path with its own semantics. This design ensures consistency across protocols while maximizing performance.

The keyspace is split across N shards (default GOMAXPROCS, configurable via --num-shards) by key hash. Each shard has its own RWMutex, so operations against different shards proceed without contending on a single global lock — this is what lets throughput scale close to linearly as you add cores. The sharded design minimizes lock contention and maximizes parallelism.

An active timing wheel (the “chronometer”) walks expiring keys and evicts them in O(1) per tick, configurable via --evict-interval and --evict-slots. Lazy eviction on read backs this up, so a key that expired between ticks is still treated as gone the moment it’s accessed, even before the chronometer catches up. This dual approach ensures efficient TTL management without impacting performance.

Tellstone includes a per-shard, append-only write-ahead log (WAL) for crash recovery. Each shard writes to its own file (shard_000.db, shard_001.db, …), and every SET and DEL is recorded before it hits the in-memory engine. On startup, the WAL is replayed to restore the keyspace, skipping expired keys and applying tombstone deletes.

The WAL is disabled by default. Enable it with --enable-persistence and optionally set --persistence-dir to choose where the .db files are written. See Configuration for details.

Both listeners support optional TLS 1.3 transport encryption, served by a forked, single-binary implementation (no external OpenSSL dependency) that wraps gnet connections:

  • The binary listener is encrypted implicitly — every connection is TLS from the moment it is accepted.
  • The RESP listener has two modes: implicit TLS (the default when certs are configured) or STARTTLS, where the listener stays plaintext and each client upgrades the connection in place with a STARTTLS command. The upgrade precedes AUTH, so credentials are never sent in the clear.
  • With --tls-ca, clients must present certificates (mTLS).
  • A filesystem watcher automatically rotates certificates, keys, and CAs on disk or Kubernetes projected Secret swaps, with zero downtime.

TLS is off by default. See Security for the full flag semantics, certificate generation, and rotation details.

RBAC is an optional, protocol-agnostic authorization layer sitting in front of the storage engine. When --rbac-config points at a policy file, both listeners switch from the shared --require-pass password to per-user credentials and role-based command gating:

  • A policy snapshot (roles, users, default role) is immutable; writes build a complete replacement and publish it with one atomic swap, so readers never block and never see a half-applied policy. SIGHUP hot-reloads the file the same way.
  • Each connection resolves its role once at handshake and pins it for its lifetime — a later policy swap never re-evaluates a live session.
  • Authorization is fail-closed: no role means deny-all, and a non-matching key namespace means deny. Denials surface per command as -NOPERM (or -NOAUTH before authentication) rather than by dropping the connection.
  • The hot path is a single bit test in a command bitset plus a key-prefix scan — single-digit nanoseconds, zero allocations, no locks.

RBAC is off by default. See RBAC for policy files, rule syntax, and the ROLE command family.

Tellstone runs as a single node today — there is no replication or cluster membership yet. See Clustering & Replication for what’s planned and how to scale without built-in clustering. The architecture is designed to support future distributed features while maintaining the core principles of performance and simplicity.