Daeseon Yoo
Back to project
·Tech retro·4 min·Review needed

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된 몽키패치 실행으로 의심(단정 못 함).

해결:

  1. 몽키패치 전면 revert (PGlite 내부는 건드리지 않는다).
  2. 손상된 DB 삭제: rm -rf .data/pglite.
  3. 재검증 — 몽키패치 없이, 손상 제거 후, 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.tsunhandledRejection 핸들러를 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.