Skip to content

Commit 08cab3c

Browse files
committed
Parameterize loadshed request values
AI assisted with development. Every line of code was either written by or carefully reviewed by me :) Signed-off-by: Brett Wines <bwines@slack-corp.com>
1 parent 1cbdb64 commit 08cab3c

13 files changed

Lines changed: 121 additions & 148 deletions

go/pools/smartconnpool/waitlist.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"time"
2424

2525
"vitess.io/vitess/go/vt/servenv"
26+
"vitess.io/vitess/go/vt/sqlparser"
2627
"vitess.io/vitess/go/vt/vttablet/tabletserver/loadshed"
2728
)
2829

@@ -34,7 +35,7 @@ type PoolConfig interface {
3435
type waiter[C Connection] struct {
3536
// conn is a channel that will receive the connection when it's ready
3637
conn chan *Pooled[C]
37-
request *loadshed.Request
38+
request *loadshed.Request[*waiter[C]]
3839
err error
3940
}
4041

@@ -98,7 +99,7 @@ func (wl *waitlist[C]) waitForConn(ctx context.Context, setting *Setting, closeC
9899
}
99100
return nil, ErrPoolWaiterCapReached
100101
}
101-
request, dropped := wl.snake.Enqueue(elem, valveID, priority)
102+
request, dropped := wl.snake.Enqueue(elem, valveID, snakePriority(priority))
102103
elem.request = request
103104
wl.syncTimer()
104105
wl.mu.Unlock()
@@ -149,6 +150,13 @@ func (wl *waitlist[C]) aboveWaiterCap(maxWaiters uint) bool {
149150
return maxWaiters > 0 && wl.snake.Len() >= int(maxWaiters)
150151
}
151152

153+
func snakePriority(priority float64) float64 {
154+
if priority == loadshed.PriorityUndroppable {
155+
return priority
156+
}
157+
return float64(sqlparser.MaxPriorityValue) - priority
158+
}
159+
152160
func (wl *waitlist[C]) maybeStarvingCount() int {
153161
return wl.snake.Len()
154162
}

go/pools/smartconnpool/waitlist_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,10 @@ func TestWaitlistShedsQueuedRequests(t *testing.T) {
138138
<-errs
139139
}
140140
}
141+
142+
func TestSnakePriority(t *testing.T) {
143+
assert.Equal(t, float64(100), snakePriority(0))
144+
assert.Equal(t, float64(70), snakePriority(30))
145+
assert.Equal(t, float64(0), snakePriority(100))
146+
assert.Equal(t, loadshed.PriorityUndroppable, snakePriority(loadshed.PriorityUndroppable))
147+
}

go/vt/vttablet/tabletserver/loadshed/codelq.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ type (
144144
// CoDelQueue implements the CoDel (Controlled Delay) load-shedding
145145
// algorithm. All methods are prefixed locked* and assume the caller holds
146146
// the mutex, which is defined in the files for the higher-level structure
147-
CoDelQueue struct {
147+
CoDelQueue[T any] struct {
148148
queue *list.List
149149
dropping bool
150150
dropNextNs int64
@@ -153,16 +153,16 @@ type (
153153
// droppable indexes the droppable queue entries by priority so the
154154
// lowest-priority one is found in O(1) rather than an O(n) scan. Kept in
155155
// lockstep with droppableLen: every insert/remove pairs with a ++/--.
156-
droppable droppableIndex
156+
droppable droppableIndex[T]
157157

158158
cfg CoDelConfig
159159
nowNs func() int64
160160
scheduleDropTimer func(delayNs int64)
161161
}
162162
)
163163

164-
func newCoDelQueue(cfg CoDelConfig, nowNs func() int64, scheduleDropTimer func(delayNs int64)) *CoDelQueue {
165-
q := &CoDelQueue{
164+
func newCoDelQueue[T any](cfg CoDelConfig, nowNs func() int64, scheduleDropTimer func(delayNs int64)) *CoDelQueue[T] {
165+
q := &CoDelQueue[T]{
166166
queue: list.New(),
167167
count: 1,
168168
cfg: cfg,
@@ -173,15 +173,15 @@ func newCoDelQueue(cfg CoDelConfig, nowNs func() int64, scheduleDropTimer func(d
173173
return q
174174
}
175175

176-
func (q *CoDelQueue) lockedLen() int {
176+
func (q *CoDelQueue[T]) lockedLen() int {
177177
return q.queue.Len()
178178
}
179179

180-
func (q *CoDelQueue) lockedIsHealthy() bool {
180+
func (q *CoDelQueue[T]) lockedIsHealthy() bool {
181181
return !q.dropping
182182
}
183183

184-
func (q *CoDelQueue) lockedEnqueue(req *Request) {
184+
func (q *CoDelQueue[T]) lockedEnqueue(req *Request[T]) {
185185
now := q.nowNs()
186186

187187
req.codelqEnqueuedAtNs = now
@@ -204,16 +204,16 @@ func (q *CoDelQueue) lockedEnqueue(req *Request) {
204204
}
205205

206206
// lockedFirstWaiting returns the first waiting request in the queue.
207-
func (q *CoDelQueue) lockedFirstWaiting() *Request {
207+
func (q *CoDelQueue[T]) lockedFirstWaiting() *Request[T] {
208208
first := q.queue.Front()
209209
if first == nil {
210210
return nil
211211
}
212-
return first.Value.(*Request)
212+
return first.Value.(*Request[T])
213213
}
214214

215215
// lockedRemove removes a specific request from the queue.
216-
func (q *CoDelQueue) lockedRemove(r *Request) {
216+
func (q *CoDelQueue[T]) lockedRemove(r *Request[T]) {
217217
if r.codelqElem == nil {
218218
return
219219
}
@@ -230,7 +230,7 @@ func (q *CoDelQueue) lockedRemove(r *Request) {
230230
}
231231
}
232232

233-
func (q *CoDelQueue) lockedDequeue(r *Request) {
233+
func (q *CoDelQueue[T]) lockedDequeue(r *Request[T]) {
234234
// CoDel health check, measured at dequeue: if this request's queue-wait
235235
// (now - enqueue) was under target, the system is healthy — leave the
236236
// dropping state. Separate from the droppableLen==0 clear below.
@@ -243,7 +243,7 @@ func (q *CoDelQueue) lockedDequeue(r *Request) {
243243
// lockedFindLowestPriorityDroppable finds the lowest-priority droppable
244244
// element in the queue — the oldest one at the lowest priority present — or nil
245245
// if none exists. O(1) via the droppable priority index (see droppableIndex).
246-
func (q *CoDelQueue) lockedFindLowestPriorityDroppable() *list.Element {
246+
func (q *CoDelQueue[T]) lockedFindLowestPriorityDroppable() *list.Element {
247247
req := q.droppable.min()
248248
if req == nil {
249249
return nil
@@ -254,11 +254,11 @@ func (q *CoDelQueue) lockedFindLowestPriorityDroppable() *list.Element {
254254
// lockedRunTimer runs the CoDel drop logic. It is invoked both by the backstop
255255
// timer and synchronously from the dequeue path, so shedding is driven
256256
// as slots free rather than waiting for the (possibly late) timer to fire.
257-
func (q *CoDelQueue) lockedRunTimer(dropFn func() bool) {
257+
func (q *CoDelQueue[T]) lockedRunTimer(dropFn func() bool) {
258258
q.lockedRunTimerLimited(dropFn, -1)
259259
}
260260

261-
func (q *CoDelQueue) lockedRunTimerLimited(dropFn func() bool, maxDrops int) {
261+
func (q *CoDelQueue[T]) lockedRunTimerLimited(dropFn func() bool, maxDrops int) {
262262
now := q.nowNs()
263263

264264
// Paced work: only advance the drop/ease control law and re-arm when a drop
@@ -282,11 +282,11 @@ func (q *CoDelQueue) lockedRunTimerLimited(dropFn func() bool, maxDrops int) {
282282
// `now`, so calling it is idempotent and safe outside the timer — the dequeue
283283
// path invokes it to shed stale requests in real time rather than
284284
// waiting on the possibly-late backstop timer. It does NOT arm/disarm the timer.
285-
func (q *CoDelQueue) lockedAdvance(now int64, dropFn func() bool) {
285+
func (q *CoDelQueue[T]) lockedAdvance(now int64, dropFn func() bool) {
286286
q.lockedAdvanceLimited(now, dropFn, -1)
287287
}
288288

289-
func (q *CoDelQueue) lockedAdvanceLimited(now int64, dropFn func() bool, maxDrops int) {
289+
func (q *CoDelQueue[T]) lockedAdvanceLimited(now int64, dropFn func() bool, maxDrops int) {
290290
drops := 0
291291
// Step the control law per interval while a drop is due AND there is still
292292
// work to do: either a droppable backlog to shed, or an elevated count that
@@ -325,7 +325,7 @@ func (q *CoDelQueue) lockedAdvanceLimited(now int64, dropFn func() bool, maxDrop
325325
// lockedEaseCount returns the next drop count during easing:
326326
// count -= floor(log_base(count) / base), floored at 1. A larger base yields a
327327
// smaller step (gentler ease-out); base defaults to 3 when unset or <= 1.
328-
func (q *CoDelQueue) lockedEaseCount() int {
328+
func (q *CoDelQueue[T]) lockedEaseCount() int {
329329
base := 3.0
330330
if q.cfg.EasingLogBase != nil {
331331
base = q.cfg.EasingLogBase()
@@ -341,15 +341,15 @@ func (q *CoDelQueue) lockedEaseCount() int {
341341
// inverse proportion to count^exponent, exploiting the non-linear
342342
// relationship between drop rate and throughput to achieve linear change
343343
// in throughput.
344-
func (q *CoDelQueue) lockedControlLaw(t int64) int64 {
344+
func (q *CoDelQueue[T]) lockedControlLaw(t int64) int64 {
345345
return t + q.lockedCurrentInterval()
346346
}
347347

348348
// lockedCurrentInterval returns the current interval for the control law.
349349
// The interval is compressed whenever count > 1 — both in the dropping state
350350
// and during easing (!dropping, count > 1), so that the ease-out timer fires
351351
// at progressively longer intervals as the count decays toward 1.
352-
func (q *CoDelQueue) lockedCurrentInterval() int64 {
352+
func (q *CoDelQueue[T]) lockedCurrentInterval() int64 {
353353
interval := q.cfg.IntervalNs()
354354
if q.count <= 1 {
355355
return interval
@@ -360,7 +360,7 @@ func (q *CoDelQueue) lockedCurrentInterval() int64 {
360360
return max(result, 1)
361361
}
362362

363-
func (q *CoDelQueue) lockedArmDropTimer() {
363+
func (q *CoDelQueue[T]) lockedArmDropTimer() {
364364
// Mark the episode active for this armed interval; the next timer fire
365365
// re-evaluates health.
366366
q.dropping = q.droppableLen > 0

go/vt/vttablet/tabletserver/loadshed/codelq_test.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import (
2424
"github.com/stretchr/testify/require"
2525
)
2626

27+
type testRequest = Request[struct{}]
28+
type testCoDelQueue = CoDelQueue[struct{}]
29+
2730
func defaultTestConfig() CoDelConfig {
2831
return CoDelConfig{
2932
IntervalNs: func() int64 { return int64(1e9) },
@@ -74,14 +77,14 @@ func (r *testDropTimerRecorder) reset() {
7477
r.scheduled = false
7578
}
7679

77-
func newTestQueue(cfg CoDelConfig, clock *testClock) (*CoDelQueue, *testDropTimerRecorder) {
80+
func newTestQueue(cfg CoDelConfig, clock *testClock) (*testCoDelQueue, *testDropTimerRecorder) {
7881
rec := &testDropTimerRecorder{}
79-
q := newCoDelQueue(cfg, clock.nowFunc, rec.schedule)
82+
q := newCoDelQueue[struct{}](cfg, clock.nowFunc, rec.schedule)
8083
return q, rec
8184
}
8285

83-
func testEnqueue(q *CoDelQueue, priority float64) *Request {
84-
req := newRequest(priority)
86+
func testEnqueue(q *testCoDelQueue, priority float64) *testRequest {
87+
req := newRequest[struct{}](priority)
8588
q.lockedEnqueue(req)
8689
return req
8790
}
@@ -135,7 +138,7 @@ func TestCoDelQueue_Enqueue_UndroppableNoSchedule(t *testing.T) {
135138
}
136139

137140
// testDequeue removes the oldest waiting request.
138-
func testDequeue(q *CoDelQueue) *Request {
141+
func testDequeue(q *testCoDelQueue) *testRequest {
139142
req := q.lockedFirstWaiting()
140143
if req == nil {
141144
return nil
@@ -226,7 +229,7 @@ func TestCoDelQueue_FindLowestPriorityDroppable_Basic(t *testing.T) {
226229

227230
elem := q.lockedFindLowestPriorityDroppable()
228231
require.NotNil(t, elem)
229-
dropped := elem.Value.(*Request)
232+
dropped := elem.Value.(*testRequest)
230233
q.lockedRemove(dropped)
231234
assert.Equal(t, float64(1), dropped.priority)
232235
assert.Equal(t, 2, q.lockedLen())
@@ -243,7 +246,7 @@ func TestCoDelQueue_FindLowestPriorityDroppable_ZeroInstantPick(t *testing.T) {
243246

244247
elem := q.lockedFindLowestPriorityDroppable()
245248
require.NotNil(t, elem)
246-
assert.Same(t, r2, elem.Value.(*Request))
249+
assert.Same(t, r2, elem.Value.(*testRequest))
247250
}
248251

249252
func TestCoDelQueue_DropSkipsUndroppable(t *testing.T) {
@@ -255,7 +258,7 @@ func TestCoDelQueue_DropSkipsUndroppable(t *testing.T) {
255258

256259
elem := q.lockedFindLowestPriorityDroppable()
257260
require.NotNil(t, elem)
258-
assert.Same(t, droppable, elem.Value.(*Request))
261+
assert.Same(t, droppable, elem.Value.(*testRequest))
259262
q.lockedRemove(droppable)
260263
assert.Equal(t, 1, q.lockedLen())
261264
}
@@ -280,7 +283,7 @@ func TestCoDelQueue_DropUndroppableVsInf(t *testing.T) {
280283

281284
elem := q.lockedFindLowestPriorityDroppable()
282285
require.NotNil(t, elem)
283-
assert.Same(t, inf, elem.Value.(*Request))
286+
assert.Same(t, inf, elem.Value.(*testRequest))
284287
}
285288

286289
func TestCoDelQueue_DropAllInf_NoPanic(t *testing.T) {
@@ -351,7 +354,7 @@ func TestCoDelQueue_RunScheduledDrop_EntersDropping(t *testing.T) {
351354
if elem == nil {
352355
return false
353356
}
354-
q.lockedRemove(elem.Value.(*Request))
357+
q.lockedRemove(elem.Value.(*testRequest))
355358
return true
356359
}
357360
rec.reset()
@@ -373,7 +376,7 @@ func TestCoDelQueue_RunScheduledDrop_NothingDroppable(t *testing.T) {
373376
if elem == nil {
374377
return false
375378
}
376-
q.lockedRemove(elem.Value.(*Request))
379+
q.lockedRemove(elem.Value.(*testRequest))
377380
return true
378381
}
379382
q.lockedRunTimer(dropFn)
@@ -667,7 +670,7 @@ func TestCoDelQueue_Easing_DroppableLen_ReentersDroppingWithCurrentCount(t *test
667670
if elem == nil {
668671
return false
669672
}
670-
q.lockedRemove(elem.Value.(*Request))
673+
q.lockedRemove(elem.Value.(*testRequest))
671674
return true
672675
}
673676

@@ -794,7 +797,7 @@ func TestCoDelQueue_SlowMoving_Drops(t *testing.T) {
794797
if elem == nil {
795798
return false
796799
}
797-
q.lockedRemove(elem.Value.(*Request))
800+
q.lockedRemove(elem.Value.(*testRequest))
798801
return true
799802
}
800803
q.lockedRunTimer(dropFn)

go/vt/vttablet/tabletserver/loadshed/dequeue_shed_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ import (
2424
)
2525

2626
// dropAll is the standard test dropFn: sheds the lowest-priority droppable head.
27-
func dropAllFn(q *CoDelQueue) func() bool {
27+
func dropAllFn(q *testCoDelQueue) func() bool {
2828
return func() bool {
2929
elem := q.lockedFindLowestPriorityDroppable()
3030
if elem == nil {
3131
return false
3232
}
33-
q.lockedRemove(elem.Value.(*Request))
33+
q.lockedRemove(elem.Value.(*testRequest))
3434
return true
3535
}
3636
}
@@ -117,7 +117,7 @@ func TestValved_DisabledDropAdvancesCoDelWithoutDropping(t *testing.T) {
117117
sq.codelq.cfg.IntervalNs = func() int64 { return 10_000_000 }
118118

119119
const backlog = 5
120-
reqs := make([]*Request, backlog)
120+
reqs := make([]*testRequest, backlog)
121121
for i := range reqs {
122122
reqs[i] = sq.lockedEnqueue(string(rune('a'+i)), 0)
123123
}
@@ -142,7 +142,7 @@ func TestValved_EnablementSnapshottedOncePerBatch(t *testing.T) {
142142
sq.codelq.cfg.IntervalNs = func() int64 { return 10_000_000 }
143143

144144
const backlog = 10
145-
reqs := make([]*Request, backlog)
145+
reqs := make([]*testRequest, backlog)
146146
for i := range reqs {
147147
reqs[i] = sq.lockedEnqueue(string(rune('a'+i)), 0)
148148
}

go/vt/vttablet/tabletserver/loadshed/request.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ import (
2424
type (
2525
// Request represents an entry in the CoDel queue. Named a 'request' since
2626
// it may be dropped or dequeued.
27-
Request struct {
27+
Request[T any] struct {
2828
priority float64
2929
codelqEnqueuedAtNs int64
3030
codelqElem *list.Element
3131
valveID string
3232
queued bool
33-
value any
33+
value T
3434

3535
// bucketElem locates this request in the droppableIndex while it is a
3636
// droppable queue entry: it is the request's node in its priority
@@ -48,12 +48,12 @@ type (
4848
// schemas).
4949
var PriorityUndroppable = math.Inf(-1)
5050

51-
func newRequest(priority float64) *Request {
52-
return &Request{
51+
func newRequest[T any](priority float64) *Request[T] {
52+
return &Request[T]{
5353
priority: priority,
5454
}
5555
}
5656

57-
func (r *Request) isDroppable() bool {
57+
func (r *Request[T]) isDroppable() bool {
5858
return r.priority != PriorityUndroppable
5959
}

0 commit comments

Comments
 (0)