import { MigrationInterface, QueryRunner, TableIndex } from 'typeorm' /** * 「이 종류의 사실을 시각순으로」 — 그 축에 인덱스가 없었다. * * ── 무엇이 났나 (2026-08-25 실측 · 승화푸드) ───────────────────────────────── * 지난 기록 조회는 필터가 없을 때 `revision DESC`(적힌 순서)로 잘랐다. 연결된 시스템을 되읽는 * 트윈에서는 그 순서가 일어난 순서와 달라서, 공정 타임라인이 4월 사실을 최근 것으로 그렸다. 그래서 * 기본을 시각순으로 고쳤는데(`journal-order.ts`), 그때 이 인덱스가 없는 것이 드러났다. * * ix_2 (domain, instance, eventType, revision) 종류는 걸러지고 정렬이 적힌 순서다 * ix_1 (domain, instance, eventTime) 정렬은 맞고 종류를 못 거른다 * * 종류로 거르고 시각으로 정렬하면 `ix_1` 로 걷다가 행마다 테이블을 열어 종류를 확인한다. 찾는 종류가 * 며칠 전에 멈춘 트윈에서는 그 사이 행을 전부 읽고 버린다. 실측: 200건에 1.7초(같은 조회가 옛 정렬로는 * 0.001초). 승화푸드에서 작업 사실은 8월 22일에 멈춰 있고 그 뒤로 리비전이 110만 늘었다. * * ── 그 1.7초가 서버 전체를 막았다 ─────────────────────────────────────────── * 공정 타임라인은 5초마다 세 종류를 부른다. sqlite 드라이버는 연결이 하나라서, 5초 주기에 5초가 넘는 * 일감이 들어오면 나머지 질의가 전부 뒤에 선다. 39분 동안 느린 질의 3,152건이 쌓였고, 한 줄짜리 * `domains` 조회가 중간 7.4초로 찍혔다 — 그 질의의 문제가 아니라 줄을 선 값이다. * * ── 왜 이제 얹나 ──────────────────────────────────────────────────────────── * 엔티티 주석이 「저널 하나가 아주 커져서 이 축들의 조회가 느려지면 그때 측정을 근거로 인덱스를 추가할 * 일이지, 지레 얹어 쓰기를 무겁게 할 일은 아니다」라고 적어 두었다. 그 측정이 나왔다. * * 큰 기록에서는 생성에 시간이 걸린다(47GB 개발 파일에서 수분). 멱등이므로 다시 돌려도 안전하고, * TypeORM API 만 쓴다(드라이버 다섯을 다 지나가야 하므로 raw SQL 금지). */ const EVENTS = 'twin_events' const IX = 'ix_twin_event_7' const COLUMNS = ['domain_id', 'instance_id', 'event_type', 'event_time'] export class IndexEventTypeByTime1786300000000 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { const events = await queryRunner.getTable(EVENTS) if (!events) return if (events.indices.some(i => i.name === IX)) return await queryRunner.createIndex(events, new TableIndex({ name: IX, columnNames: COLUMNS })) } public async down(queryRunner: QueryRunner): Promise { const events = await queryRunner.getTable(EVENTS) if (!events) return if (!events.indices.some(i => i.name === IX)) return await queryRunner.dropIndex(events, IX) } }