You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
All replica writes fire in parallel goroutines. The master returns 200 as long as at least one write succeeds. If one replica is temporarily down, the write still lands on the other.
169
169
170
-
**Read path** — primary-first with fallback:
170
+
**Read path** — random replica selection with fallback:
171
171
172
-
The master tries the primary node first. If it is unreachable or returns a non-200, it falls through to the secondary. The client sees a 200 either way.
172
+
The master shuffles the replica list for each GET before trying nodes in order. This spreads reads evenly across all replicas so no single node becomes a bottleneck when one key receives disproportionately high traffic (a hot key). If the chosen replica is unreachable or returns a non-200, the master falls through to the next one. The client sees a 200 either way.
173
173
174
174
```
175
175
GET "hello":
176
-
try aux3 → unreachable (node is down)
176
+
replicas shuffled → [aux1, aux3]
177
177
try aux1 → 200 {"key":"hello","value":"world"}
178
+
179
+
GET "hello" (next request):
180
+
replicas shuffled → [aux3, aux1]
181
+
try aux3 → 200 {"key":"hello","value":"world"}
178
182
```
179
183
184
+
Bulk GETs apply the same logic per key: each key is independently assigned to a random replica when batching outbound requests to aux nodes.
185
+
180
186
**Delete path** — remove from all replicas:
181
187
182
188
A DELETE is sent to all replica nodes. Returns 200 if at least one held the key. This prevents "ghost reads" where a deleted key re-appears from a surviving replica.
@@ -252,9 +258,13 @@ The primary pushes a `RingUpdate{action, aux}` event to the standby over `/ring-
252
258
1. Client sends GET /data/x
253
259
2. Nginx routes to either master (reads are load-balanced)
|`ReadHeavyUser`| 70% | 4 GETs per PUT across a 500-key pool — simulates a typical cache consumer |
853
863
|`WriteHeavyUser`| 20% | High write rate with unique keys + write-then-read consistency checks — simulates an ingestion pipeline |
854
864
|`BulkUser`| 10% | Bulk PUT and bulk GET with batches of 10-50 keys — exercises the shard batch-locking path |
865
+
|`HotKeyUser`| 10% | Hammers a single key (`hotkey:burn`) at high frequency to verify that replica-shuffle distributes hot key reads across all replicas rather than saturating one aux node |
866
+
867
+
**Verifying hot key distribution:**
868
+
869
+
After a run, query each aux node's Prometheus metrics to confirm reads were spread across the replicas for the hot key:
| awk '/^auxiliary_request_total\{method="GET"/ {sum += $2} END {print sum+0}')
875
+
echo "aux:$port GETs = $gets"
876
+
done
877
+
```
878
+
879
+
The two aux nodes that own the hot key's ring slot should show comparable GET counts. Nodes that don't own the slot will show significantly lower counts (they only serve other keys in the general pool).
0 commit comments