Skip to content

QoS for system critical events ( such as cluster heartbeats, replication streams, and slot migration) - #4076

Merged
JimB123 merged 14 commits into
valkey-io:unstablefrom
satheeshaGowda:socket-prioritization
Sep 14, 2026
Merged

JimB123 merged 14 commits into
valkey-io:unstablefrom
satheeshaGowda:socket-prioritization

Conversation

@satheeshaGowda

@satheeshaGowda satheeshaGowda commented Jul 1, 2026 •

Copy link
Copy Markdown
Member

This PR focuses on implementing #3927 specifically on socket prioritization for system-critical events, such as cluster heartbeats, replication streams, and slot migrations.

1. Highlevel Summary & Scope

The Socket Prioritization feature introduces Quality of Service (QoS) scheduling into the Valkey engine. It establishes a dedicated, high-priority event handling path for system-critical events, such as cluster heartbeats, replication streams, and slot migrations to prevent Head-of-Line (HoL) blocking and starvation caused by heavy client traffic.

Subsystems in Scope (High Priority / CONN_PRIORITY_HIGH):

  1. Replication Links:
    • Inbound replica connections (SYNC/PSYNC).
    • Outbound primary replication links .
  2. Cluster Bus Links:
    • Inbound accepted cluster links .
    • Outbound cluster node interconnects .
  3. Slot Migration:
    • Inbound slot import jobs .
    • Outbound slot export connections .
    • Cached migration sockets.

2. User-Facing Changes

• New config preemptive-poll-interval-us – defines the maximum elapsed time (in microseconds) between preemptive poll of the high-priority events while iterating through normal client events.

• CLIENT LIST / CLIENT INFO: Displays the H flag in flags, ex: when the replica link is high priority, flags=SH.

127.0.0.1:16380> client list
id=4 addr=127.0.0.1:39006 laddr=127.0.0.1:16380 fd=26 name= age=68 idle=1 flags=SH capa= db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=1 omem=20504 tot-mem=22168 events=r cmd=replconf user=default redir=-1 resp=2 lib-name= lib-ver= tot-net-in=3614 tot-net-out=111 tot-cmds=69
id=5 addr=127.0.0.1:41322 laddr=127.0.0.1:16380 fd=15 name= age=27 idle=0 flags=N capa= db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=0 qbuf-free=0 argv-mem=10 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=1690 events=r cmd=client|list user=default redir=-1 resp=2 lib-name= lib-ver= tot-net-in=123 tot-net-out=241195 tot-cmds=3

• CLIENT LIST flags H / CLIENT KILL flags H: Added support for filtering and selectively terminating connections by priority (flags H and negative filter not-flags H).

127.0.0.1:16380> client list flags H
id=4 addr=127.0.0.1:39006 laddr=127.0.0.1:16380 fd=26 name= age=63 idle=1 flags=SH capa= db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=1 omem=20504 tot-mem=22168 events=r cmd=replconf user=default redir=-1 resp=2 lib-name= lib-ver= tot-net-in=3349 tot-net-out=97 tot-cmds=64

• Info Stats: Observability metrics

127.0.0.1:16380> info stats
eventloop_cycles:3547
eventloop_duration_sum:349454
eventloop_duration_cmd_sum:3742
instantaneous_eventloop_cycles_per_sec:29
instantaneous_eventloop_duration_usec:82
################################### Following are the new ones introduced
eventloop_priority_cycles:1567                            
eventloop_priority_duration_sum:85503
eventloop_priority_duration_cmd_sum:846

• Info debug: Observability metrics

127.0.0.1:16380> info debug
eventloop_duration_aof_sum:1441
eventloop_duration_cron_sum:185019
eventloop_duration_max:6317
eventloop_cmd_per_cycle_max:3
################################### Following are the new ones introduced
eventloop_priority_duration_max:453
eventloop_priority_cmd_per_cycle_max:1

3. Platform Support & Compatibility Matrix

Multiplexer Platform Implementation Status
epoll Linux epfd nested in main epoll Fully Supported
kqueue macOS, FreeBSD, OpenBSD kqfd nested in main kqueue Fully Supported
evport Solaris / Illumos doesn't support Graceful Fallback (Single loop, no QoS)
select POSIX Fallback doesn't support Graceful Fallback (Single loop, no QoS)

4. Core Architecture & Implementation Highlights

4.1. How Events Are Categorized by Priority

Connections are tagged as high priority either at creation time for outbound connections (such as cluster links, replication connections) or dynamically promoted for incoming connections (such as PSYNC/SYNC command or slot import) as high priority. all other incoming client connections are default tagged as normal priority.

4.2. Where Events Get Registered

• Dual-Tier Event Loops: Valkey maintains a Main Event Loop for normal client traffic and a nested High-Priority Event Loop for high priority events.
• Kernel Multiplexer Nesting: The operating system multiplexer file descriptor (e.g., epoll fd on Linux, kqueue fd on macOS/BSD) of the high-priority event loop is registered directly inside the main event loop.
• Virtual Routing: When a socket registers for read or write readiness, its priority tag automatically directs the registration to the corresponding event loop.

4.3. How Events Get Processed

                          ┌──────────────────────────┐
                          │   Main Event Loop Wake   │
                          └─────────────┬────────────┘
                                        │
                       Did High-Priority Multiplexer Fire?
                                        │
                         Yes ───────────┴─────────── No
                          │                          │
                          ▼                          ▼
               ┌───────────────────────┐  ┌───────────────────────┐
               │  Drain High-Priority  │  │  Process Batch of     │
               │  Events Completely    │  │  Normal Client Events │
               └──────────┬────────────┘  └──────────┬────────────┘
                          │                          │
                          ▼                          │
               ┌───────────────────────┐             │
               │  Begin Normal Client  │◄────────────┘
               │  Event Batch          │
               └──────────┬────────────┘
                          │
          Every 4 events, elapsed time > 1ms?
                          │
                         Yes ───────────► Preemptively Poll & Drain
                          │               High-Priority Events
                          No
                          │
                          ▼
               ┌───────────────────────┐
               │  Complete Main Cycle  │
               └───────────────────────┘
  1. Immediate Head-of-Line Processing: When the main event loop awakens, if the high-priority multiplexer descriptor has fired, all pending high-priority events are drained and dispatched immediately before the normal client events.
  2. Periodic Preemptive Interleaving: If processing a large batch of normal client requests exceeds the threshold (1 millisecond), the engine yields control over to preemptively poll and drain any newly arrived high-priority events.
  3. Threaded I/O Prioritization: If I/O threads offloading enabled, we have introduced end-to-end Swim Lane queues, Inboxes and outboxes are partitioned by priority: io_shared_inbox[CONN_PRIORITY_NORMAL], io_shared_inbox[CONN_PRIORITY_HIGH] and io_shared_outbox[CONN_PRIORITY_COUNT], io_shared_outbox[CONN_PRIORITY_HIGH], such that high-priority queues are drained first, then preemptively polls high priority events while processing normal queues to be consistent with the behavior when io-threads disabled.

5. Performance Benchmarking results.

There are two types of tests conducted to understand the performance characteristics of this feature.

1 Smoke Test This test has conducted with 94 concurrent active connections , to keep the latency under sub millisecond to mimic the happy path workload.

2 Stress Test This teas has conducted with 940 concurrent active connections to saturate CPU to validate the benefits of QoS when system under heavy load.

TLDR;
As the number of concurrent clients increases, under 100% cpu load, QoS yields better results , i.e significantly reduces the atomic slot migration duration and cut down number of full syncs, without introducing any noticeable regression.

I believe thats the core of the problem we are trying to address with QoS, when system is under heavy load prioritize cluster stability/availability over normal client traffic.

5.1 Smoke test (Under minimum load to keep the latency sub-milliseconds)

Compare latency, throughput, and QoS benefits against base line with ~100 concurrent clients under following 3 circumstances...

  • Cluster Steady state
  • Cluster Full sync loop
  • Cluster Atomic Slot Migration
5.1.1 Setup
  • 3 VMs of type n2-standard-16 (16 vCPUs, 64 GB Memory)
  • Each VM has 3 Valkey nodes
  • 9 nodes cluster
  • 3 shards with 2 replicas each
5.1.2 Valkey node config
$BIN_DIR/valkey-server \
        --port 6379 \
        --requirepass Valkey143 \
        --primaryauth Valkey143 \   
        --dir . \
        --save "" \
        --dbfilename 6379.rdb \
        --repl-diskless-sync yes \
        --repl-diskless-sync-delay 0 \
        --io-threads 4 \
        --client-output-buffer-limit "normal 0 0 0 replica 1536mb 1024mb 60" \
        --repl-backlog-size "1536mb" \
        --protected-mode no \
        --cluster-enabled yes \
        --cluster-config-file nodes.conf \
        --cluster-node-timeout 6000
5.1.3 Valkey cluster nodes
4602cae2f4ebe99c6239675bc95b9f95db8ee5f7 10.*.*.32:6379@16379 myself,master - 0 0 1 connected 0-5460
407b875ecc08e001235f64f888c62cf07ca0ad32 10.*.*.33:6379@16379 master - 0 1785195675000 2 connected 5461-10922
36d35b7305925f8666427c57fc7766235ec74868 10.*.*.34:6379@16379 master - 0 1785195677058 3 connected 10923-16383
65919c9d946de2e0f267469314e7159913a7be56 10.*.*.32:6380@16380 slave 36d35b7305925f8666427c57fc7766235ec74868 0 1785195676153 3 connected
4cd5374cd37008abf3b23574eab2f68dd08cac6d 10.*.*.32:6381@16381 slave 407b875ecc08e001235f64f888c62cf07ca0ad32 0 1785195676153 2 connected
f66046b40f2378895b6197c5867a16786153f39c 10.*.*.33:6380@16380 slave 4602cae2f4ebe99c6239675bc95b9f95db8ee5f7 0 1785195675147 1 connected
6f6db0efdb202e65826346dcf1db05b8e508d936 10.*.*.33:6381@16381 slave 36d35b7305925f8666427c57fc7766235ec74868 0 1785195677058 3 connected
eacf14c7f7396e16f15f0559f0d95b5a29d69c65 10.*.*.34:6380@16380 slave 4602cae2f4ebe99c6239675bc95b9f95db8ee5f7 0 1785195676000 1 connected
b5b45f8852463f30872952ab515b93c80799241f 10.*.*.34:6381@16381 slave 407b875ecc08e001235f64f888c62cf07ca0ad32 0 1785195677159 2 connected
5.1.4 Cluster Steady state
nohup taskset -c 0-46 $BIN_DIR/memtier_benchmark -a Valkey143  --server="${LEADER_HOST}" --port="${LEADER_PORT}"  --threads=94 --clients=1  --key-pattern=S:S --key-minimum=1 --key-maximum=15000000 --data-size=100 --test-time=300 --pipeline=3 --ratio=1:3 --hide-histogram  --out-file "${LEADER_HOST}_${LEADER_PORT}.benchmark" --cluster-mode > "${LEADER_HOST}_${LEADER_PORT}.log" 2>&1 &

Baseline

94        Threads
1         Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       379200.56          ---          ---         0.00         0.00         0.55776         0.55100         0.85500         1.01500     54466.34
Gets      1137621.56   1137621.56         0.00         0.00         0.00         0.55525         0.54300         0.84700         1.00700    158527.08
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1516822.12   1137621.56         0.00         0.00         0.00         0.55588         0.54300         0.85500         1.00700    212993.43

QoS

94        Threads
1         Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       400792.17          ---          ---         0.00         0.00         0.52767         0.52700         0.73500         0.91100     57587.00
Gets      1202378.12   1202378.12         0.00         0.00         0.00         0.52523         0.52700         0.73500         0.91100    167570.24
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1603170.29   1202378.12         0.00         0.00         0.00         0.52584         0.52700         0.73500         0.91100    225157.24

5.1.5 Cluster Full Sync Loop
nohup taskset -c 0-46 $BIN_DIR/memtier_benchmark -a Valkey143  --server="${LEADER_HOST}" --port="${LEADER_PORT}"  --threads=94 --clients=1  --key-pattern=S:S --key-minimum=1 --key-maximum=15000000 --data-size=100 --test-time=300 --pipeline=3 --ratio=1:0 --hide-histogram  --out-file "${LEADER_HOST}_${LEADER_PORT}.benchmark" --cluster-mode > "${LEADER_HOST}_${LEADER_PORT}.log" 2>&1 &

Baseline

94        Threads
1         Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets      1111045.18          ---          ---         0.00         0.00         0.75990         0.72700         1.15100         4.86300    160240.76
Gets            0.00         0.00         0.00         0.00         0.00             ---             ---             ---             ---         0.00
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1111045.18         0.00         0.00         0.00         0.00         0.75990         0.72700         1.15100         4.86300    160240.76

sync_full: 5
last_successful_sync_duration_ms: 17134

QoS

94        Threads
1         Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets      1036834.46          ---          ---         0.00         0.00         0.81449         0.78300         1.21500         1.36700    149514.99
Gets            0.00         0.00         0.00         0.00         0.00             ---             ---             ---             ---         0.00
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1036834.46         0.00         0.00         0.00         0.00         0.81449         0.78300         1.21500         1.36700    149514.99

sync_full:0
last_successful_sync_duration_ms:-1
5.1.6 Cluster Atomic Slot Migration
nohup taskset -c 0-46 $BIN_DIR/memtier_benchmark -a Valkey143  --server="${LEADER_HOST}" --port="${LEADER_PORT}"  --threads=94 --clients=1  --key-pattern=S:S --key-minimum=1 --key-maximum=15000000 --data-size=100 --test-time=300 --pipeline=3 --ratio=1:3 --hide-histogram  --out-file "${LEADER_HOST}_${LEADER_PORT}.benchmark" --cluster-mode > "${LEADER_HOST}_${LEADER_PORT}.log" 2>&1 &

Baseline

94        Threads
1         Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       369967.59          ---          ---         0.20         0.00         0.56960         0.55900         0.79100         1.29500     53161.40
Gets      1120045.72   1120045.39         0.00         0.32         0.00         0.56459         0.55900         0.79100         1.28700    156086.96
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1490013.31   1120045.39         0.00         0.52         0.00         0.56584         0.55900         0.79100         1.28700    209248.36

atomic slot migration complete time : 1787077283 last_update_time - 1787077274 create_time   = 9secs

QoS

94        Threads
1         Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       385484.04          ---          ---         0.18         0.00         0.54646         0.52700         0.79100         1.42300     55401.46
Gets      1170811.34   1170811.04         0.00         0.30         0.00         0.54002         0.52700         0.79100         1.40700    163161.96
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1556295.39   1170811.04         0.00         0.48         0.00         0.54162         0.52700         0.79100         1.41500    218563.42

atomic slot migration complete time : 1787076732 last_update_time - 1787076724 create_time   = 8secs 

5.1.7 Executive Summary

  1. Cluster Steady State: QoS improves throughput by +5.69% (1.603M vs 1.517M ops/sec) and reduces p99 tail latency by -14.04% (0.735 ms vs 0.855 ms) by expediting replication streams. we noticed that memory fragmentation and allocation churns were greatly reduced with QoS.
  2. Replication Full Sync Loop (The Key Result): Under a write-heavy flood (~1.1M writes/sec), Baseline experienced 5 full resynchronization cycles (sync_full: 5), each causing massive 17.13-second stalls and ballooning p99.9 latency to 4.863 ms. QoS completely eliminated all full resyncs (sync_full: 0), collapsing p99.9 tail latency by -71.89% down to 1.367 ms.
  3. Atomic Slot Migration: QoS completed slot migration 11.11% faster (8s vs 9s) while delivering +4.45% higher aggregate throughput and -5.72% lower p50 latency across the benchmark window.

5.2 Stress test (CPU saturated)

Compare latency, throughput, and QoS benefits against base line with ~1000 concurrent clients under following 3 circumstances...

  • Cluster Steady state
  • Cluster Full sync loop
  • Cluster Atomic Slot Migration

5.2.1 Setup

  • 3 VMs of type n2-standard-16 (16 vCPUs, 64 GB Memory)
  • Each VM has 3 Valkey nodes
  • 9 nodes cluster
  • 3 shards with 2 replicas each

5.2.2 Valkey node config

$BIN_DIR/valkey-server \
        --port 6379 \
        --requirepass Valkey143 \
        --primaryauth Valkey143 \   
        --dir . \
        --save "" \
        --dbfilename 6379.rdb \
        --repl-diskless-sync yes \
        --repl-diskless-sync-delay 0 \
        --io-threads 4 \
        --client-output-buffer-limit "normal 0 0 0 replica 1536mb 1024mb 60" \
        --repl-backlog-size "1536mb" \
        --protected-mode no \
        --cluster-enabled yes \
        --cluster-config-file nodes.conf \
        --cluster-node-timeout 6000

5.2.3 Valkey cluster nodes

4602cae2f4ebe99c6239675bc95b9f95db8ee5f7 10.*.*.32:6379@16379 myself,master - 0 0 1 connected 0-5460
407b875ecc08e001235f64f888c62cf07ca0ad32 10.*.*.33:6379@16379 master - 0 1785195675000 2 connected 5461-10922
36d35b7305925f8666427c57fc7766235ec74868 10.*.*.34:6379@16379 master - 0 1785195677058 3 connected 10923-16383
65919c9d946de2e0f267469314e7159913a7be56 10.*.*.32:6380@16380 slave 36d35b7305925f8666427c57fc7766235ec74868 0 1785195676153 3 connected
4cd5374cd37008abf3b23574eab2f68dd08cac6d 10.*.*.32:6381@16381 slave 407b875ecc08e001235f64f888c62cf07ca0ad32 0 1785195676153 2 connected
f66046b40f2378895b6197c5867a16786153f39c 10.*.*.33:6380@16380 slave 4602cae2f4ebe99c6239675bc95b9f95db8ee5f7 0 1785195675147 1 connected
6f6db0efdb202e65826346dcf1db05b8e508d936 10.*.*.33:6381@16381 slave 36d35b7305925f8666427c57fc7766235ec74868 0 1785195677058 3 connected
eacf14c7f7396e16f15f0559f0d95b5a29d69c65 10.*.*.34:6380@16380 slave 4602cae2f4ebe99c6239675bc95b9f95db8ee5f7 0 1785195676000 1 connected
b5b45f8852463f30872952ab515b93c80799241f 10.*.*.34:6381@16381 slave 407b875ecc08e001235f64f888c62cf07ca0ad32 0 1785195677159 2 connected

5.2.4 Cluster Steady state

nohup taskset -c 0-46 $BIN_DIR/memtier_benchmark -a Valkey143  --server="${LEADER_HOST}" --port="${LEADER_PORT}"  --threads=94 --clients=10  --key-pattern=S:S --key-minimum=1 --key-maximum=15000000 --data-size=100 --test-time=300 --pipeline=3 --ratio=1:3 --hide-histogram  --out-file "${LEADER_HOST}_${LEADER_PORT}.benchmark" --cluster-mode > "${LEADER_HOST}_${LEADER_PORT}.log" 2>&1 &

Baseline

94        Threads
10        Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       498754.49          ---          ---         0.00         0.00         4.25179         4.04700         5.79100        10.43100     71257.41
Gets      1496067.55   1496067.55         0.00         0.00         0.00         4.24882         4.04700         5.75900        10.36700    207121.36
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1994822.05   1496067.55         0.00         0.00         0.00         4.24956         4.04700         5.75900        10.36700    278378.77

QoS

94        Threads
10        Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       515730.85          ---          ---         0.00         0.00         4.11184         3.99900         5.37500         8.83100     73694.45
Gets      1547183.08   1547183.08         0.00         0.00         0.00         4.10752         3.98300         5.34300         8.76700    214209.65
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    2062913.93   1547183.08         0.00         0.00         0.00         4.10860         3.99900         5.34300         8.76700    287904.09

5.2.5 Cluster Full Sync Loop

nohup taskset -c 0-46 $BIN_DIR/memtier_benchmark -a Valkey143  --server="${LEADER_HOST}" --port="${LEADER_PORT}"  --threads=94 --clients=10  --key-pattern=S:S --key-minimum=1 --key-maximum=15000000 --data-size=100 --test-time=300 --pipeline=3 --ratio=1:0 --hide-histogram  --out-file "${LEADER_HOST}_${LEADER_PORT}.benchmark" --cluster-mode > "${LEADER_HOST}_${LEADER_PORT}.log" 2>&1 &

Baseline

94        Threads
10        Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets      1344515.86          ---          ---         0.00         0.00         6.30695         5.75900        10.75100        14.14300    192670.42
Gets            0.00         0.00         0.00         0.00         0.00             ---             ---             ---             ---         0.00
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1344515.86         0.00         0.00         0.00         0.00         6.30695         5.75900        10.75100        14.14300    192670.42

sync_full: 6
last_successful_sync_duration_ms: 25941

QoS

94        Threads
10        Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets      1328251.89          ---          ---         0.00         0.00         6.38473         6.01500         9.02300        14.07900    190335.66
Gets            0.00         0.00         0.00         0.00         0.00             ---             ---             ---             ---         0.00
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1328251.89         0.00         0.00         0.00         0.00         6.38473         6.01500         9.02300        14.07900    190335.66

sync_full:0
last_successful_sync_duration_ms:-1

5.2.6 Cluster Atomic Slot Migration

nohup taskset -c 0-46 $BIN_DIR/memtier_benchmark -a Valkey143  --server="${LEADER_HOST}" --port="${LEADER_PORT}"  --threads=94 --clients=10  --key-pattern=S:S --key-minimum=1 --key-maximum=15000000 --data-size=100 --test-time=300 --pipeline=3 --ratio=1:3 --hide-histogram  --out-file "${LEADER_HOST}_${LEADER_PORT}.benchmark" --cluster-mode > "${LEADER_HOST}_${LEADER_PORT}.log" 2>&1 &

Baseline

94        Threads
10        Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       470020.91          ---          ---         1.82         0.00         4.49536         4.28700         6.49500        15.42300     67139.51
Gets      1410076.39   1410073.08         0.00         3.31         0.00         4.47197         4.28700         6.49500        15.23100    195198.82
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1880097.30   1410073.08         0.00         5.14         0.00         4.47782         4.28700         6.49500        15.29500    262338.33

atomic slot migration complete time : 1787034817 last_update_time - 1787034772 create_time   = 45secs

QoS

94        Threads
10        Connections per thread
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       458155.86          ---          ---         2.28         0.00         4.63061         4.28700         7.87100        45.56700     65435.23
Gets      1374388.56   1374385.88         0.00         2.68         0.00         4.60227         4.28700         7.83900        45.05500    190248.84
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1832544.42   1374385.88         0.00         4.96         0.00         4.60936         4.28700         7.83900        45.05500    255684.06

atomic slot migration complete time : 1787035586 last_update_time - 1787035553 create_time   = 33secs 

5.2.7 Executive Summary

  1. Cluster Steady State: QoS yields a +3.41% boost in throughput (2.06M vs 1.99M ops/sec) and a -15.43% reduction in p99.9 tail latency (8.77 ms vs 10.37 ms) by expediting replication streams. we noticed that memory fragmentation and allocation churns were greatly reduced with QoS.
  2. Replication Full Sync Loop (The Key Result): Under a write flood (~1.33M writes/sec), Baseline suffered 6 catastrophic full resynchronizations (sync_full: 6), each stalling replicas for ~25.9 seconds. QoS completely eliminated replication disconnects (sync_full:0) and reduced p99 tail latency by -16.07% (9.02 ms vs 10.75 ms).
  3. Atomic Slot Migration: QoS accelerated slot migration completion by 26.67% (33s vs 45s), significantly shrinking the cluster rebalance vulnerability window with only a minor, transient client preemption trade-off during the 33-second migration window.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduces a secondary high-priority event loop linked into the main event loop, adds a connection priority (connPriority) concept with get/set APIs used across socket/TLS registration, IO threads, and cluster/replication/migration connections. Adds CLIENT-facing QoS filtering/reporting (CLIENT LIST/KILL qos, CLUSTER LINKS qos), IO-thread queues split by priority, and corresponding unit/integration tests.

Changes

Socket prioritization implementation

Layer / File(s) Summary
High-priority event loop core
src/ae.c, src/ae.h, src/ae_epoll.c, src/ae_evport.c, src/ae_kqueue.c, src/ae_select.c
Adds hp_event_loop/last_poll_us state, macro-driven polling of a nested high-priority loop during aeProcessEvents, per-backend aeApiGetPollFd, and exported aeLinkHighPriorityEventLoop/aeGetPollFd.
Connection priority API
src/connection.c, src/connection.h
Introduces connPriority enum, priority field, connSetPriority/connGetPriority, and hp_el-aware connGetEventLoop.
Server wiring of the high-priority loop
src/server.c, src/server.h
initServer creates/links server.hp_el; shutdown deletes it; struct/prototype additions.
Socket and TLS event registration
src/socket.c, src/tls.c
Registers/deregisters AE events on connGetEventLoop(conn) rather than always server.el, with error handling on failed registration.
Cluster/replication/migration priority upgrades
src/cluster.c, src/cluster_legacy.c, src/cluster_migrateslots.c, src/replication.c
Sets CONN_PRIORITY_HIGH on cluster bus, migration, and replication connections; CLUSTER LINKS gains a qos field.
Priority-aware IO-thread queues
src/io_threads.c
Splits shared inbox/outbox, tickets, and pending responses per priority; adds tagged job dispatch and bounded high-priority draining; excludes writes during active slot migration unless allowed.
CLIENT QoS filtering and reporting
src/networking.c, src/commands.def, src/commands/client-kill.json, src/commands/client-list.json
Adds qos filter parsing/matching, qos= info fields, sentinel-name auto-upgrade, and updated command schemas/histories.
Native and Tcl test suites
src/unit/test_socket_prioritization.cpp, tests/unit/cluster/socket-prioritization.tcl, tests/unit/socket-prioritization.tcl, tests/unit/introspection.tcl, tests/unit/cluster/links.tcl
Adds unit and integration tests covering dual-loop behavior, priority migration, QoS filtering, cluster link qos, and replication prioritization.

Estimated code review effort: 5 (Critical) | ~100 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MainThread
  participant IOThread
  participant HighInbox as io_shared_inbox[HIGH]
  participant NormalInbox as io_shared_inbox[NORMAL]
  MainThread->>HighInbox: enqueue read/write job (qidx=HIGH)
  MainThread->>NormalInbox: enqueue read/write job (qidx=NORMAL)
  IOThread->>HighInbox: drain bounded high-priority jobs
  IOThread->>NormalInbox: process normal-priority jobs
  IOThread->>MainThread: sendToMainThread via io_shared_outbox[qidx]
  MainThread->>MainThread: processIOThreadsResponses drains high then normal
Loading
sequenceDiagram
  participant Client
  participant Server_el as server.el
  participant Server_hp_el as server.hp_el
  Client->>Server_el: connect (normal priority)
  Server_el->>Server_el: register AE_READABLE/AE_WRITABLE
  Client->>Server_el: setname sentinel-...
  Server_el->>Server_hp_el: connSetPriority(CONN_PRIORITY_HIGH), migrate AE events
  Server_el->>Server_hp_el: PROCESS_HP_EVENTS_IF_NEEDED during poll
  Server_hp_el-->>Client: prioritized handling of events
Loading

Possibly related PRs

  • valkey-io/valkey#3938: Both PRs modify the IO-threads implementation (src/io_threads.c), overlapping in queue configuration and scheduling refactors.

Suggested reviewers: madolson, zuiderkwast

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: QoS prioritization for system-critical event traffic.
Description check ✅ Passed The description accurately describes this PR as socket prioritization for system-critical events and its follow-up steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@satheeshaGowda
satheeshaGowda force-pushed the socket-prioritization branch from c5bdcff to 86ff5b2 Compare July 1, 2026 03:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
src/tls.c (1)

1201-1201: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated priority-derivation logic.

The ae_pri = (conn->c.priority > CONN_PRIORITY_NORMAL) ? AE_PRIORITY_HIGH : AE_PRIORITY_NORMAL; computation is repeated verbatim in registerSSLEvent and updateSSLEvent. Extracting it into a small helper (e.g. connPriorityToAePriority(conn)) would avoid divergence if the mapping logic changes later.

♻️ Proposed refactor
+static inline int connPriorityToAePriority(connection *conn) {
+    return (conn->c.priority > CONN_PRIORITY_NORMAL) ? AE_PRIORITY_HIGH : AE_PRIORITY_NORMAL;
+}
+
 static void registerSSLEvent(tls_connection *conn) {
     int mask = aeGetFileEvents(server.el, conn->c.fd);
-    int ae_pri = (conn->c.priority > CONN_PRIORITY_NORMAL) ? AE_PRIORITY_HIGH : AE_PRIORITY_NORMAL;
+    int ae_pri = connPriorityToAePriority((connection *)conn);
 static void updateSSLEvent(tls_connection *conn) {
     ...
-    int ae_pri = (conn->c.priority > CONN_PRIORITY_NORMAL) ? AE_PRIORITY_HIGH : AE_PRIORITY_NORMAL;
+    int ae_pri = connPriorityToAePriority((connection *)conn);

Also applies to: 1253-1253

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tls.c` at line 1201, The AE priority mapping is duplicated in both
registerSSLEvent and updateSSLEvent, so extract the conn->c.priority to
AE_PRIORITY_HIGH/AE_PRIORITY_NORMAL conversion into a small shared helper such
as connPriorityToAePriority and use it from both call sites. Keep the helper
near the existing SSL event logic in src/tls.c so the priority-derivation stays
consistent and any future mapping change only needs one update.
src/commands/client-list.json (1)

211-217: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same documentation/metadata suggestions as client-kill.json.

This QOS argument has the identical gap: no history entry for the 10.0.0 addition, and it's typed as a generic string rather than oneof with pure-tokens for the two fixed values (normal-priority/high-priority), unlike other fixed-value filters in this file (e.g., client-type/TYPE, skipme/SKIPME).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/client-list.json` around lines 211 - 217, The QOS argument
metadata in client-list.json is missing the same documentation and schema
updates as client-kill.json: add a history entry for the 10.0.0 introduction and
change the definition from a generic string to a oneof with the two fixed
pure-token values. Update the QOS entry in the client-list argument schema to
match the pattern used by fixed-value filters like client-type/TYPE and
skipme/SKIPME, using the relevant metadata fields on the QOS item.
src/networking.c (1)

4912-4919: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider adding a not-qos negative filter for consistency.

Every other filter in this function (type, addr, laddr, user, name, flags, lib-name, lib-ver, db, capa, ip) has a corresponding not-* negative counterpart, but qos only supports positive matching. This is a minor completeness/consistency gap rather than a defect.

Also applies to: 4998-5003

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/networking.c` around lines 4912 - 4919, The client-filter parsing in
networking.c should add a negative counterpart for qos to match the existing
not-* pattern used by type, addr, laddr, user, name, flags, lib-name, lib-ver,
db, capa, and ip. Update the filter handling around the qos branch in the client
filter parser (the same logic used in the nearby helper that processes other
filter tokens) so that not-qos is recognized and mapped to the corresponding
negated filter field, mirroring how the other positive/negative pairs are
implemented.
tests/unit/socket-prioritization.tcl (1)

20-83: 🩺 Stability & Availability | 🔵 Trivial | ⚖️ Poor tradeoff

Timing-based throttling test may be flaky under load, but is appropriately scoped.

This test relies on fixed sleeps (after 50, after 1100) combined with DEBUG SLEEP to force event accumulation. It's already gated behind needs:debug external:skip, which limits its blast radius to internal test runs, but such fixed-delay synchronization can still be flaky on slow/loaded CI runners (e.g., if the server doesn't finish accepting 250 connections or the sleep window is too tight).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/socket-prioritization.tcl` around lines 20 - 83, The socket
prioritization test in the test body that uses DEBUG SLEEP, after 50, and after
1100 is timing-sensitive and can be flaky on slow runners; replace the
fixed-delay synchronization with a more deterministic readiness check or polling
loop before sending the 250 SET commands and before reading results. Keep the
existing scope markers (needs:debug external:skip) and adjust the test around
the existing client setup, control_cl flow, and telemetry assertions so it waits
for the intended server state instead of assuming exact sleep timing.
src/commands/client-kill.json (1)

229-235: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider documenting the QOS addition in history, and modeling it as oneof.

Two small metadata improvements for this new argument:

  1. Other additions in this file (e.g., the 9.0.0 entry) get a top-level history entry describing the new filter; this QOS addition at 10.0.0 doesn't have one.
  2. QOS only accepts two fixed values (normal-priority/high-priority), yet is typed as a generic string. Other fixed-value filters here (client-type/TYPE, skipme/SKIPME) use "type": "oneof" with pure-token sub-arguments, which better documents allowed values via COMMAND DOCS/COMMAND INFO. Since the actual parsing in parseClientFiltersOrReply (src/networking.c) is hand-written via strcasecmp, this would be a metadata-only change with no functional impact.
💡 Example history + oneof restructuring
         [
             "9.0.0",
             "Added filters NAME, IDLE, FLAGS, LIB-NAME, LIB-VER, DB, CAPA, and IP. And negative filters NOT-ID, NOT-TYPE, NOT-ADDR, NOT-LADDR, NOT-USER, NOT-FLAGS, NOT-NAME, NOT-LIB-NAME, NOT-LIB-VER, NOT-DB, NOT-CAPA, NOT-IP."
+        ],
+        [
+            "10.0.0",
+            "Added the QOS filter option."
         ]
     ],
...
                            {
                                "token": "QOS",
                                "name": "qos",
-                               "type": "string",
-                               "optional": true,
-                               "since": "10.0.0"
+                               "type": "oneof",
+                               "optional": true,
+                               "since": "10.0.0",
+                               "arguments": [
+                                   {"name": "normal-priority", "type": "pure-token", "token": "normal-priority"},
+                                   {"name": "high-priority", "type": "pure-token", "token": "high-priority"}
+                               ]
                            },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/client-kill.json` around lines 229 - 235, Add a top-level
history entry for the new QOS filter in client-kill.json so it is documented
alongside the other versioned additions, and change the QOS argument definition
from a generic string to a oneof. Model the allowed values as pure-token
sub-arguments for the two fixed options (normal-priority and high-priority),
matching the existing client-type/TYPE and skipme/SKIPME patterns so COMMAND
DOCS and COMMAND INFO expose the real choices. Keep this as metadata only; no
changes are needed in parseClientFiltersOrReply.
src/unit/test_socket_prioritization.cpp (1)

120-150: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add one unit case for listener accept-handler priority propagation.

The suite covers direct file-event priority and connSetPriority(), but it does not hit the new createSocketAcceptHandler()/connListener::priority path in src/server.c. A small test that registers a listener with CONN_PRIORITY_HIGH and inspects the created aeFileEvent would close that gap. As per coding guidelines, src/unit/**/*.{c,cpp} low-level logic changes should be covered by unit tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/unit/test_socket_prioritization.cpp` around lines 120 - 150, Add a unit
test to cover listener accept-handler priority propagation through
createSocketAcceptHandler() and connListener::priority. The current
SocketPrioritizationTest only verifies connSetPriority() and direct file-event
priority updates, so extend the suite with a case that creates a listener, sets
CONN_PRIORITY_HIGH before registering the accept handler, and asserts the
resulting aeFileEvent in server.el has AE_PRIORITY_HIGH. Use the existing
connection/listener setup patterns in test_socket_prioritization.cpp to keep the
test focused and locate the new path in src/server.c.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ae.c`:
- Around line 532-535: Use aeProcessEvent()’s actual return value instead of
collapsing it to 1 in aeProcessEvents() and the priority-events loop. Update the
processed accumulator and both telemetry counters to add the number of callbacks
executed by aeProcessEvent(), so the return semantics and
*_events_processed_total stats reflect distinct read/write callback executions.
Locate the fix in aeProcessEvents() and the priority event handling around
aeProcessEvent().
- Around line 524-571: The event-loop dispatch in aeProcessEvents re-reads
eventLoop->events[fd].priority during both passes, which lets connSetPriority()
changes mid-cycle alter whether a fired entry runs again or gets skipped.
Snapshot each fired item’s priority once before callbacks start, and use that
captured value to decide whether the entry belongs to the high-priority pass or
the normal-priority pass for the rest of the cycle. Keep the fix localized
around the fired[j] handling in aeProcessEvents and preserve the existing
AE_NONE safety check.

In `@src/io_threads.c`:
- Line 473: The deadlock guard in drainIOThreadsQueue() still uses a combined
capacity check, which is unsafe now that responses are split between
io_shared_outbox[CONN_PRIORITY_HIGH] and io_shared_outbox[CONN_PRIORITY_NORMAL].
Update the logic to track pending completions separately by priority and compare
each shard against its own outbox queue_size so the high-priority path cannot
fill and block workers while the normal queue remains empty. Use the existing
priority-specific outbox symbols in io_threads.c to locate and adjust the check.

In `@src/replication.c`:
- Around line 1085-1088: Delay the CONN_PRIORITY_HIGH upgrade in replication.c
until after the SYNC/PSYNC request has passed all remaining validation and
rejection paths. The current connSetPriority call on c->conn runs too early and
can leave rejected requests permanently elevated, so move that logic below the
acceptance checks or explicitly restore normal priority on every early-return
rejection branch in the request handling flow.

In `@src/socket.c`:
- Around line 122-123: The connect path in src/socket.c currently ignores the
return value from aeCreateFileEvent(), so a failed writable-event registration
can still look like success and leave the connection stuck in
CONN_STATE_CONNECTING. Update the connect handling around aeCreateFileEvent() to
check for AE_ERR, and on failure unwind the fd/state the same way
connSocketSetWriteHandler() and connSocketSetReadHandler() do, so the socket is
not left half-initialized.

---

Nitpick comments:
In `@src/commands/client-kill.json`:
- Around line 229-235: Add a top-level history entry for the new QOS filter in
client-kill.json so it is documented alongside the other versioned additions,
and change the QOS argument definition from a generic string to a oneof. Model
the allowed values as pure-token sub-arguments for the two fixed options
(normal-priority and high-priority), matching the existing client-type/TYPE and
skipme/SKIPME patterns so COMMAND DOCS and COMMAND INFO expose the real choices.
Keep this as metadata only; no changes are needed in parseClientFiltersOrReply.

In `@src/commands/client-list.json`:
- Around line 211-217: The QOS argument metadata in client-list.json is missing
the same documentation and schema updates as client-kill.json: add a history
entry for the 10.0.0 introduction and change the definition from a generic
string to a oneof with the two fixed pure-token values. Update the QOS entry in
the client-list argument schema to match the pattern used by fixed-value filters
like client-type/TYPE and skipme/SKIPME, using the relevant metadata fields on
the QOS item.

In `@src/networking.c`:
- Around line 4912-4919: The client-filter parsing in networking.c should add a
negative counterpart for qos to match the existing not-* pattern used by type,
addr, laddr, user, name, flags, lib-name, lib-ver, db, capa, and ip. Update the
filter handling around the qos branch in the client filter parser (the same
logic used in the nearby helper that processes other filter tokens) so that
not-qos is recognized and mapped to the corresponding negated filter field,
mirroring how the other positive/negative pairs are implemented.

In `@src/tls.c`:
- Line 1201: The AE priority mapping is duplicated in both registerSSLEvent and
updateSSLEvent, so extract the conn->c.priority to
AE_PRIORITY_HIGH/AE_PRIORITY_NORMAL conversion into a small shared helper such
as connPriorityToAePriority and use it from both call sites. Keep the helper
near the existing SSL event logic in src/tls.c so the priority-derivation stays
consistent and any future mapping change only needs one update.

In `@src/unit/test_socket_prioritization.cpp`:
- Around line 120-150: Add a unit test to cover listener accept-handler priority
propagation through createSocketAcceptHandler() and connListener::priority. The
current SocketPrioritizationTest only verifies connSetPriority() and direct
file-event priority updates, so extend the suite with a case that creates a
listener, sets CONN_PRIORITY_HIGH before registering the accept handler, and
asserts the resulting aeFileEvent in server.el has AE_PRIORITY_HIGH. Use the
existing connection/listener setup patterns in test_socket_prioritization.cpp to
keep the test focused and locate the new path in src/server.c.

In `@tests/unit/socket-prioritization.tcl`:
- Around line 20-83: The socket prioritization test in the test body that uses
DEBUG SLEEP, after 50, and after 1100 is timing-sensitive and can be flaky on
slow runners; replace the fixed-delay synchronization with a more deterministic
readiness check or polling loop before sending the 250 SET commands and before
reading results. Keep the existing scope markers (needs:debug external:skip) and
adjust the test around the existing client setup, control_cl flow, and telemetry
assertions so it waits for the intended server state instead of assuming exact
sleep timing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d4cc48f1-b170-409d-bb38-ff12aa49313d

📥 Commits

Reviewing files that changed from the base of the PR and between 58cba3b and 86ff5b2.

📒 Files selected for processing (31)
  • src/ae.c
  • src/ae.h
  • src/cluster.c
  • src/cluster_legacy.c
  • src/cluster_migrateslots.c
  • src/commands.def
  • src/commands/client-kill.json
  • src/commands/client-list.json
  • src/config.c
  • src/connection.c
  • src/connection.h
  • src/io_threads.c
  • src/module.c
  • src/networking.c
  • src/rdb.c
  • src/rdma.c
  • src/replication.c
  • src/sentinel.c
  • src/server.c
  • src/server.h
  • src/socket.c
  • src/tls.c
  • src/unit/test_socket_prioritization.cpp
  • src/valkey-benchmark.c
  • tests/sentinel/tests/19-socket-prioritization.tcl
  • tests/sentinel/tests/20-socket-prioritization-bench.tcl
  • tests/unit/cluster/socket-prioritization-bench.tcl
  • tests/unit/cluster/socket-prioritization.tcl
  • tests/unit/socket-prioritization-bench.tcl
  • tests/unit/socket-prioritization.tcl
  • valkey.conf

Comment thread src/ae.c Outdated
Comment thread src/ae.c Outdated
Comment thread src/io_threads.c Outdated
Comment thread src/replication.c Outdated
Comment thread src/socket.c Outdated
@codecov

codecov Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.60902% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.19%. Comparing base (95e0e9a) to head (089e2c0).
⚠️ Report is 11 commits behind head on unstable.

Files with missing lines Patch % Lines
src/io_threads.c 77.39% 33 Missing ⚠️
src/ae.c 89.00% 11 Missing ⚠️
src/unit/test_socket_prioritization.cpp 99.56% 2 Missing ⚠️
src/cluster_migrateslots.c 75.00% 1 Missing ⚠️
src/connection.c 95.23% 1 Missing ⚠️
src/replication.c 75.00% 1 Missing ⚠️
src/server.c 94.11% 1 Missing ⚠️
src/socket.c 92.30% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           unstable    #4076      +/-   ##
============================================
+ Coverage     80.12%   80.19%   +0.07%     
============================================
  Files           189      190       +1     
  Lines         97253    97907     +654     
============================================
+ Hits          77923    78521     +598     
- Misses        19330    19386      +56     
Files with missing lines Coverage Δ
src/ae_epoll.c 90.90% <100.00%> (+6.53%) ⬆️
src/cluster.c 92.47% <100.00%> (-0.23%) ⬇️
src/cluster_legacy.c 89.05% <100.00%> (+0.14%) ⬆️
src/commands.def 100.00% <ø> (ø)
src/config.c 80.05% <100.00%> (+0.05%) ⬆️
src/connection.h 92.50% <100.00%> (+1.34%) ⬆️
src/networking.c 92.08% <100.00%> (+0.10%) ⬆️
src/server.h 100.00% <ø> (ø)
src/tls.c 17.64% <ø> (ø)
src/unit/test_cluster_io_offload.cpp 100.00% <100.00%> (ø)
... and 8 more

... and 20 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/commands/client-list.json (1)

215-233: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding a NOT-QOS negative filter for consistency.

Every other filter added in 9.0.0 (TYPE, ID, USER, ADDR, LADDR, NAME, IDLE, FLAGS, LIB-NAME, LIB-VER, DB, CAPA, IP) has a matching NOT-* counterpart, but QOS doesn't. If negative filtering is intended to be supported uniformly, this may be an oversight; if not, it's fine as is.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/client-list.json` around lines 215 - 233, The QOS filter entry
in client-list.json is missing a matching negative counterpart, unlike the other
9.0.0 filters. Update the client filter definition around the qos token to add a
NOT-QOS variant if negative filtering is meant to be supported consistently,
using the same pattern as the existing NOT-* filter entries and keeping the new
symbol aligned with the QOS/NOT-QOS naming convention.
tests/unit/cluster/manual-failover.tcl (1)

119-123: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Undocumented magic delay added twice.

Both instances add after 200 after DEBUG SLEEP 2 with no comment explaining why it's needed. Given this PR introduces event-loop priority scheduling for cluster/replication traffic, this is likely compensating for a timing change elsewhere, but that's not evident from the test alone.

💬 Suggested comment addition
 test "Make instance `#0` unreachable without killing it" {
     R 0 deferred 1
     R 0 DEBUG SLEEP 2
+    # Give the server a moment to actually start blocking on DEBUG SLEEP
+    # before considering it unreachable (needed due to QoS/event-loop
+    # scheduling changes).
     after 200
 }

Also applies to: 164-168

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/cluster/manual-failover.tcl` around lines 119 - 123, Add an inline
comment near the repeated after 200 pause in the manual-failover test cases so
the timing dependency is documented; the delay in the "Make instance `#0`
unreachable without killing it" block (and the matching one in the other
affected case) should explain that it intentionally gives the event loop time to
process the scheduling/replication state change introduced by this PR. Update
the relevant test helper/block in manual-failover.tcl so future readers
understand why DEBUG SLEEP 2 is followed by the extra wait.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/commands/client-list.json`:
- Around line 215-233: The QOS filter entry in client-list.json is missing a
matching negative counterpart, unlike the other 9.0.0 filters. Update the client
filter definition around the qos token to add a NOT-QOS variant if negative
filtering is meant to be supported consistently, using the same pattern as the
existing NOT-* filter entries and keeping the new symbol aligned with the
QOS/NOT-QOS naming convention.

In `@tests/unit/cluster/manual-failover.tcl`:
- Around line 119-123: Add an inline comment near the repeated after 200 pause
in the manual-failover test cases so the timing dependency is documented; the
delay in the "Make instance `#0` unreachable without killing it" block (and the
matching one in the other affected case) should explain that it intentionally
gives the event loop time to process the scheduling/replication state change
introduced by this PR. Update the relevant test helper/block in
manual-failover.tcl so future readers understand why DEBUG SLEEP 2 is followed
by the extra wait.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 050e6393-b400-4521-adbe-50678dd0d060

📥 Commits

Reviewing files that changed from the base of the PR and between 86ff5b2 and f892ee7.

📒 Files selected for processing (14)
  • src/ae.c
  • src/commands.def
  • src/commands/client-kill.json
  • src/commands/client-list.json
  • src/connection.c
  • src/connection.h
  • src/io_threads.c
  • src/rdma.c
  • src/replication.c
  • src/socket.c
  • src/tls.c
  • src/unit/test_socket_prioritization.cpp
  • tests/unit/cluster/manual-failover.tcl
  • tests/unit/socket-prioritization.tcl
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/tls.c
  • src/rdma.c
  • src/replication.c
  • src/connection.h
  • tests/unit/socket-prioritization.tcl
  • src/socket.c
  • src/commands.def
  • src/ae.c
  • src/io_threads.c

Comment thread src/ae.c Outdated
Comment thread src/ae.c Outdated
Comment thread src/ae.c Outdated
Comment thread src/io_threads.c
Comment thread src/networking.c Outdated
Comment thread src/commands/client-kill.json Outdated
Comment thread src/server.c Outdated
@satheeshaGowda
satheeshaGowda force-pushed the socket-prioritization branch from f892ee7 to b8b71bf Compare July 5, 2026 04:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/rdma.c (1)

926-948: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unwind server.el registration if server.hp_el registration fails (same pattern as src/socket.c).

In all three call sites, when the initial aeCreateFileEvent on server.el succeeds but the mirrored call on server.hp_el fails (Lines 936-938, 1220-1222, 1246-1248), the function returns C_ERR while leaving a live registration on server.el. The caller believes the operation failed, but rdmaCMeventHandler/conn->type->ae_handler will still fire from the normal loop.

🐛 Proposed fix (illustrated for `connRdmaSetRwHandler`; apply analogously to `connRdmaConnect`/`connRdmaBlockingConnect`)
     if (conn->read_handler || conn->write_handler) {
         if (aeCreateFileEvent(server.el, conn->fd, AE_READABLE, conn->type->ae_handler, conn) == AE_ERR) {
             return C_ERR;
         }
         if (connGetPriority(conn) == CONN_PRIORITY_HIGH && server.hp_el) {
             if (aeCreateFileEvent(server.hp_el, conn->fd, AE_READABLE, conn->type->ae_handler, conn) == AE_ERR) {
+                aeDeleteFileEvent(server.el, conn->fd, AE_READABLE);
                 return C_ERR;
             }
         }
     }

Also applies to: 1197-1228, 1230-1258

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rdma.c` around lines 926 - 948, The mirrored event registration in
connRdmaSetRwHandler, connRdmaConnect, and connRdmaBlockingConnect can leave a
live server.el file event behind if the server.hp_el aeCreateFileEvent call
fails. Update each path to unwind the earlier server.el registration before
returning C_ERR, matching the cleanup pattern used in src/socket.c, so
conn->type->ae_handler is not left armed after a failed high-priority
registration.
src/replication.c (1)

1172-1175: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move the priority upgrade before the replica read handler is registered
connSetPriority(c->conn, CONN_PRIORITY_HIGH) only updates metadata; connSocketSetReadHandler() mirrors the readable event onto server.hp_el only when the handler is installed while the connection is already high-priority. In this SYNC/PSYNC path, the replica read handler is not reinstalled afterward, so the connection stays on the normal loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/replication.c` around lines 1172 - 1175, Move the replica connection
priority upgrade earlier in the SYNC/PSYNC flow so it happens before the replica
read handler is installed. In the replication path around the existing
connSetPriority(c->conn, CONN_PRIORITY_HIGH) logic, ensure the connection is
marked high-priority before connSocketSetReadHandler() is called, or re-register
the read handler after the priority change so it mirrors onto server.hp_el.
src/cluster_migrateslots.c (1)

828-831: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Set the priority before the read handler is registered

createClient() already installs readQueryFromClient, so changing c->conn to high priority here won’t move the existing readable event to server.hp_el. Re-register the handler after the priority change, or move this upgrade earlier.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cluster_migrateslots.c` around lines 828 - 831, The connection priority
upgrade in createClient()/migrateslots handling is happening too late, after
readQueryFromClient is already registered on the normal event loop. Move the
connSetPriority(c->conn, CONN_PRIORITY_HIGH) step earlier in the flow, or
explicitly re-register the read handler after the priority change so the
existing readable event is moved to server.hp_el. Use the createClient and
readQueryFromClient path to locate the right place to apply the priority change.
🧹 Nitpick comments (3)
src/unit/test_socket_prioritization.cpp (1)

298-345: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test name/comment overstate what's verified.

The comment says this verifies PROCESS_HP_EVENTS_IF_NEEDED checks hp_el on iterations where (itr_cnt & 3) == 0, but the assertions only check g_execution_order[0] == 999 — i.e., preemption at iteration 0. This doesn't actually validate the masked/periodic check behavior across later iterations (e.g., iteration 4, 8, etc.), so the test name is broader than its actual coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/unit/test_socket_prioritization.cpp` around lines 298 - 345, The current
test in SocketPrioritizationTest is broader in name/comment than what its
assertions prove: it only confirms high-priority preemption at the first
iteration via g_execution_order[0] == 999. Update the test so
MultiIterationPreemptionCheckMask actually exercises and asserts the periodic
PROCESS_HP_EVENTS_IF_NEEDED behavior in aeProcessEvents, by arranging enough
normal events/iterations and checking that hp_el is serviced on later masked
iterations as well (not just iteration 0). If you keep the test limited to
first-iteration preemption, rename the test and comment to match that narrower
behavior.
tests/unit/cluster/socket-prioritization.tcl (2)

11-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Simplify: use direct command dispatch instead of hand-built RESP.

Manually crafting the RESP array for CLIENT SETNAME is unnecessary and brittle — a single encoding mistake (length prefix, \r\n) silently breaks the test. The rest of this PR's tests (e.g. tests/unit/socket-prioritization.tcl line 8: $c1 client setname qostestclient) invoke commands directly on the client object.

♻️ Proposed simplification
-        set normal_cl [valkey 127.0.0.1 [srv 0 port] 0 $::tls]
-        $normal_cl write "*3\r\n\$6\r\nCLIENT\r\n\$7\r\nSETNAME\r\n\$11\r\nclusternorm\r\n"
-        $normal_cl flush
-        assert_equal "OK" [$normal_cl read]
+        set normal_cl [valkey 127.0.0.1 [srv 0 port] 0 $::tls]
+        assert_equal "OK" [$normal_cl client setname clusternorm]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/cluster/socket-prioritization.tcl` around lines 11 - 18, The test
in socket-prioritization.tcl is manually building RESP for CLIENT SETNAME
instead of using the client API. Replace the hand-crafted write/flush/read
sequence on normal_cl with a direct client command call, matching the style used
elsewhere in the test suite (for example the client setname usage in
socket-prioritization.tcl). Keep the existing assertions unchanged.

32-46: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Pipeline load test has no correctness assertion.

Every write/read is wrapped in catch { ... } and total_ops is never checked. The test will pass even if all writes fail or if prioritization causes data loss/incorrect cluster state — it only exercises the code path without verifying functional correctness. Compare to the analogous benchmark in tests/unit/socket-prioritization.tcl (lines 112-118), which at least waits for dbsize to reach an expected count.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/cluster/socket-prioritization.tcl` around lines 32 - 46, The
pipeline load test in socket-prioritization.tcl only exercises writes through
the load clients and swallows all failures via catch, so it never verifies
correctness. Update the test around the load_clients loop and total_ops tracking
to assert an expected outcome after the writes complete, using the existing
socket-prioritization test helpers and dbsize-style verification pattern from
the analogous test so failures in write/read or lost keys are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/io_threads.c`:
- Around line 242-268: The teardown path in flushPendingIOResponses() can block
indefinitely when cleanupThreadResources() calls it with blocking enabled while
the main thread is already waiting in pthread_join(). Update the shutdown
behavior so IO-thread cleanup does not spin on mpscEnqueue for io_shared_outbox;
either make the cleanup flush non-blocking or ensure the main thread drains the
queue before cancel/join. Use the existing flushPendingIOResponses() and
cleanupThreadResources() flow to locate and adjust the shutdown path.

In `@src/socket.c`:
- Around line 274-286: `connSocketSetReadHandler` has the same unwind-on-failure
bug as `connSocketSetWriteHandler`: if `aeCreateFileEvent(server.hp_el, ...)`
fails after `server.el` registration succeeds, the first `AE_READABLE` event is
left installed. Update the failure path in this function to clean up the
already-created `server.el` event before returning `C_ERR`, mirroring the
rollback used for the high-priority registration and preserving consistent state
for `connGetPriority(conn) == CONN_PRIORITY_HIGH`.
- Around line 251-263: The writable-event setup in the connection registration
path leaves `server.el` armed when `aeCreateFileEvent(server.hp_el, ...)` fails
for a high-priority connection, so unwind the partial registration before
returning `C_ERR`. In the branch that handles `conn->write_handler` in
`src/socket.c`, add cleanup to remove the `AE_WRITABLE` event from `server.el`
and clear any write-handler state if the `server.hp_el` registration fails, so
`conn->type->ae_handler` is not left active on the main event loop after an
error.

In `@tests/unit/socket-prioritization.tcl`:
- Around line 91-118: The dbsize check in the socket prioritization test is too
strict because the pipelined SET traffic from the load clients introduces a new
unique key via the pipeline used in the replication/load section. Update the
assertion around wait_for_condition to account for the extra replicated key, or
change the load to reuse an existing key so the replica key count remains
unchanged. Use the existing test flow around the load_clients loop, replicaof
setup, and the dbsize checks on replica1/replica2 to keep the expectation
aligned with the actual replicated dataset.
- Line 69: The tags declaration in start_server is combining two intended tags
into one quoted string, so external:skip is not applied separately. Update the
tags list in the socket-prioritization benchmark to use separate list elements,
following the pattern used by the cluster/socket-prioritization test and the
single-tag form already present in this file. Ensure the start_server wrapper
still references socket-prioritization and external:skip as distinct tags.

---

Outside diff comments:
In `@src/cluster_migrateslots.c`:
- Around line 828-831: The connection priority upgrade in
createClient()/migrateslots handling is happening too late, after
readQueryFromClient is already registered on the normal event loop. Move the
connSetPriority(c->conn, CONN_PRIORITY_HIGH) step earlier in the flow, or
explicitly re-register the read handler after the priority change so the
existing readable event is moved to server.hp_el. Use the createClient and
readQueryFromClient path to locate the right place to apply the priority change.

In `@src/rdma.c`:
- Around line 926-948: The mirrored event registration in connRdmaSetRwHandler,
connRdmaConnect, and connRdmaBlockingConnect can leave a live server.el file
event behind if the server.hp_el aeCreateFileEvent call fails. Update each path
to unwind the earlier server.el registration before returning C_ERR, matching
the cleanup pattern used in src/socket.c, so conn->type->ae_handler is not left
armed after a failed high-priority registration.

In `@src/replication.c`:
- Around line 1172-1175: Move the replica connection priority upgrade earlier in
the SYNC/PSYNC flow so it happens before the replica read handler is installed.
In the replication path around the existing connSetPriority(c->conn,
CONN_PRIORITY_HIGH) logic, ensure the connection is marked high-priority before
connSocketSetReadHandler() is called, or re-register the read handler after the
priority change so it mirrors onto server.hp_el.

---

Nitpick comments:
In `@src/unit/test_socket_prioritization.cpp`:
- Around line 298-345: The current test in SocketPrioritizationTest is broader
in name/comment than what its assertions prove: it only confirms high-priority
preemption at the first iteration via g_execution_order[0] == 999. Update the
test so MultiIterationPreemptionCheckMask actually exercises and asserts the
periodic PROCESS_HP_EVENTS_IF_NEEDED behavior in aeProcessEvents, by arranging
enough normal events/iterations and checking that hp_el is serviced on later
masked iterations as well (not just iteration 0). If you keep the test limited
to first-iteration preemption, rename the test and comment to match that
narrower behavior.

In `@tests/unit/cluster/socket-prioritization.tcl`:
- Around line 11-18: The test in socket-prioritization.tcl is manually building
RESP for CLIENT SETNAME instead of using the client API. Replace the
hand-crafted write/flush/read sequence on normal_cl with a direct client command
call, matching the style used elsewhere in the test suite (for example the
client setname usage in socket-prioritization.tcl). Keep the existing assertions
unchanged.
- Around line 32-46: The pipeline load test in socket-prioritization.tcl only
exercises writes through the load clients and swallows all failures via catch,
so it never verifies correctness. Update the test around the load_clients loop
and total_ops tracking to assert an expected outcome after the writes complete,
using the existing socket-prioritization test helpers and dbsize-style
verification pattern from the analogous test so failures in write/read or lost
keys are caught.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8184d4c2-f38b-4d8a-bfba-cb8a15d2b460

📥 Commits

Reviewing files that changed from the base of the PR and between b8b71bf and 959cb5c.

📒 Files selected for processing (20)
  • src/ae.c
  • src/ae.h
  • src/cluster_legacy.c
  • src/cluster_migrateslots.c
  • src/commands.def
  • src/commands/client-kill.json
  • src/commands/client-list.json
  • src/connection.c
  • src/connection.h
  • src/io_threads.c
  • src/networking.c
  • src/rdma.c
  • src/replication.c
  • src/server.c
  • src/server.h
  • src/socket.c
  • src/tls.c
  • src/unit/test_socket_prioritization.cpp
  • tests/unit/cluster/socket-prioritization.tcl
  • tests/unit/socket-prioritization.tcl
💤 Files with no reviewable changes (2)
  • src/connection.c
  • src/cluster_legacy.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/rdma.c (1)

926-948: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unwind server.el registration if server.hp_el registration fails (same pattern as src/socket.c).

In all three call sites, when the initial aeCreateFileEvent on server.el succeeds but the mirrored call on server.hp_el fails (Lines 936-938, 1220-1222, 1246-1248), the function returns C_ERR while leaving a live registration on server.el. The caller believes the operation failed, but rdmaCMeventHandler/conn->type->ae_handler will still fire from the normal loop.

🐛 Proposed fix (illustrated for `connRdmaSetRwHandler`; apply analogously to `connRdmaConnect`/`connRdmaBlockingConnect`)
     if (conn->read_handler || conn->write_handler) {
         if (aeCreateFileEvent(server.el, conn->fd, AE_READABLE, conn->type->ae_handler, conn) == AE_ERR) {
             return C_ERR;
         }
         if (connGetPriority(conn) == CONN_PRIORITY_HIGH && server.hp_el) {
             if (aeCreateFileEvent(server.hp_el, conn->fd, AE_READABLE, conn->type->ae_handler, conn) == AE_ERR) {
+                aeDeleteFileEvent(server.el, conn->fd, AE_READABLE);
                 return C_ERR;
             }
         }
     }

Also applies to: 1197-1228, 1230-1258

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rdma.c` around lines 926 - 948, The mirrored event registration in
connRdmaSetRwHandler, connRdmaConnect, and connRdmaBlockingConnect can leave a
live server.el file event behind if the server.hp_el aeCreateFileEvent call
fails. Update each path to unwind the earlier server.el registration before
returning C_ERR, matching the cleanup pattern used in src/socket.c, so
conn->type->ae_handler is not left armed after a failed high-priority
registration.
src/replication.c (1)

1172-1175: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move the priority upgrade before the replica read handler is registered
connSetPriority(c->conn, CONN_PRIORITY_HIGH) only updates metadata; connSocketSetReadHandler() mirrors the readable event onto server.hp_el only when the handler is installed while the connection is already high-priority. In this SYNC/PSYNC path, the replica read handler is not reinstalled afterward, so the connection stays on the normal loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/replication.c` around lines 1172 - 1175, Move the replica connection
priority upgrade earlier in the SYNC/PSYNC flow so it happens before the replica
read handler is installed. In the replication path around the existing
connSetPriority(c->conn, CONN_PRIORITY_HIGH) logic, ensure the connection is
marked high-priority before connSocketSetReadHandler() is called, or re-register
the read handler after the priority change so it mirrors onto server.hp_el.
src/cluster_migrateslots.c (1)

828-831: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Set the priority before the read handler is registered

createClient() already installs readQueryFromClient, so changing c->conn to high priority here won’t move the existing readable event to server.hp_el. Re-register the handler after the priority change, or move this upgrade earlier.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cluster_migrateslots.c` around lines 828 - 831, The connection priority
upgrade in createClient()/migrateslots handling is happening too late, after
readQueryFromClient is already registered on the normal event loop. Move the
connSetPriority(c->conn, CONN_PRIORITY_HIGH) step earlier in the flow, or
explicitly re-register the read handler after the priority change so the
existing readable event is moved to server.hp_el. Use the createClient and
readQueryFromClient path to locate the right place to apply the priority change.
🧹 Nitpick comments (3)
src/unit/test_socket_prioritization.cpp (1)

298-345: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test name/comment overstate what's verified.

The comment says this verifies PROCESS_HP_EVENTS_IF_NEEDED checks hp_el on iterations where (itr_cnt & 3) == 0, but the assertions only check g_execution_order[0] == 999 — i.e., preemption at iteration 0. This doesn't actually validate the masked/periodic check behavior across later iterations (e.g., iteration 4, 8, etc.), so the test name is broader than its actual coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/unit/test_socket_prioritization.cpp` around lines 298 - 345, The current
test in SocketPrioritizationTest is broader in name/comment than what its
assertions prove: it only confirms high-priority preemption at the first
iteration via g_execution_order[0] == 999. Update the test so
MultiIterationPreemptionCheckMask actually exercises and asserts the periodic
PROCESS_HP_EVENTS_IF_NEEDED behavior in aeProcessEvents, by arranging enough
normal events/iterations and checking that hp_el is serviced on later masked
iterations as well (not just iteration 0). If you keep the test limited to
first-iteration preemption, rename the test and comment to match that narrower
behavior.
tests/unit/cluster/socket-prioritization.tcl (2)

11-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Simplify: use direct command dispatch instead of hand-built RESP.

Manually crafting the RESP array for CLIENT SETNAME is unnecessary and brittle — a single encoding mistake (length prefix, \r\n) silently breaks the test. The rest of this PR's tests (e.g. tests/unit/socket-prioritization.tcl line 8: $c1 client setname qostestclient) invoke commands directly on the client object.

♻️ Proposed simplification
-        set normal_cl [valkey 127.0.0.1 [srv 0 port] 0 $::tls]
-        $normal_cl write "*3\r\n\$6\r\nCLIENT\r\n\$7\r\nSETNAME\r\n\$11\r\nclusternorm\r\n"
-        $normal_cl flush
-        assert_equal "OK" [$normal_cl read]
+        set normal_cl [valkey 127.0.0.1 [srv 0 port] 0 $::tls]
+        assert_equal "OK" [$normal_cl client setname clusternorm]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/cluster/socket-prioritization.tcl` around lines 11 - 18, The test
in socket-prioritization.tcl is manually building RESP for CLIENT SETNAME
instead of using the client API. Replace the hand-crafted write/flush/read
sequence on normal_cl with a direct client command call, matching the style used
elsewhere in the test suite (for example the client setname usage in
socket-prioritization.tcl). Keep the existing assertions unchanged.

32-46: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Pipeline load test has no correctness assertion.

Every write/read is wrapped in catch { ... } and total_ops is never checked. The test will pass even if all writes fail or if prioritization causes data loss/incorrect cluster state — it only exercises the code path without verifying functional correctness. Compare to the analogous benchmark in tests/unit/socket-prioritization.tcl (lines 112-118), which at least waits for dbsize to reach an expected count.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/cluster/socket-prioritization.tcl` around lines 32 - 46, The
pipeline load test in socket-prioritization.tcl only exercises writes through
the load clients and swallows all failures via catch, so it never verifies
correctness. Update the test around the load_clients loop and total_ops tracking
to assert an expected outcome after the writes complete, using the existing
socket-prioritization test helpers and dbsize-style verification pattern from
the analogous test so failures in write/read or lost keys are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/io_threads.c`:
- Around line 242-268: The teardown path in flushPendingIOResponses() can block
indefinitely when cleanupThreadResources() calls it with blocking enabled while
the main thread is already waiting in pthread_join(). Update the shutdown
behavior so IO-thread cleanup does not spin on mpscEnqueue for io_shared_outbox;
either make the cleanup flush non-blocking or ensure the main thread drains the
queue before cancel/join. Use the existing flushPendingIOResponses() and
cleanupThreadResources() flow to locate and adjust the shutdown path.

In `@src/socket.c`:
- Around line 274-286: `connSocketSetReadHandler` has the same unwind-on-failure
bug as `connSocketSetWriteHandler`: if `aeCreateFileEvent(server.hp_el, ...)`
fails after `server.el` registration succeeds, the first `AE_READABLE` event is
left installed. Update the failure path in this function to clean up the
already-created `server.el` event before returning `C_ERR`, mirroring the
rollback used for the high-priority registration and preserving consistent state
for `connGetPriority(conn) == CONN_PRIORITY_HIGH`.
- Around line 251-263: The writable-event setup in the connection registration
path leaves `server.el` armed when `aeCreateFileEvent(server.hp_el, ...)` fails
for a high-priority connection, so unwind the partial registration before
returning `C_ERR`. In the branch that handles `conn->write_handler` in
`src/socket.c`, add cleanup to remove the `AE_WRITABLE` event from `server.el`
and clear any write-handler state if the `server.hp_el` registration fails, so
`conn->type->ae_handler` is not left active on the main event loop after an
error.

In `@tests/unit/socket-prioritization.tcl`:
- Around line 91-118: The dbsize check in the socket prioritization test is too
strict because the pipelined SET traffic from the load clients introduces a new
unique key via the pipeline used in the replication/load section. Update the
assertion around wait_for_condition to account for the extra replicated key, or
change the load to reuse an existing key so the replica key count remains
unchanged. Use the existing test flow around the load_clients loop, replicaof
setup, and the dbsize checks on replica1/replica2 to keep the expectation
aligned with the actual replicated dataset.
- Line 69: The tags declaration in start_server is combining two intended tags
into one quoted string, so external:skip is not applied separately. Update the
tags list in the socket-prioritization benchmark to use separate list elements,
following the pattern used by the cluster/socket-prioritization test and the
single-tag form already present in this file. Ensure the start_server wrapper
still references socket-prioritization and external:skip as distinct tags.

---

Outside diff comments:
In `@src/cluster_migrateslots.c`:
- Around line 828-831: The connection priority upgrade in
createClient()/migrateslots handling is happening too late, after
readQueryFromClient is already registered on the normal event loop. Move the
connSetPriority(c->conn, CONN_PRIORITY_HIGH) step earlier in the flow, or
explicitly re-register the read handler after the priority change so the
existing readable event is moved to server.hp_el. Use the createClient and
readQueryFromClient path to locate the right place to apply the priority change.

In `@src/rdma.c`:
- Around line 926-948: The mirrored event registration in connRdmaSetRwHandler,
connRdmaConnect, and connRdmaBlockingConnect can leave a live server.el file
event behind if the server.hp_el aeCreateFileEvent call fails. Update each path
to unwind the earlier server.el registration before returning C_ERR, matching
the cleanup pattern used in src/socket.c, so conn->type->ae_handler is not left
armed after a failed high-priority registration.

In `@src/replication.c`:
- Around line 1172-1175: Move the replica connection priority upgrade earlier in
the SYNC/PSYNC flow so it happens before the replica read handler is installed.
In the replication path around the existing connSetPriority(c->conn,
CONN_PRIORITY_HIGH) logic, ensure the connection is marked high-priority before
connSocketSetReadHandler() is called, or re-register the read handler after the
priority change so it mirrors onto server.hp_el.

---

Nitpick comments:
In `@src/unit/test_socket_prioritization.cpp`:
- Around line 298-345: The current test in SocketPrioritizationTest is broader
in name/comment than what its assertions prove: it only confirms high-priority
preemption at the first iteration via g_execution_order[0] == 999. Update the
test so MultiIterationPreemptionCheckMask actually exercises and asserts the
periodic PROCESS_HP_EVENTS_IF_NEEDED behavior in aeProcessEvents, by arranging
enough normal events/iterations and checking that hp_el is serviced on later
masked iterations as well (not just iteration 0). If you keep the test limited
to first-iteration preemption, rename the test and comment to match that
narrower behavior.

In `@tests/unit/cluster/socket-prioritization.tcl`:
- Around line 11-18: The test in socket-prioritization.tcl is manually building
RESP for CLIENT SETNAME instead of using the client API. Replace the
hand-crafted write/flush/read sequence on normal_cl with a direct client command
call, matching the style used elsewhere in the test suite (for example the
client setname usage in socket-prioritization.tcl). Keep the existing assertions
unchanged.
- Around line 32-46: The pipeline load test in socket-prioritization.tcl only
exercises writes through the load clients and swallows all failures via catch,
so it never verifies correctness. Update the test around the load_clients loop
and total_ops tracking to assert an expected outcome after the writes complete,
using the existing socket-prioritization test helpers and dbsize-style
verification pattern from the analogous test so failures in write/read or lost
keys are caught.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8184d4c2-f38b-4d8a-bfba-cb8a15d2b460

📥 Commits

Reviewing files that changed from the base of the PR and between b8b71bf and 959cb5c.

📒 Files selected for processing (20)
  • src/ae.c
  • src/ae.h
  • src/cluster_legacy.c
  • src/cluster_migrateslots.c
  • src/commands.def
  • src/commands/client-kill.json
  • src/commands/client-list.json
  • src/connection.c
  • src/connection.h
  • src/io_threads.c
  • src/networking.c
  • src/rdma.c
  • src/replication.c
  • src/server.c
  • src/server.h
  • src/socket.c
  • src/tls.c
  • src/unit/test_socket_prioritization.cpp
  • tests/unit/cluster/socket-prioritization.tcl
  • tests/unit/socket-prioritization.tcl
💤 Files with no reviewable changes (2)
  • src/connection.c
  • src/cluster_legacy.c
🛑 Comments failed to post (5)
src/io_threads.c (1)

242-268: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find call sites that invoke flushPendingIOResponses with blocking=1
rg -n 'flushPendingIOResponses\s*\(\s*1' src/io_threads.c
rg -n 'flushPendingIOResponses' -C3 src/io_threads.c

Repository: valkey-io/valkey

Length of output: 1362


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/io_threads.c around flush call sites =="
sed -n '250,290p' src/io_threads.c
echo
sed -n '350,390p' src/io_threads.c
echo
sed -n '760,790p' src/io_threads.c

echo
echo "== mpscEnqueue definition =="
rg -n 'mpscEnqueue|typedef.*mpsc' src include . -g '!*.o' -g '!*.a' -g '!*.so' -g '!*.dylib' -g '!*.dll' -C 3

echo
echo "== search for io_shared_outbox / io_thread_ticket behavior =="
rg -n 'io_shared_outbox|io_thread_ticket|pending_io_responses' src include -C 2

Repository: valkey-io/valkey

Length of output: 16080


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the surrounding implementation without executing repository code.
echo "== src/io_threads.c around the blocking flush =="
cat -n src/io_threads.c | sed -n '260,285p'

echo
echo "== src/io_threads.c around the non-blocking flush paths =="
cat -n src/io_threads.c | sed -n '360,375p'
echo
cat -n src/io_threads.c | sed -n '760,775p'

echo
echo "== locate mpscEnqueue implementation =="
rg -n 'mpscEnqueue\s*\(' src include . -C 2

echo
echo "== locate related queue/termination state =="
rg -n 'io_shared_outbox|io_thread_ticket|pending_io_responses|server\.crashed' src include -C 2

Repository: valkey-io/valkey

Length of output: 10874


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the blocking flush path and the related call sites.
echo "== src/io_threads.c around the blocking flush =="
cat -n src/io_threads.c | sed -n '260,285p'

echo
echo "== src/io_threads.c around the non-blocking flush paths =="
cat -n src/io_threads.c | sed -n '360,375p'
echo
cat -n src/io_threads.c | sed -n '760,775p'

echo
echo "== locate mpscEnqueue implementation =="
rg -n 'mpscEnqueue\s*\(' src include . -C 2

echo
echo "== locate related queue/termination state =="
rg -n 'io_shared_outbox|io_thread_ticket|pending_io_responses|server\.crashed' src include -C 2

Repository: valkey-io/valkey

Length of output: 10874


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== cleanup and thread shutdown wiring in src/io_threads.c =="
rg -n 'cleanupThreadResources|pthread_cleanup_push|pthread_cleanup_pop|pthread_join|server\.io_threads_num|stop' src/io_threads.c -C 3

echo
echo "== mpscEnqueue implementation details =="
cat -n src/queues.c | sed -n '1,120p'

Repository: valkey-io/valkey

Length of output: 9987


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== cleanup and thread shutdown wiring in src/io_threads.c =="
rg -n 'cleanupThreadResources|pthread_cleanup_push|pthread_cleanup_pop|pthread_join|server\.io_threads_num|stop' src/io_threads.c -C 3

echo
echo "== mpscEnqueue implementation details =="
cat -n src/queues.c | sed -n '1,140p'

Repository: valkey-io/valkey

Length of output: 10565


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== shutdownIOThread and resize path =="
cat -n src/io_threads.c | sed -n '395,475p'

echo
echo "== any explicit cancellation points in src/io_threads.c =="
rg -n 'pthread_testcancel|pthread_cancel|pthread_cleanup_push|pthread_cleanup_pop|sleep|usleep|sched_yield|nanosleep' src/io_threads.c -C 2

Repository: valkey-io/valkey

Length of output: 4840


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== killIOThreads call sites =="
rg -n 'killIOThreads\s*\(' src -C 3

echo
echo "== drainIOThreadsQueue definition/callers =="
rg -n 'drainIOThreadsQueue\s*\(' src -C 3

Repository: valkey-io/valkey

Length of output: 3662


Avoid blocking flush during IO-thread teardown flushPendingIOResponses(1) is called from cleanupThreadResources() while the main thread is already in pthread_join(). If io_shared_outbox is full, the IO thread can spin without making progress because nothing is draining that queue during shutdown. Consider a non-blocking cleanup path or draining the queue before cancel/join.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/io_threads.c` around lines 242 - 268, The teardown path in
flushPendingIOResponses() can block indefinitely when cleanupThreadResources()
calls it with blocking enabled while the main thread is already waiting in
pthread_join(). Update the shutdown behavior so IO-thread cleanup does not spin
on mpscEnqueue for io_shared_outbox; either make the cleanup flush non-blocking
or ensure the main thread drains the queue before cancel/join. Use the existing
flushPendingIOResponses() and cleanupThreadResources() flow to locate and adjust
the shutdown path.
src/socket.c (2)

251-263: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unwind the server.el registration when server.hp_el registration fails.

If aeCreateFileEvent on server.hp_el fails at Line 260-261, the function returns C_ERR, but the AE_WRITABLE event already created on server.el (Line 257) is never removed, and conn->write_handler remains set. The caller sees a failure, yet the write handler will still fire from server.el. This is a new failure mode introduced by this PR (previously there was only one loop to register on).

🐛 Proposed fix
     } else {
         if (aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE, conn->type->ae_handler, conn) == AE_ERR)
             return C_ERR;
         if (connGetPriority(conn) == CONN_PRIORITY_HIGH) {
-            if (aeCreateFileEvent(server.hp_el, conn->fd, AE_WRITABLE, conn->type->ae_handler, conn) == AE_ERR)
+            if (aeCreateFileEvent(server.hp_el, conn->fd, AE_WRITABLE, conn->type->ae_handler, conn) == AE_ERR) {
+                aeDeleteFileEvent(server.el, conn->fd, AE_WRITABLE);
                 return C_ERR;
+            }
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (!conn->write_handler) {
        aeDeleteFileEvent(server.el, conn->fd, AE_WRITABLE);
        if (connGetPriority(conn) == CONN_PRIORITY_HIGH) {
            aeDeleteFileEvent(server.hp_el, conn->fd, AE_WRITABLE);
        }
    } else {
        if (aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE, conn->type->ae_handler, conn) == AE_ERR)
            return C_ERR;
        if (connGetPriority(conn) == CONN_PRIORITY_HIGH) {
            if (aeCreateFileEvent(server.hp_el, conn->fd, AE_WRITABLE, conn->type->ae_handler, conn) == AE_ERR) {
                aeDeleteFileEvent(server.el, conn->fd, AE_WRITABLE);
                return C_ERR;
            }
        }
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/socket.c` around lines 251 - 263, The writable-event setup in the
connection registration path leaves `server.el` armed when
`aeCreateFileEvent(server.hp_el, ...)` fails for a high-priority connection, so
unwind the partial registration before returning `C_ERR`. In the branch that
handles `conn->write_handler` in `src/socket.c`, add cleanup to remove the
`AE_WRITABLE` event from `server.el` and clear any write-handler state if the
`server.hp_el` registration fails, so `conn->type->ae_handler` is not left
active on the main event loop after an error.

274-286: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Same unwind-on-failure gap as connSocketSetWriteHandler.

If registration on server.hp_el fails at Line 283-284, the already-created AE_READABLE event on server.el (Line 280-281) is left dangling while the function reports C_ERR.

🐛 Proposed fix
     } else {
         if (aeCreateFileEvent(server.el, conn->fd, AE_READABLE, conn->type->ae_handler, conn) == AE_ERR)
             return C_ERR;
         if (connGetPriority(conn) == CONN_PRIORITY_HIGH) {
-            if (aeCreateFileEvent(server.hp_el, conn->fd, AE_READABLE, conn->type->ae_handler, conn) == AE_ERR)
+            if (aeCreateFileEvent(server.hp_el, conn->fd, AE_READABLE, conn->type->ae_handler, conn) == AE_ERR) {
+                aeDeleteFileEvent(server.el, conn->fd, AE_READABLE);
                 return C_ERR;
+            }
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (!conn->read_handler) {
        aeDeleteFileEvent(server.el, conn->fd, AE_READABLE);
        if (connGetPriority(conn) == CONN_PRIORITY_HIGH) {
            aeDeleteFileEvent(server.hp_el, conn->fd, AE_READABLE);
        }
    } else {
        if (aeCreateFileEvent(server.el, conn->fd, AE_READABLE, conn->type->ae_handler, conn) == AE_ERR)
            return C_ERR;
        if (connGetPriority(conn) == CONN_PRIORITY_HIGH) {
            if (aeCreateFileEvent(server.hp_el, conn->fd, AE_READABLE, conn->type->ae_handler, conn) == AE_ERR) {
                aeDeleteFileEvent(server.el, conn->fd, AE_READABLE);
                return C_ERR;
            }
        }
    }
🧰 Tools
🪛 Cppcheck (2.21.0)

[error] 284-284: #error "Undefined or invalid BYTE_ORDER"

(preprocessorErrorDirective)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/socket.c` around lines 274 - 286, `connSocketSetReadHandler` has the same
unwind-on-failure bug as `connSocketSetWriteHandler`: if
`aeCreateFileEvent(server.hp_el, ...)` fails after `server.el` registration
succeeds, the first `AE_READABLE` event is left installed. Update the failure
path in this function to clean up the already-created `server.el` event before
returning `C_ERR`, mirroring the rollback used for the high-priority
registration and preserving consistent state for `connGetPriority(conn) ==
CONN_PRIORITY_HIGH`.
tests/unit/socket-prioritization.tcl (2)

69-69: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Tags string merges two tags into one, losing external:skip.

tags {"socket-prioritization external:skip"} is a single quoted string, so it parses as one list element socket-prioritization external:skip, not two separate tags. This differs from the correct multi-tag pattern used at tests/unit/cluster/socket-prioritization.tcl line 2 (tags {socket-prioritization external:skip cluster}, unquoted words) and from the single-tag pattern at line 5 of this same file (tags {"socket-prioritization"}). As written, external:skip is not recognized as its own tag, so this heavy nested-server benchmark isn't actually excluded when running against external servers.

🐛 Proposed fix
-start_server {tags {"socket-prioritization external:skip"}} {
+start_server {tags {"socket-prioritization" "external:skip"}} {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

start_server {tags {"socket-prioritization" "external:skip"}} {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/socket-prioritization.tcl` at line 69, The tags declaration in
start_server is combining two intended tags into one quoted string, so
external:skip is not applied separately. Update the tags list in the
socket-prioritization benchmark to use separate list elements, following the
pattern used by the cluster/socket-prioritization test and the single-tag form
already present in this file. Ensure the start_server wrapper still references
socket-prioritization and external:skip as distinct tags.

91-118: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

dbsize assertion will never be satisfied — load writes an extra key beyond the populated 1000.

The primary is pre-populated with exactly 1000 keys (benchkey:0..benchkey:999), but the load clients then repeatedly SET pipekey ... on the same primary connections (lines 91-110), adding a 1001st unique key that also replicates to both replicas. The wait_for_condition at lines 112-118 asserts [$replica1 dbsize] == 1000 && [$replica2 dbsize] == 1000, which will never hold once replication propagates pipekey, causing the test to time out and always fail.

🐛 Proposed fix
                 wait_for_condition 100 50 {
                     [status $primary connected_slaves] == 2 &&
-                    [$replica1 dbsize] == 1000 &&
-                    [$replica2 dbsize] == 1000
+                    [$replica1 dbsize] == 1001 &&
+                    [$replica2 dbsize] == 1001
                 } else {
                     fail "Replicas failed to complete sync during pipelined load"
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                set val [string repeat "x" 256]
                set pipeline ""
                for {set p 0} {$p < 20} {incr p} {
                    append pipeline "*3\r\n\$3\r\nSET\r\n\$7\r\npipekey\r\n\$256\r\n$val\r\n"
                }

                $replica1 replicaof $primary_host $primary_port
                $replica2 replicaof $primary_host $primary_port

                set total_ops 0
                for {set iter 0} {$iter < 10} {incr iter} {
                    foreach cl $load_clients {
                        catch {
                            $cl write $pipeline
                            $cl flush
                            incr total_ops 20
                            for {set r 0} {$r < 20} {incr r} { $cl read }
                        }
                    }
                }

                wait_for_condition 100 50 {
                    [status $primary connected_slaves] == 2 &&
                    [$replica1 dbsize] == 1001 &&
                    [$replica2 dbsize] == 1001
                } else {
                    fail "Replicas failed to complete sync during pipelined load"
                }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/socket-prioritization.tcl` around lines 91 - 118, The dbsize check
in the socket prioritization test is too strict because the pipelined SET
traffic from the load clients introduces a new unique key via the pipeline used
in the replication/load section. Update the assertion around wait_for_condition
to account for the extra replicated key, or change the load to reuse an existing
key so the replica key count remains unchanged. Use the existing test flow
around the load_clients loop, replicaof setup, and the dbsize checks on
replica1/replica2 to keep the expectation aligned with the actual replicated
dataset.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/unit/test_socket_prioritization.cpp (2)

319-390: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Timing-based assertion has a thin margin and may flake under CI load.

The test sleeps usleep(1500) to exceed a presumed ~1000us poll-check threshold, only a 500us margin. Under a loaded CI runner, scheduler jitter could cause the sleep to not effectively separate poll checks, making g_execution_order assertions (Lines 366-375) flaky.

Consider widening the margin (e.g., a few ms) to reduce flakiness risk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/unit/test_socket_prioritization.cpp` around lines 319 - 390, The timing
assertions in MultiIterationPreemptionCheckMask are too close to the hp_el
polling threshold and can flake under CI jitter. Update the test setup around
aeProcessEvents, server.hp_el->last_poll_us, and the g_execution_order checks to
use a wider timing margin or a more deterministic trigger so the high-priority
event is clearly separated from the masked iterations. Keep the intent of
verifying preemption, but avoid relying on a borderline usleep window.

52-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shared static test globals reset only at test-end risk leaking state on assertion failure.

g_hp_pipe2_write_fd, g_normal_cb_count, g_execution_count/g_execution_order are file-scope statics reused across tests, but reset happens near the end of each test body (e.g., Line 377). If an earlier ASSERT_* in the same test fails and returns early, the reset is skipped and stale values (e.g., a dangling g_hp_pipe2_write_fd pointing to an fd closed in this test) leak into the next test that shares these globals, risking cascading/flaky failures unrelated to the actual bug under test.

Move the reset of all shared statics into SocketPrioritizationTest::SetUp() (already reset there per the summary for some counters) so each test starts from a guaranteed-clean state regardless of how the previous test exited.

Also applies to: 359-377

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/unit/test_socket_prioritization.cpp` around lines 52 - 65, Shared
file-scope test state in the socket prioritization tests is only being reset at
the end of individual test bodies, so an early ASSERT failure can leak stale
values into later tests. Move the reset for all shared statics, including
g_hp_pipe2_write_fd, g_normal_cb_count, g_execution_count, and
g_execution_order, into SocketPrioritizationTest::SetUp() so every test starts
clean regardless of how the previous one exits. Keep the existing callback logic
in testNormalEventCallbackMaskCheck unchanged apart from relying on the reset
state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/connection.c`:
- Around line 172-195: connSetPriority() is registering high-priority file
descriptors on server.hp_el without removing or synchronizing the existing
server.el registration, which can let aeProcessEvents() dispatch the same fd
twice in one loop. Update connSetPriority() so switching to CONN_PRIORITY_HIGH
mirrors the fd registration state between server.el and server.hp_el, or
otherwise prevents an already-fired fd from being processed twice; use conn->fd,
server.el, server.hp_el, and the read/write handler checks to locate the change.

---

Nitpick comments:
In `@src/unit/test_socket_prioritization.cpp`:
- Around line 319-390: The timing assertions in
MultiIterationPreemptionCheckMask are too close to the hp_el polling threshold
and can flake under CI jitter. Update the test setup around aeProcessEvents,
server.hp_el->last_poll_us, and the g_execution_order checks to use a wider
timing margin or a more deterministic trigger so the high-priority event is
clearly separated from the masked iterations. Keep the intent of verifying
preemption, but avoid relying on a borderline usleep window.
- Around line 52-65: Shared file-scope test state in the socket prioritization
tests is only being reset at the end of individual test bodies, so an early
ASSERT failure can leak stale values into later tests. Move the reset for all
shared statics, including g_hp_pipe2_write_fd, g_normal_cb_count,
g_execution_count, and g_execution_order, into SocketPrioritizationTest::SetUp()
so every test starts clean regardless of how the previous one exits. Keep the
existing callback logic in testNormalEventCallbackMaskCheck unchanged apart from
relying on the reset state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4235a1d1-0c20-4558-a982-f694f1260a93

📥 Commits

Reviewing files that changed from the base of the PR and between 959cb5c and 018f2be.

📒 Files selected for processing (6)
  • src/connection.c
  • src/connection.h
  • src/server.c
  • src/server.h
  • src/unit/test_socket_prioritization.cpp
  • tests/unit/cluster/socket-prioritization.tcl
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/cluster/socket-prioritization.tcl
  • src/server.h
  • src/connection.h

Comment thread src/connection.c Outdated
@satheeshaGowda

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/unit/test_socket_prioritization.cpp (1)

52-65: 🩺 Stability & Availability | 🔵 Trivial | ⚖️ Poor tradeoff

Timing-dependent assertions may flake under CI load.

MultiIterationPreemptionCheckMask relies on usleep(1500) plus the exact (itr_cnt & 3) == 0 cadence to force specific ordering in g_execution_order. Under scheduler jitter/CI contention this could produce nondeterministic ordering and intermittent failures.

Consider decoupling the assertions from wall-clock timing (e.g., inject a mockable clock/hook for last_poll_us) if flakiness surfaces in CI.

Also applies to: 319-390

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/unit/test_socket_prioritization.cpp` around lines 52 - 65, The test logic
in testNormalEventCallbackMaskCheck and the related
MultiIterationPreemptionCheckMask flow depends on usleep-driven timing and the
exact polling cadence, which can make g_execution_order assertions flaky under
CI load. Refactor the test to avoid wall-clock scheduling assumptions by using a
controllable or mockable clock/hook for last_poll_us, or another deterministic
trigger, and update the assertions so they verify ordering through explicit
events rather than timing luck.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/unit/test_socket_prioritization.cpp`:
- Around line 52-65: The test logic in testNormalEventCallbackMaskCheck and the
related MultiIterationPreemptionCheckMask flow depends on usleep-driven timing
and the exact polling cadence, which can make g_execution_order assertions flaky
under CI load. Refactor the test to avoid wall-clock scheduling assumptions by
using a controllable or mockable clock/hook for last_poll_us, or another
deterministic trigger, and update the assertions so they verify ordering through
explicit events rather than timing luck.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: aabcf896-5bac-4158-bdc1-505fcc4fa25d

📥 Commits

Reviewing files that changed from the base of the PR and between 959cb5c and 018f2be.

📒 Files selected for processing (6)
  • src/connection.c
  • src/connection.h
  • src/server.c
  • src/server.h
  • src/unit/test_socket_prioritization.cpp
  • tests/unit/cluster/socket-prioritization.tcl
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/server.c
  • tests/unit/cluster/socket-prioritization.tcl
  • src/server.h
  • src/connection.h

@satheeshaGowda
satheeshaGowda force-pushed the socket-prioritization branch from 018f2be to 58164d1 Compare July 7, 2026 17:49
Comment thread src/commands/client-kill.json Outdated
Comment thread tests/unit/cluster/manual-failover.tcl Outdated
Comment thread tests/unit/cluster/socket-prioritization.tcl Outdated
Comment thread src/ae.c Outdated
Comment thread src/io_threads.c
Comment thread src/cluster.c Outdated
@satheeshaGowda

Copy link
Copy Markdown
Member Author

@coderabbitai summary

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Summary regeneration triggered.

Comment thread src/server.c Outdated
Comment thread src/ae.h Outdated
Comment thread src/io_threads.c Outdated
Comment thread src/io_threads.c
Comment thread src/io_threads.c
Comment thread src/tls.c Outdated
Comment thread src/io_threads.c
@satheeshaGowda
satheeshaGowda force-pushed the socket-prioritization branch 2 times, most recently from 1e11c9a to 91e6dbf Compare July 13, 2026 05:26
@madolson madolson moved this from Todo to Needs Review in Valkey 9.2 Jul 20, 2026
@satheeshaGowda
satheeshaGowda force-pushed the socket-prioritization branch from 22a783b to 0927c6f Compare July 23, 2026 22:21
@satheeshaGowda

Copy link
Copy Markdown
Member Author

Three small suggestions on naming, otherwise looks good. Let's get this cleaned up and merged.

PR has been updated with following changes - 707e106

  • Instantaneous metrics for qos have been removed

    • instantaneous_qos_eventloop_cycles_per_sec
    • instantaneous_qos_eventloop_duration_usec
  • New metrics have been renamed as following

    • eventloop_priority_cycles
    • eventloop_priority_duration_sum
    • eventloop_priority_duration_cmd_sum
    • eventloop_priority_duration_max
    • eventloop_priority_cmd_per_cycle_max
  • cluster links new fields "qos":"prioritized have been removed

satheeshaGowda and others added 14 commits September 10, 2026 22:36
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
… feature

Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
…os_'

Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
@soloestoy

Copy link
Copy Markdown
Member

This PR is very interesting. We previously implemented a similar approach internally, but did not continue with it because servicing high-priority system events consumed additional CPU and caused a noticeable increase in normal read/write latency and a significant QPS regression. This PR looks much more carefully designed, and the benchmark results are encouraging, so I think it is worth moving forward.

I have a couple of questions about the benchmarks:

Are the results in the top comment based on the latest implementation, which keeps a single main event loop with a separate priority polling backend? I understand an earlier version used two event loops.

The steady-state results are particularly interesting: normal-command latency decreases while QPS increases. Have you investigated the reason for this improvement? For example, is it caused by smoother processing of replication and cluster traffic, reduced queue buildup, or changes in I/O-thread scheduling?

I also noticed that the server was configured with io-threads 4. It would be useful to run the same benchmarks with I/O threads disabled. Many users run multiple Valkey server processes on the same machine rather than enabling I/O threads for each process. Testing this deployment model would help determine how much of the improvement comes from the event-loop prioritization itself versus the new high/normal-priority I/O-thread queues.

@satheeshaGowda

Copy link
Copy Markdown
Member Author

This PR is very interesting. We previously implemented a similar approach internally, but did not continue with it because servicing high-priority system events consumed additional CPU and caused a noticeable increase in normal read/write latency and a significant QPS regression. This PR looks much more carefully designed, and the benchmark results are encouraging, so I think it is worth moving forward.

Thank you for taking a look at this PR. and please find my comments inline.

I have a couple of questions about the benchmarks:

Are the results in the top comment based on the latest implementation, which keeps a single main event loop with a separate priority polling backend? I understand an earlier version used two event loops.

Yes, please find the latest benchmarking on the latest code below.

The steady-state results are particularly interesting: normal-command latency decreases while QPS increases. Have you investigated the reason for this improvement? For example, is it caused by smoother processing of replication and cluster traffic, reduced queue buildup, or changes in I/O-thread scheduling?

Thats correct, it helped reduce the memory fragmentation and jmalloc arena churns, which lead to savings in main cpu cycles, which in-turn help process more qps.

I also noticed that the server was configured with io-threads 4. It would be useful to run the same benchmarks with I/O threads disabled. Many users run multiple Valkey server processes on the same machine rather than enabling I/O threads for each process. Testing this deployment model would help determine how much of the improvement comes from the event-loop prioritization itself versus the new high/normal-priority I/O-thread queues.

Good point, I have attached benchmarking reuslts with and without iothreads offloading enabled for both standalone (a leader with 2 replicas) and cluster enabled (3 leaders with 2 replicas attached) below.

@satheeshaGowda

Copy link
Copy Markdown
Member Author

Valkey QoS Steady State Benchmarking Results

Standalone

  • 3 VMs of type c4-highcpu-16 (16 vCPUs, 32 GB Memory)
  • Each VM has 1 Valkey node
  • 3 nodes standalone cluster
  • 1 shard with 2 replicas each
  • Benchmarking VM of type n2-highcpu-96 (96 vCPUs, 96 GB Memory)

Standalone IOthreads offloading disabled

Valkey node config

$BIN_DIR/valkey-server \
        --port 6379 \
        --requirepass Valkey143 \
        --primaryauth Valkey143 \   
        --dir . \
        --save "" \
        --dbfilename 6379.rdb \
        --repl-diskless-sync yes \
        --repl-diskless-sync-delay 0 \
        --io-threads 1 \
        --client-output-buffer-limit "normal 0 0 0 replica 1536mb 1024mb 60" \
        --repl-backlog-size "1536mb" \
        --protected-mode no 

Benchmark command

nohup taskset -c 0-89 $BIN_DIR/memtier_benchmark -a Valkey143  --server="${LEADER_HOST}" --port="${LEADER_PORT}"  --threads=90 --clients=10 --key-pattern=S:S --key-minimum=1 --key-maximum=5000000 --data-size=100 --test-time=300 --pipeline=3 --ratio=1:3 --hide-histogram  --out-file "${LEADER_HOST}_${LEADER_PORT}.benchmark"  > "${LEADER_HOST}_${LEADER_PORT}.log" 2>&1 &

Baseline:

90        Threads
10        Connections per thread
300       Seconds


ALL STATS
============================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
----------------------------------------------------------------------------------------------------------------------------
Sets       141351.99          ---          ---         4.77396         4.60700         9.27900         9.47100     20121.14
Gets       424051.43    424051.43         0.00         4.77315         4.60700         9.27900         9.47100     58478.48
Waits           0.00          ---          ---             ---             ---             ---             ---          ---
Totals     565403.42    424051.43         0.00         4.77335         4.60700         9.27900         9.47100     78599.62

QOS:

90        Threads
10        Connections per thread
300       Seconds


ALL STATS
============================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
----------------------------------------------------------------------------------------------------------------------------
Sets       141358.32          ---          ---         4.77380         4.60700         9.27900         9.47100     20122.04
Gets       424070.31    424070.31         0.00         4.77280         4.60700         9.27900         9.47100     58481.10
Waits           0.00          ---          ---             ---             ---             ---             ---          ---
Totals     565428.63    424070.31         0.00         4.77305         4.60700         9.27900         9.47100     78603.14  

Standalone IOthreads offloading enabled

Valkey node config

$BIN_DIR/valkey-server \
        --port 6379 \
        --requirepass Valkey143 \
        --primaryauth Valkey143 \   
        --dir . \
        --save "" \
        --dbfilename 6379.rdb \
        --repl-diskless-sync yes \
        --repl-diskless-sync-delay 0 \
        --io-threads 4 \
        --client-output-buffer-limit "normal 0 0 0 replica 1536mb 1024mb 60" \
        --repl-backlog-size "1536mb" \
        --protected-mode no 

Benchmark command

nohup taskset -c 0-89 $BIN_DIR/memtier_benchmark -a Valkey143  --server="${LEADER_HOST}" --port="${LEADER_PORT}"  --threads=90 --clients=10 --key-pattern=S:S --key-minimum=1 --key-maximum=5000000 --data-size=100 --test-time=300 --pipeline=3 --ratio=1:3 --hide-histogram  --out-file "${LEADER_HOST}_${LEADER_PORT}.benchmark"  > "${LEADER_HOST}_${LEADER_PORT}.log" 2>&1 &

Baseline:

90        Threads
10        Connections per thread
300       Seconds


ALL STATS
============================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
----------------------------------------------------------------------------------------------------------------------------
Sets       316714.08          ---          ---         2.12938         2.12700         2.28700         2.52700     45140.26
Gets       950137.66    950137.66         0.00         2.12913         2.12700         2.27100         2.52700    131431.84
Waits           0.00          ---          ---             ---             ---             ---             ---          ---
Totals    1266851.73    950137.66         0.00         2.12919         2.12700         2.28700         2.52700    176572.10 

QOS:

90        Threads
10        Connections per thread
300       Seconds


ALL STATS
============================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
----------------------------------------------------------------------------------------------------------------------------
Sets       324710.84          ---          ---         2.07701         2.07900         2.22300         2.60700     46288.24
Gets       974127.97    974127.97         0.00         2.07667         2.07900         2.22300         2.60700    134758.62
Waits           0.00          ---          ---             ---             ---             ---             ---          ---
Totals    1298838.80    974127.97         0.00         2.07676         2.07900         2.22300         2.60700    181046.86

Clustermode

  • 3 VMs of type c4-highcpu-16 (16 vCPUs, 32 GB Memory)
  • Each VM has 3 Valkey nodes
  • 9 nodes cluster
  • 3 shards with 2 replicas each
  • Benchmarking VM of type n2-highcpu-96 (96 vCPUs, 96 GB Memory)

Clustermode IOthreads offloading disabled

Valkey node config

$BIN_DIR/valkey-server \
        --port 6379 \
        --requirepass Valkey143 \
        --primaryauth Valkey143 \   
        --dir . \
        --save "" \
        --dbfilename 6379.rdb \
        --repl-diskless-sync yes \
        --repl-diskless-sync-delay 0 \
        --io-threads 1 \
        --client-output-buffer-limit "normal 0 0 0 replica 1536mb 1024mb 60" \
        --repl-backlog-size "1536mb" \
        --protected-mode no \
        --cluster-enabled yes \
        --cluster-config-file nodes.conf \
        --cluster-node-timeout 6000

Benchmark command

nohup taskset -c 0-89 $BIN_DIR/memtier_benchmark -a Valkey143  --server="${LEADER_HOST}" --port="${LEADER_PORT}"  --threads=90 --clients=10 --key-pattern=S:S --key-minimum=1 --key-maximum=15000000 --data-size=100 --test-time=300 --pipeline=3 --ratio=1:3 --hide-histogram  --out-file "${LEADER_HOST}_${LEADER_PORT}.benchmark" --cluster-mode > "${LEADER_HOST}_${LEADER_PORT}.log" 2>&1 &

Baseline:

90        Threads
10        Connections per thread
9         Cluster endpoints (3 primaries, 6 replicas)
8100      Total connections (90 x 10 x 9)
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       357012.38          ---          ---         0.00         0.00         4.93006         4.60700        10.23900        10.94300     50925.27
Gets      1071032.71   1071032.71         0.00         0.00         0.00         4.93174         4.60700        10.23900        10.94300    148196.64
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1428045.09   1071032.71         0.00         0.00         0.00         4.93132         4.60700        10.23900        10.94300    199121.91    

QOS:

90        Threads
10        Connections per thread
9         Cluster endpoints (3 primaries, 6 replicas)
8100      Total connections (90 x 10 x 9)
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       358035.20          ---          ---         0.00         0.00         4.87432         4.60700        10.43100        11.13500     51072.10
Gets      1074101.54   1074101.54         0.00         0.00         0.00         4.87370         4.60700        10.43100        11.13500    148622.20
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    1432136.74   1074101.54         0.00         0.00         0.00         4.87385         4.60700        10.43100        11.13500    199694.30  

Clustermode IOthreads offloading enabled

Valkey node config

$BIN_DIR/valkey-server \
        --port 6379 \
        --requirepass Valkey143 \
        --primaryauth Valkey143 \   
        --dir . \
        --save "" \
        --dbfilename 6379.rdb \
        --repl-diskless-sync yes \
        --repl-diskless-sync-delay 0 \
        --io-threads 4 \
        --client-output-buffer-limit "normal 0 0 0 replica 1536mb 1024mb 60" \
        --repl-backlog-size "1536mb" \
        --protected-mode no \
        --cluster-enabled yes \
        --cluster-config-file nodes.conf \
        --cluster-node-timeout 6000

Benchmark command

nohup taskset -c 0-89 $BIN_DIR/memtier_benchmark -a Valkey143  --server="${LEADER_HOST}" --port="${LEADER_PORT}"  --threads=90 --clients=10 --key-pattern=S:S --key-minimum=1 --key-maximum=15000000 --data-size=100 --test-time=300 --pipeline=3 --ratio=1:3 --hide-histogram  --out-file "${LEADER_HOST}_${LEADER_PORT}.benchmark" --cluster-mode > "${LEADER_HOST}_${LEADER_PORT}.log" 2>&1 &

Baseline:

90        Threads
10        Connections per thread
9         Cluster endpoints (3 primaries, 6 replicas)
8100      Total connections (90 x 10 x 9)
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       640617.82          ---          ---         0.00         0.00         2.00672         2.41500         3.66300         6.39900     91638.16
Gets      1921850.63   1921850.63         0.00         0.00         0.00         2.00555         2.41500         3.66300         6.39900    266181.16
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    2562468.45   1921850.63         0.00         0.00         0.00         2.00584         2.41500         3.66300         6.39900    357819.32  

QOS:

90        Threads
10        Connections per thread
9         Cluster endpoints (3 primaries, 6 replicas)
8100      Total connections (90 x 10 x 9)
300       Seconds


ALL STATS
======================================================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    MOVED/sec      ASK/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
------------------------------------------------------------------------------------------------------------------------------------------------------
Sets       675217.29          ---          ---         0.00         0.00         1.95256         2.12700         3.74300         6.39900     96605.08
Gets      2025645.60   2025645.60         0.00         0.00         0.00         1.95427         2.12700         3.74300         6.39900    280574.60
Waits           0.00          ---          ---          ---          ---             ---             ---             ---             ---          ---
Totals    2700862.88   2025645.60         0.00         0.00         0.00         1.95384         2.12700         3.74300         6.39900    377179.68  

@madolson madolson added major-decision-approved Major decision approved by TSC team and removed major-decision-pending-auto-approve labels Sep 14, 2026
@JimB123
JimB123 merged commit 88a5360 into valkey-io:unstable Sep 14, 2026
102 checks passed
@github-project-automation github-project-automation Bot moved this from Needs Review to Merged in Valkey 9.2 Sep 14, 2026
JimB123 added a commit that referenced this pull request Sep 14, 2026
Follow-on to #4076 which defines priority sources for anti-starvation.
---------

Signed-off-by: Satheesha Gowda <satheesha.balaji@gmail.com>
Co-authored-by: Jim Brunner <brunnerj@amazon.com>
sarthakaggarwal97 added a commit that referenced this pull request Sep 16, 2026
Remove three entries whose changes shipped before 9.2.0, so 9.1 users
don't see them advertised as new:
- #3533 (dismissHashtable madvise size) was backported to 9.1 and is in
  the 9.1.0, 9.1.1 and 9.1.2 tags. It was added here by mistake.
- #3551 (hashtable iterator invalidate on exhaustion) is likewise in
  9.1.0; it just never made the 9.1 notes.
- #3586 (valkey-cli --cluster fix slot spreading) is already documented
  in both the 9.0 and 9.1 release notes and shipped in 9.0.6 and 9.1.0.

Fix the #4076 config name: the registered config is
priority-preemptive-poll-interval-us, not preemptive-poll-interval-us,
so the name as written would be rejected by CONFIG SET.

Rewrite the #4005 entry, which described only the internal tagging. The
user-facing surface is two new configs, priority-subnets and
maxclients-reserved, which together cap normal clients at maxclients
minus the reserved count so administrative clients keep guaranteed
slots, plus a connected_priority_clients field in INFO clients.

Move the sorted set B+ tree note (#4359) back to Performance. Listing it
as a behavior change for its OBJECT ENCODING output while #3212 changes
hash encoding output from hashtable to listpack in the Performance
section was inconsistent; both now sit together.

Drop the Redis comparison from the #4629 entry, which no other entry in
the 9.0, 9.1 or 9.2 notes makes.

Signed-off-by: Sarthak Aggarwal <sarthagg@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

major-decision-approved Major decision approved by TSC team

Projects

Status: Merged

Development

Successfully merging this pull request may close these issues.

9 participants