4P by GN⁺ | ★ favorite | 댓글 1개
  • pgmock는 단위 테스트와 E2E 테스트를 위한 인메모리 PostgreSQL 모의 서버이며, 외부 의존성 없이 Node.js와 브라우저에서 WebAssembly로 실행됨
  • node-postgres 사용자는 포트를 열지 않는 설정 객체를 받아 연결할 수 있고, 이 방식은 브라우저에서도 동작함
  • 브라우저에서는 웹앱이 TCP 포트를 열 수 없지만 PostgresMock.createSocketnode-postgres 설정을 사용할 수 있으며, 번들러가 정적 import를 분석하면 선택적 Node.js 모듈 경고가 날 수 있음
  • 구현은 현재 x86 에뮬레이터 안에서 PostgreSQL 서버를 실행하는 방식을 쓰며, 테스트와 프로덕션 간 동작 차이 방지를 성능보다 우선함
  • 장기적으로는 네이티브 PostgreSQL WASM 포크가 성숙하면 두 방식을 모두 제공하고, 이후 네이티브 WASM을 기본값으로 전환할 계획이 있음

pgmock가 제공하는 것

  • pgmock는 단위 테스트와 E2E 테스트용 인메모리 PostgreSQL 모의 서버임
  • 외부 의존성이 필요 없고, Node.js와 브라우저 양쪽에서 WebAssembly 안에서 실행됨
  • 설치는 npm으로 가능함
npm install pgmock

기본 사용 흐름

  • 인메모리 서버는 PostgresMock.create()로 만들고, listen(5432)로 연결 문자열을 받을 수 있음
import { PostgresMock } from "pgmock";

const mock = await PostgresMock.create();
const connectionString = await mock.listen(5432);
  • node-postgres를 쓰는 경우 mock.getNodePostgresConfig()가 포트 리스닝 없이 연결할 수 있는 설정 객체를 제공함
  • 작업이 끝나면 리소스를 해제하기 위해 mock.destroy()를 호출하는 방식이 권장됨
mock.destroy();

브라우저 지원과 pglite와의 차이

  • pgmock브라우저 환경을 완전히 지원함
  • 웹앱은 TCP 포트를 열 수 없지만, PostgresMock.createSocketnode-postgres 설정을 사용할 수 있음
  • 번들러가 import를 정적으로 분석하면 선택적 Node.js 모듈이 없다는 경고가 날 수 있으며, Webpack 설정 예시는 examples/web-demo/next.config.mjs에 있음
  • 브라우저에서 데이터베이스만 실행하려는 경우 pglite를 고려할 수 있음
    • pglite는 더 빠르고 가볍지만 기능 집합이 제한적임
    • pgmock는 테스트 환경에서 원하는 프로덕션 PostgreSQL과의 기능 동등성을 목표로 설계됨

WebAssembly에서 PostgreSQL을 실행하는 방식

  • WebAssembly에서 PostgreSQL을 실행하는 방식은 두 가지가 있음
  • 네이티브 WASM 포크 방식은 더 빠르고 메모리를 훨씬 적게 쓰지만, 단일 사용자 모드만 지원하고 연결과 확장을 지원하지 않음
  • pgmock는 현재 x86 에뮬레이터 방식을 사용함
    • 테스트와 프로덕션 간 불일치를 막는 것이 목적임
    • 테스트에서는 성능이 보통 큰 문제가 아니기 때문임
  • 중기적으로 네이티브 PostgreSQL WASM 포크가 성숙하면 두 옵션을 모두 제공할 계획임
  • 이후에는 네이티브 WASM을 기본값으로 전환할 계획이며, PostgresMock.subtle 내부 API 외에는 큰 breaking change가 많지 않을 것으로 예상함

기존 브라우저 PostgreSQL 프로젝트와의 차이

  • pgmock는 JavaScript 런타임 안에서 전체 기능 호환성을 제공하며, 통신을 위해 네트워크 프록시에 의존하지 않음
  • JavaScript로 네트워크 스택을 시뮬레이션해 실제 네트워크처럼 동작하게 만들고, raw socket 접근을 허용하지 않는 플랫폼에서도 TCP 연결을 시뮬레이션할 수 있음

확장 가능성과 관련 프로젝트

  • 다른 Docker 이미지나 데이터베이스도 이론적으로 실행 가능하지만 테스트되지는 않았음
  • 관련 구현과 기반 프로젝트로 다음이 언급됨
    • v86: x86 에뮬레이터
    • Supabase & Snaplet: WebAssembly 안에서 PostgreSQL을 실행하는 접근의 기반
    • Stackframe: pgmock 개발 중 급여를 제공한 회사로 언급됨

댓글과 토론

Hacker News 의견들
  • 몇 달 동안 회사에서 인메모리 Postgres 버전을 만들고 있었고, 운영 데이터베이스와 기능 동등성을 갖추고 있음
    장점은 외부 프로세스나 프록시가 필요 없다는 점임. 플랫폼이 WASM을 실행할 수 있으면 Node.js나 브라우저에서도 pgmock을 실행할 수 있고, 목 데이터가 들어간 새 데이터베이스도 JavaScript 객체를 만드는 정도로 간단함
    pgmock을 오픈소스로 공개하게 만든 pglite와는 조금 다름. pgmock은 원본 Postgres를 x86 에뮬레이터 안에서 실행하고, pglite는 Postgres 포크를 네이티브 WASM으로 직접 컴파일해서 더 빠르고 가벼움
    다만 pglite는 단일 사용자 모드와 일부 확장만 지원해서 일반 Postgres 클라이언트로 접속할 수 없고, 이는 E2E 테스트에서는 꽤 중요함
    이론적으로는 WebAssembly 플랫폼에서 어떤 Docker 이미지든 실행하도록 바꿀 수 있을 텐데, 보고 싶은 구체적인 대상이 있는지 궁금함

    • 멋진 작업임. PGlite가 현재 단일 사용자만 지원하는 건 맞고, 일부 환경에서 통합 테스트에 쓰기에는 문제가 될 수 있음
      다중 연결 모드를 추가할 방법을 몇 가지 생각하고 있지만 시간이 좀 걸릴 듯함. PGlite에는 단일 사용자 모드와 관련된 다른 제한도 있는데, 예를 들어 pg_notify를 아직 지원하지 않으며 이것도 고칠 계획임
      반면 이 프로젝트는 실제 Postgres에 훨씬 가까워서 그냥 동작할 가능성이 큼. 이런 인메모리 Postgres 프로젝트들은 테스트 실행 시간을 4분의 1 이하로 줄일 수 있어 보이고, 테스트 분야에서 큰 가능성이 있음
      PGlite 작업자 입장에서 본 생각임
    • Docker 이미지를 WASM에서 실행한다는 아이디어는 여러 문제에 유망해 보임
      최근 클라이언트에서 FFMPEG/SoX 파이프라인을 돌리고 싶었는데, 의존성이 너무 많아 Emscripten으로 쉽게 다시 컴파일하기 어려웠음. 이 접근이 그런 경우에도 도움이 될 수 있는지 궁금함
    • pgvector 확장을 지원할 수 있다면 Postgres의 힘을 모두 가진 아주 빠른 벡터 데이터베이스가 될 수 있음
      관계형 기능 덕분에 보통 관계형 데이터베이스에 들어 있는 풍부한 도메인 특화 메타데이터를 추가하고 함께 질의할 수 있음
    • 참고로 온라인 데모는 마음에 들지 않는 질의에서 깨지는 듯함
      select foo();를 실행하면 Error.captureStackTrace is not a function이 나오고, Linux의 Firefox 124.0.2에서 발생함
    • 훌륭하지만, E2E 테스트라는 개념 자체가 구성요소를 목으로 대체하지 않고 실제 환경을 쓰는 것을 의미하지 않나 싶음
  • 그냥 Postgres 파일을 ramdisk에 올려 실행하면 안 되는지 궁금함
    업데이트: 브라우저/Node 환경에서 실행될 수 있어서 테스트가 생성·갱신·삭제할 수 있는 듯함. 너무 백엔드 개발자라 일반적인 개발 환경 대비 장점을 잘 모르겠음. 어디서, 언제, 어떻게 더 나은지 설명해줄 수 있으면 좋겠음

    • 에뮬레이터 내부에서도 대체로 비슷한 일이 벌어짐. 에뮬레이션된 디스크가 인메모리 9P 파일 시스템
      WebAssembly로 만든 이유는 플랫폼, 아키텍처, 브라우저나 엣지 환경까지 동작을 더 이식성 있게 맞출 수 있고, Docker조차 필요 없는 외부 의존성 없는 구성이 되기 때문임
      에뮬레이터가 이미 실행된 상태를 바로 부팅할 수 있게 해주기 때문에 실제 데이터베이스나 Docker 컨테이너를 띄우는 것보다 에뮬레이션된 데이터베이스 부팅이 더 빠름. 다만 이는 설계 목표라기보다는 운 좋게 얻은 효과에 가까움
    • 나도 잘 모르겠음. 에뮬레이터, 네트워크 스택 등 불필요한 코드가 너무 많아 보임
      https://testcontainers.com/ 같은 걸 쓰면 안 되는지 궁금함. 컨테이너 엔진이 외부 의존성이라는 게 그렇게 나쁜 일인가 싶음
    • E2E 테스트의 목적은 시스템을 실제 상태로 테스트하는 것임. 운영 환경의 에뮬레이션이기 때문에 전원을 뽑거나 디스크가 꽉 찼을 때 무슨 일이 생기는지도 확인할 수 있음
      그 안에 목을 밀어 넣는 순간 단위 테스트가 됨. 효과는 있지만 같은 것은 아님. E2E의 핵심 중 하나는 목이 없기 때문에 테스트가 정확하다고 알 수 있다는 점임. 이건 Postgres를 테스트하는 것이 아니라 매번 이 시스템을 테스트하는 셈임
      임베디드, 경량, 저성능 시스템용 PG를 만들고 있다면 느린 실제 E2E 테스트 전에 검증 테스트로는 말이 됨. 그런 용도가 내게도 있음
      그 외에는 멋진 프로젝트이고, PG shim이 필요할 때 쓸 수 있는 도구로 보임
    • 테스트 격리에 유용할 수 있음. Redis 백엔드를 테스트에서 FakeRedis로 옮기자 테스트 스위트의 잡음이 꽤 줄었음
      Postgres는 savepoint를 쓰고 있는데 ramdisk에서도 그리 빠르지 않음
  • 예전에는 테스트에서 온갖 커스텀 가짜 인메모리 서버를 돌렸음. 요즘은 https://testcontainers.com으로 실제 대상을 실행함

  • Prisma/Node.js 개발자가 로컬 개발용으로 “캔에 든 Postgres”만 원한다면, 최근 나온 PGlite 서버화 버전인 pglite-server가 더 나을 수 있음: https://github.com/kamilogorek/pglite-server
    더 빠르고 파일 시스템에 데이터를 유지할 수 있지만, 사용량이 많을 때는 전체 x86 에뮬레이터 기반 E2E 테스트 서버보다 덜 안정적임. pglite-server는 메모리를 150MB만 쓰는 반면 pgmock-server는 830MB를 썼음
    dotenv로 새 .env.local을 체크아웃하고 모든 nextjs/prisma package.json 실행 스크립트의 DATABASE_URL을 바꾸면 됨
    DATABASE_URL="postgresql://postgres@localhost:5432/awesomeproject"
    "db:pushlocal": "dotenv -e .env.local -- pnpm prisma db push"
    어떤 프로젝트에도 아주 쉽게 붙일 수 있고, Neon이 이 영역을 후원하는 것도 이해됨

  • 찬물을 끼얹고 싶진 않지만, 나는 이걸 사용할 생각은 없음
    단순한 애플리케이션이라면 동작할 수 있겠지만, 교착 상태 위험이 있거나 데이터베이스 형태에 의존하는 등 복잡성이 커지면 작은 동작 차이도 치명적인 문제로 번질 수 있어 가치가 줄어듦
    요즘은 자원 제약이 있는 E2E 환경을 선호함. 로컬 테스트 실행기가 누군가 심하게 비효율적인 코드를 작성했을 때 깨질 기회를 주기 때문임
    또한 데이터베이스를 몇 초 뒤 스냅샷으로 만들고 그 스냅샷을 테스트 파티션에 배포하는 방식은 매우 빠르며, 테스트 스위트에서 몇 분씩 줄여준 적이 많음
    흥미로운 아이디어이고 좋은 학습 경험이겠지만, 대상 사용자는 제한적이라고 봄

  • 제목이 조금 헷갈림. “회사에서 만들었다”면 회사 자원을 썼다는 전제에서 이 프로젝트의 지식재산권은 고용주에게 있는 것 아닌가 싶음
    그렇다면 기술적으로 오픈소스로 공개해도 되는지 궁금함

    • 스타트업이라 오픈소스 공개는 팀의 나머지 멤버들에게 승인받는 것만큼 쉬웠음
    • 저장소는 Stackframe 소유이고 LICENSE 파일에 Copyright 2024 Stackframe.이라고 되어 있음. 작성자가 Stackframe에서 일하는 듯함
  • 이것은 H2의 Postgres 호환 모드와 비교하면 어떤지 궁금함

    • 틀릴 수도 있지만 H2에서는 PostgreSQL 저장 프로시저를 쓸 수 없을 것 같음
  • 꽤 멋짐. 답할 수 있다면 몇 가지 궁금함
    회사에서 이 프로젝트를 만들게 된 계기가 무엇이었는지, Docker 컨테이너에서 Postgres를 실행하는 것이 너무 느렸던 건지 궁금함
    pgmock을 흐름에 통합하기 전후로 E2E 테스트용 CI 구성이 어떻게 달라졌는지도 알고 싶음
    이 솔루션으로 옮기는 과정이 어려웠는지도 궁금함

  • 운영 데이터를 덤프하고 민감한 데이터를 모두 제거한 뒤, 로그 테이블처럼 필요 없는 테이블은 truncate하면 좋은 개발용 복사본이 생김
    이를 개발, QA, E2E 등에 복제하면 됨. E2E에 필요한 것은 바로 그 확장, 트리거, 함수, 뷰, 인덱스, 데이터

  • 그냥 Docker를 쓰고 별도의 테스트 데이터베이스를 두면 안 되는지 궁금함
    Elixir는 그렇게 하고, 테스트 프레임워크가 각 테스트를 트랜잭션으로 감싸서 격리를 위해 롤백함. 이 접근의 장점이 무엇인지 알면 흥미로울 듯함