v0.4.67
v0.4.67
Performance: reference mock rows inherit their getters from a shared prototype (cache ON)
v0.4.66 made ReferenceQueryBuilder.createMockTargetRow() share one PropertyDescriptorMap
per (schema, alias, navigation path) signature — but every row still paid one
Object.defineProperties(row, descriptors), i.e. O(columns + relations) property
definitions per mock row, per query build. On a real workload that stayed the single largest
CPU item of query building: a V8 profile of the gopass-eshop order/create burst showed
createMockTargetRow self time at ~6 ms per order request (~40 % of the request's remaining
event-loop CPU) with the descriptor cache already on.
The descriptors' getters already read all per-row state through this-bound symbol slots, so
the shared object can be the row's prototype instead: MockRowCache now caches one
prototype object per signature (the descriptors applied once), and a mock row is
Object.create(prototype) plus its two own state slots — O(1) per row.
Behaviour
- Switch OFF (default): unchanged memory profile — a fresh prototype is built and discarded per
row, exactly as the fresh descriptor set was before. - Switch ON: identical query results; measured on the gopass-eshop checkout burst (30–100
concurrentorder/create, one process): 42–45 → 92–104 orders/s (v0.4.66 descriptor
cache alone: 51–64), sequential p50 99 → 76 ms, GC self time −77 %. MockRowCache.getOrBuildnow builds/returnsobjectprototypes (wasPropertyDescriptorMap).
setEnabled/isEnabled/diagnostics/resetare unchanged.
Internal consumers
Three selection resolvers used to recognise a navigation mock by probing its OWN properties
(Object.getOwnPropertyNames / Object.getOwnPropertyDescriptor / a plain-object prototype
check). Inherited getters are invisible to those, so they now go through
findFirstGetterKey (first enumerable getter, own or inherited) and isReferenceMockRow
(own state-slot marker). Root mocks (_createMockRow) keep their own getters and pass the same
probes unchanged.