I have built TQCache as a fast and simple key-value store that can be used as a drop-in replacement for Memcached or as an alternative to Redis. While surely not as mature, it does offer a specific set of trade-offs that may interest you. It is designed for workloads where both memory efficiency and persistence are priorities, such as session storage.

See: https://github.com/mevdschee/tqcache

What is TQCache?

Implemented in Go, TQCache works as both an embeddable library and a standalone server. It speaks the Memcached protocol (both text and binary), meaning it works out-of-the-box with existing clients, including PHP’s native memcached session handler.

The Trade-off: Memory vs. Disk

The primary architectural difference between TQCache and its alternatives is how it stores data. Memcached stores everything in RAM and it is fast. Redis (typically) keeps the dataset in RAM for speed, periodically dumping to disk or appending to a log for persistence. TQCache keeps only the keys in RAM, while the actual values rely on the operating system’s page cache and are stored on disk (using fixed-size records).

The “Niche” Advantage

This architecture makes TQCache extremely memory efficient. In benchmarks with 100,000 keys (10KB payloads), TQCache used approximately 70-83MB of RAM, whereas Redis used ~1372MB.

If you have a dataset larger than your available RAM, TQCache allows you to serve it with reasonable performance, relying on fast SSDs/NVMe, whereas Memcached would evict keys.

Benchmark Graphs

During benchmarks, TQCache used roughly the same total memory as Redis. The difference: it didn’t reserve that memory. The OS kept the disk blocks hot in its block cache, giving TQCache fast access without dedicated RAM.

Realistic Performance & Limitations

It is important to be realistic: TQCache is generally slower than in-memory solutions, especially for writes, because it prioritizes durability guarantees (or at least disk-backed storage).

TQCache achieves ~96k RPS write throughput via socket in its periodic sync mode, which outperforms Redis (~60k RPS). Using the package directly (no network overhead), writes reach ~180k RPS. For reads, performance is excellent: ~184k RPS via socket or ~498k RPS via package, compared to Redis’s ~100k RPS.

The trade-off is CPU usage: TQCache uses approximately 4 cores (calculated as shards/4) compared to Redis which is single-threaded (~1 core). You can limit CPU usage by reducing shard count.

Additionally, TQCache is a pure Key-Value store. It does not support the rich data structures (Sets, Sorted Sets, Lists, etc.) that make Redis so versatile. If you use Redis for more than just SET/GET, TQCache is not a replacement.

How it works

The storage engine uses a lock-free, worker-based architecture with one goroutine per shard:

  • Keys File (Disk): Fixed-size records (1051 bytes) storing key, metadata, and data pointer.
  • Data Files (Disk): 16 bucket files with slot sizes from 1KB to 64MB.
  • B-Tree (RAM): Maintains the key-to-record mapping for O(log n) lookups.
  • Min-Heap (RAM): Allows for efficient expiration-based cleanup.

The RAM structures are rebuilt from the keys file on startup.

Each shard has a dedicated worker goroutine that owns all shard state. Requests are sent via buffered channels and processed sequentially. There are no locks needed within a shard. This provides predictable latency and simple reasoning.

To reduce lock contention, TQCache shards its data across 16 independent shards. Keys are distributed using a FNV-1a hash function, allowing concurrent operations on different shards without blocking each other.

Continuous Defragmentation

Instead of append-only logs with periodic compaction, TQCache uses continuous defragmentation to keep files compact:

  • On delete/expiry: move tail slot data to freed slot position
  • Update the moved entry’s index to point to new slot
  • Truncate file by one slot

This means files are always compact with no wasted space, O(1) allocation (always append to end), and no fragmentation over time. About 25-33% disk space is wasted on average due to the bucket sizing.

Unlike Memcached, TQCache does not support LRU eviction. Instead, it focuses on reliable persistence with a configurable max-ttl option (which Memcached lacks). This ensures disk space is bounded while guaranteeing data reliability.

When to use it?

TQCache shines as a persistent Memcached. It is fully compatible with PHP sessions. Unlike Memcached, TQCache will persists sessions to disk, so you don’t lose all user logins if the service restarts. It may also be a match if you value storage capacity over latency. It allows you to cache terabytes of data without paying for terabytes of RAM. It uses a single binary (or Go library import) with a simple fixed-size record format that is easy to back up.

Conclusion

TQCache is not a “Redis Killer.” It is a specialized tool that offers the protocol simplicity of Memcached with the persistence of Redis, all while keeping a small memory footprint.

Disclaimer: I’ve built TQCache in one weekend and it has not been tested in production, you’ve been warned!

See: https://github.com/mevdschee/tqcache