@cmdoss/memwal-sdk 0.6.2 → 0.7.0
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/ARCHITECTURE.md +547 -547
- package/BENCHMARKS.md +238 -238
- package/README.md +181 -181
- package/dist/ai-sdk/tools.d.ts +2 -2
- package/dist/ai-sdk/tools.js +2 -2
- package/dist/client/PersonalDataWallet.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.d.ts +1 -1
- package/dist/client/SimplePDWClient.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.js +16 -7
- package/dist/client/SimplePDWClient.js.map +1 -1
- package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
- package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
- package/dist/client/namespaces/MemoryNamespace.d.ts +27 -0
- package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/MemoryNamespace.js +104 -0
- package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
- package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
- package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js +22 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
- package/dist/graph/GraphService.js +1 -1
- package/dist/graph/GraphService.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/retrieval/MemoryRetrievalService.d.ts +31 -0
- package/dist/retrieval/MemoryRetrievalService.d.ts.map +1 -1
- package/dist/retrieval/MemoryRetrievalService.js +44 -4
- package/dist/retrieval/MemoryRetrievalService.js.map +1 -1
- package/dist/services/EmbeddingService.d.ts +28 -1
- package/dist/services/EmbeddingService.d.ts.map +1 -1
- package/dist/services/EmbeddingService.js +54 -0
- package/dist/services/EmbeddingService.js.map +1 -1
- package/dist/services/IndexManager.d.ts +5 -1
- package/dist/services/IndexManager.d.ts.map +1 -1
- package/dist/services/IndexManager.js +17 -40
- package/dist/services/IndexManager.js.map +1 -1
- package/dist/services/QueryService.js +1 -1
- package/dist/services/QueryService.js.map +1 -1
- package/dist/services/StorageService.d.ts +10 -0
- package/dist/services/StorageService.d.ts.map +1 -1
- package/dist/services/StorageService.js +13 -0
- package/dist/services/StorageService.js.map +1 -1
- package/dist/services/storage/QuiltBatchManager.d.ts +101 -1
- package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
- package/dist/services/storage/QuiltBatchManager.js +410 -20
- package/dist/services/storage/QuiltBatchManager.js.map +1 -1
- package/dist/services/storage/index.d.ts +1 -1
- package/dist/services/storage/index.d.ts.map +1 -1
- package/dist/services/storage/index.js.map +1 -1
- package/dist/utils/LRUCache.d.ts +106 -0
- package/dist/utils/LRUCache.d.ts.map +1 -0
- package/dist/utils/LRUCache.js +281 -0
- package/dist/utils/LRUCache.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/memoryIndexOnChain.d.ts +212 -0
- package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
- package/dist/utils/memoryIndexOnChain.js +312 -0
- package/dist/utils/memoryIndexOnChain.js.map +1 -0
- package/dist/utils/rebuildIndexNode.d.ts +29 -0
- package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
- package/dist/utils/rebuildIndexNode.js +366 -98
- package/dist/utils/rebuildIndexNode.js.map +1 -1
- package/dist/vector/HnswWasmService.d.ts +20 -5
- package/dist/vector/HnswWasmService.d.ts.map +1 -1
- package/dist/vector/HnswWasmService.js +73 -40
- package/dist/vector/HnswWasmService.js.map +1 -1
- package/dist/vector/IHnswService.d.ts +10 -1
- package/dist/vector/IHnswService.d.ts.map +1 -1
- package/dist/vector/IHnswService.js.map +1 -1
- package/dist/vector/NodeHnswService.d.ts +16 -0
- package/dist/vector/NodeHnswService.d.ts.map +1 -1
- package/dist/vector/NodeHnswService.js +84 -5
- package/dist/vector/NodeHnswService.js.map +1 -1
- package/dist/vector/createHnswService.d.ts +1 -1
- package/dist/vector/createHnswService.js +1 -1
- package/dist/vector/index.d.ts +1 -1
- package/dist/vector/index.js +1 -1
- package/package.json +157 -157
- package/src/access/index.ts +8 -8
- package/src/aggregation/index.ts +8 -8
- package/src/ai-sdk/tools.ts +2 -2
- package/src/client/SimplePDWClient.ts +23 -8
- package/src/client/namespaces/EmbeddingsNamespace.ts +1 -1
- package/src/client/namespaces/MemoryNamespace.ts +137 -0
- package/src/client/namespaces/consolidated/AINamespace.ts +2 -2
- package/src/client/namespaces/consolidated/BlockchainNamespace.ts +20 -2
- package/src/client/signers/DappKitSigner.ts +207 -207
- package/src/core/types/index.ts +1 -1
- package/src/generated/pdw/deps/sui/object.ts +12 -12
- package/src/generated/pdw/deps/sui/vec_map.ts +32 -32
- package/src/generated/pdw/memory.ts +1087 -1087
- package/src/generated/pdw/wallet.ts +123 -123
- package/src/generated/utils/index.ts +159 -159
- package/src/graph/GraphService.ts +1 -1
- package/src/index.ts +25 -1
- package/src/permissions/index.ts +9 -9
- package/src/retrieval/MemoryRetrievalService.ts +78 -4
- package/src/services/EmbeddingService.ts +66 -1
- package/src/services/IndexManager.ts +18 -45
- package/src/services/QueryService.ts +1 -1
- package/src/services/StorageService.ts +15 -0
- package/src/services/storage/QuiltBatchManager.ts +492 -22
- package/src/services/storage/index.ts +6 -1
- package/src/utils/LRUCache.ts +378 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/memoryIndexOnChain.ts +507 -0
- package/src/utils/rebuildIndexNode.ts +453 -106
- package/src/vector/HnswWasmService.ts +95 -43
- package/src/vector/IHnswService.ts +10 -1
- package/src/vector/NodeHnswService.ts +103 -5
- package/src/vector/createHnswService.ts +1 -1
- package/src/vector/index.ts +1 -1
- package/src/wallet/index.ts +17 -17
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - ✅ Walrus storage integration
|
|
12
12
|
* - ✅ Near-native performance via WASM
|
|
13
13
|
* - ✅ Safe for Node.js/SSR (uses dynamic import)
|
|
14
|
+
* - ✅ LRU cache with memory limits to prevent OOM
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
// Dynamic import for hnswlib-wasm to avoid bundling issues in Node.js
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
BatchStats,
|
|
36
37
|
VectorError
|
|
37
38
|
} from '../embedding/types';
|
|
39
|
+
import { LRUCache, estimateIndexCacheSize } from '../utils/LRUCache';
|
|
38
40
|
|
|
39
41
|
interface IndexCacheEntry {
|
|
40
42
|
index: HierarchicalNSW;
|
|
@@ -44,6 +46,8 @@ interface IndexCacheEntry {
|
|
|
44
46
|
version: number;
|
|
45
47
|
metadata: Map<number, any>; // vectorId -> metadata
|
|
46
48
|
dimensions: number;
|
|
49
|
+
/** Cached vectors for serialization - only store if needed */
|
|
50
|
+
vectors: Map<number, number[]>;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
interface IndexMetadata {
|
|
@@ -57,24 +61,45 @@ interface IndexMetadata {
|
|
|
57
61
|
lastUpdated: Date;
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
// Memory management constants
|
|
65
|
+
const DEFAULT_MAX_CACHED_INDEXES = 5; // Max number of user indexes to keep in memory
|
|
66
|
+
const DEFAULT_INDEX_TTL_MS = 10 * 60 * 1000; // 10 minutes TTL for idle indexes
|
|
67
|
+
const DEFAULT_MAX_MEMORY_MB = 512; // 512MB max memory for index cache
|
|
68
|
+
const DEFAULT_CLEANUP_INTERVAL_MS = 60 * 1000; // Check every 1 minute
|
|
69
|
+
|
|
60
70
|
/**
|
|
61
71
|
* Browser-compatible HNSW vector indexing service using WebAssembly
|
|
62
72
|
* Drop-in replacement for HnswIndexService with identical API
|
|
73
|
+
*
|
|
74
|
+
* Memory Management:
|
|
75
|
+
* - LRU cache limits number of indexes in memory (default: 5)
|
|
76
|
+
* - TTL-based expiration for idle indexes (default: 10 minutes)
|
|
77
|
+
* - Optional memory limit (default: 512MB)
|
|
78
|
+
* - Automatic cleanup of expired/evicted indexes
|
|
63
79
|
*/
|
|
64
80
|
export class HnswWasmService {
|
|
65
81
|
private hnswlib: HnswlibModule | null = null;
|
|
66
|
-
private readonly indexCache
|
|
82
|
+
private readonly indexCache: LRUCache<IndexCacheEntry>;
|
|
67
83
|
private readonly batchJobs = new Map<string, BatchJob>();
|
|
68
84
|
private readonly config: Required<BatchConfig>;
|
|
69
85
|
private readonly indexConfig: Required<HNSWIndexConfig>;
|
|
70
86
|
private batchProcessor?: ReturnType<typeof setInterval>;
|
|
71
|
-
private cacheCleanup?: ReturnType<typeof setInterval>;
|
|
72
87
|
private initPromise: Promise<void> | null = null;
|
|
73
88
|
|
|
89
|
+
// Memory management settings
|
|
90
|
+
private readonly maxCachedIndexes: number;
|
|
91
|
+
private readonly indexTtlMs: number;
|
|
92
|
+
private readonly maxMemoryBytes: number;
|
|
93
|
+
|
|
74
94
|
constructor(
|
|
75
95
|
private storageService: StorageService,
|
|
76
96
|
indexConfig: Partial<HNSWIndexConfig> = {},
|
|
77
|
-
batchConfig: Partial<BatchConfig> = {}
|
|
97
|
+
batchConfig: Partial<BatchConfig> = {},
|
|
98
|
+
memoryConfig?: {
|
|
99
|
+
maxCachedIndexes?: number;
|
|
100
|
+
indexTtlMs?: number;
|
|
101
|
+
maxMemoryMB?: number;
|
|
102
|
+
}
|
|
78
103
|
) {
|
|
79
104
|
// Default HNSW configuration (matching HnswIndexService)
|
|
80
105
|
this.indexConfig = {
|
|
@@ -94,6 +119,42 @@ export class HnswWasmService {
|
|
|
94
119
|
cacheTtlMs: batchConfig.cacheTtlMs || 30 * 60 * 1000 // 30 minutes
|
|
95
120
|
};
|
|
96
121
|
|
|
122
|
+
// Memory management configuration
|
|
123
|
+
this.maxCachedIndexes = memoryConfig?.maxCachedIndexes ?? DEFAULT_MAX_CACHED_INDEXES;
|
|
124
|
+
this.indexTtlMs = memoryConfig?.indexTtlMs ?? DEFAULT_INDEX_TTL_MS;
|
|
125
|
+
this.maxMemoryBytes = (memoryConfig?.maxMemoryMB ?? DEFAULT_MAX_MEMORY_MB) * 1024 * 1024;
|
|
126
|
+
|
|
127
|
+
// Initialize LRU cache with memory limits
|
|
128
|
+
this.indexCache = new LRUCache<IndexCacheEntry>({
|
|
129
|
+
maxSize: this.maxCachedIndexes,
|
|
130
|
+
ttlMs: this.indexTtlMs,
|
|
131
|
+
cleanupIntervalMs: DEFAULT_CLEANUP_INTERVAL_MS,
|
|
132
|
+
maxMemoryBytes: this.maxMemoryBytes,
|
|
133
|
+
sizeEstimator: (entry) => estimateIndexCacheSize({
|
|
134
|
+
vectors: entry.vectors || new Map(),
|
|
135
|
+
metadata: entry.metadata,
|
|
136
|
+
pendingVectors: entry.pendingVectors,
|
|
137
|
+
}),
|
|
138
|
+
onEvict: (userAddress, entry, reason) => {
|
|
139
|
+
console.log(`🧹 [HnswWasmService] Evicting index for ${userAddress} (reason: ${reason})`);
|
|
140
|
+
// Dispose WASM resources
|
|
141
|
+
if (entry.index) {
|
|
142
|
+
try {
|
|
143
|
+
if (typeof entry.index.free === 'function') {
|
|
144
|
+
entry.index.free();
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
// Ignore cleanup errors
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Remove associated batch job
|
|
151
|
+
this.batchJobs.delete(userAddress);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
console.log(`✅ HnswWasmService initialized with memory limits:`);
|
|
156
|
+
console.log(` Max indexes: ${this.maxCachedIndexes}, TTL: ${this.indexTtlMs / 1000}s, Max memory: ${this.maxMemoryBytes / 1024 / 1024}MB`);
|
|
157
|
+
|
|
97
158
|
// Initialize WASM library asynchronously
|
|
98
159
|
this.initPromise = this.initialize();
|
|
99
160
|
}
|
|
@@ -107,9 +168,8 @@ export class HnswWasmService {
|
|
|
107
168
|
this.hnswlib = await loadHnswlibDynamic();
|
|
108
169
|
console.log('✅ hnswlib-wasm loaded successfully');
|
|
109
170
|
|
|
110
|
-
// Start
|
|
171
|
+
// Start batch processor (cache cleanup is handled by LRUCache)
|
|
111
172
|
this.startBatchProcessor();
|
|
112
|
-
this.startCacheCleanup();
|
|
113
173
|
} catch (error) {
|
|
114
174
|
console.error('❌ Failed to load hnswlib-wasm:', error);
|
|
115
175
|
throw error;
|
|
@@ -142,12 +202,13 @@ export class HnswWasmService {
|
|
|
142
202
|
|
|
143
203
|
console.log(`🔨 Creating new HNSW index for user ${userAddress}`);
|
|
144
204
|
console.log(` Dimensions: ${config.dimension}, M: ${config.m}, efConstruction: ${config.efConstruction}`);
|
|
205
|
+
console.log(` Cache stats: ${this.indexCache.size}/${this.maxCachedIndexes} indexes, ${(this.indexCache.memoryBytes / 1024 / 1024).toFixed(1)}MB`);
|
|
145
206
|
|
|
146
207
|
// Create a new index using WASM (constructor takes: spaceName, numDimensions, autoSaveFilename)
|
|
147
208
|
const index = new this.hnswlib!.HierarchicalNSW(config.spaceType, config.dimension, '');
|
|
148
209
|
index.initIndex(config.maxElements, config.m, config.efConstruction, config.randomSeed);
|
|
149
210
|
|
|
150
|
-
// Create cache entry
|
|
211
|
+
// Create cache entry (LRU cache handles eviction automatically)
|
|
151
212
|
this.indexCache.set(userAddress, {
|
|
152
213
|
index,
|
|
153
214
|
lastModified: new Date(),
|
|
@@ -155,7 +216,8 @@ export class HnswWasmService {
|
|
|
155
216
|
isDirty: false,
|
|
156
217
|
version: 1,
|
|
157
218
|
metadata: new Map(),
|
|
158
|
-
dimensions: config.dimension
|
|
219
|
+
dimensions: config.dimension,
|
|
220
|
+
vectors: new Map(),
|
|
159
221
|
});
|
|
160
222
|
|
|
161
223
|
// Serialize the empty index
|
|
@@ -189,7 +251,7 @@ export class HnswWasmService {
|
|
|
189
251
|
// Validate input
|
|
190
252
|
this.validateVector(vector);
|
|
191
253
|
|
|
192
|
-
// Get or create cache entry
|
|
254
|
+
// Get or create cache entry (LRU cache will evict old entries if needed)
|
|
193
255
|
let cacheEntry = this.indexCache.get(userAddress);
|
|
194
256
|
if (!cacheEntry) {
|
|
195
257
|
console.warn(`No cached index found for user ${userAddress}, will create on first flush`);
|
|
@@ -201,7 +263,8 @@ export class HnswWasmService {
|
|
|
201
263
|
isDirty: true,
|
|
202
264
|
version: 1,
|
|
203
265
|
metadata: new Map(),
|
|
204
|
-
dimensions: vector.length
|
|
266
|
+
dimensions: vector.length,
|
|
267
|
+
vectors: new Map(),
|
|
205
268
|
};
|
|
206
269
|
this.indexCache.set(userAddress, cacheEntry);
|
|
207
270
|
}
|
|
@@ -216,6 +279,8 @@ export class HnswWasmService {
|
|
|
216
279
|
if (metadata) {
|
|
217
280
|
cacheEntry.metadata.set(vectorId, metadata);
|
|
218
281
|
}
|
|
282
|
+
// Also cache the vector for serialization
|
|
283
|
+
cacheEntry.vectors.set(vectorId, vector);
|
|
219
284
|
cacheEntry.isDirty = true;
|
|
220
285
|
cacheEntry.lastModified = new Date();
|
|
221
286
|
|
|
@@ -344,7 +409,8 @@ export class HnswWasmService {
|
|
|
344
409
|
isDirty: false,
|
|
345
410
|
version: 1,
|
|
346
411
|
metadata: new Map(),
|
|
347
|
-
dimensions: this.indexConfig.dimension
|
|
412
|
+
dimensions: this.indexConfig.dimension,
|
|
413
|
+
vectors: new Map(),
|
|
348
414
|
});
|
|
349
415
|
|
|
350
416
|
console.log(`✅ Index loaded successfully for ${userAddress}`);
|
|
@@ -384,30 +450,29 @@ export class HnswWasmService {
|
|
|
384
450
|
/**
|
|
385
451
|
* Get cache statistics
|
|
386
452
|
*/
|
|
387
|
-
getCacheStats(): BatchStats {
|
|
388
|
-
|
|
453
|
+
getCacheStats(): BatchStats & {
|
|
454
|
+
memoryUsageMB: number;
|
|
455
|
+
maxMemoryMB: number;
|
|
456
|
+
maxCachedIndexes: number;
|
|
457
|
+
} {
|
|
389
458
|
let totalPendingVectors = 0;
|
|
390
459
|
|
|
391
|
-
for (const [
|
|
392
|
-
|
|
393
|
-
totalPendingVectors += pendingCount;
|
|
394
|
-
|
|
395
|
-
cacheEntries.push({
|
|
396
|
-
userAddress,
|
|
397
|
-
pendingVectors: pendingCount,
|
|
398
|
-
lastModified: entry.lastModified,
|
|
399
|
-
isDirty: entry.isDirty,
|
|
400
|
-
indexDimensions: entry.dimensions
|
|
401
|
-
});
|
|
460
|
+
for (const [, entry] of this.indexCache.entries()) {
|
|
461
|
+
totalPendingVectors += entry.pendingVectors.size;
|
|
402
462
|
}
|
|
403
463
|
|
|
464
|
+
const lruStats = this.indexCache.getStats();
|
|
465
|
+
|
|
404
466
|
return {
|
|
405
467
|
totalUsers: this.indexCache.size,
|
|
406
468
|
totalPendingVectors,
|
|
407
469
|
activeBatchJobs: this.batchJobs.size,
|
|
408
470
|
cacheHitRate: 0, // TODO: Implement hit rate tracking
|
|
409
471
|
averageBatchSize: totalPendingVectors / Math.max(1, this.indexCache.size),
|
|
410
|
-
averageProcessingTime: 0 // TODO: Implement timing tracking
|
|
472
|
+
averageProcessingTime: 0, // TODO: Implement timing tracking
|
|
473
|
+
memoryUsageMB: lruStats.memoryBytes / 1024 / 1024,
|
|
474
|
+
maxMemoryMB: this.maxMemoryBytes / 1024 / 1024,
|
|
475
|
+
maxCachedIndexes: this.maxCachedIndexes,
|
|
411
476
|
};
|
|
412
477
|
}
|
|
413
478
|
|
|
@@ -451,10 +516,8 @@ export class HnswWasmService {
|
|
|
451
516
|
if (this.batchProcessor) {
|
|
452
517
|
clearInterval(this.batchProcessor);
|
|
453
518
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
this.indexCache.clear();
|
|
519
|
+
// LRU cache cleanup is handled internally, but we should destroy it
|
|
520
|
+
this.indexCache.destroy();
|
|
458
521
|
this.batchJobs.clear();
|
|
459
522
|
console.log('🛑 HnswWasmService destroyed');
|
|
460
523
|
}
|
|
@@ -474,7 +537,8 @@ export class HnswWasmService {
|
|
|
474
537
|
isDirty: false,
|
|
475
538
|
version: 1,
|
|
476
539
|
metadata: new Map(),
|
|
477
|
-
dimensions
|
|
540
|
+
dimensions,
|
|
541
|
+
vectors: new Map(),
|
|
478
542
|
};
|
|
479
543
|
}
|
|
480
544
|
|
|
@@ -498,11 +562,7 @@ export class HnswWasmService {
|
|
|
498
562
|
}, this.config.batchDelayMs);
|
|
499
563
|
}
|
|
500
564
|
|
|
501
|
-
|
|
502
|
-
this.cacheCleanup = setInterval(() => {
|
|
503
|
-
this.cleanupCache();
|
|
504
|
-
}, 5 * 60 * 1000); // Every 5 minutes
|
|
505
|
-
}
|
|
565
|
+
// Note: Cache cleanup is now handled by LRUCache internally
|
|
506
566
|
|
|
507
567
|
private async processBatchJobs(): Promise<void> {
|
|
508
568
|
const now = Date.now();
|
|
@@ -644,15 +704,7 @@ export class HnswWasmService {
|
|
|
644
704
|
return { ids: filteredIds, distances: filteredDistances };
|
|
645
705
|
}
|
|
646
706
|
|
|
647
|
-
|
|
648
|
-
const now = Date.now();
|
|
649
|
-
for (const [userAddress, entry] of this.indexCache.entries()) {
|
|
650
|
-
if (now - entry.lastModified.getTime() > this.config.cacheTtlMs) {
|
|
651
|
-
console.debug(`🧹 Removing stale cache entry for user ${userAddress}`);
|
|
652
|
-
this.indexCache.delete(userAddress);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
}
|
|
707
|
+
// Note: cleanupCache is now handled by LRUCache internally
|
|
656
708
|
|
|
657
709
|
private validateVector(vector: number[]): void {
|
|
658
710
|
if (!Array.isArray(vector) || vector.length === 0) {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Configuration for HNSW index
|
|
15
15
|
*/
|
|
16
16
|
export interface IHnswIndexConfig {
|
|
17
|
-
/** Vector dimension (e.g.,
|
|
17
|
+
/** Vector dimension (e.g., 3072 for text-embedding-004) */
|
|
18
18
|
dimension: number;
|
|
19
19
|
/** Maximum number of elements in the index */
|
|
20
20
|
maxElements?: number;
|
|
@@ -203,6 +203,15 @@ export interface HnswServiceConfig {
|
|
|
203
203
|
indexDirectory?: string;
|
|
204
204
|
/** Walrus backup configuration (optional) */
|
|
205
205
|
walrusBackup?: WalrusBackupConfig;
|
|
206
|
+
/** Memory management configuration for LRU cache */
|
|
207
|
+
memoryConfig?: {
|
|
208
|
+
/** Maximum number of cached indexes (default: 5) */
|
|
209
|
+
maxCachedIndexes?: number;
|
|
210
|
+
/** TTL for cached indexes in ms (default: 10 minutes) */
|
|
211
|
+
indexTtlMs?: number;
|
|
212
|
+
/** Maximum memory in MB (default: 512MB) */
|
|
213
|
+
maxMemoryMB?: number;
|
|
214
|
+
};
|
|
206
215
|
}
|
|
207
216
|
|
|
208
217
|
// ==================== Environment Detection ====================
|
|
@@ -22,6 +22,12 @@ import type {
|
|
|
22
22
|
IHnswBatchStats,
|
|
23
23
|
WalrusBackupConfig
|
|
24
24
|
} from './IHnswService';
|
|
25
|
+
import { LRUCache, estimateIndexCacheSize } from '../utils/LRUCache';
|
|
26
|
+
|
|
27
|
+
// Memory management constants
|
|
28
|
+
const DEFAULT_MAX_CACHED_INDEXES = 5;
|
|
29
|
+
const DEFAULT_INDEX_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
30
|
+
const DEFAULT_MAX_MEMORY_MB = 512;
|
|
25
31
|
|
|
26
32
|
// Dynamic import types for hnswlib-node
|
|
27
33
|
type HierarchicalNSW = any;
|
|
@@ -43,7 +49,7 @@ interface IndexCacheEntry {
|
|
|
43
49
|
*/
|
|
44
50
|
export class NodeHnswService implements IHnswService {
|
|
45
51
|
private hnswlib: any = null;
|
|
46
|
-
private readonly indexCache
|
|
52
|
+
private readonly indexCache: LRUCache<IndexCacheEntry>;
|
|
47
53
|
private readonly indexConfig: Required<IHnswIndexConfig>;
|
|
48
54
|
private readonly indexDirectory: string;
|
|
49
55
|
private readonly walrusConfig?: WalrusBackupConfig;
|
|
@@ -52,6 +58,11 @@ export class NodeHnswService implements IHnswService {
|
|
|
52
58
|
private _isInitialized = false;
|
|
53
59
|
private walrusClient: ReturnType<typeof this.createWalrusClient> | null = null;
|
|
54
60
|
|
|
61
|
+
// Memory management config
|
|
62
|
+
private readonly maxCachedIndexes: number;
|
|
63
|
+
private readonly indexTtlMs: number;
|
|
64
|
+
private readonly maxMemoryMB: number;
|
|
65
|
+
|
|
55
66
|
/**
|
|
56
67
|
* Create Walrus client using @mysten/walrus SDK
|
|
57
68
|
*/
|
|
@@ -83,12 +94,86 @@ export class NodeHnswService implements IHnswService {
|
|
|
83
94
|
this.indexDirectory = config.indexDirectory || './.pdw-indexes';
|
|
84
95
|
this.walrusConfig = config.walrusBackup;
|
|
85
96
|
|
|
97
|
+
// Memory management configuration
|
|
98
|
+
this.maxCachedIndexes = config.memoryConfig?.maxCachedIndexes || DEFAULT_MAX_CACHED_INDEXES;
|
|
99
|
+
this.indexTtlMs = config.memoryConfig?.indexTtlMs || DEFAULT_INDEX_TTL_MS;
|
|
100
|
+
this.maxMemoryMB = config.memoryConfig?.maxMemoryMB || DEFAULT_MAX_MEMORY_MB;
|
|
101
|
+
|
|
102
|
+
// Initialize LRU cache with eviction callback
|
|
103
|
+
this.indexCache = new LRUCache<IndexCacheEntry>({
|
|
104
|
+
maxSize: this.maxCachedIndexes,
|
|
105
|
+
ttlMs: this.indexTtlMs,
|
|
106
|
+
maxMemoryBytes: this.maxMemoryMB * 1024 * 1024,
|
|
107
|
+
sizeEstimator: (entry) => estimateIndexCacheSize({
|
|
108
|
+
vectors: new Map(), // Vectors are stored in the native index, not JS
|
|
109
|
+
metadata: entry.metadata,
|
|
110
|
+
pendingVectors: entry.pendingVectors
|
|
111
|
+
}),
|
|
112
|
+
onEvict: (userAddress, entry, reason) => {
|
|
113
|
+
console.log(`[NodeHnswService] Evicting index for ${userAddress.slice(0, 10)}... (reason: ${reason})`);
|
|
114
|
+
// Save dirty index before eviction
|
|
115
|
+
if (entry.isDirty) {
|
|
116
|
+
this.saveIndexSync(userAddress, entry).catch(err => {
|
|
117
|
+
console.error(`[NodeHnswService] Failed to save evicted index: ${err}`);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
console.log(`[NodeHnswService] Initialized with LRU cache: maxIndexes=${this.maxCachedIndexes}, ttl=${this.indexTtlMs}ms, maxMemory=${this.maxMemoryMB}MB`);
|
|
124
|
+
|
|
86
125
|
// Initialize Walrus SDK client for cloud backup
|
|
87
126
|
if (this.walrusConfig?.enabled) {
|
|
88
127
|
this.walrusClient = this.createWalrusClient('testnet');
|
|
89
128
|
}
|
|
90
129
|
}
|
|
91
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Save index synchronously (for eviction callback)
|
|
133
|
+
*/
|
|
134
|
+
private async saveIndexSync(userAddress: string, entry: IndexCacheEntry): Promise<void> {
|
|
135
|
+
try {
|
|
136
|
+
const indexPath = this.getIndexPath(userAddress);
|
|
137
|
+
entry.index.writeIndex(indexPath);
|
|
138
|
+
|
|
139
|
+
const fs = await import('fs/promises');
|
|
140
|
+
const metadataPath = indexPath + '.meta.json';
|
|
141
|
+
const metadataObj: Record<number, any> = {};
|
|
142
|
+
for (const [k, v] of entry.metadata) {
|
|
143
|
+
metadataObj[k] = v;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await fs.writeFile(metadataPath, JSON.stringify({
|
|
147
|
+
version: entry.version,
|
|
148
|
+
dimensions: entry.dimensions,
|
|
149
|
+
metadata: metadataObj,
|
|
150
|
+
walrusBlobId: entry.walrusBlobId,
|
|
151
|
+
}));
|
|
152
|
+
|
|
153
|
+
console.log(`[NodeHnswService] Saved evicted index for ${userAddress.slice(0, 10)}...`);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error(`[NodeHnswService] Error saving evicted index:`, error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get cache statistics for monitoring
|
|
161
|
+
*/
|
|
162
|
+
getCacheStats(): {
|
|
163
|
+
totalUsers: number;
|
|
164
|
+
maxCachedIndexes: number;
|
|
165
|
+
memoryUsageMB: number;
|
|
166
|
+
maxMemoryMB: number;
|
|
167
|
+
} {
|
|
168
|
+
const stats = this.indexCache.getStats();
|
|
169
|
+
return {
|
|
170
|
+
totalUsers: stats.size,
|
|
171
|
+
maxCachedIndexes: this.maxCachedIndexes,
|
|
172
|
+
memoryUsageMB: stats.memoryBytes / (1024 * 1024),
|
|
173
|
+
maxMemoryMB: this.maxMemoryMB,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
92
177
|
/**
|
|
93
178
|
* Initialize hnswlib-node (dynamic import)
|
|
94
179
|
*/
|
|
@@ -145,7 +230,7 @@ export class NodeHnswService implements IHnswService {
|
|
|
145
230
|
}
|
|
146
231
|
|
|
147
232
|
private async processPendingBatches(): Promise<void> {
|
|
148
|
-
for (const [userAddress, entry] of this.indexCache) {
|
|
233
|
+
for (const [userAddress, entry] of this.indexCache.entries()) {
|
|
149
234
|
if (entry.isDirty && entry.pendingVectors.size > 0) {
|
|
150
235
|
try {
|
|
151
236
|
await this.flushBatch(userAddress);
|
|
@@ -341,8 +426,20 @@ export class NodeHnswService implements IHnswService {
|
|
|
341
426
|
|
|
342
427
|
async flushBatch(userAddress: string): Promise<void> {
|
|
343
428
|
const entry = this.indexCache.get(userAddress);
|
|
344
|
-
if (!entry
|
|
345
|
-
console.log(`[NodeHnswService] flushBatch: No
|
|
429
|
+
if (!entry) {
|
|
430
|
+
console.log(`[NodeHnswService] flushBatch: No cache entry for ${userAddress.slice(0, 10)}..., skipping`);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Even if no pending vectors, we may need to save metadata that was added
|
|
435
|
+
if (entry.pendingVectors.size === 0) {
|
|
436
|
+
// Still save index to persist any metadata that was added
|
|
437
|
+
if (entry.isDirty || entry.metadata.size > 0) {
|
|
438
|
+
console.log(`[NodeHnswService] flushBatch: No pending vectors but isDirty=${entry.isDirty}, metadata.size=${entry.metadata.size}, saving index...`);
|
|
439
|
+
await this.saveIndex(userAddress);
|
|
440
|
+
} else {
|
|
441
|
+
console.log(`[NodeHnswService] flushBatch: No pending vectors and nothing to save for ${userAddress.slice(0, 10)}...`);
|
|
442
|
+
}
|
|
346
443
|
return;
|
|
347
444
|
}
|
|
348
445
|
|
|
@@ -728,7 +825,8 @@ export class NodeHnswService implements IHnswService {
|
|
|
728
825
|
this.batchProcessor = undefined;
|
|
729
826
|
}
|
|
730
827
|
|
|
731
|
-
|
|
828
|
+
// Destroy LRU cache (triggers cleanup and stops internal timers)
|
|
829
|
+
this.indexCache.destroy();
|
|
732
830
|
this._isInitialized = false;
|
|
733
831
|
console.log('[NodeHnswService] Service destroyed');
|
|
734
832
|
}
|
|
@@ -30,7 +30,7 @@ let instanceCount = 0;
|
|
|
30
30
|
* @example
|
|
31
31
|
* ```typescript
|
|
32
32
|
* const hnswService = await createHnswService({
|
|
33
|
-
* indexConfig: { dimension:
|
|
33
|
+
* indexConfig: { dimension: 3072 }
|
|
34
34
|
* });
|
|
35
35
|
*
|
|
36
36
|
* await hnswService.addVector(userAddress, vectorId, embedding);
|
package/src/vector/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* ```typescript
|
|
9
9
|
* // Auto-detect environment and create appropriate service
|
|
10
10
|
* import { createHnswService } from 'personal-data-wallet-sdk/vector';
|
|
11
|
-
* const service = await createHnswService({ indexConfig: { dimension:
|
|
11
|
+
* const service = await createHnswService({ indexConfig: { dimension: 3072 } });
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
14
|
|
package/src/wallet/index.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wallet Module
|
|
3
|
-
*
|
|
4
|
-
* @deprecated This module is deprecated. Use CapabilityService and ContextNamespace instead.
|
|
5
|
-
* - MainWalletService → CapabilityService (from services/CapabilityService)
|
|
6
|
-
* - ContextWalletService → ContextNamespace (from client/namespaces/ContextNamespace)
|
|
7
|
-
*
|
|
8
|
-
* These services will be removed in the next major version.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/** @deprecated Use CapabilityService instead */
|
|
12
|
-
export { MainWalletService } from './MainWalletService';
|
|
13
|
-
export type { MainWalletServiceConfig } from './MainWalletService';
|
|
14
|
-
|
|
15
|
-
/** @deprecated Use ContextNamespace instead */
|
|
16
|
-
export { ContextWalletService } from './ContextWalletService';
|
|
17
|
-
export type { ContextWalletServiceConfig } from './ContextWalletService';
|
|
1
|
+
/**
|
|
2
|
+
* Wallet Module
|
|
3
|
+
*
|
|
4
|
+
* @deprecated This module is deprecated. Use CapabilityService and ContextNamespace instead.
|
|
5
|
+
* - MainWalletService → CapabilityService (from services/CapabilityService)
|
|
6
|
+
* - ContextWalletService → ContextNamespace (from client/namespaces/ContextNamespace)
|
|
7
|
+
*
|
|
8
|
+
* These services will be removed in the next major version.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** @deprecated Use CapabilityService instead */
|
|
12
|
+
export { MainWalletService } from './MainWalletService';
|
|
13
|
+
export type { MainWalletServiceConfig } from './MainWalletService';
|
|
14
|
+
|
|
15
|
+
/** @deprecated Use ContextNamespace instead */
|
|
16
|
+
export { ContextWalletService } from './ContextWalletService';
|
|
17
|
+
export type { ContextWalletServiceConfig } from './ContextWalletService';
|