/* * 커널 인제스트 인터페이스 부하 실측(가정 아닌 측정) — Face1/커널 동기 인터페이스의 처리량 특성. * apply(이벤트 흡수) · snapshot(전체 투영) · buildEntityDeltas(브로드캐스팅 payload, 't1') 의 비용을 규모별로 잰다. * 결론은 design/integration/ingest-scale.md §커널 인터페이스에 스펙으로 반영. * (순수 커널+호스트 매핑, shell 무관 → node:test.) */ import { test } from 'node:test' import assert from 'node:assert/strict' import { StateProjector } from '@operato/twin-kernel' import { objectEvent, BIZSTEP, DISP } from '@operato/ops-contract' import { buildEntityDeltas } from '../shared/entity-delta.ts' const T0 = '2026-07-19T00:00:00.000Z' const model = (nNodes: number) => ({ locations: Array.from({ length: nNodes }, (_, i) => ({ id: `bin-${i}`, type: 'storage', capacity: 100000 })), equipment: [] }) function envelope(epc: string, location: string): any { const ev = objectEvent({ eventTime: T0, action: 'OBSERVE' as any, bizStep: BIZSTEP.storing, disposition: DISP.sellable, epcList: [epc], readPoint: location }) return { eventId: 'x', eventType: `epcis.${ev.type}`, eventTime: T0, tenantId: 't', data: ev } } test('bench: apply 처리량(이벤트 흡수)', () => { const nNodes = 200 const M = 200_000 const proj = new StateProjector(model(nNodes)) const t0 = performance.now() for (let i = 0; i < M; i++) proj.apply(envelope(`urn:epc:id:sscc:0614141.${i}`, `bin-${i % nNodes}`)) const dt = performance.now() - t0 console.log(` [apply] ${M.toLocaleString()} events in ${dt.toFixed(0)}ms = ${Math.round((M / dt) * 1000).toLocaleString()} ev/s`) assert.ok(dt > 0) }) test('bench: snapshot 비용 vs item 수 (전체 O(N) 재구성)', () => { for (const N of [1000, 10000, 50000]) { const proj = new StateProjector(model(200)) for (let i = 0; i < N; i++) proj.apply(envelope(`urn:epc:id:sscc:0614141.${i}`, `bin-${i % 200}`)) const iters = 200 const t0 = performance.now() for (let k = 0; k < iters; k++) proj.snapshot() const per = (performance.now() - t0) / iters console.log(` [snapshot] @ ${N.toLocaleString()} items: ${per.toFixed(3)}ms/call → 최대 ${Math.round(1000 / per).toLocaleString()} snapshot/s`) } }) test('bench: buildEntityDeltas 비용 vs 엔티티 수 (전 엔티티 매번 방출)', () => { for (const N of [100, 1000, 5000]) { const proj = new StateProjector(model(N)) for (let i = 0; i < N * 5; i++) proj.apply(envelope(`urn:epc:id:sscc:0614141.${i}`, `bin-${i % N}`)) const snap = proj.snapshot() const iters = 200 const t0 = performance.now() for (let k = 0; k < iters; k++) buildEntityDeltas(snap, 't1') const per = (performance.now() - t0) / iters console.log(` [buildEntityDeltas] @ ${N.toLocaleString()} locations: ${per.toFixed(3)}ms/call`) } }) test('bench: 브로드캐스팅 1회 총비용(snapshot + buildEntityDeltas) vs 상태 규모', () => { for (const [locations, items] of [[200, 5000], [1000, 50000], [2000, 100000]]) { const proj = new StateProjector(model(locations)) for (let i = 0; i < items; i++) proj.apply(envelope(`urn:epc:id:sscc:0614141.${i}`, `bin-${i % locations}`)) const iters = 100 const t0 = performance.now() for (let k = 0; k < iters; k++) buildEntityDeltas(proj.snapshot(), 't1') const per = (performance.now() - t0) / iters console.log(` [브로드캐스팅1회] ${locations.toLocaleString()} locations / ${items.toLocaleString()} items: ${per.toFixed(3)}ms → 최대 ${Math.round(1000 / per).toLocaleString()} 브로드캐스팅/s`) } })