@@ -47,7 +47,7 @@ func NewLowLatency[T any](ecsClient desc.Client[T], clk clock.WithTicker, perReq
4747 ecsClient : ecsClient ,
4848 requestChan : make (chan * getRequest [* T ]),
4949 tokens : tokenBucket {
50- limit : burst , perToken : perRequest ,
50+ limit : time . Duration ( burst ) * perRequest , perToken : perRequest ,
5151 },
5252 clk : clk ,
5353 }
@@ -71,28 +71,23 @@ func (w *LowLatency[T]) Describe(ctx context.Context, id string) (*T, error) {
7171type tokenBucket struct {
7272 zeroAt time.Time
7373
74- limit int
75- perToken time.Duration
74+ limit time. Duration // How long it takes to fully refill the bucket
75+ perToken time.Duration // How long it takes to refill one token
7676}
7777
7878func (tb * tokenBucket ) tokenAt (t time.Time ) float64 {
79- elapsed := t .Sub (tb .zeroAt )
80- token := float64 (elapsed ) / float64 (tb .perToken )
81- if token > float64 (tb .limit ) {
82- token = float64 (tb .limit )
83- }
84- return token
79+ elapsed := min (t .Sub (tb .zeroAt ), tb .limit )
80+ return float64 (elapsed ) / float64 (tb .perToken )
8581}
8682
8783func (tb * tokenBucket ) takeAt (t time.Time ) {
8884 elapsed := t .Sub (tb .zeroAt )
89- if elapsed >= time .Duration (tb .limit )* tb .perToken {
90- tb .zeroAt = t .Add (- time .Duration (tb .limit - 1 ) * tb .perToken )
91- } else {
92- tb .zeroAt = tb .zeroAt .Add (tb .perToken )
93- if tb .zeroAt .After (t ) {
94- tb .zeroAt = t
95- }
85+ if elapsed >= tb .limit {
86+ tb .zeroAt = t .Add (- tb .limit )
87+ }
88+ tb .zeroAt = tb .zeroAt .Add (tb .perToken )
89+ if tb .zeroAt .After (t ) {
90+ tb .zeroAt = t
9691 }
9792}
9893
@@ -109,6 +104,8 @@ func (w *LowLatency[T]) Run(ctx context.Context) {
109104 timeout = nil
110105 }
111106
107+ var d time.Duration
108+ nInefficient := 0
112109 for {
113110 select {
114111 case <- ctx .Done ():
@@ -121,17 +118,34 @@ func (w *LowLatency[T]) Run(ctx context.Context) {
121118 requests [r .id ] = append (requests [r .id ], r )
122119 if len (requests ) == batchSize {
123120 logger .V (4 ).Info ("batch full" , "n" , batchSize )
121+ nInefficient = 0
124122 descBatch (t )
125123 } else if timeout == nil {
126124 // add some artificial delay for better batching
127125 // the less tokens left, the more we wait
128126 tokens := w .tokens .tokenAt (t )
129- d : = time .Duration (math .Pow (0.5 , tokens ) * float64 (w .tokens .perToken ))
127+ d = time .Duration (math .Pow (0.5 , tokens ) * float64 (w .tokens .perToken ))
130128 timeout = w .clk .After (d )
131129 logger .V (4 ).Info ("batch waiting" , "timeout" , d )
132130 }
133131 case t := <- timeout :
134- logger .V (4 ).Info ("batch timeout" , "n" , len (requests ))
132+ v := 4
133+ if d > w .tokens .perToken / 2 { // We are becoming the bottleneck of system throughput
134+ v = 2
135+ if len (requests ) <= 1 {
136+ // We have waited, but didn't get the second request.
137+ // We increased the latency with no benefit :(
138+ nInefficient ++
139+ v = 1
140+ if nInefficient == 3 {
141+ logger .V (1 ).Info ("Inefficient batching, please increase upstream concurrency" )
142+ }
143+ }
144+ }
145+ if v > 1 {
146+ nInefficient = 0
147+ }
148+ logger .V (v ).Info ("batch timeout" , "timeout" , d , "n" , len (requests ))
135149 descBatch (t )
136150 }
137151 }
0 commit comments