Help us shape the future of Go! Take the 2026 Go Developer Survey

boltcache

command module
v2.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 1 Imported by: 0

README ΒΆ

BoltCache ⚑

A Redis-compatible in-memory cache with a built-in REST/WebSocket API, written in Go.

BoltCache speaks the Redis protocol (RESP), so redis-cli, redis-benchmark and client libraries such as go-redis, ioredis and redis-py work out of the box. The same data is also available over a JSON REST API with WebSocket pub/sub, so browsers, serverless functions and shell scripts can use it without a Redis client or a proxy. One static binary, no dependencies.

Project status: beta. 2.x is a rewrite with an extensive test suite, but it hasn't been battle-tested in production yet and has no replication. Read When to use BoltCache and How this project is built before relying on it.

CI Release Go Reference Go Version License

Keywords: redis, resp, cache, in-memory, golang, rest-api, websocket, pub-sub, lua, docker

✨ Features

  • πŸ”Œ Redis compatible: 138 commands over RESP2 for strings, lists, sets, sorted sets, hashes, keys/TTL, SCAN, pub/sub, MULTI/EXEC/WATCH, Lua and server commands
  • ⚑ Multi-core: sharded keyspace and an event-driven network layer (gnet); see the reproducible benchmarks
  • 🌐 REST API: JSON over HTTP, WebSocket subscriptions, a generic /command endpoint, OpenAPI docs at /docs and a built-in web client
  • πŸ”§ Real Lua scripting: EVAL/EVALSHA/SCRIPT with redis.call/redis.pcall; scripts run atomically on their keys, in a sandbox, with a timeout
  • πŸ”’ Atomic by design: every command is atomic, and multi-key commands, transactions and scripts lock all their keys
  • 🧹 Memory management: max_memory/max_keys limits with lru, lfu, random, ttl or noeviction policies
  • πŸ’Ύ Persistence: typed, crash-safe snapshots (atomic rename + fsync), optional gzip, rotating backups, restored on startup
  • πŸ›‘οΈ Security: token auth on every protocol (REST, RESP, TCP), CORS allow-list, per-IP rate limiting, TLS for HTTP, request size limits
  • πŸ“ˆ Observability: Prometheus /metrics, /health, INFO, structured JSON logs, optional pprof
  • 🐳 Cloud native: multi-arch distroless Docker image, configuration through ${ENV} variables, graceful shutdown

πŸš€ Quick Start

Docker
docker run -d --name boltcache -p 6382:6382 -p 8090:8090 -v boltcache-data:/data ghcr.io/wutlu/boltcache:latest

redis-cli -p 6382 SET greeting "hello world" EX 60
curl localhost:8090/cache/greeting

Enable authentication with -e BOLTCACHE_AUTH=true -e BOLTCACHE_TOKEN=change-me. See docker-compose.yml.

Binary

Download a binary for Linux, macOS, Windows or FreeBSD from Releases, or install with Go 1.26+:

go install github.com/wutlu/boltcache/v2@latest
boltcache config generate   # writes a documented config.yaml
boltcache server
From source
git clone https://github.com/wutlu/boltcache.git
cd boltcache
make run          # or: make run-dev (auth enabled, token dev-token-123)

By default the server listens on:

Port Protocol Use it with
6382 RESP (Redis protocol) redis-cli -p 6382, go-redis, ioredis, redis-py, redis-benchmark
6380 Text protocol + RESP telnet/nc (SET k v β†’ OK), BoltCache 1.x clients, and Redis clients
8090 HTTP REST API, WebSocket pub/sub, /metrics, /docs, web client at /

πŸ“– Usage

Redis clients
redis-cli -p 6382
127.0.0.1:6382> HSET user:1 name Ada lang Go
(integer) 2
127.0.0.1:6382> EXPIRE user:1 3600
(integer) 1
127.0.0.1:6382> EVAL "return redis.call('HGET', KEYS[1], 'name')" 1 user:1
"Ada"
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6382"})
rdb.Set(ctx, "key", "value", time.Minute)
Supported commands
Group Commands
Strings GET SET (EX/PX/EXAT/PXAT/NX/XX/KEEPTTL/GET) SETNX SETEX PSETEX GETSET GETDEL GETEX MGET MSET MSETNX INCR DECR INCRBY DECRBY INCRBYFLOAT APPEND STRLEN GETRANGE SUBSTR
Keys DEL UNLINK EXISTS TOUCH TYPE EXPIRE PEXPIRE EXPIREAT PEXPIREAT (NX/XX/GT/LT) TTL PTTL EXPIRETIME PEXPIRETIME PERSIST KEYS SCAN (MATCH/COUNT/TYPE) RANDOMKEY RENAME RENAMENX DBSIZE FLUSHALL FLUSHDB
Lists LPUSH RPUSH LPUSHX RPUSHX LPOP RPOP (with count) LLEN LRANGE LINDEX LSET LTRIM LREM RPOPLPUSH LMOVE
Sets SADD SREM SMEMBERS SISMEMBER SMISMEMBER SCARD SPOP SRANDMEMBER SINTER SUNION SDIFF SINTERSTORE SUNIONSTORE SDIFFSTORE SMOVE SSCAN
Hashes HSET HMSET HSETNX HGET HMGET HGETALL HDEL HEXISTS HLEN HKEYS HVALS HSTRLEN HINCRBY HINCRBYFLOAT HSCAN
Sorted sets ZADD (NX/XX/GT/LT/CH/INCR) ZINCRBY ZSCORE ZMSCORE ZCARD ZCOUNT ZRANK ZREVRANK ZREM ZRANGE (BYSCORE/REV/LIMIT/WITHSCORES) ZREVRANGE ZRANGEBYSCORE ZREVRANGEBYSCORE ZPOPMIN ZPOPMAX ZREMRANGEBYRANK ZREMRANGEBYSCORE ZSCAN
Pub/Sub SUBSCRIBE PSUBSCRIBE UNSUBSCRIBE PUNSUBSCRIBE PUBLISH PUBSUB (CHANNELS/NUMSUB/NUMPAT)
Transactions MULTI EXEC DISCARD WATCH UNWATCH
Scripting EVAL EVALSHA EVAL_RO EVALSHA_RO SCRIPT (LOAD/EXISTS/FLUSH)
Server PING ECHO AUTH HELLO QUIT RESET SELECT TIME INFO CONFIG GET CLIENT COMMAND SAVE BGSAVE LASTSAVE MEMORY WAIT

Not supported yet: lexicographic sorted set ranges (ZRANGEBYLEX, BYLEX), sorted set algebra (ZUNIONSTORE, ZINTER...), streams, bitmaps, HyperLogLog, geo commands, blocking commands (BLPOP...), RESP3, ACL users and multiple databases. Unknown commands return an error rather than being ignored.

REST API
# Strings (with optional TTL)
curl -X PUT localhost:8090/cache/session:42 -d '{"value": "active", "ttl": "30m"}'
curl localhost:8090/cache/session:42
curl localhost:8090/cache/session:42/ttl

# Lists, sets, hashes
curl -X POST localhost:8090/list/jobs -d '["job1", "job2"]'
curl -X POST localhost:8090/set/tags -d '["go", "cache"]'
curl -X PUT  localhost:8090/hash/user:1/name -d '{"value": "Ada"}'
curl localhost:8090/hash/user:1            # {"value": {"name": "Ada"}}

# Any Redis command
curl -X POST localhost:8090/command -d '{"args": ["INCRBY", "visits", "5"]}'

# Pub/Sub: WebSocket subscribers receive messages from REST and RESP publishers
websocat ws://localhost:8090/subscribe/news &
curl -X POST localhost:8090/publish/news -d '{"message": "hello"}'

Full reference: README-REST.md and the interactive docs at http://localhost:8090/docs.

Lua scripting

Scripts get KEYS and ARGV and call commands with redis.call/redis.pcall, using the same type conversions as Redis. A script holds the locks of the keys passed in KEYS, so it runs atomically; touching keys that weren't declared is an error (the same rule Redis Cluster has).

# A fixed-window rate limiter
redis-cli -p 6382 EVAL "
  local n = redis.call('INCR', KEYS[1])
  if n == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1]) end
  return n" 1 rate:client42 60

Scripts run in a sandbox (no file access, no load), cannot leak globals and are stopped after features.lua_timeout (5s by default).

Text protocol

Port 6380 keeps the line-based protocol of BoltCache 1.x for telnet/nc users and existing clients:

$ nc localhost 6380
SET user:1 John
OK
GET user:1
VALUE John
SET session abc 5m
OK
SMEMBERS tags
ARRAY go cache

βš™οΈ Configuration

All settings are optional; see the documented config.yaml and README-CONFIG.md.

cache:
  max_memory: "1GB"
  eviction_policy: "lru"     # lru, lfu, random, ttl, noeviction
persistence:
  enabled: true
  file: "./data/boltcache.json"
  interval: "30s"
security:
  auth:
    enabled: true
    tokens: ["${BOLTCACHE_TOKEN}"]   # read from the environment
boltcache config generate     # write a documented default config
boltcache config validate     # check a config file (reports every error)
boltcache config show         # print the effective configuration
boltcache server --config config-prod.yaml

πŸ›‘οΈ Security

When security.auth.enabled is true, every protocol requires a token:

curl -H "Authorization: Bearer $TOKEN" localhost:8090/cache/key   # or X-API-Token, or ?token=
redis-cli -p 6382 -a "$TOKEN" GET key                             # AUTH <token>

/ping, /health, /ready, /docs and the web client stay public; everything else, including /metrics, requires the token. Tokens are compared in constant time. See README-AUTH.md and SECURITY.md.

πŸ“ˆ Monitoring

  • GET /metrics: Prometheus metrics (boltcache_keys, boltcache_memory_used_bytes, boltcache_keyspace_hits_total, boltcache_evicted_keys_total, boltcache_commands_processed_total...)
  • GET /health, GET /ready: liveness/readiness probes
  • INFO over RESP, GET /info over HTTP
  • logging.format: json for structured logs; monitoring.profiling.enabled exposes pprof

πŸ“Š Performance

See BENCHMARKS.md for the full results, method and caveats. Every number comes from bench/run.sh, which runs redis-benchmark against BoltCache, Redis, Valkey and Dragonfly with the same pinned CPU budget, and runs in CI.

Summary of the latest run (GitHub Actions, 2 server cores):

  • Without pipelining: BoltCache is in the top group with Dragonfly, at about half of Redis' median latency.
  • With pipeline 16: BoltCache leads LPUSH/HSET and is 10–18% behind single-threaded Redis on SET/GET/INCR.
  • ZADD with pipelining reaches only about 40% of Redis' throughput. This is a known weakness.

BoltCache 2.x is about twice as fast as 1.0. Measured on an M1 Pro with boltcache client benchmark, 50 clients: SET 78K β†’ 147K ops/s and GET with pipeline 16 1.13M β†’ 2.32M ops/s.

Measure on your own hardware:

bench/run.sh                                     # needs Docker; Linux recommended
SERVER_CPUS=0-7 CLIENT_CPUS=8-15 bench/run.sh    # bigger machines
boltcache client benchmark --addr localhost:6382 --pipeline 16   # quick check against a running server

πŸ€” When to use BoltCache

BoltCache is a good fit when you want:

  • A cache or ephemeral store that both backend services (Redis clients) and HTTP-only environments can use: browsers, edge/serverless functions, shell scripts, low-code tools, IoT devices. A single binary serves RESP, REST and WebSocket pub/sub over the same data, with no extra proxy.
  • A small, self-contained Redis-compatible server for development, CI, internal tools or single-node deployments, with Prometheus metrics and a web UI built in.
  • Something you can read and extend: plain Go with no cgo, one command engine shared by every protocol, and straightforward data structures.

Look elsewhere when you need:

  • High availability or horizontal scaling: BoltCache has no replication or clustering yet. Use Redis, Valkey or Dragonfly.
  • Full Redis compatibility: streams, RESP3, ACLs, modules, client-side caching and the commands listed above as unsupported are missing.
  • Durable messaging: pub/sub here is fire-and-forget, like Redis pub/sub. For persistent streams, at-least-once delivery or request/reply, use NATS (JetStream) or Kafka.
  • Strongly consistent configuration or coordination (leader election, locks across machines): use etcd or Consul, which replicate with Raft.
  • Data that must not be lost: snapshots are periodic (every 30s by default), so a crash loses recent writes. There's no append-only file yet.
BoltCache Redis / Valkey Dragonfly NATS etcd
Primary use Cache + HTTP API Cache, data structures Cache, data structures Messaging, streams Consistent config / coordination
Redis protocol βœ… (subset) βœ… βœ… (most) ❌ ❌
Built-in REST + WebSocket βœ… ❌ ❌ ❌ (WebSocket for NATS clients) gRPC + JSON gateway
Multi-threaded βœ… I/O threads βœ… βœ… βœ…
Replication / HA ❌ (roadmap) βœ… βœ… βœ… βœ… (Raft)
Persistence Snapshots RDB + AOF Snapshots JetStream Raft log

πŸ§ͺ How this project is built

BoltCache started as an AI-assisted ("vibe coded") experiment, and 1.0 shipped with claims it couldn't back up. The first Hacker News thread called that out, fairly. There were no tests, the performance numbers couldn't be reproduced, and the "Lua engine" understood a single hard-coded call.

2.x was rewritten, again with heavy use of AI coding tools (Claude). The difference is in how the result is verified:

  • Tests: unit tests for every package, plus integration tests that start real servers and drive them with go-redis, raw TCP and WebSocket clients. The sorted set is checked against a naive reference model with randomized operations.
  • Race detector and fuzzing: CI runs every test with -race on Linux and macOS and fuzzes the protocol parser and glob matcher on every push.
  • Independent review: before 2.0 shipped, a separate review pass found 9 bugs, including quadratic parsing of large commands, a Lua sandbox escape between scripts and data loss in multi-step commands under memory pressure. All were fixed with regression tests.
  • Reproducible benchmarks: BENCHMARKS.md lists every number with the script that produced it. The script runs in CI with pinned CPUs against Redis, Valkey and Dragonfly, including the cases where BoltCache loses.
  • Behaviour matches Redis where it implements a command, with the same replies, errors and edge cases. A difference from Redis is a bug; please report it.

πŸ—οΈ Architecture

 redis-cli / go-redis ─┐                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 telnet / 1.x clients ─┼─ gnet event loops ─┐  β”‚ command engine (138 cmds)   β”‚
                       β”‚  (RESP + text)     β”œβ”€β–Άβ”‚  auth Β· MULTI/EXEC Β· Lua    │──▢ sharded keyspace
 HTTP / WebSocket ─────┴─ net/http (REST) β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    1024 shards, RWMutex each
                                                         β”‚                         typed values, TTL,
                                                  pub/sub hub                      LRU/LFU eviction
                                                                                        β”‚
                                                                         snapshots (atomic, gzip, backups)
  • Keyspace: 1024 shards, each with its own lock and counters, so cores don't contend. Every command runs under its key's shard lock, which makes single-key commands atomic. Multi-key commands, MULTI/EXEC and scripts lock their shards in a fixed order, so they're atomic and deadlock-free.
  • Network: gnet event loops (one per core) parse pipelined RESP incrementally without copying; HTTP uses the standard library.
  • One engine: RESP, the text protocol, REST and Lua all execute through the same command implementation, so behaviour is identical everywhere.
  • Eviction: like Redis, approximated by sampling: candidates are compared by last access (LRU), a logarithmic access counter (LFU) or expiry time (TTL).

⬆️ Upgrading from 1.x

  • Snapshots written by 1.x are converted automatically on first start; the next save uses the new typed format.
  • Authentication is now enforced when security.auth.enabled is true. In 1.x it was configured but never checked.
  • The Redis-compatible port (6382) now supports every command listed above instead of only PING/SET/GET. The separate gnet text port (6381) is gone: port 6380 is served by gnet now and understands both protocols.
  • Writing a different type to an existing key returns WRONGTYPE (1.x silently replaced it), and commands follow Redis semantics, e.g. HSET returns the number of new fields.
  • The cluster command was removed: it never replicated data. Replication is on the roadmap.
  • The Go module path is now github.com/wutlu/boltcache/v2, and Go 1.26+ is required to build.

See CHANGELOG.md for everything that changed.

πŸ—ΊοΈ Roadmap

  • Replication (primary/replica) and clustering
  • Append-only file persistence
  • Lexicographic sorted set ranges, streams and blocking list commands
  • RESP3, ACL users and TLS for the RESP listener

πŸ”§ Development

make help          # list targets
make test-race     # unit + integration tests with the race detector
make lint          # golangci-lint
make fuzz          # fuzz the protocol parser
make bench         # micro benchmarks

Integration tests start real servers and drive them with go-redis, raw TCP and WebSocket clients. See CONTRIBUTING.md.

πŸ“š Documentation

πŸ“„ License

MIT. See LICENSE.

πŸ“ž Support


Created by Alper Mutlu Toksâz with ❀️

Documentation ΒΆ

The Go Gopher

There is no documentation for this package.

Directories ΒΆ

Path Synopsis
Package appinfo holds build and version information.
Package appinfo holds build and version information.
internal
auth
Package auth manages API tokens shared by every protocol: REST requests (Authorization: Bearer / X-API-Token / ?token=), and RESP/TCP clients (AUTH <token>).
Package auth manages API tokens shared by every protocol: REST requests (Authorization: Bearer / X-API-Token / ?token=), and RESP/TCP clients (AUTH <token>).
cache
Package cache implements BoltCache's in-memory keyspace: typed values (strings, lists, sets, hashes), expiration, memory/key limits with eviction, multi-key atomic transactions and snapshot persistence.
Package cache implements BoltCache's in-memory keyspace: typed values (strings, lists, sets, hashes), expiration, memory/key limits with eviction, multi-key atomic transactions and snapshot persistence.
command
Package command implements BoltCache's Redis-compatible command set.
Package command implements BoltCache's Redis-compatible command set.
glob
Package glob implements Redis-style glob matching used by KEYS, SCAN MATCH and PSUBSCRIBE.
Package glob implements Redis-style glob matching used by KEYS, SCAN MATCH and PSUBSCRIBE.
pubsub
Package pubsub implements channel and pattern based publish/subscribe.
Package pubsub implements channel and pattern based publish/subscribe.
resp
Package resp implements the Redis Serialization Protocol (RESP2): an incremental, zero-copy command parser that also accepts inline commands (what you type in telnet), and reply writers.
Package resp implements the Redis Serialization Protocol (RESP2): an incremental, zero-copy command parser that also accepts inline commands (what you type in telnet), and reply writers.
server
Package server wires the cache, command engine and network listeners (TCP text/RESP, RESP and HTTP) together and manages their lifecycle.
Package server wires the cache, command engine and network listeners (TCP text/RESP, RESP and HTTP) together and manages their lifecycle.
Package logger configures structured logging (log/slog) and prints the startup banner.
Package logger configures structured logging (log/slog) and prints the startup banner.
Package swaggerui serves the OpenAPI document and a Swagger UI page.
Package swaggerui serves the OpenAPI document and a Swagger UI page.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL