- 분야: DB
- 기술 스택: TypeScript · SQLite · Cloudflare D1
문제와 원인
현재 만료 행 정리 함수는 테이블마다 최대 500행을 한 번 삭제하고 반환한다. 만료 이벤트가 500행을 넘으면 유지보수 작업이 정상 종료해도 backlog가 남는다.
현재 코드
cleanupExpiredRows
이벤트 삭제는 다음 배치 크기와 SQL을 사용한다.
const CLEANUP_BATCH_SIZE = 500;
DELETE FROM analytics_events
WHERE id IN (
SELECT id FROM analytics_events
WHERE occurred_at < datetime('now', ?)
ORDER BY occurred_at, id LIMIT ?
);
보관기간 90일의 매개변수는 -90 days와 500이다. 감사 로그와 시스템 오류도 같은 방식으로 한 배치씩 정리하고, 게스트 세션 두 테이블에는 별도 만료 조건을 적용한다.
runOperationalMaintenance
유지보수 호출부는 정리 함수를 한 번 호출한 뒤 백업과 완료 처리를 진행한다.
const retentionDays = await repository.analyticsRetentionDays();
const cleanup = await repository.cleanupExpiredRows(retentionDays);
const backup = await maybeCreateAutomaticBackup({
email: "scheduled-maintenance@baeumzip.local",
hash: "scheduled-maintenance",
});
await repository.completeLease(TASK, owner);
일일 예약 워크플로는 자동 백업의 진행 응답 202에 따라 유지보수 POST를 반복할 수 있다. 삭제량은 호출당 500행이며 하루 호출 수는 백업 진행에 따라 달라진다. 정리 함수 자체는 만료 행을 끝까지 삭제하는 반복 없이 반환한다.
진단 결과
합성 이벤트 정리
Node.js, SQLite의 메모리 DB에 100일 전 만료 이벤트 500·1,500·50,000행과 1일 전 새 이벤트 1행을 넣었다. 감사·오류·게스트 세션 테이블은 비운 상태에서 cleanupExpiredRows(90)를 각 규모별 1회 호출하고 잔여량을 조회했다. 이어 같은 함수를 직접 반복 호출해 만료 행이 0이 될 때까지의 총 호출 수를 기록했다.
| 만료 입력 | 첫 호출 삭제 | 첫 호출 뒤 만료 잔여량 | 만료 행을 비우는 총 호출 수 | 새 행 보존 |
|---|---|---|---|---|
| 500행 | 500행 | 0행 | 1회 | 1행 |
| 1,500행 | 500행 | 1,000행 | 3회 | 1행 |
| 50,000행 | 500행 | 49,500행 | 100회 | 1행 |
Chrome의 SQLite Fiddle 3.54.0에서도 고정 seed SQL로 만료 1,500행과 새 행 1행을 만든 뒤 위 이벤트 DELETE를 1회 실행했다.
| 조회 항목 | DELETE 전 | DELETE 후 |
|---|---|---|
| 전체 행 | 1,501행 | 1,001행 |
| 만료 행 | 1,500행 | 1,000행 |
| 새 행 | 1행 | 1행 |
changes() | — | 500행 |
DELETE 전 조회는 전체 1,501행 중 만료 1,500행·새 행 1행을 반환했다.
DELETE를 1회 실행한 뒤 changes()는 500행이었다. 만료 행은 1,000행이 남았고 새 행 1행은 유지됐다.