@sparkleideas/plugins 3.0.0-alpha.10
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/README.md +401 -0
- package/__tests__/collection-manager.test.ts +332 -0
- package/__tests__/dependency-graph.test.ts +434 -0
- package/__tests__/enhanced-plugin-registry.test.ts +488 -0
- package/__tests__/plugin-registry.test.ts +368 -0
- package/__tests__/ruvector-bridge.test.ts +2429 -0
- package/__tests__/ruvector-integration.test.ts +1602 -0
- package/__tests__/ruvector-migrations.test.ts +1099 -0
- package/__tests__/ruvector-quantization.test.ts +846 -0
- package/__tests__/ruvector-streaming.test.ts +1088 -0
- package/__tests__/sdk.test.ts +325 -0
- package/__tests__/security.test.ts +348 -0
- package/__tests__/utils/ruvector-test-utils.ts +860 -0
- package/examples/plugin-creator/index.ts +636 -0
- package/examples/plugin-creator/plugin-creator.test.ts +312 -0
- package/examples/ruvector/README.md +288 -0
- package/examples/ruvector/attention-patterns.ts +394 -0
- package/examples/ruvector/basic-usage.ts +288 -0
- package/examples/ruvector/docker-compose.yml +75 -0
- package/examples/ruvector/gnn-analysis.ts +501 -0
- package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
- package/examples/ruvector/init-db.sql +119 -0
- package/examples/ruvector/quantization.ts +680 -0
- package/examples/ruvector/self-learning.ts +447 -0
- package/examples/ruvector/semantic-search.ts +576 -0
- package/examples/ruvector/streaming-large-data.ts +507 -0
- package/examples/ruvector/transactions.ts +594 -0
- package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
- package/examples/ruvector-plugins/index.ts +79 -0
- package/examples/ruvector-plugins/intent-router.ts +354 -0
- package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
- package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
- package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
- package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
- package/examples/ruvector-plugins/shared/index.ts +20 -0
- package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
- package/examples/ruvector-plugins/sona-learning.ts +445 -0
- package/package.json +97 -0
- package/src/collections/collection-manager.ts +661 -0
- package/src/collections/index.ts +56 -0
- package/src/collections/official/index.ts +1040 -0
- package/src/core/base-plugin.ts +416 -0
- package/src/core/plugin-interface.ts +215 -0
- package/src/hooks/index.ts +685 -0
- package/src/index.ts +378 -0
- package/src/integrations/agentic-flow.ts +743 -0
- package/src/integrations/index.ts +88 -0
- package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
- package/src/integrations/ruvector/attention-advanced.ts +1040 -0
- package/src/integrations/ruvector/attention-executor.ts +782 -0
- package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
- package/src/integrations/ruvector/attention.ts +1063 -0
- package/src/integrations/ruvector/gnn.ts +3050 -0
- package/src/integrations/ruvector/hyperbolic.ts +1948 -0
- package/src/integrations/ruvector/index.ts +394 -0
- package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
- package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
- package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
- package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
- package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
- package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
- package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
- package/src/integrations/ruvector/migrations/index.ts +35 -0
- package/src/integrations/ruvector/migrations/migrations.ts +647 -0
- package/src/integrations/ruvector/quantization.ts +2036 -0
- package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
- package/src/integrations/ruvector/self-learning.ts +2376 -0
- package/src/integrations/ruvector/streaming.ts +1737 -0
- package/src/integrations/ruvector/types.ts +1945 -0
- package/src/providers/index.ts +643 -0
- package/src/registry/dependency-graph.ts +568 -0
- package/src/registry/enhanced-plugin-registry.ts +994 -0
- package/src/registry/plugin-registry.ts +604 -0
- package/src/sdk/index.ts +563 -0
- package/src/security/index.ts +594 -0
- package/src/types/index.ts +446 -0
- package/src/workers/index.ts +700 -0
- package/tmp.json +0 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +23 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuVector PostgreSQL Bridge - Streaming Large Data Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates:
|
|
5
|
+
* - Streaming millions of vectors efficiently
|
|
6
|
+
* - Processing with backpressure handling
|
|
7
|
+
* - Monitoring progress and throughput
|
|
8
|
+
* - Memory-efficient batch processing
|
|
9
|
+
*
|
|
10
|
+
* Run with: npx ts-node examples/ruvector/streaming-large-data.ts
|
|
11
|
+
*
|
|
12
|
+
* @module @sparkleideas/plugins/examples/ruvector/streaming-large-data
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
createRuVectorBridge,
|
|
17
|
+
type RuVectorBridge,
|
|
18
|
+
type VectorRecord,
|
|
19
|
+
} from '../../src/integrations/ruvector/index.js';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Configuration
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
const config = {
|
|
26
|
+
connection: {
|
|
27
|
+
host: process.env.POSTGRES_HOST || 'localhost',
|
|
28
|
+
port: parseInt(process.env.POSTGRES_PORT || '5432', 10),
|
|
29
|
+
database: process.env.POSTGRES_DB || 'vectors',
|
|
30
|
+
user: process.env.POSTGRES_USER || 'postgres',
|
|
31
|
+
password: process.env.POSTGRES_PASSWORD || 'postgres',
|
|
32
|
+
},
|
|
33
|
+
dimensions: 128,
|
|
34
|
+
// Adjust these for your machine's resources
|
|
35
|
+
totalVectors: 100000, // Total vectors to process
|
|
36
|
+
batchSize: 1000, // Vectors per batch
|
|
37
|
+
maxConcurrentBatches: 5, // Max batches in flight
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Progress Tracking
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
interface ProgressStats {
|
|
45
|
+
totalProcessed: number;
|
|
46
|
+
totalErrors: number;
|
|
47
|
+
startTime: number;
|
|
48
|
+
lastBatchTime: number;
|
|
49
|
+
batchDurations: number[];
|
|
50
|
+
memoryUsage: number[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class ProgressTracker {
|
|
54
|
+
private stats: ProgressStats;
|
|
55
|
+
private totalTarget: number;
|
|
56
|
+
private lastPrintTime: number = 0;
|
|
57
|
+
|
|
58
|
+
constructor(totalTarget: number) {
|
|
59
|
+
this.totalTarget = totalTarget;
|
|
60
|
+
this.stats = {
|
|
61
|
+
totalProcessed: 0,
|
|
62
|
+
totalErrors: 0,
|
|
63
|
+
startTime: Date.now(),
|
|
64
|
+
lastBatchTime: Date.now(),
|
|
65
|
+
batchDurations: [],
|
|
66
|
+
memoryUsage: [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
recordBatch(count: number, durationMs: number, errors: number = 0): void {
|
|
71
|
+
this.stats.totalProcessed += count;
|
|
72
|
+
this.stats.totalErrors += errors;
|
|
73
|
+
this.stats.batchDurations.push(durationMs);
|
|
74
|
+
this.stats.lastBatchTime = Date.now();
|
|
75
|
+
|
|
76
|
+
// Track memory usage
|
|
77
|
+
const memUsage = process.memoryUsage();
|
|
78
|
+
this.stats.memoryUsage.push(memUsage.heapUsed);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getProgress(): number {
|
|
82
|
+
return this.stats.totalProcessed / this.totalTarget;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getElapsedMs(): number {
|
|
86
|
+
return Date.now() - this.stats.startTime;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getThroughput(): number {
|
|
90
|
+
const elapsedSec = this.getElapsedMs() / 1000;
|
|
91
|
+
return elapsedSec > 0 ? this.stats.totalProcessed / elapsedSec : 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getAverageBatchDuration(): number {
|
|
95
|
+
if (this.stats.batchDurations.length === 0) return 0;
|
|
96
|
+
return this.stats.batchDurations.reduce((a, b) => a + b, 0) / this.stats.batchDurations.length;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getMemoryUsageMB(): number {
|
|
100
|
+
const memUsage = process.memoryUsage();
|
|
101
|
+
return memUsage.heapUsed / (1024 * 1024);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getETA(): number {
|
|
105
|
+
const throughput = this.getThroughput();
|
|
106
|
+
if (throughput <= 0) return Infinity;
|
|
107
|
+
const remaining = this.totalTarget - this.stats.totalProcessed;
|
|
108
|
+
return remaining / throughput;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
print(): void {
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
// Throttle printing to every 2 seconds
|
|
114
|
+
if (now - this.lastPrintTime < 2000 && this.stats.totalProcessed < this.totalTarget) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.lastPrintTime = now;
|
|
118
|
+
|
|
119
|
+
const progress = (this.getProgress() * 100).toFixed(1);
|
|
120
|
+
const throughput = this.getThroughput().toFixed(0);
|
|
121
|
+
const eta = this.getETA();
|
|
122
|
+
const etaStr = eta === Infinity ? 'N/A' : `${eta.toFixed(0)}s`;
|
|
123
|
+
const memory = this.getMemoryUsageMB().toFixed(1);
|
|
124
|
+
const avgBatch = this.getAverageBatchDuration().toFixed(1);
|
|
125
|
+
|
|
126
|
+
const bar = this.createProgressBar(this.getProgress(), 30);
|
|
127
|
+
|
|
128
|
+
console.log(
|
|
129
|
+
` ${bar} ${progress}% | ` +
|
|
130
|
+
`${this.stats.totalProcessed.toLocaleString()}/${this.totalTarget.toLocaleString()} | ` +
|
|
131
|
+
`${throughput} vec/s | ` +
|
|
132
|
+
`ETA: ${etaStr} | ` +
|
|
133
|
+
`Mem: ${memory}MB | ` +
|
|
134
|
+
`Errors: ${this.stats.totalErrors}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private createProgressBar(progress: number, width: number): string {
|
|
139
|
+
const filled = Math.floor(progress * width);
|
|
140
|
+
const empty = width - filled;
|
|
141
|
+
return '[' + '='.repeat(filled) + '>'.slice(0, empty > 0 ? 1 : 0) + ' '.repeat(Math.max(0, empty - 1)) + ']';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getSummary(): {
|
|
145
|
+
totalProcessed: number;
|
|
146
|
+
totalErrors: number;
|
|
147
|
+
durationMs: number;
|
|
148
|
+
throughput: number;
|
|
149
|
+
avgBatchDuration: number;
|
|
150
|
+
peakMemoryMB: number;
|
|
151
|
+
} {
|
|
152
|
+
return {
|
|
153
|
+
totalProcessed: this.stats.totalProcessed,
|
|
154
|
+
totalErrors: this.stats.totalErrors,
|
|
155
|
+
durationMs: this.getElapsedMs(),
|
|
156
|
+
throughput: this.getThroughput(),
|
|
157
|
+
avgBatchDuration: this.getAverageBatchDuration(),
|
|
158
|
+
peakMemoryMB: Math.max(...this.stats.memoryUsage) / (1024 * 1024),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// Vector Generator (Simulated Data Source)
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Async generator that produces vectors in batches.
|
|
169
|
+
* In production, this could read from files, APIs, or databases.
|
|
170
|
+
*/
|
|
171
|
+
async function* vectorGenerator(
|
|
172
|
+
total: number,
|
|
173
|
+
batchSize: number,
|
|
174
|
+
dimensions: number
|
|
175
|
+
): AsyncGenerator<VectorRecord[], void, unknown> {
|
|
176
|
+
let generated = 0;
|
|
177
|
+
|
|
178
|
+
while (generated < total) {
|
|
179
|
+
const currentBatchSize = Math.min(batchSize, total - generated);
|
|
180
|
+
const batch: VectorRecord[] = [];
|
|
181
|
+
|
|
182
|
+
for (let i = 0; i < currentBatchSize; i++) {
|
|
183
|
+
const id = `vec_${generated + i}`;
|
|
184
|
+
|
|
185
|
+
// Generate random normalized vector
|
|
186
|
+
const embedding = new Array(dimensions);
|
|
187
|
+
let sumSq = 0;
|
|
188
|
+
for (let d = 0; d < dimensions; d++) {
|
|
189
|
+
embedding[d] = Math.random() * 2 - 1;
|
|
190
|
+
sumSq += embedding[d] * embedding[d];
|
|
191
|
+
}
|
|
192
|
+
const magnitude = Math.sqrt(sumSq);
|
|
193
|
+
for (let d = 0; d < dimensions; d++) {
|
|
194
|
+
embedding[d] /= magnitude;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
batch.push({
|
|
198
|
+
id,
|
|
199
|
+
embedding,
|
|
200
|
+
metadata: {
|
|
201
|
+
batchIndex: Math.floor(generated / batchSize),
|
|
202
|
+
itemIndex: i,
|
|
203
|
+
timestamp: new Date().toISOString(),
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
generated += currentBatchSize;
|
|
209
|
+
|
|
210
|
+
// Simulate some async delay (e.g., network latency, file I/O)
|
|
211
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
212
|
+
|
|
213
|
+
yield batch;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ============================================================================
|
|
218
|
+
// Backpressure Handler
|
|
219
|
+
// ============================================================================
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Semaphore for controlling concurrency with backpressure.
|
|
223
|
+
*/
|
|
224
|
+
class Semaphore {
|
|
225
|
+
private permits: number;
|
|
226
|
+
private waitQueue: Array<() => void> = [];
|
|
227
|
+
|
|
228
|
+
constructor(permits: number) {
|
|
229
|
+
this.permits = permits;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async acquire(): Promise<void> {
|
|
233
|
+
if (this.permits > 0) {
|
|
234
|
+
this.permits--;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return new Promise<void>(resolve => {
|
|
239
|
+
this.waitQueue.push(resolve);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
release(): void {
|
|
244
|
+
if (this.waitQueue.length > 0) {
|
|
245
|
+
const next = this.waitQueue.shift()!;
|
|
246
|
+
next();
|
|
247
|
+
} else {
|
|
248
|
+
this.permits++;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
get availablePermits(): number {
|
|
253
|
+
return this.permits;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ============================================================================
|
|
258
|
+
// Streaming Processor
|
|
259
|
+
// ============================================================================
|
|
260
|
+
|
|
261
|
+
interface ProcessorConfig {
|
|
262
|
+
bridge: RuVectorBridge;
|
|
263
|
+
collectionName: string;
|
|
264
|
+
batchSize: number;
|
|
265
|
+
maxConcurrency: number;
|
|
266
|
+
onProgress?: (processed: number, total: number) => void;
|
|
267
|
+
onError?: (error: Error, batch: VectorRecord[]) => void;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Process streaming data with backpressure and concurrency control.
|
|
272
|
+
*/
|
|
273
|
+
async function processStream(
|
|
274
|
+
generator: AsyncGenerator<VectorRecord[], void, unknown>,
|
|
275
|
+
config: ProcessorConfig,
|
|
276
|
+
tracker: ProgressTracker
|
|
277
|
+
): Promise<void> {
|
|
278
|
+
const semaphore = new Semaphore(config.maxConcurrency);
|
|
279
|
+
const pendingBatches: Promise<void>[] = [];
|
|
280
|
+
|
|
281
|
+
for await (const batch of generator) {
|
|
282
|
+
// Apply backpressure - wait if too many batches in flight
|
|
283
|
+
await semaphore.acquire();
|
|
284
|
+
|
|
285
|
+
const batchPromise = (async () => {
|
|
286
|
+
const startTime = performance.now();
|
|
287
|
+
let errors = 0;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
// Insert batch
|
|
291
|
+
await config.bridge.insertBatch(config.collectionName, batch);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
errors = batch.length;
|
|
294
|
+
config.onError?.(error as Error, batch);
|
|
295
|
+
} finally {
|
|
296
|
+
const duration = performance.now() - startTime;
|
|
297
|
+
tracker.recordBatch(batch.length, duration, errors);
|
|
298
|
+
tracker.print();
|
|
299
|
+
semaphore.release();
|
|
300
|
+
}
|
|
301
|
+
})();
|
|
302
|
+
|
|
303
|
+
pendingBatches.push(batchPromise);
|
|
304
|
+
|
|
305
|
+
// Periodically clean up resolved promises
|
|
306
|
+
if (pendingBatches.length > config.maxConcurrency * 2) {
|
|
307
|
+
await Promise.all(pendingBatches.splice(0, pendingBatches.length));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Wait for all remaining batches
|
|
312
|
+
await Promise.all(pendingBatches);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ============================================================================
|
|
316
|
+
// Main Example
|
|
317
|
+
// ============================================================================
|
|
318
|
+
|
|
319
|
+
async function main(): Promise<void> {
|
|
320
|
+
console.log('RuVector PostgreSQL Bridge - Streaming Large Data Example');
|
|
321
|
+
console.log('============================================================\n');
|
|
322
|
+
|
|
323
|
+
console.log('Configuration:');
|
|
324
|
+
console.log(` Total vectors: ${config.totalVectors.toLocaleString()}`);
|
|
325
|
+
console.log(` Batch size: ${config.batchSize.toLocaleString()}`);
|
|
326
|
+
console.log(` Max concurrent batches: ${config.maxConcurrentBatches}`);
|
|
327
|
+
console.log(` Vector dimensions: ${config.dimensions}`);
|
|
328
|
+
console.log();
|
|
329
|
+
|
|
330
|
+
const bridge: RuVectorBridge = createRuVectorBridge({
|
|
331
|
+
connectionString: `postgresql://${config.connection.user}:${config.connection.password}@${config.connection.host}:${config.connection.port}/${config.connection.database}`,
|
|
332
|
+
poolSize: config.maxConcurrentBatches + 2,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
await bridge.connect();
|
|
337
|
+
console.log('Connected to PostgreSQL\n');
|
|
338
|
+
|
|
339
|
+
// ========================================================================
|
|
340
|
+
// 1. Create Collection
|
|
341
|
+
// ========================================================================
|
|
342
|
+
console.log('1. Creating collection...');
|
|
343
|
+
|
|
344
|
+
await bridge.createCollection('large_dataset', {
|
|
345
|
+
dimensions: config.dimensions,
|
|
346
|
+
distanceMetric: 'cosine',
|
|
347
|
+
indexType: 'hnsw',
|
|
348
|
+
indexParams: {
|
|
349
|
+
m: 16,
|
|
350
|
+
efConstruction: 64,
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
console.log(' Collection created\n');
|
|
354
|
+
|
|
355
|
+
// ========================================================================
|
|
356
|
+
// 2. Stream and Insert Data
|
|
357
|
+
// ========================================================================
|
|
358
|
+
console.log('2. Streaming and inserting vectors...');
|
|
359
|
+
console.log(' ' + '-'.repeat(70));
|
|
360
|
+
|
|
361
|
+
const tracker = new ProgressTracker(config.totalVectors);
|
|
362
|
+
const generator = vectorGenerator(config.totalVectors, config.batchSize, config.dimensions);
|
|
363
|
+
|
|
364
|
+
const errorLog: Array<{ error: Error; batchSize: number }> = [];
|
|
365
|
+
|
|
366
|
+
await processStream(generator, {
|
|
367
|
+
bridge,
|
|
368
|
+
collectionName: 'large_dataset',
|
|
369
|
+
batchSize: config.batchSize,
|
|
370
|
+
maxConcurrency: config.maxConcurrentBatches,
|
|
371
|
+
onError: (error, batch) => {
|
|
372
|
+
errorLog.push({ error, batchSize: batch.length });
|
|
373
|
+
},
|
|
374
|
+
}, tracker);
|
|
375
|
+
|
|
376
|
+
// Final progress update
|
|
377
|
+
tracker.print();
|
|
378
|
+
console.log();
|
|
379
|
+
|
|
380
|
+
// ========================================================================
|
|
381
|
+
// 3. Summary Statistics
|
|
382
|
+
// ========================================================================
|
|
383
|
+
console.log('3. Processing Summary');
|
|
384
|
+
console.log(' ' + '-'.repeat(50));
|
|
385
|
+
|
|
386
|
+
const summary = tracker.getSummary();
|
|
387
|
+
console.log(` Total processed: ${summary.totalProcessed.toLocaleString()} vectors`);
|
|
388
|
+
console.log(` Total errors: ${summary.totalErrors}`);
|
|
389
|
+
console.log(` Duration: ${(summary.durationMs / 1000).toFixed(2)} seconds`);
|
|
390
|
+
console.log(` Throughput: ${summary.throughput.toFixed(0)} vectors/second`);
|
|
391
|
+
console.log(` Avg batch duration: ${summary.avgBatchDuration.toFixed(2)}ms`);
|
|
392
|
+
console.log(` Peak memory: ${summary.peakMemoryMB.toFixed(2)} MB`);
|
|
393
|
+
|
|
394
|
+
if (errorLog.length > 0) {
|
|
395
|
+
console.log(`\n Errors encountered: ${errorLog.length}`);
|
|
396
|
+
errorLog.slice(0, 3).forEach((e, i) => {
|
|
397
|
+
console.log(` ${i + 1}. ${e.error.message} (batch size: ${e.batchSize})`);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
console.log();
|
|
401
|
+
|
|
402
|
+
// ========================================================================
|
|
403
|
+
// 4. Verify Data
|
|
404
|
+
// ========================================================================
|
|
405
|
+
console.log('4. Verifying inserted data...');
|
|
406
|
+
console.log(' ' + '-'.repeat(50));
|
|
407
|
+
|
|
408
|
+
const stats = await bridge.getCollectionStats('large_dataset');
|
|
409
|
+
console.log(` Collection stats:`);
|
|
410
|
+
console.log(` Vector count: ${stats.vectorCount.toLocaleString()}`);
|
|
411
|
+
console.log(` Dimensions: ${stats.dimensions}`);
|
|
412
|
+
console.log(` Index type: ${stats.indexType}`);
|
|
413
|
+
console.log(` Index size: ${(stats.indexSizeBytes / (1024 * 1024)).toFixed(2)} MB`);
|
|
414
|
+
console.log();
|
|
415
|
+
|
|
416
|
+
// ========================================================================
|
|
417
|
+
// 5. Test Search Performance
|
|
418
|
+
// ========================================================================
|
|
419
|
+
console.log('5. Testing search performance...');
|
|
420
|
+
console.log(' ' + '-'.repeat(50));
|
|
421
|
+
|
|
422
|
+
// Generate random query vector
|
|
423
|
+
const queryVector = Array.from({ length: config.dimensions }, () => Math.random() * 2 - 1);
|
|
424
|
+
const magnitude = Math.sqrt(queryVector.reduce((s, v) => s + v * v, 0));
|
|
425
|
+
queryVector.forEach((_, i) => queryVector[i] /= magnitude);
|
|
426
|
+
|
|
427
|
+
// Warm up
|
|
428
|
+
await bridge.search('large_dataset', queryVector, { k: 10 });
|
|
429
|
+
|
|
430
|
+
// Benchmark
|
|
431
|
+
const searchIterations = 100;
|
|
432
|
+
const searchTimes: number[] = [];
|
|
433
|
+
|
|
434
|
+
for (let i = 0; i < searchIterations; i++) {
|
|
435
|
+
const start = performance.now();
|
|
436
|
+
await bridge.search('large_dataset', queryVector, { k: 10 });
|
|
437
|
+
searchTimes.push(performance.now() - start);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
searchTimes.sort((a, b) => a - b);
|
|
441
|
+
const avgSearch = searchTimes.reduce((a, b) => a + b, 0) / searchTimes.length;
|
|
442
|
+
const p50 = searchTimes[Math.floor(searchTimes.length * 0.5)];
|
|
443
|
+
const p95 = searchTimes[Math.floor(searchTimes.length * 0.95)];
|
|
444
|
+
const p99 = searchTimes[Math.floor(searchTimes.length * 0.99)];
|
|
445
|
+
|
|
446
|
+
console.log(` Search performance (${searchIterations} iterations, k=10):`);
|
|
447
|
+
console.log(` Average: ${avgSearch.toFixed(2)}ms`);
|
|
448
|
+
console.log(` P50: ${p50.toFixed(2)}ms`);
|
|
449
|
+
console.log(` P95: ${p95.toFixed(2)}ms`);
|
|
450
|
+
console.log(` P99: ${p99.toFixed(2)}ms`);
|
|
451
|
+
console.log(` QPS: ${(1000 / avgSearch).toFixed(0)}`);
|
|
452
|
+
console.log();
|
|
453
|
+
|
|
454
|
+
// ========================================================================
|
|
455
|
+
// 6. Streaming Read (Export)
|
|
456
|
+
// ========================================================================
|
|
457
|
+
console.log('6. Streaming read (simulated export)...');
|
|
458
|
+
console.log(' ' + '-'.repeat(50));
|
|
459
|
+
|
|
460
|
+
// In production, you would use COPY or cursors for efficient streaming
|
|
461
|
+
const exportBatchSize = 10000;
|
|
462
|
+
let exportedCount = 0;
|
|
463
|
+
const exportStart = performance.now();
|
|
464
|
+
|
|
465
|
+
// Simulate streaming export using offset/limit pagination
|
|
466
|
+
// Note: For production, use database cursors for better performance
|
|
467
|
+
const sampleIds = await bridge.search('large_dataset', queryVector, {
|
|
468
|
+
k: Math.min(50000, config.totalVectors),
|
|
469
|
+
includeMetadata: false,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
exportedCount = sampleIds.length;
|
|
473
|
+
const exportDuration = performance.now() - exportStart;
|
|
474
|
+
|
|
475
|
+
console.log(` Exported ${exportedCount.toLocaleString()} vectors in ${exportDuration.toFixed(2)}ms`);
|
|
476
|
+
console.log(` Export throughput: ${(exportedCount / (exportDuration / 1000)).toFixed(0)} vectors/second`);
|
|
477
|
+
console.log();
|
|
478
|
+
|
|
479
|
+
// ========================================================================
|
|
480
|
+
// 7. Cleanup
|
|
481
|
+
// ========================================================================
|
|
482
|
+
console.log('7. Cleanup (optional)...');
|
|
483
|
+
console.log(' ' + '-'.repeat(50));
|
|
484
|
+
|
|
485
|
+
// Uncomment to drop the collection
|
|
486
|
+
// await bridge.dropCollection('large_dataset');
|
|
487
|
+
// console.log(' Collection dropped');
|
|
488
|
+
|
|
489
|
+
console.log(' Skipping collection drop (uncomment to enable)');
|
|
490
|
+
|
|
491
|
+
// ========================================================================
|
|
492
|
+
// Done
|
|
493
|
+
// ========================================================================
|
|
494
|
+
console.log('\n' + '='.repeat(60));
|
|
495
|
+
console.log('Streaming large data example completed!');
|
|
496
|
+
console.log('='.repeat(60));
|
|
497
|
+
|
|
498
|
+
} catch (error) {
|
|
499
|
+
console.error('Error:', error);
|
|
500
|
+
throw error;
|
|
501
|
+
} finally {
|
|
502
|
+
await bridge.disconnect();
|
|
503
|
+
console.log('\nDisconnected from PostgreSQL.');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
main().catch(console.error);
|