I have built TQCache as a fast and simple key-value store that can be used as a drop-in replacement for Memcached and in some cases for 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 55MB of RAM, whereas Memcached usage was ~680MB and Redis ~768MB.

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 and Memcached. 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 ~87k RPS write throughput in its periodic sync mode. Compared to Memcached (~124k RPS) it is slower, but it outperforms Redis (~61k RPS) in this mode. If you need massive write throughput, TQCache is likely not the right tool. On reads it performs well, achieving ~146k RPS, which outperforms Redis (~108k RPS) but is still significantly slower than Memcached (~212k 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). Memcached uses around 2 cores. This is a conscious design choice to maximize throughput through parallel shard processing. 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 persists sessions to disk, so you don’t lose all user logins if the service restarts. If you value storage capacity over raw microsecond latency and want 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 robust and 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