Skip to content

Instantly share code, notes, and snippets.

@mgravell
Last active August 27, 2025 16:27
Show Gist options
  • Save mgravell/4492a1d61032da14609b88e0f6e9c9c7 to your computer and use it in GitHub Desktop.
Save mgravell/4492a1d61032da14609b88e0f6e9c9c7 to your computer and use it in GitHub Desktop.
SE.Redis v3 core rework status

What is this

SE.Redis; working on stability+performance work - basically: removing all of "pipelines", "arenas", and a bunch of other things that are a decade of cumulative hacks, to a re-architected implementation designed from first principles with all the good things that didn't exist way back in the never.

Also, adding features and removing tech debt:

  • cancellation supported from the outset on both sync+async paths
  • zero-alloc routes if you can use a new API (damn you, Task!)
  • much easier and more efficient to add new commands, both 1st and 3rd-party

Observations:

  • performance looks great
  • stability: rock - it keeps working when SE.Redis v2.* starts panicking
  • features: work
  • faster than the C redis-benchmark implementation in many cases, especially when the incoming buffers get huge (see the LRANGE_* tests)
    • LRANGE_100: new=134kops/sec, old=22kops/sec; baseline redis-benchmark=90kops/sec
    • LRANGE_300: new=49kops/sec, old=7.6kops/sec; baseline redis-benchmark=22kops/sec

Branch/test (very likely to not even compile at any random moment): https://github.com/StackExchange/StackExchange.Redis/tree/resp-core/tests/BasicTest

Vanilla redis-benchmark C implementation - not as "friendly" an API, but fast; 50 separate clients, batching to 100:

> ./redis-benchmark -c 50 -P 100 -q -n 1000000
PING_INLINE: 1574803.12 requests per second, p50=2.831 msec                     
PING_MBULK: 2415459.00 requests per second, p50=1.719 msec                     
SET: 1187648.50 requests per second, p50=3.879 msec                     
GET: 1736111.12 requests per second, p50=2.527 msec                     
INCR: 1477104.88 requests per second, p50=3.055 msec                     
LPUSH: 1095290.25 requests per second, p50=4.239 msec                     
RPUSH: 1262626.25 requests per second, p50=3.623 msec                     
LPOP: 1019368.00 requests per second, p50=4.535 msec                     
RPOP: 1122334.50 requests per second, p50=4.087 msec                     
SADD: 1506024.12 requests per second, p50=3.015 msec                     
HSET: 1149425.25 requests per second, p50=4.031 msec                     
SPOP: 1904762.00 requests per second, p50=2.327 msec                     
ZADD: 1041666.69 requests per second, p50=4.439 msec                     
ZPOPMIN: 1792114.62 requests per second, p50=2.471 msec                     
LPUSH (needed to benchmark LRANGE): 1102535.88 requests per second, p50=4.215 msec                     
LRANGE_100 (first 100 elements): 89814.98 requests per second, p50=23.519 msec                     
LRANGE_300 (first 300 elements): 22282.63 requests per second, p50=24.543 msec                      
LRANGE_500 (first 500 elements): 13051.08 requests per second, p50=28.431 msec                      
LRANGE_600 (first 600 elements): 10317.47 requests per second, p50=28.911 msec                    
MSET (10 keys): 237247.92 requests per second, p50=12.927 msec                     
XADD: 518134.72 requests per second, p50=9.447 msec 

New proposed SE.Redis v3.* core, multiplexed, 50 logical clients (sharing a single underlying connection), batching 100 commands at a time, async without cancellation (i.e. CancellationToken.None)

> dotnet run -f net9.0 -c Release -- -c 50 -P 100 -n 1000000 +m

====== PING_BULK ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.22 seconds, 818,608 ops/sec
Typical result: BulkString, Length: 3, Protocol Bytes: 9

====== SET ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.96 seconds, 1,037,525 ops/sec
Typical result: SimpleString, Length: 2, Protocol Bytes: 5

====== GET ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.90 seconds, 1,117,281 ops/sec
Typical result: BulkString, Length: 3, Protocol Bytes: 9

====== INCR ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.99 seconds, 1,007,144 ops/sec
Typical result: 995800

====== LPUSH ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.88 seconds, 1,139,742 ops/sec
Typical result: 997500

====== RPUSH ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.89 seconds, 1,124,598 ops/sec
Typical result: 996500

====== LPOP ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.95 seconds, 1,051,192 ops/sec
Typical result: BulkString, Length: 3, Protocol Bytes: 9

====== RPOP ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.83 seconds, 1,197,680 ops/sec
Typical result: BulkString, Length: 3, Protocol Bytes: 9

====== SADD ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.80 seconds, 1,243,322 ops/sec
Typical result: 0

====== HSET ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.81 seconds, 1,233,935 ops/sec
Typical result: 0

====== SPOP ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.83 seconds, 1,199,329 ops/sec
Typical result: BulkString, Length: 0, Protocol Bytes: 5

====== ZADD ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.87 seconds, 1,146,431 ops/sec
Typical result: 0

====== ZPOPMIN ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 0.82 seconds, 1,225,703 ops/sec
Typical result: Array, Length: 0, Protocol Bytes: 4

====== MSET (10 keys) ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 4.24 seconds, 235,600 ops/sec

====== XADD ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.58 seconds, 632,482 ops/sec
Typical result: BulkString, Length: 16, Protocol Bytes: 23

====== LRANGE_100 ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 7.45 seconds, 134,157 ops/sec
Typical result: Array, Length: 100, Protocol Bytes: 906

====== LRANGE_300 ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 20.58 seconds, 48,600 ops/sec
Typical result: Array, Length: 300, Protocol Bytes: 2706

====== LRANGE_500 ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 33.52 seconds, 29,829 ops/sec
Typical result: Array, Length: 500, Protocol Bytes: 4506

====== LRANGE_600 ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 40.02 seconds, 24,987 ops/sec
Typical result: Array, Length: 600, Protocol Bytes: 5406

New proposed SE.Redis v3.* core, multiplexed, 50 logical clients (sharing a single underlying connection), batching 100 commands at a time, async with cancellation (set to 20 seconds per test):

> dotnet run -f net9.0 -c Release -- -c 50 -P 100 -n 1000000 +m +x
====== PING_BULK ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.47 seconds, 678,526 ops/sec
Typical result: BulkString, Length: 3, Protocol Bytes: 9

====== SET ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.19 seconds, 842,392 ops/sec
Typical result: SimpleString, Length: 2, Protocol Bytes: 5

====== GET ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.18 seconds, 847,497 ops/sec
Typical result: BulkString, Length: 3, Protocol Bytes: 9

====== INCR ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.21 seconds, 823,785 ops/sec
Typical result: 994700

====== LPUSH ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.36 seconds, 737,442 ops/sec
Typical result: 994700

====== RPUSH ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.15 seconds, 870,133 ops/sec
Typical result: 994700

====== LPOP ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.09 seconds, 918,295 ops/sec
Typical result: BulkString, Length: 3, Protocol Bytes: 9

====== RPOP ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.24 seconds, 808,545 ops/sec
Typical result: BulkString, Length: 3, Protocol Bytes: 9

====== SADD ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.10 seconds, 906,856 ops/sec
Typical result: 0

====== HSET ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.36 seconds, 733,637 ops/sec
Typical result: 0

====== SPOP ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.08 seconds, 925,854 ops/sec
Typical result: BulkString, Length: 0, Protocol Bytes: 5

====== ZADD ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.06 seconds, 940,692 ops/sec
Typical result: 0

====== ZPOPMIN ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.24 seconds, 806,145 ops/sec
Typical result: Array, Length: 0, Protocol Bytes: 4

====== MSET (10 keys) ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 4.10 seconds, 243,689 ops/sec

====== XADD ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 1.60 seconds, 623,848 ops/sec
Typical result: BulkString, Length: 17, Protocol Bytes: 24

====== LRANGE_100 ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
1,000,000 requests completed in 7.50 seconds, 133,254 ops/sec
Typical result: Array, Length: 100, Protocol Bytes: 906

====== LRANGE_300 ====== (clients: 50, ops: 1,000,000, mux, Batch: 100)
<LRange300>b__0 failed after 19600 operations
<LRange300>b__0 failed after 19600 operations
<LRange300>b__0 failed after 19600 operations
(etc, basically a pile of fail because of going over-time - clean OCE, nothing broken)

Pre-existing v2.* SE.Redis core, multiplexed, 50 logical clients (sharing a single underlying connection), batching 100 commands at a time, async, no cancellation (not supported)

> dotnet run -f net9.0 -c Release -- -c 50 -P 100 -n 1000000 --old
====== PING_BULK ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.68 seconds, 373,362 ops/sec
Typical result: 00:00:00.0012350

====== SET ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.24 seconds, 446,371 ops/sec
Typical result: True

====== GET ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.42 seconds, 413,999 ops/sec
Typical result: 3

====== INCR ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.01 seconds, 497,241 ops/sec
Typical result: 991744

====== LPUSH ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.11 seconds, 474,842 ops/sec
Typical result: 988296

====== RPUSH ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.13 seconds, 469,101 ops/sec
Typical result: 994500

====== LPOP ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.02 seconds, 493,871 ops/sec
Typical result: 

====== RPOP ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 1.96 seconds, 509,523 ops/sec
Typical result: 

====== SADD ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.23 seconds, 449,290 ops/sec
Typical result: False

====== HSET ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.19 seconds, 456,296 ops/sec
Typical result: False

====== SPOP ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 1.98 seconds, 504,825 ops/sec
Typical result: 

====== ZADD ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.28 seconds, 439,080 ops/sec
Typical result: False

====== ZPOPMIN ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.16 seconds, 462,445 ops/sec
Typical result: 0

====== MSET ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 5.75 seconds, 174,008 ops/sec
Typical result: True

====== XADD ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 2.59 seconds, 385,525 ops/sec
Typical result: 1756310248300-81

====== LRANGE_100 ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 45.30 seconds, 22,073 ops/sec
Typical result: 100

====== LRANGE_300 ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 130.98 seconds, 7,635 ops/sec
Typical result: 300

====== LRANGE_500 ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 214.32 seconds, 4,666 ops/sec
Typical result: 500

====== LRANGE_600 ====== (clients: 50, ops: 1,000,000, Batch: 100)
1,000,000 requests completed in 256.64 seconds, 3,897 ops/sec
Typical result: 600
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment