PGlite 임베디드 DB: 동시성·손상·빌드 충돌과 그 해결의 전말
외부 DB 0개를 위해 택한 PGlite(WASM PostgreSQL)에서 부딪힌 세 가지 함정 — 동시 쿼리, 디스크 손상, 빌드 다중워커 충돌 — 과 lazy-init + 프로세스 가드로의 수렴.
AI version
PGlite는 "PostgreSQL을 지키면서 외부 서버 0개"라는 목표엔 완벽했지만, 단일 연결 WASM이라는 본질에서 세 함정이 나왔다.
함정 1 — 동시 쿼리 → Aborted()
dev 서버 검증 중 간헐적으로:
Unhandled Rejection: RuntimeError: Aborted(). Build with -sASSERTIONS for more info.⚠️ 이 초기 간헐 에러의 근본 원인은 끝내 단정하지 못했다. 의심: 요청 핸들러와 cron이 같은 PGlite 인스턴스에 동시 질의(미검증). 그 가설을 믿고 "FIFO 뮤텍스로 query/exec를 직렬화하자"며 PGlite 메서드를 몽키패치했는데 — 이게 더 큰 사고를 냈다.
함정 2 — 몽키패치가 init을 깨고, 디스크가 손상됨
몽키패치 직후 부팅 자체가 죽음:
An error occurred while loading instrumentation hook: Aborted().
at async Module.register (instrumentation.ts:22:3)- 관찰된 사실: 몽키패치를 넣자 부팅이 죽고, 빼자 부팅이 돌아왔다.
- 가설(미검증): PGlite의
exec가 내부적으로query를 호출하는 구조라면, 둘 다 같은 single-tail 체인으로 감싸 첫 DDL에서 self-deadlock/abort가 났을 것. PGlite 소스를 직접 읽어 확인하진 않았다. - 손상(행동으로 검증): 몽키패치를 되돌려도 부팅이 abort했고,
rm -rf .data/pglite후에야 정상 부팅 → 디스크 손상 맞음. 손상 트리거는 그 abort된 몽키패치 실행으로 의심(단정 못 함).
해결:
- 몽키패치 전면 revert (PGlite 내부는 건드리지 않는다).
- 손상된 DB 삭제:
rm -rf .data/pglite. - 재검증 — 몽키패치 없이, 손상 제거 후, lazy-init+가드 상태에서 동시 30건 쓰기 →
Aborted()0건(테스트 출력으로 확인).
⚠️ "PGlite가 내부적으로 큐잉하므로 직렬화가 불필요하다"는 단정은 하지 않는다 — PGlite 내부를 안 읽었다. 관찰된 것은 위 3번의 결과뿐이고, 초기 간헐 에러의 근본 원인은 미확정으로 남았다.
함정 3 — next build 다중 워커 충돌
Generating static pages using 11 workers ...
Error: PGlite failed to initialize properly
RuntimeError: Aborted().검증된 원인: lib/db/index.ts가 모듈 import 시점에 PGlite 인스턴스를 생성 → 빌드의 11개 병렬 워커가 같은 디스크를 동시에 열었다.
해결: lazy-init. getBundle()이 첫 getDb() 호출 때 인스턴스를 만든다(import 시점 아님). 재빌드 클린.
function getBundle(): DbBundle {
if (!globalForDb.__bk_db) globalForDb.__bk_db = createBundle();
return globalForDb.__bk_db;
}무인 실행 안정성 — 프로세스 가드
밤새 무인으로 돌 때 stray async rejection이 프로세스를 죽이지 않도록, lib/guards.ts의 unhandledRejection 핸들러를 instrumentation.ts에서 동적 import(Node 전용, Edge 컴파일과 분리). 이 분리는 process.on의 Edge-Runtime 경고도 함께 해결했다.
커밋
- lazy-init + 프로세스 가드 + Edge 분리:
e1a924e - 몽키패치/
turbopack.root실험: 커밋 전에 revert — 해시 없음
교훈
- WASM 라이브러리의 내부 메서드를 몽키패치하지 마라.
- 임베디드 DB는 모듈 top-level에서 열지 마라 — lazy-init.
- 임베디드 DB는 aborted write에 손상될 수 있다 —
rm -rf .data/pglite가 dev 복구. - 프로덕션의 실제 PostgreSQL에선 이 세 함정 모두 존재하지 않는다 (다중 연결·서버 분리). PGlite는 개발 한정 편의다.
Review needed
No human review on this entry yet.