@@ -38,23 +38,30 @@ type SendManager struct {
38
38
cancelAll context.CancelFunc
39
39
ctx context.Context
40
40
41
- singleSendInterval time.Duration
41
+ queueProcessInterval time.Duration
42
42
backfillQueueInterval time.Duration
43
43
sortByTimestampInterval time.Duration
44
+ batchSendInterval time.Duration
45
+ batchSize int
44
46
45
- bufferSize int
46
- callbackList * list.List
47
+ bufferSize int
48
+ callbackQueue * list.List
47
49
48
50
now func () time.Time
49
51
}
50
52
51
53
const (
52
- entriesBufferSize = 10000
53
-
54
- singleSendIntervalDefault = 5 * time .Second
54
+ entriesBufferSize = 10000
55
+ batchSizeDefault = 50
56
+ queueProcessIntervalDefault = 5 * time .Second
55
57
backfillQueueIntervalDefault = 5 * time .Second
56
58
expirationDefault = 24 * time .Hour
57
59
sortByTimestampIntervalDefault = 10 * time .Second
60
+ batchSendIntervalDefault = 5 * time .Second
61
+ )
62
+
63
+ var (
64
+ ErrSendBatchedCallbacks = errors .New ("failed to send batched callback" )
58
65
)
59
66
60
67
func WithNow (nowFunc func () time.Time ) func (* SendManager ) {
@@ -69,9 +76,9 @@ func WithBufferSize(size int) func(*SendManager) {
69
76
}
70
77
}
71
78
72
- func WithSingleSendInterval (d time.Duration ) func (* SendManager ) {
79
+ func WithQueueProcessInterval (d time.Duration ) func (* SendManager ) {
73
80
return func (m * SendManager ) {
74
- m .singleSendInterval = d
81
+ m .queueProcessInterval = d
75
82
}
76
83
}
77
84
@@ -93,20 +100,34 @@ func WithSortByTimestampInterval(d time.Duration) func(*SendManager) {
93
100
}
94
101
}
95
102
103
+ func WithBatchSendInterval (d time.Duration ) func (* SendManager ) {
104
+ return func (m * SendManager ) {
105
+ m .batchSendInterval = d
106
+ }
107
+ }
108
+
109
+ func WithBatchSize (size int ) func (* SendManager ) {
110
+ return func (m * SendManager ) {
111
+ m .batchSize = size
112
+ }
113
+ }
114
+
96
115
func New (url string , sender callbacker.SenderI , store SendManagerStore , logger * slog.Logger , opts ... func (* SendManager )) * SendManager {
97
116
m := & SendManager {
98
117
url : url ,
99
118
sender : sender ,
100
119
store : store ,
101
120
logger : logger ,
102
121
103
- singleSendInterval : singleSendIntervalDefault ,
122
+ queueProcessInterval : queueProcessIntervalDefault ,
104
123
expiration : expirationDefault ,
105
124
backfillQueueInterval : backfillQueueIntervalDefault ,
106
125
sortByTimestampInterval : sortByTimestampIntervalDefault ,
126
+ batchSendInterval : batchSendIntervalDefault ,
127
+ batchSize : batchSizeDefault ,
107
128
108
- callbackList : list .New (),
109
- bufferSize : entriesBufferSize ,
129
+ callbackQueue : list .New (),
130
+ bufferSize : entriesBufferSize ,
110
131
}
111
132
112
133
for _ , opt := range opts {
@@ -121,17 +142,17 @@ func New(url string, sender callbacker.SenderI, store SendManagerStore, logger *
121
142
}
122
143
123
144
func (m * SendManager ) Enqueue (entry callbacker.CallbackEntry ) {
124
- if m .callbackList .Len () >= m .bufferSize {
145
+ if m .callbackQueue .Len () >= m .bufferSize {
125
146
m .storeToDB (entry )
126
147
return
127
148
}
128
149
129
- m .callbackList .PushBack (entry )
150
+ m .callbackQueue .PushBack (entry )
130
151
}
131
152
132
153
func (m * SendManager ) sortByTimestamp () error {
133
- current := m .callbackList .Front ()
134
- if m .callbackList .Front () == nil {
154
+ current := m .callbackQueue .Front ()
155
+ if m .callbackQueue .Front () == nil {
135
156
return nil
136
157
}
137
158
for current != nil {
@@ -160,20 +181,31 @@ func (m *SendManager) sortByTimestamp() error {
160
181
}
161
182
162
183
func (m * SendManager ) CallbacksQueued () int {
163
- return m .callbackList .Len ()
184
+ return m .callbackQueue .Len ()
164
185
}
165
186
166
187
func (m * SendManager ) Start () {
167
- queueTicker := time .NewTicker (m .singleSendInterval )
188
+ queueTicker := time .NewTicker (m .queueProcessInterval )
168
189
sortQueueTicker := time .NewTicker (m .sortByTimestampInterval )
169
190
backfillQueueTicker := time .NewTicker (m .backfillQueueInterval )
191
+ batchSendTicker := time .NewTicker (m .batchSendInterval )
170
192
171
193
m .entriesWg .Add (1 )
194
+ var callbackBatch []* list.Element
195
+
172
196
go func () {
173
197
var err error
174
198
defer func () {
175
199
// read all from callback queue and store in database
176
- data := make ([]* store.CallbackData , m .callbackList .Len ())
200
+ data := make ([]* store.CallbackData , m .callbackQueue .Len ()+ len (callbackBatch ))
201
+
202
+ for _ , callbackElement := range callbackBatch {
203
+ entry , ok := callbackElement .Value .(callbacker.CallbackEntry )
204
+ if ! ok {
205
+ continue
206
+ }
207
+ m .callbackQueue .PushBack (entry )
208
+ }
177
209
178
210
for i , entry := range m .dequeueAll () {
179
211
data [i ] = toStoreDto (m .url , entry )
@@ -189,6 +221,8 @@ func (m *SendManager) Start() {
189
221
m .entriesWg .Done ()
190
222
}()
191
223
224
+ lastIterationWasBatch := false
225
+
192
226
for {
193
227
select {
194
228
case <- m .ctx .Done ():
@@ -202,54 +236,132 @@ func (m *SendManager) Start() {
202
236
case <- backfillQueueTicker .C :
203
237
m .backfillQueue ()
204
238
239
+ m .logger .Debug ("Callback queue backfilled" , slog .Int ("callback elements" , len (callbackBatch )), slog .Int ("queue length" , m .CallbacksQueued ()), slog .String ("url" , m .url ))
240
+ case <- batchSendTicker .C :
241
+ if len (callbackBatch ) == 0 {
242
+ continue
243
+ }
244
+
245
+ err = m .sendElementBatch (callbackBatch )
246
+ if err != nil {
247
+ m .logger .Error ("Failed to send batch of callbacks" , slog .String ("url" , m .url ))
248
+ continue
249
+ }
250
+
251
+ callbackBatch = callbackBatch [:0 ]
252
+ m .logger .Debug ("Batched callbacks sent on interval" , slog .Int ("callback elements" , len (callbackBatch )), slog .Int ("queue length" , m .CallbacksQueued ()), slog .String ("url" , m .url ))
205
253
case <- queueTicker .C :
206
- m .processQueueSingle ()
254
+ front := m .callbackQueue .Front ()
255
+ if front == nil {
256
+ continue
257
+ }
258
+
259
+ callbackEntry , ok := front .Value .(callbacker.CallbackEntry )
260
+ if ! ok {
261
+ continue
262
+ }
263
+
264
+ // If item is expired - dequeue without storing
265
+ if m .now ().Sub (callbackEntry .Data .Timestamp ) > m .expiration {
266
+ m .logger .Warn ("Callback expired" , slog .Time ("timestamp" , callbackEntry .Data .Timestamp ), slog .String ("hash" , callbackEntry .Data .TxID ), slog .String ("status" , callbackEntry .Data .TxStatus ))
267
+ m .callbackQueue .Remove (front )
268
+ continue
269
+ }
270
+
271
+ if callbackEntry .AllowBatch {
272
+ lastIterationWasBatch = true
273
+
274
+ if len (callbackBatch ) < m .batchSize {
275
+ callbackBatch = append (callbackBatch , front )
276
+ queueTicker .Reset (m .queueProcessInterval )
277
+ m .callbackQueue .Remove (front )
278
+ continue
279
+ }
280
+
281
+ err = m .sendElementBatch (callbackBatch )
282
+ if err != nil {
283
+ m .logger .Error ("Failed to send batch of callbacks" , slog .String ("url" , m .url ))
284
+ continue
285
+ }
286
+
287
+ callbackBatch = callbackBatch [:0 ]
288
+ m .logger .Debug ("Batched callbacks sent" , slog .Int ("callback elements" , len (callbackBatch )), slog .Int ("queue length" , m .CallbacksQueued ()), slog .String ("url" , m .url ))
289
+ continue
290
+ }
291
+
292
+ if lastIterationWasBatch {
293
+ lastIterationWasBatch = false
294
+ if len (callbackBatch ) > 0 {
295
+ // if entry is not a batched entry, but last one was, send batch to keep the order
296
+ err = m .sendElementBatch (callbackBatch )
297
+ if err != nil {
298
+ m .logger .Error ("Failed to send batch of callbacks" , slog .String ("url" , m .url ))
299
+ continue
300
+ }
301
+ callbackBatch = callbackBatch [:0 ]
302
+ m .logger .Debug ("Batched callbacks sent before sending single callback" , slog .Int ("callback elements" , len (callbackBatch )), slog .Int ("queue length" , m .CallbacksQueued ()), slog .String ("url" , m .url ))
303
+ }
304
+ }
305
+
306
+ success , retry := m .sender .Send (m .url , callbackEntry .Token , callbackEntry .Data )
307
+ if ! retry || success {
308
+ m .callbackQueue .Remove (front )
309
+ m .logger .Debug ("Single callback sent" , slog .Int ("callback elements" , len (callbackBatch )), slog .Int ("queue length" , m .CallbacksQueued ()), slog .String ("url" , m .url ))
310
+ continue
311
+ }
312
+ m .logger .Error ("Failed to send single callback" , slog .String ("url" , m .url ))
207
313
}
208
314
}
209
315
}()
210
316
}
211
317
212
- func (m * SendManager ) backfillQueue () {
213
- capacityLeft := m .bufferSize - m .callbackList .Len ()
214
- if capacityLeft == 0 {
215
- return
318
+ func (m * SendManager ) sendElementBatch (callbackElements []* list.Element ) error {
319
+ var callbackElement * list.Element
320
+ callbackBatch := make ([]callbacker.CallbackEntry , 0 , len (callbackElements ))
321
+ for _ , element := range callbackElements {
322
+ callback , ok := element .Value .(callbacker.CallbackEntry )
323
+ if ! ok {
324
+ continue
325
+ }
326
+ callbackBatch = append (callbackBatch , callback )
216
327
}
328
+ success , retry := m .sendBatch (callbackBatch )
329
+ if ! retry || success {
330
+ for _ , callbackElement = range callbackElements {
331
+ m .callbackQueue .Remove (callbackElement )
332
+ }
217
333
218
- callbacks , err := m .store .GetAndDelete (m .ctx , m .url , capacityLeft )
219
- if err != nil {
220
- m .logger .Error ("Failed to load callbacks" , slog .String ("err" , err .Error ()))
221
- return
334
+ return nil
222
335
}
223
336
224
- for _ , callback := range callbacks {
225
- m .Enqueue (toEntry (callback ))
226
- }
337
+ return ErrSendBatchedCallbacks
227
338
}
228
339
229
- func (m * SendManager ) processQueueSingle () {
230
- front := m .callbackList .Front ()
231
- if front == nil {
232
- return
340
+ func (m * SendManager ) sendBatch (batch []callbacker.CallbackEntry ) (success , retry bool ) {
341
+ token := batch [0 ].Token
342
+ callbacks := make ([]* callbacker.Callback , len (batch ))
343
+ for i , e := range batch {
344
+ callbacks [i ] = e .Data
233
345
}
234
346
235
- callbackEntry , ok := front .Value .(callbacker.CallbackEntry )
236
- if ! ok {
347
+ return m .sender .SendBatch (m .url , token , callbacks )
348
+ }
349
+
350
+ func (m * SendManager ) backfillQueue () {
351
+ capacityLeft := m .bufferSize - m .callbackQueue .Len ()
352
+ if capacityLeft == 0 {
237
353
return
238
354
}
239
355
240
- // If item is expired - dequeue without storing
241
- if m .now ().Sub (callbackEntry .Data .Timestamp ) > m .expiration {
242
- m .logger .Warn ("callback expired" , slog .Time ("timestamp" , callbackEntry .Data .Timestamp ), slog .String ("hash" , callbackEntry .Data .TxID ), slog .String ("status" , callbackEntry .Data .TxStatus ))
243
- m .callbackList .Remove (front )
356
+ callbacks , err := m .store .GetAndDelete (m .ctx , m .url , capacityLeft )
357
+ if err != nil {
358
+ m .logger .Error ("Failed to load callbacks" , slog .String ("err" , err .Error ()))
244
359
return
245
360
}
246
361
247
- success , retry := m .sender .Send (m .url , callbackEntry .Token , callbackEntry .Data )
248
- if ! retry || success {
249
- m .callbackList .Remove (front )
250
- return
362
+ for _ , callback := range callbacks {
363
+ m .Enqueue (toEntry (callback ))
251
364
}
252
- m .logger .Error ("failed to send single callback" , slog .String ("url" , m .url ))
253
365
}
254
366
255
367
func (m * SendManager ) storeToDB (entry callbacker.CallbackEntry ) {
@@ -297,18 +409,18 @@ func toEntry(callbackData *store.CallbackData) callbacker.CallbackEntry {
297
409
}
298
410
299
411
func (m * SendManager ) dequeueAll () []callbacker.CallbackEntry {
300
- callbacks := make ([]callbacker.CallbackEntry , 0 , m .callbackList .Len ())
412
+ callbacks := make ([]callbacker.CallbackEntry , 0 , m .callbackQueue .Len ())
301
413
302
414
var next * list.Element
303
- for front := m .callbackList .Front (); front != nil ; front = next {
415
+ for front := m .callbackQueue .Front (); front != nil ; front = next {
304
416
next = front .Next ()
305
417
entry , ok := front .Value .(callbacker.CallbackEntry )
306
418
if ! ok {
307
419
continue
308
420
}
309
421
callbacks = append (callbacks , entry )
310
422
311
- m .callbackList .Remove (front )
423
+ m .callbackQueue .Remove (front )
312
424
}
313
425
314
426
return callbacks
0 commit comments