새로운 메시지 브로커를 도입했다는 이야기는 흔하지만, 운영 중이던 브로커를 깔끔하게 제거했다는 이야기는 드뭅니다.
사내 서비스의 이체 요청 비동기 처리를 위해 RabbitMQ를 운영하고 있었습니다. 이를 PostgreSQL 기반 큐 익스텐션인 pgmq로 이전하고 독립 브로커 인프라를 완전히 철거했습니다.
독립 메시지 브로커가 요구하는 숨은 비용
메시지 브로커의 진짜 비용은 인스턴스 요금에 그치지 않습니다. 운영팀이 24시간 감시하고 백업해야 할 상태 저장 시스템(Stateful System)이 하나 더 늘어난다는 점이 가장 큰 부담입니다.
환경(dev, stg, prd)마다 클러스터 인스턴스를 유지해야 하고, 영구 디스크(PV)를 관리해야 하며, 버전 업그레이드, 장애 복구, 모니터링 알림 설정, 신규 입사자 온보딩 문서까지 관리 포인트가 기하급수적으로 늘어납니다.
이러한 운영 비용을 감당하려면 브로커가 반드시 필요한 타당한 이유(초당 수만 건 이상의 극단적인 처리량, 복잡한 AMQP 토픽 라우팅 등)가 있어야 합니다. 하지만 당시 워크로드는 안정적인 단일 작업 큐 1개면 충분한 수준이었습니다.
이미 운영 중인 PostgreSQL 활용하기
데이터베이스는 이미 전사적으로 고가용성(HA) 구성, 백업, 모니터링 체계를 갖추어 안정적으로 운영되고 있었습니다.
PostgreSQL 위에서 동작하는 가벼운 큐 엔진을 사용하면 추가적인 인프라 운영 부담이 0에 수렴합니다. 새로운 시스템을 학습할 필요 없이, 이미 익숙한 DB 도구와 트랜잭션 안에서 큐를 다룰 수 있습니다.
"DB를 큐로 쓰는 것은 안티패턴이다"라는 통념이 있지만, 이는 초당 수만 건의 대규모 트래픽이 발생하는 서비스에 해당합니다. 비즈니스 규모와 트래픽 특성에 맞지 않는 과도한 아키텍처는 불필요한 복잡성만 낳을 뿐입니다.
이름 기반 격리가 아닌 DB Role 기반 권한 통제
자체 호스팅 DB 환경에서 여러 스테이지가 하나의 DB 엔진을 공유할 때, 가장 위험한 것은 휴먼 에러로 인한 환경 혼선입니다.
단순히 큐 이름에 dev_queue, prd_queue처럼 접두어를 붙이는 방식은 강제력이 없습니다. 개발 환경의 앱이 실수로 운영 큐 이름을 바라보면 대형 사고로 이어집니다.
이를 방지하기 위해 데이터베이스 Role 단위의 명확한 권한 격리를 적용했습니다:
- 개발용 계정은 오직 개발 큐 테이블에만 접근할 수 있도록 권한을 제한했습니다.
- 애플리케이션 계정에 스키마 생성 권한(
CREATE)을 주지 않아, 잘못된 큐 이름으로 요청 시 조용히 새 큐가 생성되는 대신 즉시 에러를 발생시키도록 설계했습니다. - 운영 마이그레이션 전까지는 운영 전용 계정 자체를 생성하지 않아 접근 가능성을 원천 차단했습니다.
철거 직전 멈추어 확인한 30분의 가치
기존 RabbitMQ를 철거하기 직전, 운영 큐에 1,290건의 처리되지 않은 메시지가 남아있는 것을 발견했습니다.
"어차피 새 시스템으로 옮겼으니 테스트 잔여물이겠지" 하고 바로 삭제할 수도 있었지만, 작업을 멈추고 페이로드를 전수 분석했습니다. 확인 결과 과거 비정상 종료된 요청 기록들이었고, 안전하게 아카이빙한 후 철거를 완료했습니다.
인프라 철거 작업에서 가장 중요한 태도는 "확인되지 않은 상태는 단 하나도 남겨두지 않는다"는 꼼꼼함입니다.
요약
아키텍처의 완성도는 기능을 더하는 것보다, 불필요한 구성 요소를 걷어내어 시스템을 단순하고 견고하게 유지하는 것에서 나옵니다.