fix: prevent cache pollution from empty-vector objects in HNSW lazy mode
Add comprehensive validation to detect and reject cached objects with empty vectors (vector: []) that can occur during HNSW adaptive caching with large datasets. Invalid cached objects are now automatically removed from cache and reloaded from storage. Changes: - GCS: Add empty vector validation (vector.length === 0 check) - GCS: Prevent caching nodes with empty vectors in saveNode() - GCS: Add detailed cached object structure logging - GCS: Auto-remove invalid cached objects - S3: Add same validation logic as GCS - Both: Fall through to storage loading when cache is invalid This fixes silent failures where getNode() returned null despite valid data existing in cloud storage. The root cause was empty-vector objects from HNSW's lazy-loading mode being cached and returned as valid, causing entity retrieval to fail after container restarts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
64fcaf3bf8
commit
91d233e1da
2 changed files with 74 additions and 17 deletions
|
|
@ -1094,11 +1094,36 @@ export class S3CompatibleStorage extends BaseStorage {
|
|||
type: typeof cached
|
||||
})
|
||||
|
||||
// CRITICAL FIX: Only return cached value if it's valid (not null/undefined)
|
||||
// CRITICAL FIX (v3.37.8): Validate cached object before returning
|
||||
if (cached !== undefined && cached !== null) {
|
||||
prodLog.info(`[getNode] ✅ Cache HIT - returning cached node for ${id.substring(0, 8)}...`)
|
||||
this.logger.trace(`Cache hit for node ${id}`)
|
||||
return cached
|
||||
// Log cached object structure to diagnose incomplete objects
|
||||
prodLog.info(`[getNode] Cached object structure:`, {
|
||||
hasId: !!cached.id,
|
||||
idMatches: cached.id === id,
|
||||
hasVector: !!cached.vector,
|
||||
vectorLength: cached.vector?.length,
|
||||
hasConnections: !!cached.connections,
|
||||
connectionsType: typeof cached.connections,
|
||||
objectKeys: Object.keys(cached || {})
|
||||
})
|
||||
|
||||
// Validate cached object has required fields (including non-empty vector!)
|
||||
if (!cached.id || !cached.vector || !Array.isArray(cached.vector) || cached.vector.length === 0) {
|
||||
prodLog.error(`[getNode] ❌ INVALID cached object for ${id.substring(0, 8)}...:`, {
|
||||
reason: !cached.id ? 'missing id' :
|
||||
!cached.vector ? 'missing vector' :
|
||||
!Array.isArray(cached.vector) ? 'vector not array' :
|
||||
cached.vector.length === 0 ? 'vector is empty array' :
|
||||
'unknown'
|
||||
})
|
||||
prodLog.error(`[getNode] Removing invalid object from cache and loading from S3`)
|
||||
this.nodeCache.delete(id)
|
||||
// Fall through to load from S3
|
||||
} else {
|
||||
prodLog.info(`[getNode] ✅ Valid cached object - returning`)
|
||||
this.logger.trace(`Cache hit for node ${id}`)
|
||||
return cached
|
||||
}
|
||||
} else if (cached === null) {
|
||||
prodLog.warn(`[getNode] ⚠️ Cache contains NULL for ${id.substring(0, 8)}... - ignoring and loading from S3`)
|
||||
} else {
|
||||
|
|
@ -1169,12 +1194,12 @@ export class S3CompatibleStorage extends BaseStorage {
|
|||
level: parsedNode.level || 0
|
||||
}
|
||||
|
||||
// CRITICAL FIX: Only cache valid nodes (never cache null)
|
||||
if (node && node.id && node.vector && Array.isArray(node.vector)) {
|
||||
// CRITICAL FIX: Only cache valid nodes with non-empty vectors (never cache null or empty)
|
||||
if (node && node.id && node.vector && Array.isArray(node.vector) && node.vector.length > 0) {
|
||||
this.nodeCache.set(id, node)
|
||||
prodLog.info(`[getNode] 💾 Cached node ${id.substring(0, 8)}... successfully`)
|
||||
} else {
|
||||
prodLog.warn(`[getNode] ⚠️ NOT caching invalid node for ${id.substring(0, 8)}...`)
|
||||
prodLog.warn(`[getNode] ⚠️ NOT caching invalid node for ${id.substring(0, 8)}... (missing id/vector or empty vector)`)
|
||||
}
|
||||
|
||||
this.logger.trace(`Successfully retrieved node ${id}`)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue