
"PgQue"를 검색하면 엉뚱한 결과가 많이 섞입니다. 그런데 지금 정말 중요한 건 이름이 아니라, 최근 등장한 PgQue 같은 시도가 다시 묻고 있는 질문입니다. 큐를 별도 시스템에 둘 것인가, 아니면 PostgreSQL 안에 넣을 것인가.1
이게 지금 중요한 이유는 단순합니다. Postgres 기반 큐는 초기 복잡성을 크게 줄여주지만, 운영 단계에 들어가면 같은 선택이 성능·신뢰성·관측성 문제로 되돌아올 수 있기 때문입니다. 특히 최근 자료들은 "Postgres로도 큐는 충분히 된다"는 점과, 동시에 "잘못 돌리면 죽은 튜플과 vacuum 문제로 애플리케이션 전체를 흔들 수 있다"는 점을 함께 보여줍니다.2
PgQue의 진짜 의미: 단순화인가, 비용의 이연인가
PgQue를 "새로운 큐 제품"으로 보기보다 "Postgres를 큐로 쓰는 설계"로 보는 편이 정확합니다. 이 접근의 가장 큰 장점은 트랜잭션 일관성입니다.
예를 들어 주문 데이터를 저장하는 트랜잭션 안에서 "이메일 발송 작업"이나 "인보이스 생성 작업"도 함께 기록할 수 있습니다. 이 경우 애플리케이션 상태와 작업 예약 상태가 한 데이터베이스 안에서 같이 움직입니다. 외부 큐를 붙일 때 자주 생기는 "DB에는 반영됐는데 메시지는 안 나감" 같은 불일치를 줄이기 쉽습니다.2
그래서 작은 팀, 단일 서비스, 백오피스 작업, moderate한 처리량에서는 Postgres 큐가 아주 현실적인 기본값이 될 수 있습니다. 별도 Redis, SQS, Kafka 운영 없이도 시작할 수 있으니까요.
하지만 이 단순함은 공짜가 아닙니다. 큐 테이블은 대부분의 행이 "들어왔다가, 한 번 읽히고, 사라지는" 짧은 생명주기를 갖습니다. Postgres에서는 삭제된 행이 즉시 없어지지 않고 dead tuple로 남았다가 vacuum이 정리합니다.2 처리량이 높고, 동시에 긴 트랜잭션이나 분석 쿼리가 섞이면 이 청소가 밀리기 시작합니다. 그때부터 큐만 느려지는 게 아니라 같은 인스턴스의 다른 워크로드까지 영향을 받을 수 있습니다.23
즉, PgQue의 핵심 질문은 이겁니다. "지금의 단순함이 내 서비스 수명주기 전체에서도 단순함인가?"
Redis, SQS, Kafka와 비교하면 무엇이 달라지나
Postgres 큐가 강한 지점은 "업무 데이터와 작업 상태를 함께 다룰 수 있다"는 데 있습니다. 반면 Redis, SQS, Kafka는 각각 다른 강점을 줍니다.
Redis 계열 큐는 보통 지연이 낮고 빠르지만, 영속성과 재처리 모델은 구현 방식에 따라 차이가 큽니다. SQS는 운영 부담이 낮고 visibility timeout 기반 소비 모델이 잘 정리돼 있습니다. pgmq도 이와 유사한 API와 visibility timeout 개념을 제공합니다.45 Kafka는 메시지 보존, 재생, 스트림 처리, 높은 처리량에서 유리하지만, "단순한 백그라운드 잡 큐" 용도로는 과할 수 있습니다.
중요한 건 보장 방식이 다르다는 점입니다. Supabase 문서는 pgmq를 "visibility window 안에서 exactly once"라고 설명하지만,45 이런 표현은 보통 엄밀한 분산 시스템 의미의 절대적 exactly-once라기보다, 특정 소비 모델 안에서의 중복 억제를 뜻하는 경우가 많습니다. 실무에서는 여전히 idempotency를 전제로 보는 편이 안전합니다. 이건 Postgres 큐든 SQS든 크게 다르지 않습니다.
그래서 판단 기준은 기술 취향보다 워크로드 모양입니다.
주문 후 이메일, 썸네일 생성, 웹훅 재시도처럼 "업무 트랜잭션에 밀접하고 유실이 치명적이며 처리량이 폭발적이지 않은 작업"이면 Postgres 큐가 잘 맞습니다.
반대로 대량 이벤트 스트리밍, 긴 보존 기간, 다중 소비자, 초당 매우 높은 처리량, 팀 간 독립 배포가 중요하면 전용 시스템 쪽이 더 자연스럽습니다.
Postgres 큐를 쓴다면, 튜토리얼보다 한 단계 더 안전하게
가장 널리 알려진 패턴은 FOR UPDATE SKIP LOCKED로 작업을 집어 가는 방식입니다. 이건 동시 소비에서 중복 처리를 줄이는 데 유효합니다.2 다만 많은 예제가 놓치는 건 "작업 실행 시간을 트랜잭션 안에 오래 두지 말라"는 점입니다. 트랜잭션이 길어질수록 vacuum이 밀릴 수 있습니다.2
실무적으로는 최소한 이 정도는 있어야 합니다.
pending / processing / done / failed 같은 상태,run_at과 재시도 횟수,
마지막 에러,
오랫동안 processing에 머문 작업을 되살릴 lease 또는 timeout,
최대 재시도 초과 시 dead-letter 또는 archive 경로입니다.
pgmq가 아예 archive, delete, visibility timeout 같은 조작을 함수로 제공하는 이유도 결국 여기 있습니다.4 메시지를 읽고 바로 삭제하는 pop은 소비자가 실패하면 유실될 수 있으니, 적어도 중요한 작업에는 더 보수적인 읽기-확인 삭제 패턴이 낫습니다.4
그리고 "제로 블로트" 같은 표현은 마케팅 문구로 받아들이는 편이 좋습니다. 확인 가능한 사실은 Postgres 큐가 잘 설계되면 충분히 실용적이라는 점이고, 동시에 dead tuple과 vacuum은 구조적으로 관리해야 할 문제라는 점입니다.12 완전히 사라지는 문제가 아니라, 설계와 운영으로 통제하는 문제에 가깝습니다.
모니터링도 필수입니다. 큐 길이만 보지 말고, 처리 지연, 재시도율, stuck job 수, 테이블/인덱스 비대화, autovacuum 상태를 함께 봐야 합니다. Postgres 큐의 실패는 종종 "작업이 조금 늦어짐"이 아니라 "문제가 보이지 않게 쌓이다가 갑자기 전체 DB가 무거워짐"으로 나타납니다.
결론적으로 PgQue에 관심이 있다면, 당신이 봐야 할 건 라이브러리 이름이 아닙니다. 지금 필요한 게 "새 인프라 없이 얻는 단순함"인지, 아니면 "나중에 반드시 분리해야 할 부하를 미루는 것"인지입니다. 팀이 작고, 트랜잭션 일관성이 중요하고, 큐가 앱의 보조 기능이라면 Postgres 큐는 훌륭한 선택입니다. 하지만 분석 쿼리와 고 churn 큐를 같은 인스턴스에 오래 섞어 돌릴 생각이라면, 그 단순함은 생각보다 빨리 비싸질 수 있습니다.23
참고
1PgQue: Zero-Bloat Postgres Queue
2Keeping a Postgres queue healthy
3Keeping a Postgres Queue Healthy | Hacker News
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.
