Skip to content
/ ember Public

Ember is a low-latency, memory-efficient, distributed cache written in Rust. A drop-in redis replacement with a small, elegant codebase. Protobuf storage, encryption at rest, prometheus, raft consensus, and swim gossip all built in

License

Notifications You must be signed in to change notification settings

kacy/ember

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

511 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

ember logo

build status crates.io rust version license helm chart homebrew

ember

a low-latency, memory-efficient, distributed cache written in Rust. designed to outperform Redis on throughput, latency, and memory efficiency while keeping the codebase small and readable.

features

  • resp3 protocol — full compatibility with redis-cli and existing Redis clients
  • string commands — GET, SET (with NX/XX/EX/PX), MGET, MSET, MSETNX, INCR, DECR, INCRBY, DECRBY, INCRBYFLOAT, APPEND, STRLEN, GETRANGE, SETRANGE, GETSET, GETDEL, GETEX
  • list operations — LPUSH, RPUSH, LPOP, RPOP, LRANGE, LLEN, LINDEX, LSET, LTRIM, LINSERT, LREM, LPOS, LMOVE, LMPOP, BLPOP, BRPOP
  • sorted sets — ZADD (with NX/XX/GT/LT/CH), ZREM, ZSCORE, ZRANK, ZREVRANK, ZRANGE, ZREVRANGE, ZRANGEBYSCORE, ZREVRANGEBYSCORE, ZINCRBY, ZCOUNT, ZPOPMIN, ZPOPMAX, ZCARD, ZSCAN, ZUNION, ZINTER, ZDIFF, ZUNIONSTORE, ZINTERSTORE, ZDIFFSTORE, ZMPOP, ZRANDMEMBER
  • hashes — HSET, HGET, HGETALL, HDEL, HEXISTS, HLEN, HINCRBY, HINCRBYFLOAT, HKEYS, HVALS, HMGET, HSCAN, HRANDFIELD
  • sets — SADD, SREM, SMEMBERS, SISMEMBER, SCARD, SMISMEMBER, SUNION, SINTER, SDIFF, SUNIONSTORE, SINTERSTORE, SDIFFSTORE, SRANDMEMBER, SPOP, SSCAN, SMOVE, SINTERCARD
  • bitmaps — GETBIT, SETBIT, BITCOUNT, BITPOS, BITOP
  • key commands — DEL, UNLINK, EXISTS, EXPIRE, EXPIREAT, EXPIRETIME, TTL, PEXPIRE, PEXPIREAT, PEXPIRETIME, PTTL, PERSIST, TYPE, SCAN, KEYS, RENAME, COPY, TOUCH, RANDOMKEY, SORT, OBJECT ENCODING/REFCOUNT, WAIT
  • server commands — PING, ECHO, INFO, DBSIZE, FLUSHDB, FLUSHALL, MEMORY USAGE, BGSAVE, BGREWRITEAOF, AUTH, QUIT, CONFIG GET/SET/REWRITE, SLOWLOG, CLIENT ID/SETNAME/GETNAME/LIST, TIME, LASTSAVE, ROLE, MONITOR, COMMAND/COMMAND COUNT/COMMAND INFO/COMMAND DOCS
  • transactions — MULTI, EXEC, DISCARD, WATCH/UNWATCH for optimistic locking
  • acl — per-user command permissions and key pattern restrictions: ACL SETUSER, GETUSER, DELUSER, LIST, WHOAMI, CAT, USERS
  • pub/sub — SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, plus PUBSUB introspection
  • vector similarity search — HNSW-backed approximate nearest neighbor search with cosine, L2, and inner product metrics (compile with --features vector)
  • protobuf storage — schema-validated protobuf values with field-level access (compile with --features protobuf)
  • authentication--requirepass for redis-compatible AUTH (legacy and username/password forms), optional ACL for per-user access control
  • tls support — redis-compatible TLS on a separate port, with optional mTLS for client certificates
  • protected mode — rejects non-loopback connections when no password is set on public binds
  • observability — prometheus metrics (--metrics-port), enriched INFO with 6 sections, SLOWLOG command
  • sharded engine — shared-nothing, thread-per-core design with no cross-shard locking
  • cluster mode — distributed keyspace across multiple nodes: 16384 hash slots, SWIM gossip, Raft-based topology, MOVED/ASK redirects, live slot migration
  • active expiration — background sampling cleans up expired keys without client access
  • memory limits — per-shard byte-level accounting with configurable limits
  • lru eviction — approximate LRU via random sampling when memory pressure hits
  • persistence — append-only file (AOF) and point-in-time snapshots
  • encryption at rest — optional AES-256-GCM encryption for AOF and snapshot files (compile with --features encryption)
  • pipelining — multiple commands per read for high throughput
  • interactive CLIember-cli with REPL, syntax highlighting, tab-completion, inline hints, cluster subcommands, and built-in benchmark
  • graceful shutdown — drains active connections on SIGINT/SIGTERM before exiting

quickest start

# docker — no install needed
docker run -p 6379:6379 ghcr.io/kacy/ember:latest
redis-cli ping
# => PONG

or with docker compose for a persistent single-node setup:

curl -O https://raw.githubusercontent.com/kacy/ember/main/docker-compose.yml
docker compose up -d
redis-cli ping

not supported

ember is purpose-built for caching. some Redis features are intentionally excluded:

  • lua scriptingEVAL, EVALSHA, SCRIPT * are not implemented and not planned. server-side logic may be supported via WASM extensions in a future release.
  • streamsXADD, XREAD, and related commands are not implemented. use a dedicated stream store (Kafka, Redpanda, etc.) for this.
  • multiple databasesSELECT, MOVE, SWAPDB are not supported. ember is single-database by design.
  • bitfieldBITFIELD and BITFIELD_RO are not planned; use application-level serialization instead.

see docs/compatibility.md for the full command support matrix.

notable differences from redis

  • RENAME requires the source and destination keys to hash to the same shard. cross-shard renames return an error.
  • transactions (MULTI/EXEC) are fully atomic within a single shard. cross-shard transactions execute in order but are not globally atomic — the same limitation as Redis Cluster.
  • geo commands are coming in a future release.
  • redis-cli connects on port 6379 (RESP3). the typed gRPC API is on port 6380 (when --features grpc is compiled in).

install

homebrew (macOS/Linux)

brew tap kacy/ember
brew install ember-db

this installs both ember-server and ember-cli.

install script

curl -fsSL https://emberdb.com/install | bash

installs the latest ember-server and ember-cli to /usr/local/bin. useful flags:

# server only (skip ember-cli)
curl -fsSL https://emberdb.com/install | bash -s -- --server-only

# pin to a specific version
... | bash -s -- --version v0.4.6

# custom install directory
... | bash -s -- --prefix ~/.local/bin

from source

git clone https://github.com/kacy/ember
cd ember
cargo build --release

quickstart

# build server and cli
cargo build --release

# run the server (defaults to 127.0.0.1:6379)
./target/release/ember-server

# with memory limit and eviction
./target/release/ember-server --max-memory 256M --eviction-policy allkeys-lru

# with persistence
./target/release/ember-server --data-dir ./data --appendonly

# with encryption at rest (requires --features encryption)
./target/release/ember-server --data-dir ./data --appendonly \
  --encryption-key-file /path/to/keyfile

# with TLS (runs alongside plain TCP)
./target/release/ember-server --tls-port 6380 \
  --tls-cert-file cert.pem --tls-key-file key.pem

ember speaks RESP3, so redis-cli works as a drop-in replacement — but ember-cli adds syntax highlighting, tab-completion, inline hints, and auto-reconnect.

# ember-cli: interactive REPL with autocomplete and help
ember-cli                       # starts REPL at 127.0.0.1:6379>
ember-cli -H 10.0.0.1 -p 6380  # connect to a different host
ember-cli -a mypassword         # authenticate

# one-shot mode
ember-cli SET hello world       # => OK
ember-cli GET hello             # => "hello"

# cluster management subcommands
ember-cli cluster info          # cluster state
ember-cli cluster nodes         # list nodes
ember-cli cluster meet 10.0.0.1 6379

# built-in benchmark
ember-cli benchmark -n 100000 -c 50 -P 16

# redis-cli works too — same protocol, same port
redis-cli SET hello world       # => OK
redis-cli GET hello             # => "world"
# everything below works with either ember-cli or redis-cli
SET counter 10
INCR counter                    # => (integer) 11

LPUSH mylist a b c              # => (integer) 3
LRANGE mylist 0 -1              # => 1) "c" 2) "b" 3) "a"

ZADD board 100 alice 200 bob
ZRANGE board 0 -1 WITHSCORES

HSET user:1 name alice age 30
HGETALL user:1                  # => 1) "name" 2) "alice" 3) "age" 4) "30"

SADD tags rust cache fast       # => (integer) 3
SMEMBERS tags                   # => 1) "cache" 2) "fast" 3) "rust"

SET temp data EX 60
TTL temp                        # => 59

SCAN 0 MATCH "user:*" COUNT 100
DBSIZE                          # => (integer) 6

# blocking list ops (work queues)
BLPOP tasks 5                   # blocks up to 5 seconds for an element
# in another terminal: LPUSH tasks job1 => unblocks with ["tasks", "job1"]

# transactions
MULTI
SET account:a 100
SET account:b 200
EXEC                            # => [OK, OK] — atomic batch

# TLS
ember-cli -p 6380 --tls --tls-insecure PING

clustering

ember distributes keys across 16384 hash slots using CRC16. each node owns a non-overlapping range of slots. gossip (SWIM) propagates membership and health; Raft commits topology changes; MOVED and ASK redirects keep clients pointing at the right node during normal operation and live slot migration.

quickstart

the fastest way to get a 3-node cluster running locally:

make cluster

this builds the release binaries if needed, starts nodes on ports 6379–6381, joins them together, and assigns all 16384 hash slots evenly. you should see something like:

starting node 1 (port 6379, bootstrap)...
starting node 2 (port 6380)...
starting node 3 (port 6381)...
  node :6379 ready
  node :6380 ready
  node :6381 ready
joining cluster...
assigning slots...

cluster_state:ok
cluster_slots_assigned:16384
cluster_known_nodes:3
cluster_size:3

to start nodes manually:

# node A — bootstrap a new cluster and own all slots initially
./target/release/ember-server --port 6379 --cluster-enabled --cluster-bootstrap \
  --data-dir ./data/node-a

# node B and C — join with --cluster-enabled, then CLUSTER MEET into A
./target/release/ember-server --port 6380 --cluster-enabled --data-dir ./data/node-b
./target/release/ember-server --port 6381 --cluster-enabled --data-dir ./data/node-c

# join B and C into the cluster
ember-cli -p 6380 cluster meet 127.0.0.1 6379
ember-cli -p 6381 cluster meet 127.0.0.1 6379

# redistribute slots (5461 slots each after rebalancing)
ember-cli -p 6379 cluster addslots $(seq 0 5460 | tr '\n' ' ')
ember-cli -p 6380 cluster addslots $(seq 5461 10922 | tr '\n' ' ')
ember-cli -p 6381 cluster addslots $(seq 10923 16383 | tr '\n' ' ')

connect to any node and start writing:

ember-cli -p 6379 SET hello world   # => OK
ember-cli -p 6380 GET hello         # => "world" or MOVED redirect

ember-cli -p 6379 cluster info      # cluster_state:ok, 16384 slots assigned
ember-cli -p 6380 cluster nodes     # 3 nodes, each with correct slot range

clients that support cluster mode (including redis-cli --cluster) follow MOVED redirects automatically. ember-cli routes each command to the correct node based on the key's CRC16 hash slot.

stop the cluster and clean up:

make cluster-stop   # SIGTERM the nodes, keep data
make cluster-clean  # stop + delete ./data/cluster/

cluster commands

command description
CLUSTER INFO cluster state, slot counts, node count
CLUSTER NODES list all known nodes with their slot ranges
CLUSTER SLOTS slot-to-node mapping in array form
CLUSTER KEYSLOT key return the hash slot for a key
CLUSTER MYID return this node's ID
CLUSTER MEET host port join another node into the cluster
CLUSTER ADDSLOTS slot [slot ...] assign slots to this node
CLUSTER DELSLOTS slot [slot ...] remove slot assignments from this node
CLUSTER FORGET node-id remove a node from the cluster view
CLUSTER SETSLOT slot IMPORTING|MIGRATING|NODE|STABLE ... manage slot migration state
CLUSTER COUNTKEYSINSLOT slot count keys owned by a slot on this node
CLUSTER GETKEYSINSLOT slot count list keys in a slot (used during migration)
MIGRATE host port key db timeout [COPY] [REPLACE] move a key to another node
RESTORE key ttl payload receive a migrated key
ASKING tell the server to honor an ASK redirect for the next command

replication commands (REPLICATE, FAILOVER) are implemented — see the status table below.

see ARCHITECTURE.md for how clustering works under the hood (raft, gossip, hash slots, migration).

protobuf storage

ember can store schema-validated protobuf messages and access individual fields server-side. compile with --features protobuf to enable.

# build with protobuf support
cargo build --release --features protobuf

commands:

command description
PROTO.REGISTER name <descriptor> register a compiled FileDescriptorSet
PROTO.SET key type_name <data> [EX s] [PX ms] [NX|XX] store a validated protobuf value
PROTO.GET key retrieve the full encoded message
PROTO.TYPE key return the message type name
PROTO.SCHEMAS list all registered schema names
PROTO.DESCRIBE name list message types in a schema
PROTO.GETFIELD key field_path read a single field (dot-separated nested paths)
PROTO.SETFIELD key field_path value update a single scalar field
PROTO.DELFIELD key field_path clear a field to its default value

field-level operations decode/mutate/re-encode on the server, so clients don't need protobuf libraries for simple reads and writes. nested paths use dot notation (e.g., address.city). complex types (repeated, map, nested messages) require PROTO.GET/PROTO.SET for full replacement.

vector similarity search

ember supports HNSW-backed approximate nearest neighbor search for building recommendation systems, semantic search, and RAG pipelines. compile with --features vector to enable.

# build with vector support
cargo build --release --features vector

commands:

command description
VADD key element f32 [f32 ...] [METRIC COSINE|L2|IP] [QUANT F32|F16|Q8] [M n] [EF n] add a vector to the set
VADD_BATCH key DIM n elem1 f32... elem2 f32... [METRIC|QUANT|M|EF] add multiple vectors in one command
VSIM key f32 [f32 ...] COUNT k [EF n] [WITHSCORES] k nearest neighbors
VREM key element remove a vector
VGET key element retrieve stored vector values
VCARD key number of vectors in the set
VDIM key dimensionality of the vector set
VINFO key metadata: dim, count, metric, quantization, M, ef

index configuration (METRIC, QUANT, M, EF) is set on the first VADD and locked after that. dimension is inferred from the first vector's length. each key owns its own independent HNSW index.

# store some embeddings
VADD docs doc1 0.1 0.2 0.3 METRIC COSINE
VADD docs doc2 0.9 0.1 0.0
VADD docs doc3 0.0 0.8 0.2

# find 2 nearest neighbors
VSIM docs 0.1 0.3 0.2 COUNT 2 WITHSCORES
# => 1) "doc1" 2) "0.05" 3) "doc3" 4) "0.12"

VCARD docs                      # => (integer) 3
VDIM docs                       # => (integer) 3
VINFO docs                      # => metric, quantization, dim, count, M, ef

distance metrics: COSINE (default), L2 (squared euclidean), IP (inner product). quantization: F32 (default), F16 (half precision), Q8 (8-bit integer). lower precision uses less memory at a small accuracy cost.

configuration

ember supports a TOML config file, environment variables, and CLI flags. the resolution order is: defaults → config file → env vars → CLI flags (highest priority wins).

# generate a config file with all defaults
ember-server --config-template > ember.toml

# start with a config file
ember-server --config ember.toml

# config file + CLI override
ember-server --config ember.toml --port 7777

# env vars use the EMBER_ prefix
EMBER_PORT=8888 ember-server --config ember.toml

see ember.example.toml for the full annotated config with all available options.

runtime changes via CONFIG SET persist in memory. use CONFIG REWRITE to flush them back to the config file.

common flags

flag default description
--config / -c path to TOML config file
--config-template print default config to stdout and exit
--host 127.0.0.1 address to bind to
--port 6379 port to listen on
--shards CPU cores number of worker threads (shards)
--max-memory unlimited memory limit (e.g., 256M, 1G)
--eviction-policy noeviction noeviction or allkeys-lru
--data-dir directory for persistence files
--appendonly false enable append-only file logging
--appendfsync everysec fsync policy: always, everysec, no
--metrics-port prometheus /metrics and /health HTTP port
--slowlog-log-slower-than 10000 log commands slower than N microseconds (-1 disables)
--slowlog-max-len 128 max entries in slow log ring buffer
--requirepass require AUTH with this password before running commands
--tls-port port for TLS connections (enables TLS when set)
--tls-cert-file path to server certificate (PEM)
--tls-key-file path to server private key (PEM)
--tls-ca-cert-file path to CA certificate for client verification
--tls-auth-clients no require client certificates (yes or no)
--encryption-key-file path to 32-byte key file for AES-256-GCM encryption at rest (requires --features encryption)
--cluster-enabled false enable cluster mode with gossip-based discovery and slot routing
--cluster-bootstrap false bootstrap a new cluster as a single node owning all 16384 slots
--cluster-port-offset 10000 port offset for the cluster gossip bus (data_port + offset)
--cluster-node-timeout 5000 node timeout in milliseconds for failure detection

build & development

make check    # fmt, clippy, tests
make build    # debug build
make release  # release build
make test     # run all tests
make docker-build   # build docker image
make helm-lint      # validate helm chart
make helm-template  # render helm templates

see CONTRIBUTING.md for development workflow and code standards.

kubernetes

deploy to kubernetes with Helm:

# add the helm repo
helm repo add ember https://charts.emberdb.com

# install with defaults (single replica, ClusterIP service)
helm install ember ember/ember

# install with custom settings
helm install ember ember/ember \
  --set ember.maxMemory=512M \
  --set ember.evictionPolicy=allkeys-lru \
  --set ember.appendonly=true

# connect via port-forward
kubectl port-forward svc/ember 6379:6379
ember-cli

see helm/ember/values.yaml for all configurable values.

project structure

crates/
  ember-server/       main server binary
  ember-core/         core engine (keyspace, types, sharding)
  ember-protocol/     RESP3 wire protocol
  ember-persistence/  AOF and snapshot durability
  ember-cluster/      raft consensus, gossip, slot management, migration
  ember-cli/          interactive CLI client (REPL, cluster subcommands, benchmark)

benchmarks

tested on GCP c2-standard-8 (8 vCPU Intel Xeon @ 3.10GHz). see bench/README.md for full results.

vs redis vs dragonfly
SET 1.9x faster 2.2x faster
GET 1.8x faster 2.2x faster

redis-benchmark, 64B values, P=16, 8 threads. take these comparisons with a grain of salt — ember is a small indie project; Redis and Dragonfly are battle-tested systems built by large teams over many years. see bench/README.md for important caveats.

highlights:

  • 1.79M SET/sec, 2.00M GET/sec (redis-benchmark, 64B, P=16)
  • p99 latency: 1.25ms SET, 1.17ms GET (P=1)
  • vector insert: 5.5k vectors/sec (8-shard), 2.4k vectors/sec (single key); query: 1.8k queries/sec, p99=0.62ms
  • vector memory: 4-6x less than chromadb/pgvector/qdrant (29-31 MB vs 139-178 MB for 100k vectors)
  • memory: 180 bytes/key for strings, 215 bytes/key for hashes (redis: 173/170 bytes/key)
./bench/bench-quick.sh       # quick sanity check
./bench/compare-redis.sh     # redis-benchmark comparison
./bench/bench-memtier.sh     # memtier_benchmark comparison
./bench/bench-vector.sh      # vector similarity (ember vs chromadb vs pgvector vs qdrant)
./bench/bench-grpc.sh        # gRPC vs RESP3
./bench/bench-all.sh         # run everything

190+ commands, 1,690+ tests, ~25k lines of code (~47k including tests and comments). see bench/README.md for full methodology and results.

architecture

ember uses a thread-per-core architecture with channel-based routing. each shard owns a partition of the keyspace with no cross-shard locking on the hot path. supports all data types and enables atomic multi-key operations.

cluster mode (--cluster-enabled): multiple ember nodes form a single distributed cache. the keyspace is partitioned into 16384 hash slots using CRC16 — each node owns a non-overlapping range. SWIM gossip propagates membership and health across nodes; topology changes (adding nodes, reassigning slots) go through a Raft consensus group so the cluster view is always consistent. clients are routed via MOVED redirects when a key lands on a different node, and ASK redirects during live slot migration.

contributions welcome — see CONTRIBUTING.md.

documentation

guide description
redis compatibility command-by-command support matrix
migrating from redis config mapping, behavioral differences, step-by-step migration
performance tuning shard count, pipeline depth, memory tuning
production checklist OS tuning, memory limits, persistence, monitoring, security
troubleshooting common issues and solutions
client libraries using existing Redis clients (ioredis, go-redis, redis-py, etc.) with Ember
architecture deep dive execution model, protocol layer, data model, persistence, clustering
changelog release history and phase summaries
security policy vulnerability reporting, deployment hardening

security

see SECURITY.md for:

  • reporting vulnerabilities
  • security considerations for deployment
  • recommended configuration

note: use --requirepass to enable authentication. protected mode is active by default when no password is set, rejecting non-loopback connections on public binds. for encryption at rest, see --encryption-key-file — key loss means data loss, so back up your key file separately from your data.

license

Apache 2.0

About

Ember is a low-latency, memory-efficient, distributed cache written in Rust. A drop-in redis replacement with a small, elegant codebase. Protobuf storage, encryption at rest, prometheus, raft consensus, and swim gossip all built in

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages