@@ -185,3 +185,55 @@ def bulk_get(self):
185185 @task (1 )
186186 def health (self ):
187187 self .client .get ("/health" , name = "/health" )
188+
189+
190+ # ---------------------------------------------------------------------------
191+ # Hot key user — hammers a single key to verify read load is spread across
192+ # replicas rather than always hitting the same aux node.
193+ # ---------------------------------------------------------------------------
194+ HOT_KEY = "hotkey:burn"
195+ HOT_VALUE = "hot-key-test-value"
196+
197+
198+ class HotKeyUser (HttpUser ):
199+ weight = 10
200+ wait_time = between (0.01 , 0.05 )
201+
202+ def on_start (self ):
203+ """Seed the hot key once when this user starts."""
204+ self .client .post (
205+ "/data" ,
206+ json = {"key" : HOT_KEY , "value" : HOT_VALUE },
207+ name = "/data PUT (hot-key seed)" ,
208+ )
209+
210+ @task (10 )
211+ def read_hot_key (self ):
212+ """Hammer a single key — all reads resolve to the same hash-ring slot."""
213+ with self .client .get (
214+ f"/data/{ HOT_KEY } " ,
215+ name = "/data/[hotkey] GET" ,
216+ catch_response = True ,
217+ ) as resp :
218+ if resp .status_code == 200 :
219+ body = resp .json ()
220+ if body .get ("value" ) != HOT_VALUE :
221+ resp .failure (
222+ f"Hot key value mismatch: got { body .get ('value' )!r} "
223+ )
224+ else :
225+ resp .success ()
226+ elif resp .status_code == 404 :
227+ # May happen briefly after eviction under memory pressure
228+ resp .success ()
229+ else :
230+ resp .failure (f"Unexpected status { resp .status_code } " )
231+
232+ @task (1 )
233+ def refresh_hot_key (self ):
234+ """Periodically re-seed so the key survives LRU eviction."""
235+ self .client .post (
236+ "/data" ,
237+ json = {"key" : HOT_KEY , "value" : HOT_VALUE },
238+ name = "/data PUT (hot-key refresh)" ,
239+ )
0 commit comments