**docs: add detailed concurrency analysis and implementation documentation**
- Introduced `CONCURRENCY_ANALYSIS.md` to outline identified concurrency issues, including statistics handling, index synchronization, and storage contention. - Added `CONCURRENCY_IMPLEMENTATION_SUMMARY.md` to summarize concurrency improvements, such as distributed locking and change log mechanisms. - Created `STORAGE_CONCURRENCY_ANALYSIS.md` to evaluate concurrency risks and applied solutions for different storage adapters (`S3CompatibleStorage`, `FileSystemStorage`, `OPFSStorage`, and `MemoryStorage`). - Updated codebase with changes related to concurrency, including distributed locking, atomic updates, event-driven synchronization, and change log support. - Refactored tests to verify behavior of new concurrency mechanisms, including robust error handling and cleanup functions. **Purpose**: Provides comprehensive documentation and implementation details to ensure robust concurrency handling in multi-instance, high-throughput environments.
This commit is contained in:
parent
0fe1486a89
commit
ac01527a6b
11 changed files with 1558 additions and 80 deletions
|
|
@ -293,6 +293,9 @@ export class HNSWIndexOptimized extends HNSWIndex {
|
|||
private quantizedVectors: Map<string, number[]> = new Map()
|
||||
private memoryUsage: number = 0
|
||||
private vectorCount: number = 0
|
||||
|
||||
// Thread safety for memory usage tracking
|
||||
private memoryUpdateLock: Promise<void> = Promise.resolve()
|
||||
|
||||
constructor(
|
||||
config: Partial<HNSWOptimizedConfig> = {},
|
||||
|
|
@ -321,6 +324,31 @@ export class HNSWIndexOptimized extends HNSWIndex {
|
|||
this.useDiskBasedIndex = this.optimizedConfig.useDiskBasedIndex || false
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread-safe method to update memory usage
|
||||
* @param memoryDelta Change in memory usage (can be negative)
|
||||
* @param vectorCountDelta Change in vector count (can be negative)
|
||||
*/
|
||||
private async updateMemoryUsage(memoryDelta: number, vectorCountDelta: number): Promise<void> {
|
||||
this.memoryUpdateLock = this.memoryUpdateLock.then(async () => {
|
||||
this.memoryUsage = Math.max(0, this.memoryUsage + memoryDelta)
|
||||
this.vectorCount = Math.max(0, this.vectorCount + vectorCountDelta)
|
||||
})
|
||||
await this.memoryUpdateLock
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread-safe method to get current memory usage
|
||||
* @returns Current memory usage and vector count
|
||||
*/
|
||||
private async getMemoryUsageAsync(): Promise<{ memoryUsage: number; vectorCount: number }> {
|
||||
await this.memoryUpdateLock
|
||||
return {
|
||||
memoryUsage: this.memoryUsage,
|
||||
vectorCount: this.vectorCount
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a vector to the index
|
||||
* Uses product quantization if enabled and memory threshold is exceeded
|
||||
|
|
@ -343,14 +371,14 @@ export class HNSWIndexOptimized extends HNSWIndex {
|
|||
const connectionsMemory = this.optimizedConfig.M * this.optimizedConfig.ml * 16 // Estimate for connections
|
||||
const totalMemory = vectorMemory + connectionsMemory
|
||||
|
||||
// Update memory usage estimate
|
||||
this.memoryUsage += totalMemory
|
||||
this.vectorCount++
|
||||
// Update memory usage estimate (thread-safe)
|
||||
await this.updateMemoryUsage(totalMemory, 1)
|
||||
|
||||
// Check if we should switch to product quantization
|
||||
const currentMemoryUsage = await this.getMemoryUsageAsync()
|
||||
if (
|
||||
this.useProductQuantization &&
|
||||
this.memoryUsage > this.optimizedConfig.memoryThreshold! &&
|
||||
currentMemoryUsage.memoryUsage > this.optimizedConfig.memoryThreshold! &&
|
||||
this.productQuantizer &&
|
||||
!this.productQuantizer.getDimension()
|
||||
) {
|
||||
|
|
@ -445,14 +473,15 @@ export class HNSWIndexOptimized extends HNSWIndex {
|
|||
})
|
||||
}
|
||||
|
||||
// Update memory usage estimate
|
||||
if (this.vectorCount > 0) {
|
||||
this.memoryUsage = Math.max(
|
||||
0,
|
||||
this.memoryUsage - this.memoryUsage / this.vectorCount
|
||||
)
|
||||
this.vectorCount--
|
||||
}
|
||||
// Update memory usage estimate (async operation, but don't block removal)
|
||||
this.getMemoryUsageAsync().then((currentMemoryUsage) => {
|
||||
if (currentMemoryUsage.vectorCount > 0) {
|
||||
const memoryPerVector = currentMemoryUsage.memoryUsage / currentMemoryUsage.vectorCount
|
||||
this.updateMemoryUsage(-memoryPerVector, -1)
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('Failed to update memory usage after removal:', error)
|
||||
})
|
||||
|
||||
// Remove the item from the in-memory index
|
||||
return super.removeItem(id)
|
||||
|
|
@ -461,7 +490,7 @@ export class HNSWIndexOptimized extends HNSWIndex {
|
|||
/**
|
||||
* Clear the index
|
||||
*/
|
||||
public override clear(): void {
|
||||
public override async clear(): Promise<void> {
|
||||
// Clear product quantization data
|
||||
if (this.useProductQuantization) {
|
||||
this.quantizedVectors.clear()
|
||||
|
|
@ -471,9 +500,9 @@ export class HNSWIndexOptimized extends HNSWIndex {
|
|||
)
|
||||
}
|
||||
|
||||
// Reset memory usage
|
||||
this.memoryUsage = 0
|
||||
this.vectorCount = 0
|
||||
// Reset memory usage (thread-safe)
|
||||
const currentMemoryUsage = await this.getMemoryUsageAsync()
|
||||
await this.updateMemoryUsage(-currentMemoryUsage.memoryUsage, -currentMemoryUsage.vectorCount)
|
||||
|
||||
// Clear the in-memory index
|
||||
super.clear()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue