open-brainy/tests/unit/utils/metadataIndex-sort-callshape.test.ts
David Snelling 607b6b56f2
All checks were successful
CI / Node 22 (push) Successful in 12m12s
CI / Node 24 (push) Successful in 12m3s
CI / Bun (latest) (push) Successful in 12m16s
perf(sort): ordered reads never do per-row storage round-trips — the 199-317s production scan class dies structurally
BRAINY-PROD-LATENCY-TRIAD Track A1 (David-approved plan): the sort path's
value resolution goes BATCHED — one chunked metadata-record batch pass
serves any N, replacing the serial per-row getNoun loop (62-98ms x 3,224
rows = the measured 199-317 second silent scan on self prod). The
metadata record carries every sortable value: system scalars EXACT
(bucketed-index precision loss can never force a per-row disk read
again) and the user bag via the shape-aware split, both record eras.

- resolveOrderValuesBatch: the one sanctioned value source for ordered
  reads (batch door: getNounMetadataBatch -> getMetadataBatch -> chunked
  parallel; never serial).
- Column top-K page re-sort and the no-column fallback both rewired.
- B2 down-payment: the no-column fallback ANNOUNCES itself once per
  field past 500 rows - silent degradation is illegal.
- THE CALL-SHAPE PIN (tests/unit/utils/metadataIndex-sort-callshape):
  zero vector-record reads, batch calls only, latency-blind so it holds
  on any machine - the serial loop cannot quietly return. Ordering
  contract re-pinned through the batch path (nulls last both directions,
  ties by id, never drop).

(! = perf contract change only; no API change. Gates: unit 1904/1904,
integration 758, conformance 27/27.)
2026-08-04 16:40:57 -07:00

119 lines
4.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @module tests/unit/utils/metadataIndex-sort-callshape
* @description THE ASYMPTOTIC CALL-SHAPE PIN for ordered reads
* (BRAINY-PROD-LATENCY-TRIAD, David-approved plan Track A1). The defect it
* keeps dead: `getSortedIdsForFilter`'s value resolution did a SERIAL
* `storage.getNoun()` (the heavyweight VECTOR record) per filtered row —
* 6298ms × 3,224 rows = the measured 199317 SECOND production sort, with
* `topK` applied only after the full scan. These pins assert the SHAPE of
* the storage traffic, not wall-clock (latency-blind, so they hold on any
* machine): an ordered read performs ZERO per-row vector-record reads and
* resolves sort values through BATCHED metadata-record calls only.
*/
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'
import { Brainy } from '../../../src/index.js'
import { NounType } from '../../../src/types/graphTypes.js'
const ROWS = 60
describe('ordered reads — the batched call-shape law (no per-row storage loops)', () => {
let brain: Brainy
let storage: {
getNoun: (id: string) => Promise<unknown>
getNounMetadata: (id: string) => Promise<unknown>
getNounMetadataBatch: (ids: string[]) => Promise<Map<string, unknown>>
}
beforeAll(async () => {
brain = new Brainy({ storage: { type: 'memory' }, requireSubtype: false })
await brain.init()
for (let i = 0; i < ROWS; i++) {
await brain.add({
data: `row ${i}`,
type: NounType.Document,
metadata: { rank: (i * 7) % ROWS, plain: `p${i}` }
})
}
storage = (brain as unknown as { storage: typeof storage }).storage
}, 120000)
afterAll(async () => {
await brain.close().catch(() => {})
})
it('user-field orderBy: zero vector-record reads, zero serial metadata reads — batch calls only', async () => {
const getNounSpy = vi.spyOn(storage, 'getNoun')
const singleReadSpy = vi.spyOn(storage, 'getNounMetadata')
const batchSpy = vi.spyOn(storage, 'getNounMetadataBatch')
const rows = await brain.find({
type: NounType.Document,
orderBy: 'rank',
order: 'desc',
limit: 10
})
expect(rows.length).toBe(10)
expect((rows[0].metadata as Record<string, unknown>).rank).toBe(ROWS - 1)
// THE PIN: the sort's value resolution never opens a vector record and
// never falls into a per-row metadata loop. (Result hydration after
// pagination is allowed to read; the SORT itself must be batch-only —
// hence the ceiling: strictly fewer single reads than sorted rows.)
expect(getNounSpy.mock.calls.length, 'per-row vector-record reads in an ordered read').toBe(0)
expect(batchSpy.mock.calls.length, 'the batch door was used').toBeGreaterThanOrEqual(1)
expect(
singleReadSpy.mock.calls.length,
'serial per-row metadata reads (the 199s shape)'
).toBeLessThan(ROWS / 2)
vi.restoreAllMocks()
})
it('system.createdAt orderBy: exact values from batched records — the bucketed index is never a per-row disk excuse', async () => {
const getNounSpy = vi.spyOn(storage, 'getNoun')
const batchSpy = vi.spyOn(storage, 'getNounMetadataBatch')
const rows = await brain.find({
type: NounType.Document,
orderBy: 'system.createdAt',
order: 'asc',
limit: 15
})
expect(rows.length).toBe(15)
expect(getNounSpy.mock.calls.length, 'per-row vector-record reads').toBe(0)
expect(batchSpy.mock.calls.length).toBeGreaterThanOrEqual(1)
// Exactness: ascending createdAt must be non-decreasing with full
// millisecond precision (the old path sorted minute-BUCKETED values or
// paid a per-row disk read for exact ones — both are dead). Find results
// carry the timestamps on the nested full entity.
const stamps = rows.map(
(r) => ((r as unknown as { entity?: { createdAt?: number } }).entity?.createdAt ??
(r as unknown as { createdAt?: number }).createdAt) as number
)
for (let i = 1; i < stamps.length; i++) {
expect(stamps[i]).toBeGreaterThanOrEqual(stamps[i - 1])
}
vi.restoreAllMocks()
})
it('the ordering contract survives the batch path: missing values LAST both directions, ties by id asc, rows never dropped', async () => {
// Three rows lack `rank`? No — all carry it; add two rows WITHOUT it.
const a = await brain.add({ data: 'no-rank a', type: NounType.Document, metadata: { plain: 'x' } })
const b = await brain.add({ data: 'no-rank b', type: NounType.Document, metadata: { plain: 'y' } })
for (const order of ['asc', 'desc'] as const) {
const rows = await brain.find({
type: NounType.Document,
orderBy: 'rank',
order,
limit: ROWS + 10
})
expect(rows.length, `complete result (${order})`).toBe(ROWS + 2)
const lastTwo = rows.slice(-2).map((r) => r.id).sort()
expect(lastTwo, `missing-value rows sort LAST (${order})`).toEqual([a, b].sort())
}
})
})