fix(8.0): drive query-cap off MemAvailable + floor auto-detected caps
The auto-detected query-limit cap was computed from os.freemem() (MemFree), which excludes reclaimable page cache. On hosts that memory-map large index files the cache holding those pages dominates RAM, so MemFree collapses to a sliver and the cap cratered to its floor on perfectly healthy machines, rejecting legitimate find() calls. - getAvailableMemory() now prefers /proc/meminfo MemAvailable (counts reclaimable cache), falling back to os.freemem() off-Linux, then a 2 GB constant where no OS module is available. - Auto-detected caps (container + free-memory tiers) are floored at MIN_AUTO_QUERY_LIMIT (10_000); a transient low reading can never throttle queries to a near-useless ceiling. Consumer-supplied maxQueryLimit / reservedQueryMemory bypass the floor — an explicit caller knows their box. Also scrubs two stale comments referencing the removed cloud storage adapters and the retired mmap-vector backend: 8.0's native vector provider persists its own .dkann file via getBinaryBlobPath, so the old rootDirectory hook does not apply on this line. Tests: memoryLimits 26/26 (4 new floor regressions), unit 1398/1398, find-limits + db-mvcc + api-parameter-validation 37/37.
This commit is contained in:
parent
c605b34f98
commit
b26d3d42b3
3 changed files with 117 additions and 17 deletions
|
|
@ -27,7 +27,37 @@ const getSystemMemory = (): number => {
|
|||
return 4 * 1024 * 1024 * 1024
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort "memory we can actually use right now", in bytes.
|
||||
*
|
||||
* Prefers `/proc/meminfo` **MemAvailable**, which the kernel computes as the
|
||||
* memory obtainable for a new workload *including reclaimable page cache*. This
|
||||
* matters on hosts that memory-map large index files: the cache holding those
|
||||
* mmap'd pages is instantly reclaimable, yet `os.freemem()` reports only
|
||||
* **MemFree** (the unused sliver), which collapses to near-zero on a warm box.
|
||||
* Driving the auto query-cap off MemFree made the cap crater to its floor on
|
||||
* perfectly healthy machines (BRAINY-QUERYCAP-MISREAD); MemAvailable fixes that.
|
||||
*
|
||||
* Falls back to `os.freemem()` where `/proc/meminfo` is absent (non-Linux), then
|
||||
* to a conservative 2 GB when no OS module is available at all (browser/edge).
|
||||
*
|
||||
* @returns Usable memory in bytes.
|
||||
*/
|
||||
const getAvailableMemory = (): number => {
|
||||
if (fs) {
|
||||
try {
|
||||
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8') as string
|
||||
const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m)
|
||||
if (match) {
|
||||
const bytes = parseInt(match[1], 10) * 1024
|
||||
if (bytes > 0) {
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// /proc/meminfo unreadable (non-Linux, sandboxed) — fall through.
|
||||
}
|
||||
}
|
||||
if (os) {
|
||||
return os.freemem()
|
||||
}
|
||||
|
|
@ -134,6 +164,18 @@ const getContainerMemoryLimit = (): number | null => {
|
|||
*/
|
||||
const MAX_LIMIT_KB_PER_RESULT = 25
|
||||
|
||||
/**
|
||||
* Floor for *auto-detected* query caps (the container- and free-memory tiers).
|
||||
*
|
||||
* Memory probing is a heuristic; a transiently low reading must never silently
|
||||
* throttle legitimate queries to a near-useless ceiling. 10 000 is a generous
|
||||
* working limit that still sits far below any OOM danger (10 000 × 25 KB ≈
|
||||
* 250 MB worst case). It is a floor, not an override: consumer-supplied
|
||||
* `maxQueryLimit` / `reservedQueryMemory` bypass it entirely (Priorities 1–2),
|
||||
* because an explicit caller knows their box better than the probe does.
|
||||
*/
|
||||
const MIN_AUTO_QUERY_LIMIT = 10000
|
||||
|
||||
/**
|
||||
* One-time-per-call-site warning dedup. Keyed on the caller location returned
|
||||
* by `findCallerLocation()` plus the exceeding limit value so the warning fires
|
||||
|
|
@ -226,9 +268,12 @@ export class ValidationConfig {
|
|||
// Reserve 25% for query operations
|
||||
const queryMemory = this.detectedContainerLimit * 0.25
|
||||
|
||||
this.maxLimit = Math.min(
|
||||
100000,
|
||||
Math.floor(queryMemory / (1024 * 1024 * MAX_LIMIT_KB_PER_RESULT)) * 1000
|
||||
this.maxLimit = Math.max(
|
||||
MIN_AUTO_QUERY_LIMIT,
|
||||
Math.min(
|
||||
100000,
|
||||
Math.floor(queryMemory / (1024 * 1024 * MAX_LIMIT_KB_PER_RESULT)) * 1000
|
||||
)
|
||||
)
|
||||
this.limitBasis = 'containerMemory'
|
||||
|
||||
|
|
@ -242,9 +287,12 @@ export class ValidationConfig {
|
|||
// Priority 4: Free memory (fallback, current behavior)
|
||||
const availableMemory = getAvailableMemory()
|
||||
|
||||
this.maxLimit = Math.min(
|
||||
100000,
|
||||
Math.floor(availableMemory / (1024 * 1024 * MAX_LIMIT_KB_PER_RESULT)) * 1000
|
||||
this.maxLimit = Math.max(
|
||||
MIN_AUTO_QUERY_LIMIT,
|
||||
Math.min(
|
||||
100000,
|
||||
Math.floor(availableMemory / (1024 * 1024 * MAX_LIMIT_KB_PER_RESULT)) * 1000
|
||||
)
|
||||
)
|
||||
this.limitBasis = 'freeMemory'
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue