queen-mq 0.12.1 → 0.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client-v2/Queen.js +32 -6
- package/client-v2/http/HttpClient.js +7 -2
- package/client-v2/utils/defaults.js +3 -1
- package/package.json +1 -1
- package/test-v2/bootstrap.js +316 -0
- package/test-v2/run.js +4 -15
- package/test-v2/AI_TEST_SUMMARY.md +0 -226
- package/test-v2/GETTING_STARTED.md +0 -181
- package/test-v2/MAINTENANCE_TEST.md +0 -148
- package/test-v2/README_SUBSCRIPTION_TESTS.md +0 -201
- package/test-v2/ai_buffering.js +0 -194
- package/test-v2/ai_error_handling.js +0 -223
- package/test-v2/ai_lease_renewal.js +0 -206
- package/test-v2/ai_mixed_scenarios.js +0 -278
- package/test-v2/ai_priority.js +0 -169
- package/test-v2/ai_resources.js +0 -217
- package/test-v2/ai_ttl_retention.js +0 -170
package/client-v2/Queen.js
CHANGED
|
@@ -34,10 +34,12 @@ export class Queen {
|
|
|
34
34
|
// Create buffer manager
|
|
35
35
|
this.#bufferManager = new BufferManager(this.#httpClient)
|
|
36
36
|
|
|
37
|
-
// Setup graceful shutdown
|
|
38
|
-
this.#
|
|
37
|
+
// Setup graceful shutdown (opt-out via handleSignals: false)
|
|
38
|
+
if (this.#config.handleSignals) {
|
|
39
|
+
this.#setupGracefulShutdown()
|
|
40
|
+
}
|
|
39
41
|
|
|
40
|
-
logger.log('Queen.constructor', { status: 'initialized', urls: this.#config.urls.length })
|
|
42
|
+
logger.log('Queen.constructor', { status: 'initialized', urls: this.#config.urls.length, handleSignals: this.#config.handleSignals })
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
#normalizeConfig(config) {
|
|
@@ -77,7 +79,7 @@ export class Queen {
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
#createHttpClient() {
|
|
80
|
-
const { urls, timeoutMillis, retryAttempts, retryDelayMillis, loadBalancingStrategy, affinityHashRing, healthRetryAfterMillis, enableFailover, bearerToken } = this.#config
|
|
82
|
+
const { urls, timeoutMillis, retryAttempts, retryDelayMillis, loadBalancingStrategy, affinityHashRing, healthRetryAfterMillis, enableFailover, bearerToken, headers } = this.#config
|
|
81
83
|
|
|
82
84
|
if (urls.length === 1) {
|
|
83
85
|
// Single server
|
|
@@ -86,7 +88,8 @@ export class Queen {
|
|
|
86
88
|
timeoutMillis,
|
|
87
89
|
retryAttempts,
|
|
88
90
|
retryDelayMillis,
|
|
89
|
-
bearerToken
|
|
91
|
+
bearerToken,
|
|
92
|
+
headers
|
|
90
93
|
})
|
|
91
94
|
}
|
|
92
95
|
|
|
@@ -101,7 +104,8 @@ export class Queen {
|
|
|
101
104
|
retryAttempts,
|
|
102
105
|
retryDelayMillis,
|
|
103
106
|
enableFailover,
|
|
104
|
-
bearerToken
|
|
107
|
+
bearerToken,
|
|
108
|
+
headers
|
|
105
109
|
})
|
|
106
110
|
}
|
|
107
111
|
|
|
@@ -452,6 +456,28 @@ export class Queen {
|
|
|
452
456
|
// Graceful Shutdown
|
|
453
457
|
// ===========================
|
|
454
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Register SIGINT/SIGTERM handlers so the client flushes buffers before exit.
|
|
461
|
+
* Called automatically unless handleSignals is set to false.
|
|
462
|
+
*/
|
|
463
|
+
enableGracefulShutdown() {
|
|
464
|
+
if (this.#shutdownHandlers.length > 0) return this
|
|
465
|
+
this.#setupGracefulShutdown()
|
|
466
|
+
return this
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Remove previously registered SIGINT/SIGTERM handlers.
|
|
471
|
+
* Use this when Queen is embedded in a larger application that manages its own lifecycle.
|
|
472
|
+
*/
|
|
473
|
+
disableGracefulShutdown() {
|
|
474
|
+
for (const cleanup of this.#shutdownHandlers) {
|
|
475
|
+
cleanup()
|
|
476
|
+
}
|
|
477
|
+
this.#shutdownHandlers = []
|
|
478
|
+
return this
|
|
479
|
+
}
|
|
480
|
+
|
|
455
481
|
async close() {
|
|
456
482
|
logger.log('Queen.close', 'Starting shutdown')
|
|
457
483
|
console.log('Closing Queen client...')
|
|
@@ -12,6 +12,7 @@ export class HttpClient {
|
|
|
12
12
|
#retryDelayMillis
|
|
13
13
|
#enableFailover
|
|
14
14
|
#bearerToken
|
|
15
|
+
#headers
|
|
15
16
|
|
|
16
17
|
constructor(options = {}) {
|
|
17
18
|
const {
|
|
@@ -21,7 +22,8 @@ export class HttpClient {
|
|
|
21
22
|
retryAttempts = 3,
|
|
22
23
|
retryDelayMillis = 1000,
|
|
23
24
|
enableFailover = true,
|
|
24
|
-
bearerToken = null
|
|
25
|
+
bearerToken = null,
|
|
26
|
+
headers = {}
|
|
25
27
|
} = options
|
|
26
28
|
|
|
27
29
|
this.#baseUrl = baseUrl
|
|
@@ -31,6 +33,7 @@ export class HttpClient {
|
|
|
31
33
|
this.#retryDelayMillis = retryDelayMillis
|
|
32
34
|
this.#enableFailover = enableFailover
|
|
33
35
|
this.#bearerToken = bearerToken
|
|
36
|
+
this.#headers = headers || {}
|
|
34
37
|
|
|
35
38
|
logger.log('HttpClient.constructor', {
|
|
36
39
|
hasLoadBalancer: !!loadBalancer,
|
|
@@ -38,7 +41,8 @@ export class HttpClient {
|
|
|
38
41
|
timeoutMillis,
|
|
39
42
|
retryAttempts,
|
|
40
43
|
enableFailover,
|
|
41
|
-
hasAuth: !!bearerToken
|
|
44
|
+
hasAuth: !!bearerToken,
|
|
45
|
+
customHeaders: Object.keys(this.#headers).length
|
|
42
46
|
})
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -54,6 +58,7 @@ export class HttpClient {
|
|
|
54
58
|
if (this.#bearerToken) {
|
|
55
59
|
headers['Authorization'] = `Bearer ${this.#bearerToken}`
|
|
56
60
|
}
|
|
61
|
+
Object.assign(headers, this.#headers)
|
|
57
62
|
|
|
58
63
|
const options = {
|
|
59
64
|
method,
|
|
@@ -14,7 +14,9 @@ export const CLIENT_DEFAULTS = {
|
|
|
14
14
|
affinityHashRing: 128, // Number of virtual nodes per server for affinity strategy
|
|
15
15
|
enableFailover: true, // Auto-failover to other servers
|
|
16
16
|
healthRetryAfterMillis: 5000, // Retry unhealthy backends after 5 seconds
|
|
17
|
-
bearerToken: null
|
|
17
|
+
bearerToken: null, // Bearer token for proxy authentication
|
|
18
|
+
headers: {}, // Custom headers to include in every request
|
|
19
|
+
handleSignals: true // Register SIGINT/SIGTERM handlers (disable when used as a library)
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export const QUEUE_DEFAULTS = {
|
package/package.json
CHANGED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer Group Bootstrap Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the partition_consumers bootstrap optimization:
|
|
5
|
+
* When a new consumer group registers (via subscription mode), all existing
|
|
6
|
+
* partition_consumers rows should be pre-seeded in bulk, avoiding slow
|
|
7
|
+
* one-at-a-time discovery.
|
|
8
|
+
*
|
|
9
|
+
* Test scenarios:
|
|
10
|
+
* 1. testCgBootstrapNew: CG with subscriptionMode('new')
|
|
11
|
+
* 2. testCgBootstrapTimestamp: CG with subscriptionFrom(timestamp)
|
|
12
|
+
*
|
|
13
|
+
* Both push 5000 partitions, register a CG (triggering bootstrap), then
|
|
14
|
+
* consume new messages and verify all partitions were discovered fast.
|
|
15
|
+
*
|
|
16
|
+
* Run: node run.js testCgBootstrapNew
|
|
17
|
+
* node run.js testCgBootstrapTimestamp
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const PARTITION_COUNT = 5000
|
|
21
|
+
const QUEUE_NAME_NEW = 'test-queue-v2-bootstrap-new'
|
|
22
|
+
const QUEUE_NAME_TS = 'test-queue-v2-bootstrap-ts'
|
|
23
|
+
|
|
24
|
+
async function pushToPartitions(client, queueName, partitionCount, prefix = '') {
|
|
25
|
+
const PARALLEL_BATCH = 50
|
|
26
|
+
let pushed = 0
|
|
27
|
+
for (let i = 0; i < partitionCount; i += PARALLEL_BATCH) {
|
|
28
|
+
const batch = []
|
|
29
|
+
for (let j = i; j < Math.min(i + PARALLEL_BATCH, partitionCount); j++) {
|
|
30
|
+
batch.push(
|
|
31
|
+
client
|
|
32
|
+
.queue(queueName)
|
|
33
|
+
.partition(`p-${j}`)
|
|
34
|
+
.push([{ data: { id: j, prefix } }])
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
await Promise.all(batch)
|
|
38
|
+
pushed += batch.length
|
|
39
|
+
}
|
|
40
|
+
return pushed
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Test bootstrap with subscriptionMode('new')
|
|
45
|
+
* - Push historical messages to 5000 partitions
|
|
46
|
+
* - Register CG with mode 'new' (triggers bootstrap)
|
|
47
|
+
* - Push new messages to all partitions
|
|
48
|
+
* - Consume with CG and verify all new messages are received fast
|
|
49
|
+
*/
|
|
50
|
+
export async function testCgBootstrapNew(client) {
|
|
51
|
+
// Cleanup
|
|
52
|
+
try {
|
|
53
|
+
await client.deleteConsumerGroup('bootstrap-cg-new', true)
|
|
54
|
+
} catch (e) {
|
|
55
|
+
// Ignore
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 1. Create queue
|
|
59
|
+
const queue = await client.queue(QUEUE_NAME_NEW).create()
|
|
60
|
+
if (!queue.configured) {
|
|
61
|
+
return { success: false, message: 'Queue not created' }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 2. Push 1 message to each of 5000 partitions (historical messages)
|
|
65
|
+
console.log(` Pushing to ${PARTITION_COUNT} partitions...`)
|
|
66
|
+
const pushStart = Date.now()
|
|
67
|
+
await pushToPartitions(client, QUEUE_NAME_NEW, PARTITION_COUNT, 'historical')
|
|
68
|
+
const pushTime = Date.now() - pushStart
|
|
69
|
+
console.log(` Push complete: ${PARTITION_COUNT} partitions in ${pushTime}ms`)
|
|
70
|
+
|
|
71
|
+
// 3. Consume all messages in queue mode (no CG) - baseline
|
|
72
|
+
console.log(` Consuming in queue mode (no CG)...`)
|
|
73
|
+
const queueModeStart = Date.now()
|
|
74
|
+
let queueModeCount = 0
|
|
75
|
+
|
|
76
|
+
await client
|
|
77
|
+
.queue(QUEUE_NAME_NEW)
|
|
78
|
+
.concurrency(10)
|
|
79
|
+
.batch(1)
|
|
80
|
+
.wait(false)
|
|
81
|
+
.idleMillis(5000)
|
|
82
|
+
.consume(async msgs => {
|
|
83
|
+
queueModeCount += msgs.length
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const queueModeTime = Date.now() - queueModeStart
|
|
87
|
+
console.log(` Queue mode: consumed ${queueModeCount} messages in ${queueModeTime}ms`)
|
|
88
|
+
|
|
89
|
+
if (queueModeCount !== PARTITION_COUNT) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
message: `Queue mode consumed ${queueModeCount}/${PARTITION_COUNT} messages in ${queueModeTime}ms`
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 4. Register a new CG with 'new' mode
|
|
97
|
+
// The first pop triggers metadata registration + bootstrap (seeds all partition_consumers)
|
|
98
|
+
console.log(` Registering CG 'bootstrap-cg-new' with mode 'new'...`)
|
|
99
|
+
const registerStart = Date.now()
|
|
100
|
+
|
|
101
|
+
const firstPop = await client
|
|
102
|
+
.queue(QUEUE_NAME_NEW)
|
|
103
|
+
.group('bootstrap-cg-new')
|
|
104
|
+
.subscriptionMode('new')
|
|
105
|
+
.batch(1)
|
|
106
|
+
.wait(false)
|
|
107
|
+
.pop()
|
|
108
|
+
|
|
109
|
+
const registerTime = Date.now() - registerStart
|
|
110
|
+
console.log(` CG registered + bootstrap in ${registerTime}ms (first pop returned ${firstPop.length} msgs)`)
|
|
111
|
+
|
|
112
|
+
// First pop should return 0 messages (mode 'new' skips historical)
|
|
113
|
+
if (firstPop.length !== 0) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
message: `CG with mode 'new' should skip historical messages, got ${firstPop.length}`
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 5. Push NEW messages to all 5000 partitions
|
|
121
|
+
console.log(` Pushing new messages to ${PARTITION_COUNT} partitions...`)
|
|
122
|
+
const pushNewStart = Date.now()
|
|
123
|
+
await pushToPartitions(client, QUEUE_NAME_NEW, PARTITION_COUNT, 'new')
|
|
124
|
+
const pushNewTime = Date.now() - pushNewStart
|
|
125
|
+
console.log(` Push new complete: ${PARTITION_COUNT} partitions in ${pushNewTime}ms`)
|
|
126
|
+
|
|
127
|
+
// 6. Consume all new messages with the CG
|
|
128
|
+
// If bootstrap worked, all partitions are already known -> fast consumption
|
|
129
|
+
// If bootstrap didn't work, it would discover one-at-a-time -> very slow
|
|
130
|
+
console.log(` Consuming with CG 'bootstrap-cg-new'...`)
|
|
131
|
+
const cgConsumeStart = Date.now()
|
|
132
|
+
let cgConsumeCount = 0
|
|
133
|
+
|
|
134
|
+
await client
|
|
135
|
+
.queue(QUEUE_NAME_NEW)
|
|
136
|
+
.group('bootstrap-cg-new')
|
|
137
|
+
.subscriptionMode('new')
|
|
138
|
+
.concurrency(10)
|
|
139
|
+
.batch(1)
|
|
140
|
+
.wait(false)
|
|
141
|
+
.idleMillis(5000)
|
|
142
|
+
.consume(async msgs => {
|
|
143
|
+
cgConsumeCount += msgs.length
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const cgConsumeTime = Date.now() - cgConsumeStart
|
|
147
|
+
console.log(` CG consume: ${cgConsumeCount} messages in ${cgConsumeTime}ms`)
|
|
148
|
+
|
|
149
|
+
// 7. Drain new messages in queue mode too (cleanup)
|
|
150
|
+
console.log(` Draining new messages in queue mode...`)
|
|
151
|
+
let queueDrainCount = 0
|
|
152
|
+
await client
|
|
153
|
+
.queue(QUEUE_NAME_NEW)
|
|
154
|
+
.concurrency(10)
|
|
155
|
+
.batch(1)
|
|
156
|
+
.wait(false)
|
|
157
|
+
.idleMillis(5000)
|
|
158
|
+
.consume(async msgs => {
|
|
159
|
+
queueDrainCount += msgs.length
|
|
160
|
+
})
|
|
161
|
+
console.log(` Queue mode drain: ${queueDrainCount} messages`)
|
|
162
|
+
|
|
163
|
+
// 8. Results
|
|
164
|
+
const allConsumed = cgConsumeCount === PARTITION_COUNT
|
|
165
|
+
const bootstrapFast = registerTime < 10000
|
|
166
|
+
|
|
167
|
+
console.log(` ---- Results (subscriptionMode='new') ----`)
|
|
168
|
+
console.log(` Partitions: ${PARTITION_COUNT}`)
|
|
169
|
+
console.log(` Push (historical): ${pushTime}ms`)
|
|
170
|
+
console.log(` Queue mode consume: ${queueModeTime}ms (${queueModeCount} msgs)`)
|
|
171
|
+
console.log(` CG registration: ${registerTime}ms (includes bootstrap)`)
|
|
172
|
+
console.log(` Push (new): ${pushNewTime}ms`)
|
|
173
|
+
console.log(` CG consume: ${cgConsumeTime}ms (${cgConsumeCount} msgs)`)
|
|
174
|
+
console.log(` Bootstrap fast: ${bootstrapFast ? 'YES' : 'NO'} (${registerTime}ms < 10000ms)`)
|
|
175
|
+
|
|
176
|
+
if (!allConsumed) {
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
message: `CG consumed ${cgConsumeCount}/${PARTITION_COUNT} in ${cgConsumeTime}ms (bootstrap: ${registerTime}ms)`
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
success: true,
|
|
185
|
+
message: `[new] ${PARTITION_COUNT} partitions: push=${pushTime}ms, queue_mode=${queueModeTime}ms, bootstrap=${registerTime}ms, cg_consume=${cgConsumeTime}ms`
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Test bootstrap with subscriptionFrom(timestamp)
|
|
191
|
+
* - Push historical messages to 5000 partitions
|
|
192
|
+
* - Record a cutoff timestamp
|
|
193
|
+
* - Register CG with subscriptionFrom(cutoff) (triggers bootstrap via 'timestamp' path)
|
|
194
|
+
* - Push new messages to all partitions
|
|
195
|
+
* - Consume with CG and verify all new messages are received fast
|
|
196
|
+
*/
|
|
197
|
+
export async function testCgBootstrapTimestamp(client) {
|
|
198
|
+
// Cleanup
|
|
199
|
+
try {
|
|
200
|
+
await client.deleteConsumerGroup('bootstrap-cg-ts', true)
|
|
201
|
+
} catch (e) {
|
|
202
|
+
// Ignore
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 1. Create queue
|
|
206
|
+
const queue = await client.queue(QUEUE_NAME_TS).create()
|
|
207
|
+
if (!queue.configured) {
|
|
208
|
+
return { success: false, message: 'Queue not created' }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 2. Push 1 message to each of 5000 partitions (historical messages)
|
|
212
|
+
console.log(` Pushing to ${PARTITION_COUNT} partitions...`)
|
|
213
|
+
const pushStart = Date.now()
|
|
214
|
+
await pushToPartitions(client, QUEUE_NAME_TS, PARTITION_COUNT, 'historical')
|
|
215
|
+
const pushTime = Date.now() - pushStart
|
|
216
|
+
console.log(` Push complete: ${PARTITION_COUNT} partitions in ${pushTime}ms`)
|
|
217
|
+
|
|
218
|
+
// 3. Record cutoff timestamp (after historical, before new messages)
|
|
219
|
+
// Wait before capturing the cutoff to handle clock skew between the
|
|
220
|
+
// client (Date.now) and PostgreSQL (NOW()). Without this, the last
|
|
221
|
+
// push batch may have a PG created_at AFTER the client-side cutoff.
|
|
222
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
223
|
+
const cutoffTimestamp = new Date().toISOString()
|
|
224
|
+
console.log(` Cutoff timestamp: ${cutoffTimestamp}`)
|
|
225
|
+
|
|
226
|
+
// 4. Register a new CG with subscriptionFrom(timestamp)
|
|
227
|
+
// The first pop triggers metadata registration + bootstrap (seeds all partition_consumers)
|
|
228
|
+
console.log(` Registering CG 'bootstrap-cg-ts' with subscriptionFrom('${cutoffTimestamp}')...`)
|
|
229
|
+
const registerStart = Date.now()
|
|
230
|
+
|
|
231
|
+
const firstPop = await client
|
|
232
|
+
.queue(QUEUE_NAME_TS)
|
|
233
|
+
.group('bootstrap-cg-ts')
|
|
234
|
+
.subscriptionFrom(cutoffTimestamp)
|
|
235
|
+
.batch(1)
|
|
236
|
+
.wait(false)
|
|
237
|
+
.pop()
|
|
238
|
+
|
|
239
|
+
const registerTime = Date.now() - registerStart
|
|
240
|
+
console.log(` CG registered + bootstrap in ${registerTime}ms (first pop returned ${firstPop.length} msgs)`)
|
|
241
|
+
|
|
242
|
+
// First pop should return 0 messages (all messages are before the cutoff)
|
|
243
|
+
if (firstPop.length !== 0) {
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
message: `CG with subscriptionFrom should skip historical messages, got ${firstPop.length}`
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 5. Push NEW messages to all 5000 partitions (after cutoff)
|
|
251
|
+
console.log(` Pushing new messages to ${PARTITION_COUNT} partitions...`)
|
|
252
|
+
const pushNewStart = Date.now()
|
|
253
|
+
await pushToPartitions(client, QUEUE_NAME_TS, PARTITION_COUNT, 'new')
|
|
254
|
+
const pushNewTime = Date.now() - pushNewStart
|
|
255
|
+
console.log(` Push new complete: ${PARTITION_COUNT} partitions in ${pushNewTime}ms`)
|
|
256
|
+
|
|
257
|
+
// 6. Consume all new messages with the CG
|
|
258
|
+
// If bootstrap worked, all partitions are already known -> fast consumption
|
|
259
|
+
// If bootstrap didn't work, it would discover one-at-a-time -> very slow
|
|
260
|
+
console.log(` Consuming with CG 'bootstrap-cg-ts'...`)
|
|
261
|
+
const cgConsumeStart = Date.now()
|
|
262
|
+
let cgConsumeCount = 0
|
|
263
|
+
|
|
264
|
+
await client
|
|
265
|
+
.queue(QUEUE_NAME_TS)
|
|
266
|
+
.group('bootstrap-cg-ts')
|
|
267
|
+
.subscriptionFrom(cutoffTimestamp)
|
|
268
|
+
.concurrency(10)
|
|
269
|
+
.batch(1)
|
|
270
|
+
.wait(false)
|
|
271
|
+
.idleMillis(5000)
|
|
272
|
+
.consume(async msgs => {
|
|
273
|
+
cgConsumeCount += msgs.length
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const cgConsumeTime = Date.now() - cgConsumeStart
|
|
277
|
+
console.log(` CG consume: ${cgConsumeCount} messages in ${cgConsumeTime}ms`)
|
|
278
|
+
|
|
279
|
+
// 7. Drain new messages in queue mode too (cleanup)
|
|
280
|
+
console.log(` Draining new messages in queue mode...`)
|
|
281
|
+
let queueDrainCount = 0
|
|
282
|
+
await client
|
|
283
|
+
.queue(QUEUE_NAME_TS)
|
|
284
|
+
.concurrency(10)
|
|
285
|
+
.batch(1)
|
|
286
|
+
.wait(false)
|
|
287
|
+
.idleMillis(5000)
|
|
288
|
+
.consume(async msgs => {
|
|
289
|
+
queueDrainCount += msgs.length
|
|
290
|
+
})
|
|
291
|
+
console.log(` Queue mode drain: ${queueDrainCount} messages`)
|
|
292
|
+
|
|
293
|
+
// 8. Results
|
|
294
|
+
const allConsumed = cgConsumeCount === PARTITION_COUNT
|
|
295
|
+
const bootstrapFast = registerTime < 10000
|
|
296
|
+
|
|
297
|
+
console.log(` ---- Results (subscriptionFrom=timestamp) ----`)
|
|
298
|
+
console.log(` Partitions: ${PARTITION_COUNT}`)
|
|
299
|
+
console.log(` Push (historical): ${pushTime}ms`)
|
|
300
|
+
console.log(` CG registration: ${registerTime}ms (includes bootstrap)`)
|
|
301
|
+
console.log(` Push (new): ${pushNewTime}ms`)
|
|
302
|
+
console.log(` CG consume: ${cgConsumeTime}ms (${cgConsumeCount} msgs)`)
|
|
303
|
+
console.log(` Bootstrap fast: ${bootstrapFast ? 'YES' : 'NO'} (${registerTime}ms < 10000ms)`)
|
|
304
|
+
|
|
305
|
+
if (!allConsumed) {
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
message: `CG consumed ${cgConsumeCount}/${PARTITION_COUNT} in ${cgConsumeTime}ms (bootstrap: ${registerTime}ms)`
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
success: true,
|
|
314
|
+
message: `[timestamp] ${PARTITION_COUNT} partitions: push=${pushTime}ms, bootstrap=${registerTime}ms, cg_consume=${cgConsumeTime}ms`
|
|
315
|
+
}
|
|
316
|
+
}
|
package/test-v2/run.js
CHANGED
|
@@ -11,13 +11,7 @@ import * as transactionTests from './transaction.js'
|
|
|
11
11
|
import * as subscriptionTests from './subscription.js'
|
|
12
12
|
import * as maintenanceTests from './maintenance.js'
|
|
13
13
|
import * as retentionTests from './retention.js'
|
|
14
|
-
import * as
|
|
15
|
-
import * as leaseRenewalTests from './ai_lease_renewal.js'
|
|
16
|
-
import * as resourcesTests from './ai_resources.js'
|
|
17
|
-
import * as bufferingTests from './ai_buffering.js'
|
|
18
|
-
import * as priorityTests from './ai_priority.js'
|
|
19
|
-
import * as ttlRetentionTests from './ai_ttl_retention.js'
|
|
20
|
-
import * as mixedScenariosTests from './ai_mixed_scenarios.js'
|
|
14
|
+
import * as bootstrapTests from './bootstrap.js'
|
|
21
15
|
import { LoadBalancer } from '../client-v2/http/LoadBalancer.js';
|
|
22
16
|
|
|
23
17
|
|
|
@@ -118,17 +112,12 @@ async function main() {
|
|
|
118
112
|
transactionTests,
|
|
119
113
|
subscriptionTests,
|
|
120
114
|
retentionTests,
|
|
121
|
-
maintenanceTests
|
|
115
|
+
maintenanceTests,
|
|
116
|
+
bootstrapTests
|
|
122
117
|
]
|
|
123
118
|
|
|
124
119
|
const aiTests = [
|
|
125
|
-
|
|
126
|
-
leaseRenewalTests,
|
|
127
|
-
resourcesTests,
|
|
128
|
-
bufferingTests,
|
|
129
|
-
priorityTests,
|
|
130
|
-
ttlRetentionTests,
|
|
131
|
-
mixedScenariosTests
|
|
120
|
+
|
|
132
121
|
]
|
|
133
122
|
|
|
134
123
|
const allTests = [...humanTests, ...aiTests]
|