/* * **backfill 이 이미 있는 사실을 다시 앉히지 않게 한다** — 저널에서 정체를 읽어 기억을 채운다. * * ── 무엇이 있었나 (2026-09-06 측정) ───────────────────────────────────────── * 중복 판정 기억(`FactDeduper`)은 **프로세스 안에만 있다.** 재기동하면 비워지고, 그 뒤 backfill 을 * 돌리면 이미 저널에 있는 사실도 「처음 본 것」이 된다. * * ``` * mes-line-a 를 재기동 뒤 backfill * 저널 44 → 83건 · 서로 다른 것 39가지 · 중복 44가지 * 같은 프로세스에서 다시 돌리면 applied 0 (기억이 살아 있어서) * ``` * * push 경로에서는 안 보인다. 재전송이 몇 분 안에 오므로 창(20,000) 안에서 걸린다. * **backfill 이 그 전제를 깬다** — 몇 달 전 사실을 지금 받으니 재기동 한 번이면 그 구간이 통째로 * 다시 앉는다. * * ── 왜 여기서만 하나 ──────────────────────────────────────────────────────── * push 경로는 뜨거운 길이다. 봉투마다 저널을 조회하면 그 길이 느려지고, 거기서는 재전송이 창 안에 * 들어와 이미 걸린다. * * backfill 은 자주 돌지 않고 한 번에 많이 받는다. 그래서 **한 번의 조회로 그 구간을 통째로** 읽어 * 기억을 채운다 — 레코드마다 묻지 않는다. * * ── 근본 고침은 따로 있다 ─────────────────────────────────────────────────── * 저널에 정체 칸과 unique index 를 두는 것이 근본이다. 그런데 저널이 2,484,472행이고, 개발 중에 * 그 표에 칸을 더하면 sqlite 가 표를 통째로 다시 쓴다. postgres 첫 부팅 직전에 한 번에 하기로 적어 * 두었다(`design/plans/postgres-column-types.md`). */ import { Between } from 'typeorm' import { getRepository } from '@things-factory/shell' import { TwinEvent } from '../twin-event/twin-event.js' import { factIdentity } from '../../engine/ingest-dedupe.js' /** 구간 판정은 가벼운 파일에 있다 — 부르는 쪽이 한 곳에서 가져가게 다시 내보낸다. */ export { spanOf } from './fill-span.js' /** 한 번에 읽을 저널 줄의 상한 — 넘으면 읽은 만큼만 채우고 그 사실을 말한다. */ const SCAN_MAX = 50_000 /** * 그 구간의 저널을 읽어 **정체 목록**을 만든다. * * 저장된 payload 가 봉투 그대로라(`eventId` · `eventType` · `eventTime` · `data`) 유입 때와 같은 * 함수로 정체를 만든다. 여기서 따로 만들면 두 곳이 다른 규칙을 갖고, 어긋나는 날 조용히 다시 앉는다. */ export async function journalIdentities( domainId: string, instanceId: string, span: { from: Date; to: Date } ): Promise<{ identities: string[]; scanned: number; truncated: boolean }> { const rows = await getRepository(TwinEvent).find({ where: { domain: { id: domainId } as any, instanceId, eventTime: Between(span.from, span.to) as any }, select: { id: true, payload: true } as any, take: SCAN_MAX }) const identities: string[] = [] for (const r of rows as any[]) { const id = factIdentity(r?.payload) if (id) identities.push(id) } return { identities, scanned: rows.length, truncated: rows.length >= SCAN_MAX } }