메시지 브로커를 도입하는 글은 흔합니다. 없애는 글은 드뭅니다.
이체 요청을 처리하는 큐가 RabbitMQ 위에 있었습니다. 그걸 PostgreSQL 기반 큐로 옮기고, 브로커를 개발·스테이징·운영에서 차례로 걷어냈습니다.
왜 없앴나
브로커가 하는 일이 큐 하나였습니다.
RabbitMQ 는 좋은 물건이지만, 그걸 쓰는 순간 운영해야 할 상태 저장 시스템이 하나 늘어납니다. 환경마다 인스턴스가 필요하고, 디스크가 붙고, 버전을 올려야 하고, 장애가 나면 봐야 합니다. 큐 하나 때문에 치르는 비용치고는 큽니다.
그런데 PostgreSQL 은 어차피 있었습니다. 그 위에서 도는 큐 확장을 쓰면 운영 대상이 하나 줄어듭니다. 처리량이 브로커를 요구할 만큼이 아니라면, 이쪽이 맞습니다.
이건 규모의 문제입니다. 초당 수만 건이 오간다면 반대 결론이 났을 겁니다.
데이터베이스 하나를 셋이 나눠 쓰는 문제
자체 호스팅이라 데이터베이스가 하나입니다. 개발·스테이징·운영이 같은 스키마를 공유합니다. 큐 이름을 환경별로 나누면 되지만, 이름은 관례일 뿐이라 지켜지지 않습니다.
그래서 환경별 데이터베이스 롤로 강제했습니다. 개발 롤은 개발 큐 테이블만 볼 수 있고, 스테이징 롤은 스테이징 것만 봅니다. 큐 확장의 함수들이 호출자 권한으로 실행되므로 이 테이블 권한이 그대로 강제됩니다.
스키마에 생성 권한은 주지 않았습니다. 큐는 인프라이지 애플리케이션이 만드는 것이 아닙니다. 앱이 큐 이름을 잘못 쓰면 새 큐가 조용히 생기는 대신 실패합니다. 그게 맞습니다.
운영 롤은 아예 만들지 않았습니다. 그때는 운영을 옮기기 전이었고, 붙을 수 있는 주체가 없어야 실수로도 안 붙습니다.
지우기 전에 한 일
메시지가 남은 큐를 지우면 그 요청은 영원히 처리되지 않습니다.
지우기 전에 세 가지를 확인했습니다. 브로커에 연결이 없을 것, 모든 가상 호스트의 큐에 메시지와 컨슈머가 0 일 것, 그리고 코드에 발행자가 남아 있지 않을 것. 앞의 둘만 보고 지우면, 다음 배포가 다시 메시지를 넣기 시작합니다.
개발·스테이징은 깨끗했습니다. 운영에는 메시지 1,290 건이 남아 있었습니다.
1,290 건의 정체
여기서 멈추고 추적했습니다.
내용을 열어 보니 차량 정보 스크래핑 요청이었고, 두 달 전 스크래퍼가 다른 방식으로 옮겨 가면서 소비자가 사라진 잔재였습니다. 발행하는 코드도 그때 함께 지워졌습니다. 즉 아무도 넣지 않고 아무도 꺼내지 않는 메시지가 두 달째 쌓여 있던 것입니다.
확인하고 버렸습니다.
이 과정이 30 분쯤 걸렸는데, 건너뛰었다면 "큐에 천 건 넘게 있는데 그냥 지웠다"가 됩니다. 그건 나중에 문제가 생겼을 때 되짚을 수 없는 종류의 결정이 됩니다.
지우는 순서
배포 도구를 먼저 뗐습니다. 안 그러면 동기화가 지운 것을 되살립니다. 리소스는 남겨 둔 채 연결만 끊고, 삭제 순서를 직접 통제했습니다.
그다음 내부 로드밸런서를 먼저 지웠습니다. DNS 자동화가 레코드를 회수한 뒤에 워크로드를 내려야, 이미 사라진 주소로 붙으려는 시도가 남지 않습니다.
결과
브로커 인스턴스가 네 환경에서 전부 사라졌습니다. 같은 작업에서 함께 정리한 캐시 서버까지 포함해 미사용 디스크가 0 개가 됐습니다.
운영해야 할 상태 저장 시스템이 하나 줄었습니다. 그게 이 작업의 전부이고, 충분한 이유였습니다.