Redis-compatible in-memory data store implemented in C# / .NET with TCP networking, RESP protocol support, transactions, optimistic concurrency, replication, persistence, eviction, Pub/Sub, lists, automated testing, and performance benchmarking.
- TCP-based client/server communication
- Concurrent client connections
- Configurable host and port
- Graceful server startup and shutdown
- Connection lifecycle management
- Command dispatching
- Master and replica server modes
- CLI configuration
RESP request parsing and response serialization for:
- Simple Strings
- Errors
- Integers
- Bulk Strings
- Arrays
- Multiple commands
- Pipelined commands
Implemented commands:
PING
ECHO
GET
SET
DEL
INCR
Implemented:
- Key expiration
- Expiration timestamps
SETwith millisecond expirationPX- Expiration checks during key access
- TTL reset/update behavior
- Concurrent expiration handling
Example:
SET name CacheVault PX 5000
Redis-style transaction support:
MULTI
EXEC
DISCARD
Implemented:
- Transaction command queue
QUEUEDresponses- Atomic transaction execution
- Transaction isolation
- Transaction state management
EXECexecutionDISCARDtransaction cancellation
Supported transactional commands include:
GET
SET
DEL
INCR
Optimistic concurrency control with:
WATCH
MULTI
EXEC
DISCARD
Implemented:
- Key version tracking
- Watched-key tracking
- Change detection
- Transaction invalidation
- Optimistic concurrency
WATCH+MULTI+EXECworkflows
Master/replica replication is implemented with:
- Master role
- Replica role
- Replication ID
- Replication offsets
- Replication state management
- Command propagation
- Replica acknowledgements
INFO replicationREPLCONFPSYNCPSYNC2FULLRESYNC- Full synchronization
- Partial synchronization
- Replication backlog
WAIT
Replication flow:
βββββββββββββββββββ
β Master β
β β
β Command Store β
ββββββββββ¬βββββββββ
β
Replication Stream
β
ββββββββββββββββ΄βββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββ βββββββββββββββββ
β Replica β β Replica β
β #1 β β #2 β
βββββββββββββββββ βββββββββββββββββ
Implemented replication acknowledgement mechanisms:
- Replica offset tracking
- Replica acknowledgement
- Waiting for replica synchronization
WAIT
Implemented snapshot persistence:
- Snapshot creation
- Snapshot loading
- Key/value persistence
- TTL persistence
- Startup restoration
- Configurable RDB directory
- Configurable RDB enable/disable
Implemented append-only persistence:
- Command logging
- AOF replay
- Startup recovery
- Configurable AOF file
- Configurable AOF directory
- AOF enable/disable
- Command persistence
Configurable maximum-memory eviction with:
- LRU
- LFU
- Random eviction
Implemented:
- Maximum memory configuration
- Memory-limit enforcement
- Eviction policy selection
- Key removal under memory pressure
- Concurrent eviction handling
Redis-style publish/subscribe functionality:
SUBSCRIBE
PUBLISH
Implemented:
- Channel subscriptions
- Multiple channel subscriptions
- Message publishing
- Subscriber tracking
- Publisher-to-subscriber delivery
- Asynchronous message delivery
- Multiple connected subscribers
Example:
SUBSCRIBE news
Another client:
PUBLISH news hello
Redis-style list data structure and commands:
LPUSH
RPUSH
LPOP
RPOP
LRANGE
LLEN
Implemented:
- Left push
- Right push
- Left pop
- Right pop
- Range retrieval
- List length
- Multiple-element push
- Correct Redis-compatible list ordering
- Thread-safe list operations
Example:
LPUSH numbers one two three
LRANGE numbers 0 -1
Result:
three
two
one
Implemented concurrency support across the server and data layer:
- Concurrent TCP clients
- Thread-safe key/value storage
- Concurrent transactions
- Concurrent list operations
- Concurrent TTL handling
- Concurrent Pub/Sub operations
- Concurrent replication handling
- Synchronization around shared server state
βββββββββββββββββββββββ
β TCP Client β
β redis-cli / App β
ββββββββββββ¬βββββββββββ
β
β RESP
βΌ
βββββββββββββββββββββββ
β CacheVault Server β
ββββββββββββ¬βββββββββββ
β
βΌ
βββββββββββββββββββββββ
β RESP Protocol β
β Parser / Serializer β
ββββββββββββ¬βββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Command Dispatcher β
ββββββββββββ¬βββββββββββ
β
βββββββββββββββββββββββΌββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββββββ ββββββββββββββ ββββββββββββββ
β Key/Value β β Lists β β Pub/Sub β
β Store β β Store β β System β
βββββββ¬βββββββ ββββββββββββββ ββββββββββββββ
β
βββββββΌβββββββββββββββ¬βββββββββββββββ
β β β β
βΌ βΌ βΌ βΌ
TTL Eviction Transactions WATCH
β β
β β
ββββββββββββ¬ββββββββββ
β
βββββββββ΄βββββββββ
β β
βΌ βΌ
βββββββββββββ ββββββββββββββββ
β RDB β β AOF β
β Persistenceβ β Persistence β
βββββββββββββ ββββββββββββββββ
Replication
β
βΌ
βββββββββββββββ
β Master β
ββββββββ¬βββββββ
β
ββββββββββ΄βββββββββ
βΌ βΌ
ββββββββββββ ββββββββββββ
β Replica β β Replica β
ββββββββββββ ββββββββββββ
Restore dependencies:
dotnet restore CacheVault.slnxBuild the complete solution:
dotnet build CacheVault.slnx -c ReleaseRun all tests:
dotnet test CacheVault.slnx -c ReleaseTest projects:
CacheVault.UnitTests
CacheVault.IntegrationTests
Start CacheVault:
dotnet run -c Release --project CacheVault.Server -- --host 127.0.0.1 --port 6379 --no-aof --no-rdbConnect using redis-cli:
redis-cli -p 6379PING
PONG
SET name CacheVault
GET name
INCR counter
DEL name
ECHO hello
SET session abc123 PX 10000
GET session
After the expiration period:
GET session
returns a nil response.
MULTI
SET name CacheVault
INCR counter
GET name
EXEC
WATCH account
MULTI
SET account updated
EXEC
If a watched key is modified before EXEC, the transaction is invalidated.
LPUSH numbers one two three
LRANGE numbers 0 -1
Result:
three
two
one
Additional operations:
RPUSH numbers four
LLEN numbers
LPOP numbers
RPOP numbers
Subscriber:
SUBSCRIBE notifications
Publisher:
PUBLISH notifications hello
The subscriber receives the published message asynchronously.
Start the master:
dotnet run -c Release --project CacheVault.Server -- --host 127.0.0.1 --port 6379Start the replica:
dotnet run -c Release --project CacheVault.Server -- --host 127.0.0.1 --port 6380 --replica --master-host 127.0.0.1 --master-port 6379Check replication information:
INFO replication
Run with RDB enabled:
dotnet run -c Release --project CacheVault.Server -- --host 127.0.0.1 --port 6379Run with RDB disabled:
dotnet run -c Release --project CacheVault.Server -- --host 127.0.0.1 --port 6379 --no-rdbRun with AOF enabled:
dotnet run -c Release --project CacheVault.Server -- --host 127.0.0.1 --port 6379Run with AOF disabled:
dotnet run -c Release --project CacheVault.Server -- --host 127.0.0.1 --port 6379 --no-aofCacheVault includes BenchmarkDotNet microbenchmarks and custom TCP load, list-scaling, and RDB snapshot/restore benchmarks. The measurements below are local results, not comparisons against Redis.
Environment: Windows 11 (build 10.0.26200), .NET SDK 10.0.301, .NET runtime 10.0.12, Intel Core Ultra 5 135H reported, 18 logical processors reported, Release build.
Build the benchmark project:
dotnet build .\benchmarks\CacheVault.Benchmarks\CacheVault.Benchmarks.csproj -c ReleaseRun a 60-second mixed workload:
dotnet run --project .\benchmarks\CacheVault.Benchmarks\CacheVault.Benchmarks.csproj -c Release --no-build -- --load --clients 16 --duration 60 --workload mixed --sample-rate 100Run a pipeline workload with batches of 100 commands:
dotnet run --project .\benchmarks\CacheVault.Benchmarks\CacheVault.Benchmarks.csproj -c Release --no-build -- --load --clients 16 --duration 60 --workload pipeline --pipeline 100 --sample-rate 100Run list scaling at 1K, 10K, and 100K items:
dotnet run --project .\benchmarks\CacheVault.Benchmarks\CacheVault.Benchmarks.csproj -c Release --no-build -- --list-scaling --sizes "1000,10000,100000" --repeats 5 --samples 500 --warmup 20Run five RDB trials with 50K string keys:
dotnet run --project .\benchmarks\CacheVault.Benchmarks\CacheVault.Benchmarks.csproj -c Release --no-build -- --rdb-benchmark --keys 50000 --repeats 5These results come from separate run sets and configurations. Percentiles are summaries of the recorded runs; custom TCP load-test latency is sampled at 1 in every 100 workload iterations.
| Workload | Configuration | Observed result | Latency / interpretation |
|---|---|---|---|
| Mixed GET/SET/INCR | 16 clients, 2 runs | 77,022 ops/s mean | Mean run p50/p95/p99: 0.173 / 0.271 / 0.463 ms; zero errors |
| Pipeline | 16 clients, pipeline depth 100, 2 runs | 238,992 commands/s mean | p50/p95/p99: 5.477 / 12.340 / 17.878 ms per batch of 100 commands; worst sampled batch latency 330.159 ms; zero errors |
| SET, AOF OFF | 16 clients, 2 runs | 86,624 ops/s mean | Mean run p50/p95/p99: 0.152 / 0.253 / 0.551 ms |
| SET, AOF ON | 16 clients, 2 runs | 26,098 ops/s mean | Mean run p50/p95/p99: 0.563 / 0.846 / 1.870 ms; approximately 69.9% lower throughput than AOF OFF in this preliminary comparison |
A five-trial GET-only study compared the command gate enabled with a diagnostic-only bypass. The bypass must not be treated as a production mode or evidence that the synchronization boundary can safely be removed.
| Clients | Condition | Median throughput | Median p50 / p95 / p99 (ms) |
|---|---|---|---|
| 16 | Gate enabled | 50,797 ops/s | 0.254 / 0.453 / 1.850 |
| 16 | Diagnostic bypass | 73,770 ops/s | 0.177 / 0.336 / 1.266 |
| 64 | Gate enabled | 48,113 ops/s | 1.073 / 2.463 / 5.861 |
| 64 | Diagnostic bypass | 47,511 ops/s | 1.060 / 2.862 / 6.851 |
At 64 clients, bypassing the gate did not improve median throughput; gate-on p95/p99 were lower in all five same-trial comparisons. At 16 clients, variation was substantial and the observed median difference does not establish causality.
An earlier two-run 64-client GET-only set measured approximately 95,028 ops/s. Its exact source revision and full raw output were not retained, and the discrepancy versus the later five-trial study remains unresolved. Treat that earlier result as historical rather than a directly comparable baseline.
For 100K-item lists, a selected comparison of three baseline captures against three optimized captures measured these reductions in the median of the per-run latency percentiles, after incremental list-value memory accounting. The benchmark used one sequential TCP client, 16-byte UTF-8 values, LRANGE 0β99, and no eviction pressure (MaxMemoryBytes=0). The detailed baseline and optimized latency values are in CacheVault_Benchmark_Report.md.
| Operation | p50 reduction | p95 reduction | p99 reduction |
|---|---|---|---|
| LPUSH | 89.1% | 90.0% | 87.8% |
| RPUSH | 90.9% | 91.0% | 88.8% |
| LPOP | 87.5% | 86.8% | 85.7% |
| RPOP | 91.2% | 92.0% | 91.1% |
| LRANGE 0β99 | 11.5% | 27.9% | 18.2% |
These are local results from the selected captures, not guarantees for other list sizes or workloads. The accounting change removes a full-list UTF-8 byte-count recalculation but does not remove the O(n) element movement from List<string> head insertion/removal. The LRANGE change is an observation from the same comparison, not evidence that the accounting change directly optimized range retrieval. The experiment did not measure behavior under active eviction or memory pressure. Individual CSVs do not embed their own source commit IDs, so source revision provenance is incomplete; the full report documents this limitation.
Five trials were run per dataset size. Snapshot timing includes graceful shutdown; restore timing includes startup and TCP endpoint readiness. Restore verification samples selected keys and the first list, not every restored key.
| String keys | RDB file size | Median shutdown + snapshot | Median restore to ready |
|---|---|---|---|
| 1,000 | 78,988 B | 2.02 ms | 1.53 ms |
| 10,000 | 799,735 B | 5.68 ms | 20.00 ms |
| 50,000 | 4,043,095 B | 30.63 ms | 72.19 ms |
The managed-heap delta is excluded because it is too noisy to represent total dataset memory. Snapshot and restore timings have noticeable run-to-run variation.
For the full methodology, individual trials, limitations, and raw-evidence inventory, see CacheVault_Benchmark_Report.md.