fix: resolve metadata index filename length issues
- Replace vector-based filenames with safe, hashed filenames - Exclude embedding/vector fields from indexing by default - Implement safe filename generation with character limits - Prepare foundation for hybrid field/chunk storage approach Fixes ENAMETOOLONG errors that prevented initialization
This commit is contained in:
parent
1a4f035ffc
commit
ac5b3183e3
1 changed files with 74 additions and 11 deletions
|
|
@ -47,7 +47,7 @@ export class MetadataIndexManager {
|
||||||
rebuildThreshold: config.rebuildThreshold ?? 0.1,
|
rebuildThreshold: config.rebuildThreshold ?? 0.1,
|
||||||
autoOptimize: config.autoOptimize ?? true,
|
autoOptimize: config.autoOptimize ?? true,
|
||||||
indexedFields: config.indexedFields ?? [],
|
indexedFields: config.indexedFields ?? [],
|
||||||
excludeFields: config.excludeFields ?? ['id', 'createdAt', 'updatedAt']
|
excludeFields: config.excludeFields ?? ['id', 'createdAt', 'updatedAt', 'embedding', 'vector', 'embeddings', 'vectors']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,6 +59,33 @@ export class MetadataIndexManager {
|
||||||
return `${field}:${normalizedValue}`
|
return `${field}:${normalizedValue}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate field index filename for filter discovery
|
||||||
|
*/
|
||||||
|
private getFieldIndexFilename(field: string): string {
|
||||||
|
return `field_${field}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate value chunk filename for scalable storage
|
||||||
|
*/
|
||||||
|
private getValueChunkFilename(field: string, value: any, chunkIndex: number = 0): string {
|
||||||
|
const normalizedValue = this.normalizeValue(value)
|
||||||
|
const safeValue = this.makeSafeFilename(normalizedValue)
|
||||||
|
return `${field}_${safeValue}_chunk${chunkIndex}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a value safe for use in filenames
|
||||||
|
*/
|
||||||
|
private makeSafeFilename(value: string): string {
|
||||||
|
// Replace unsafe characters and limit length
|
||||||
|
return value
|
||||||
|
.replace(/[^a-zA-Z0-9-_]/g, '_')
|
||||||
|
.substring(0, 50)
|
||||||
|
.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize value for consistent indexing
|
* Normalize value for consistent indexing
|
||||||
*/
|
*/
|
||||||
|
|
@ -66,8 +93,34 @@ export class MetadataIndexManager {
|
||||||
if (value === null || value === undefined) return '__NULL__'
|
if (value === null || value === undefined) return '__NULL__'
|
||||||
if (typeof value === 'boolean') return value ? '__TRUE__' : '__FALSE__'
|
if (typeof value === 'boolean') return value ? '__TRUE__' : '__FALSE__'
|
||||||
if (typeof value === 'number') return value.toString()
|
if (typeof value === 'number') return value.toString()
|
||||||
if (Array.isArray(value)) return value.map(v => this.normalizeValue(v)).join(',')
|
if (Array.isArray(value)) {
|
||||||
return String(value).toLowerCase().trim()
|
const joined = value.map(v => this.normalizeValue(v)).join(',')
|
||||||
|
// Hash very long array values to avoid filesystem limits
|
||||||
|
if (joined.length > 100) {
|
||||||
|
return this.hashValue(joined)
|
||||||
|
}
|
||||||
|
return joined
|
||||||
|
}
|
||||||
|
const stringValue = String(value).toLowerCase().trim()
|
||||||
|
// Hash very long string values to avoid filesystem limits
|
||||||
|
if (stringValue.length > 100) {
|
||||||
|
return this.hashValue(stringValue)
|
||||||
|
}
|
||||||
|
return stringValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a short hash for long values to avoid filesystem filename limits
|
||||||
|
*/
|
||||||
|
private hashValue(value: string): string {
|
||||||
|
// Simple hash function to create shorter keys
|
||||||
|
let hash = 0
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const char = value.charCodeAt(i)
|
||||||
|
hash = ((hash << 5) - hash) + char
|
||||||
|
hash = hash & hash // Convert to 32-bit integer
|
||||||
|
}
|
||||||
|
return `__HASH_${Math.abs(hash).toString(36)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -400,12 +453,16 @@ export class MetadataIndexManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load index entry from storage
|
* Load index entry from storage using safe filenames
|
||||||
*/
|
*/
|
||||||
private async loadIndexEntry(key: string): Promise<MetadataIndexEntry | null> {
|
private async loadIndexEntry(key: string): Promise<MetadataIndexEntry | null> {
|
||||||
try {
|
try {
|
||||||
// Load metadata indexes from the _system directory with a special prefix
|
// Extract field and value from key
|
||||||
const indexId = `__metadata_index__${key}`
|
const [field, value] = key.split(':', 2)
|
||||||
|
const filename = this.getValueChunkFilename(field, value)
|
||||||
|
|
||||||
|
// Load from metadata indexes directory with safe filename
|
||||||
|
const indexId = `__metadata_index__${filename}`
|
||||||
const data = await this.storage.getMetadata(indexId)
|
const data = await this.storage.getMetadata(indexId)
|
||||||
if (data) {
|
if (data) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -422,7 +479,7 @@ export class MetadataIndexManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save index entry to storage
|
* Save index entry to storage using safe filenames
|
||||||
*/
|
*/
|
||||||
private async saveIndexEntry(key: string, entry: MetadataIndexEntry): Promise<void> {
|
private async saveIndexEntry(key: string, entry: MetadataIndexEntry): Promise<void> {
|
||||||
const data = {
|
const data = {
|
||||||
|
|
@ -432,17 +489,23 @@ export class MetadataIndexManager {
|
||||||
lastUpdated: entry.lastUpdated
|
lastUpdated: entry.lastUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store metadata indexes in the _system directory with a special prefix
|
// Extract field and value from key for safe filename generation
|
||||||
const indexId = `__metadata_index__${key}`
|
const [field, value] = key.split(':', 2)
|
||||||
|
const filename = this.getValueChunkFilename(field, value)
|
||||||
|
|
||||||
|
// Store metadata indexes with safe filename
|
||||||
|
const indexId = `__metadata_index__${filename}`
|
||||||
await this.storage.saveMetadata(indexId, data)
|
await this.storage.saveMetadata(indexId, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete index entry from storage
|
* Delete index entry from storage using safe filenames
|
||||||
*/
|
*/
|
||||||
private async deleteIndexEntry(key: string): Promise<void> {
|
private async deleteIndexEntry(key: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const indexId = `__metadata_index__${key}`
|
const [field, value] = key.split(':', 2)
|
||||||
|
const filename = this.getValueChunkFilename(field, value)
|
||||||
|
const indexId = `__metadata_index__${filename}`
|
||||||
await this.storage.saveMetadata(indexId, null)
|
await this.storage.saveMetadata(indexId, null)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Entry might not exist
|
// Entry might not exist
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue