@@ -2,6 +2,7 @@ const { hrtime } = require('process');
2
2
const {
3
3
partitionKey,
4
4
} = require ( './_common' ) ;
5
+ const { Heap } = require ( './_heap' ) ;
5
6
6
7
/**
7
8
* A PerPartitionMessageCache is a cache for messages for a single partition.
@@ -62,13 +63,13 @@ class PerPartitionMessageCache {
62
63
*/
63
64
class MessageCache {
64
65
65
- constructor ( expiryDurationMs ) {
66
+ constructor ( expiryDurationMs , maxConcurrency ) {
66
67
/* Per partition cache list containing non-empty PPCs */
67
68
this . ppcList = [ ] ;
68
69
/* Map of topic+partition to PerPartitionMessageCache. */
69
70
this . tpToPpc = new Map ( ) ;
70
71
/* Index of the current PPC in the ppcList. */
71
- this . currentPpc = 0 ;
72
+ this . currentPpcTODO_remove_this = 0 ;
72
73
/* Maximum size of the cache. (Capacity) */
73
74
this . maxSize = 1 ;
74
75
/* Number of times the size has been increased in a row, used for accounting for maxSize. */
@@ -81,6 +82,15 @@ class MessageCache {
81
82
this . expiryDurationMs = expiryDurationMs ;
82
83
/* A list of caches which have been marked stale since the last call to popLocallyStale or addMessages. */
83
84
this . locallyStaleCaches = [ ] ;
85
+ /* Max allowed concurrency */
86
+ this . maxConcurrency = maxConcurrency ;
87
+ /* Contains a list of indices of ppcList from which we are allowed to consume. */
88
+ this . indices = new Heap ( ) ;
89
+ /* Largest ppc index we are allowed to consume from (inclusive). */
90
+ this . maxIndicesIndex = 0 ;
91
+ /* Contains a list of indices of ppcList from which we have sent a message returned through next, but
92
+ * the user has not returned the index back to us via next(idx) */
93
+ this . pendingIndices = new Set ( ) ;
84
94
}
85
95
86
96
/**
@@ -226,7 +236,6 @@ class MessageCache {
226
236
* run out of messages. We need to clear them, else #add() will not add
227
237
* them back to the ppcList since they're not empty. */
228
238
this . ppcList . forEach ( cache => cache . clear ( ) ) ;
229
- this . currentPpc = 0 ;
230
239
this . ppcList = [ ] ;
231
240
232
241
if ( this . locallyStaleCaches . length !== 0 && this . locallyStaleCaches . some ( tp => {
@@ -245,34 +254,72 @@ class MessageCache {
245
254
246
255
// TODO: add ppcList sort step.
247
256
// Rationale: ideally it's best to consume in the ascending order of timestamps.
257
+
258
+ /* Reset the indices and pendingIndices because ppcList is being created newly. */
259
+ this . indices . clear ( ) ;
260
+ if ( this . pendingIndices . size > 0 ) console . error ( 'addMessages: pendingIndices = ' , this . pendingIndices , console . trace ( ) ) ;
261
+ this . pendingIndices . clear ( ) ;
262
+ this . maxIndicesIndex = Math . min ( this . maxConcurrency , this . ppcList . length - 1 ) ;
263
+ for ( let i = 0 ; i <= this . maxIndicesIndex ; i ++ ) {
264
+ this . indices . push ( i ) ;
265
+ }
248
266
}
249
267
250
268
/**
251
269
* Returns the next element in the cache, or null if none exists.
252
270
*
253
271
* If the current PPC is exhausted, it moves to the next PPC.
254
272
* If all PPCs are exhausted, it returns null.
273
+ * @param {number } idx - after a consumer has consumed a message, it must return the index back to us via this parameter.
274
+ * otherwise, no messages from that topic partition will be consumed.
275
+ * @returns {Object } - the next message in the cache, or null if none exists. An `index` field is added to the message.
255
276
* @warning Does not check for global staleness. That is left up to the user.
256
277
* Skips locally stale messages.
278
+ * The topicPartition, if provided, MUST be one such that the user has fetched
279
+ * the message from the same topicPartition earlier.
280
+ * @note Whenever making changes to this function, ensure that you benchmark perf.
257
281
*/
258
- next ( ) {
259
- if ( this . currentPpc >= this . ppcList . length ) {
260
- return null ;
282
+ next ( idx = - 1 ) {
283
+ let index = idx ;
284
+ if ( ! this . pendingIndices . has ( index ) ) {
285
+ /* The user is behaving well by returning the index to us, but in the meanwhile, it's possible
286
+ * that we ran out of messages and fetched a new batch. So we just discard what the user is
287
+ * returning to us. */
288
+ index = - 1 ;
289
+ } else {
290
+ this . pendingIndices . delete ( index ) ;
261
291
}
262
292
263
- let next = null ;
264
- while ( next === null && this . currentPpc < this . ppcList . length ) {
265
- if ( this . ppcList [ this . currentPpc ] . isStale ( ) ) {
266
- this . currentPpc ++ ;
293
+ if ( index === - 1 ) {
294
+ if ( this . indices . size ( ) === 0 )
295
+ return null ;
296
+ index = this . indices . pop ( ) ; // index cannot be undefined here since indices.size > 0
297
+ }
298
+
299
+ while ( true ) {
300
+ const next = this . ppcList [ index ] . next ( ) ;
301
+ if ( this . ppcList [ index ] . isStale ( ) || next === null ) {
302
+ /* If the current PPC is stale or empty, then we move on to the next one.
303
+ * It is equally valid to choose any PPC available within this.indices, or else
304
+ * move on to the next PPC (maxIndicesIndex + 1) if available.
305
+ * We prefer the second option a bit more since we don't have to do a heap operation. */
306
+ const toAdd = this . maxIndicesIndex + 1 ;
307
+ if ( toAdd < this . ppcList . length ) {
308
+ this . maxIndicesIndex = toAdd ;
309
+ index = toAdd ;
310
+ } else if ( ! this . indices . isEmpty ( ) ) {
311
+ index = this . indices . pop ( )
312
+ } else {
313
+ break ; // nothing left.
314
+ }
267
315
continue ;
268
316
}
269
317
270
- next = this . ppcList [ this . currentPpc ] . next ( ) ;
271
- if ( next !== null )
272
- break ;
273
- this . currentPpc ++ ;
318
+ this . pendingIndices . add ( index ) ;
319
+ next . index = index ;
320
+ return next ;
274
321
}
275
- return next ; // Caller is responsible for triggering fetch logic here if next == null.
322
+ return null ; // Caller is responsible for triggering fetch logic here if next == null.
276
323
}
277
324
278
325
/**
@@ -286,12 +333,15 @@ class MessageCache {
286
333
cache . clear ( ) ;
287
334
}
288
335
this . ppcList = [ ] ;
289
- this . currentPpc = 0 ;
290
336
this . maxSize = 1 ;
291
337
this . increaseCount = 0 ;
292
338
this . stale = false ;
293
339
this . cachedTime = hrtime ( ) ;
294
340
this . locallyStaleCaches = [ ] ;
341
+ this . indices . clear ( ) ;
342
+ // if (this.pendingIndices.size > 0) console.log('clear: pendingIndices = ', this.pendingIndices, console.);
343
+ this . pendingIndices . clear ( ) ;
344
+ this . currentIndex = 0 ;
295
345
}
296
346
}
297
347
0 commit comments