@@ -49,7 +49,7 @@ func NewLowLatency[T any](ecsClient desc.Client[T], clk clock.WithTicker, perReq
4949 ecsClient : ecsClient ,
5050 requestChan : make (chan * getRequest [* T ]),
5151 tokens : tokenBucket {
52- limit : burst , perToken : perRequest ,
52+ limit : time . Duration ( burst ) * perRequest , perToken : perRequest ,
5353 },
5454 clk : clk ,
5555 }
@@ -73,28 +73,23 @@ func (w *LowLatency[T]) Describe(ctx context.Context, id string) (*T, error) {
7373type tokenBucket struct {
7474 zeroAt time.Time
7575
76- limit int
77- perToken time.Duration
76+ limit time. Duration // How long it takes to fully refill the bucket
77+ perToken time.Duration // How long it takes to refill one token
7878}
7979
8080func (tb * tokenBucket ) tokenAt (t time.Time ) float64 {
81- elapsed := t .Sub (tb .zeroAt )
82- token := float64 (elapsed ) / float64 (tb .perToken )
83- if token > float64 (tb .limit ) {
84- token = float64 (tb .limit )
85- }
86- return token
81+ elapsed := min (t .Sub (tb .zeroAt ), tb .limit )
82+ return float64 (elapsed ) / float64 (tb .perToken )
8783}
8884
8985func (tb * tokenBucket ) takeAt (t time.Time ) {
9086 elapsed := t .Sub (tb .zeroAt )
91- if elapsed >= time .Duration (tb .limit )* tb .perToken {
92- tb .zeroAt = t .Add (- time .Duration (tb .limit - 1 ) * tb .perToken )
93- } else {
94- tb .zeroAt = tb .zeroAt .Add (tb .perToken )
95- if tb .zeroAt .After (t ) {
96- tb .zeroAt = t
97- }
87+ if elapsed >= tb .limit {
88+ tb .zeroAt = t .Add (- tb .limit )
89+ }
90+ tb .zeroAt = tb .zeroAt .Add (tb .perToken )
91+ if tb .zeroAt .After (t ) {
92+ tb .zeroAt = t
9893 }
9994}
10095
@@ -111,6 +106,8 @@ func (w *LowLatency[T]) Run(ctx context.Context) {
111106 timeout = nil
112107 }
113108
109+ var d time.Duration
110+ nInefficient := 0
114111 for {
115112 select {
116113 case <- ctx .Done ():
@@ -123,17 +120,34 @@ func (w *LowLatency[T]) Run(ctx context.Context) {
123120 requests [r .id ] = append (requests [r .id ], r )
124121 if len (requests ) == batchSize {
125122 logger .V (4 ).Info ("batch full" , "n" , batchSize )
123+ nInefficient = 0
126124 descBatch (t )
127125 } else if timeout == nil {
128126 // add some artificial delay for better batching
129127 // the less tokens left, the more we wait
130128 tokens := w .tokens .tokenAt (t )
131- d : = time .Duration (math .Pow (0.5 , tokens ) * float64 (w .tokens .perToken ))
129+ d = time .Duration (math .Pow (0.5 , tokens ) * float64 (w .tokens .perToken ))
132130 timeout = w .clk .After (d )
133131 logger .V (4 ).Info ("batch waiting" , "timeout" , d )
134132 }
135133 case t := <- timeout :
136- logger .V (4 ).Info ("batch timeout" , "n" , len (requests ))
134+ v := 4
135+ if d > w .tokens .perToken / 2 { // We are becoming the bottleneck of system throughput
136+ v = 2
137+ if len (requests ) <= 1 {
138+ // We have waited, but didn't get the second request.
139+ // We increased the latency with no benefit :(
140+ nInefficient ++
141+ v = 1
142+ if nInefficient == 3 {
143+ logger .V (1 ).Info ("Inefficient batching, please increase upstream concurrency" )
144+ }
145+ }
146+ }
147+ if v > 1 {
148+ nInefficient = 0
149+ }
150+ logger .V (v ).Info ("batch timeout" , "timeout" , d , "n" , len (requests ))
137151 descBatch (t )
138152 }
139153 }
0 commit comments