Postgres LISTEN/NOTIFY는 실제로 확장 가능함
요약
Postgres의 LISTEN/NOTIFY 기능이 가진 확장성 한계와 이를 효과적으로 활용하는 설계 전략을 다룹니다. 과도한 확장성보다는 실제 예상 부하에 맞춘 적절한 기술 선택과 단순한 아키텍처 유지의 중요성을 강조합니다.
핵심 포인트
- 확장성은 이분법이 아닌 연속적인 척도로 접근해야 함
- LISTEN/NOTIFY는 적절한 설계(예: Rust 브로커 활용) 시 충분히 확장 가능함
- 과도한 확장성을 위한 선투자는 높은 비용과 운영 부담을 초래함
- 데이터 일관성이 필수적이지 않다면 SQS, Redis 같은 단순한 도구가 유리함
- 기술의 내부 동작 원리를 이해하고 사용하는 것이 확장성의 핵심임
확장성은 연속적인 척도이지 이분법이 아님. 초당 6만 건은 어떤 시스템에는 필요량보다 10만 배 많고, 다른 시스템에는 10만 배 부족할 수 있음. 흔한 개발자 실수로 “이른 최적화”보다 확장 특성이 맞지 않는 기술 선택을 꼽고 싶음
지나치게 작은 기술은 한계를 넘으면 실패가 명확하지만, 과도하게 확장 가능한 기술도 운영 부담과 제약을 가져옴. 더 풍부한 모델로 개발 노력을 크게 줄일 수 있는 작은 시스템에 이런 기술을 도입하는 것 역시 나쁜 선택임
LISTEN/NOTIFY의 한계는 주의해야 할 만큼 낮으므로 비관적인 최대 부하를 계산한 뒤에도 최소 10배의 여유를 두는 편이 좋지만, 많은 프로젝트에는 충분함. 데이터베이스와의 통합성, 가용성, 별도 서비스 운영이 필요 없다는 장점 때문에 무조건 배제할 선택지는 아니며, 기존에 제시된 초당 2천 건조차 메시지 하나를 초 단위로 처리하는 시스템에는 큰 수치임
좋은 글에 대한 사소한 반론이지만, 초당 60억 요청을 처리하는 시스템은 거의 없으며 존재하더라도 목적에 맞게 자체 제작한 도구를 사용할 가능성이 큼
초당 6만 건 이하를 예상했다가 2만 건에서 20만 건으로 급증하는 편이, 초당 100만 건을 목표로 구축했는데 실제 부하가 2만 건인 것보다 나은 문제임. 전자의 예상 밖 성공은 임시 조치와 확장 비용을 충당하지만, 후자는 높은 비용 구조와 선투자에 묶이게 됨
실제 예상 규모에 적당한 여유를 더해 설계하고, 추가 확장성이 사실상 공짜일 때만 그 이상을 선택하는 편이 좋음. 수천 달러로 더 큰 하드웨어를 사거나 확장성 외에는 동등한 선택지라면 더 큰 쪽을 고르면 됨
이 경우에는 하드웨어 최대치까지 도달하므로 확장된다고 볼 수 있음. 병목이 데이터베이스나 입출력이 아니라 하드웨어에 있으며, 최대 처리량에서 Postgres CPU가 완전히 사용돼 경합이 아닌 데이터베이스 자체의 포화를 보여줌
LISTEN/NOTIFY와 Rust GraphQL 구독 브로커를 조합해 큰 성공을 거뒀음. 구독은 수만 개였지만 LISTEN 연결은 호스트마다 하나씩 총 3~4개뿐이었음
모든 변경을 각 호스트로 보내고, 호스트가 실제 사용자 구독을 관리하며 무엇을 발행할지 결정했음. Ruby나 Node 호스트 수백 대를 Rust 호스트 몇 대로 바꾸면 구조를 크게 단순화할 수 있고, 확장되지 않는다고 여겨지는 방식도 상당히 잘 작동함
Rust용으로 추천할 만한 GraphQL 라이브러리가 궁금함
CTO로 일할 당시 모든 서비스에서 하루 약 10만 건을 처리하다가 수백만, 결국 수천만 건까지 성장했음. 그 과정에서 한 엔지니어가 데이터 모델과의 강한 일관성을 활용하려고 LISTEN/NOTIFY 의미론 위에 큐를 구축했음. 이해하기 어렵지 않았고 별도 저장·전송 계층도 없앨 수 있어 당시에는 합리적으로 보였음
하지만 직접 만든 기능을 확장하면서 PostgreSQL 내부 동작을 우회해야 해 매우 불편해졌고, 더 일찍 다른 시스템으로 옮겼어야 했음. 확장성도 좋지 않아 RDS에서 원인을 파악하기 어려운 디스크 경합이 심해졌고 해당 테이블의 VACUUM도 악몽이었음. 익숙한 큐를 PostgreSQL 내부 기능으로 낯설게 구현하니 다른 엔지니어들이 두려워하며 디버깅과 소유권을 꺼렸음
스키마와 인덱스 같은 세부 사항을 제외한 핵심 교훈은 언제나 단순하고 예상 가능한 기술부터 선택하라는 것임. 매우 강한 데이터 일관성이 꼭 필요하지 않다면 인프라 구성 요소가 하나 늘더라도 SQS나 Redis 큐처럼 API 계약상 단순한 큐를 사용하고 나머지를 거기에 맞추는 편이 나음. 핵심 데이터 저장소 하나가 맡는 기계적 책임은 적을수록 좋았음
결국 “작동 방식을 이해하지 못한 채 사용하면 확장되지 않는다”로 요약할 수 있음. 기본 LISTEN/NOTIFY는 확장되지 않지만 원문은 이를 확장하는 방법을 실제로 찾아냈으므로 각 팀이 다시 해결할 필요가 없음
분산 시스템 생태계가 발전하면서 각 구성 요소가 무엇을 할 수 있는지 이해하게 됨. 처음에는 적은 구성 요소로 시작하고 실제로 필요할 때 추가하는 편이 더 건강한 시스템을 만듦
새 구성 요소가 다른 요인을 압도하는 경우는 드물지만, 큐를 다른 아키텍처 구성 요소와 독립적으로 운영하면 큰 이점이 있음. 큐는 본래 저장 후 전달하도록 만들어졌으므로 수명 주기를 분리하면 업데이트, 장애 분석, 패치 시간 동안 다른 시스템끼리도 분리할 수 있음
이는 관리 문제에 가깝고 인프라에 새 구성 요소를 추가해야 한다는 결론에는 동의하지 않음. 개발자들이 스택 일부를 다루고 싶어 하지 않는다는 이유로 새 네트워크 노드를 추가하는 것은 타당하지 않으며, 그 부분을 맡도록 하면 됨
Postgres와 이제 SQLite까지 제대로 활용하는 DBOS가 계속 마음에 듦. 기존 CRUD 스택에도 거의 노력 없이 도입할 수 있음
내구성 있는 워크플로를 쓰기 시작하면 적용할 곳이 계속 보임. 최근에는 이메일 하나하나를 내구성 있는 워크플로로 보고, 사용자와 상대방, 에이전트, GitHub나 Attio 같은 도구가 차례로 흐름에 참여하도록 실험 중임 https://housecat.com/blog/gmail-durable-workflows-sandbox-vm
이런 글은 대체로 각자의 문제와 이해, 해결책을 독립적으로 평가한 결과임. 도구의 기본 설정으로 특정 성능을 기대했다는 이유만으로 전문성 부족이라 부르기는 애매하며, 누구나 실패를 통해 계속 학습함
실험에 96코어·384GB RAM 데이터베이스 서버를 사용한 점(https://github.com/dbos-inc/dbos-postgres-benchmark/blob/mai...)은 매우 중요한데 명확히 밝혔어야 함. 데이터베이스는 수직 확장이 가능하지만 그 역시 한계가 있음. 누가 어디에서 연결하는지도 성능과 전체 지연 시간에 영향을 줌
초당 6만 건이 커 보일 수 있지만 실제 시스템을 무너뜨리는 것은 평상시 트래픽보다 순간적인 트래픽 폭증임. 대기업이 아니라면 이런 대형 서버로 시작하지 않을 것임. 읽기 복제본과 리전 간 이중화를 포함하면 프로덕션 데이터베이스 클러스터 하나에 10만 달러가 넘게 듦
글에서 가장 중요한 부분인 소비자가 어디까지 읽었는지 추적하고 대체 경로의 새 메시지를 조회하게 해주는 오프셋 또는 시퀀스 번호 할당 방법이 빠진 듯함. 여러 방식이 있지만 복잡성이나 잠금 경합 없이 해결하기 쉽지 않고, 잘못 구현하면 소비자와 경쟁 상태도 생김. 대개 작성자가 상태 테이블의 단일 행 등에 잠금을 걸어 이벤트 주제의 다음 번호를 할당할 것임
최선의 방법이 무엇인지 궁금함. 변경 데이터 캡처(CDC)를 읽어 할당된 이벤트 번호를 다른 테이블에 쓰는 도구가 괜찮을 수도 있지만 지연 시간이 길어질 수 있으며, 그 CDC 처리기가 NOTIFY도 수행해야 함
소비자 측에서는 일괄 처리로 처리량을 크게 높일 수 있는 용도도 있음. 이때는 LISTEN/NOTIFY 없이 소비자를 반복 실행해 매번 처리되지 않은 새 메시지를 모두 처리하고, 반복 사이에 마지막 시퀀스 번호를 저장하면 됨
LISTEN/NOTIFY를 처음 지원한 릴리스에는 잠금 구현이 좋지 않아 성능 문제가 있었던 것으로 기억함. 여기서 비판받는 기존 글도 첫 문단 직후 정정문에서 이를 바로잡았음
정정 날짜가 5월 8일이라면 7월 24일 글은 이 기능이 확장되지 않는다고 했던 유명 글이 악의적으로 작성된 것도 아니고 당시 기준으로 틀리지 않았을 수도 있음을 인정할 필요가 있음
마지막으로 확인했을 때 LISTEN/NOTIFY는 알림 데이터에 8,000바이트 상한이 있어 명백히 확장되지 않는 측면이 있었음. 알림을 행으로 저장하고 ID만 전달할 수 없는 데이터라면 사용하기 어려움
웹 게임의 이벤트는 상태 변경을 설명하는 일시적인 데이터라 데이터베이스에 저장할 이유가 없었고 8,000바이트를 넘을 수도 있어 이 용도에는 맞지 않았음
확장 가능한 알림 시스템을 구현한다면 알림 크기에 상한을 두겠음. 메시지 크기를 O(1)로 유지해 알림 개수 확장에 집중할 수 있고, 임의로 큰 메시지는 성능을 멈추게 할 수 있으며 알림 시스템을 잘못 사용하고 있을 가능성도 알려줌
특정 행을 가리키지 않더라도 변경된 상태를 참조하는 메시지를 보낼 수 있지 않을지 궁금함
글에서는 전역 큐의 잠금 경합을 다루지만, 고정 크기 전역 큐의 또 다른 문제는 언급하지 않는 듯함. 한 채널의 느린 수신자 하나가 모든 채널에 대한 쓰기를 막을 수 있었음. 적어도 몇 년 전에는 그런 장애 형태가 가능했으며 지금은 바뀌었을 수도 있음
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기