ondelva

← Archive

RabbitMQ와 카프카, 큐와 로그의 차이

RabbitMQ는 꺼내면 사라지는 큐고 카프카는 읽어도 남는 로그다. 둘의 차이와 카프카 용어 다섯 개, 그리고 무엇으로 고를지 정리했다.

egg4분 읽기

“MQ 대신 카프카를 쓰는 이유” 같은 제목을 자주 본다. 그런데 카프카도 보통 메시지 큐로 묶어 부르니 대신이라는 말이 처음부터 좀 어긋나 있다.

한 회사에서 둘 다 쓰는 경우도 흔하다. 골라야 하는 대체재라기보다 맡는 일이 서로 다른 쪽에 가까운 듯 싶다.

차이를 한 줄로 줄이면 RabbitMQ는 큐고 카프카는 로그다. 읽고 난 메시지가 사라지느냐 남느냐의 문제다. 직접 운영해본 적은 없어서 아래는 공식 문서와 만든 쪽이 남긴 글을 찾아 정리한 내용이다. 설정 기본값은 2026년 10월 기준이다.

꺼내면 사라진다 - RabbitMQ

RabbitMQ에 넣은 메시지는 소비자가 받아 처리하고 ack를 보내면 큐에서 지워진다. 같은 메시지를 나중에 다시 읽을 방법은 없다.

그래서 “이 작업을 누가 하나 처리해줘”에 맞는다. 썸네일 생성이나 메일 발송처럼 한 번만 돌면 되는 일감이다.

소비자를 여러 대 붙이면 알아서 나눠 가진다. 느린 소비자 앞에 일감이 쌓이지 않도록 prefetch로 한 번에 받아갈 개수를 제한한다.

보내는 쪽이 큐를 직접 지목하지 않는다는 것도 특징이다. 생산자는 exchange에 던지고 exchange가 binding 규칙을 보고 어느 큐로 넣을지 정한다. 규칙 없이 전부 복사해 뿌리는 fanout, 키가 정확히 맞는 곳으로 보내는 direct, order.*.created 같은 패턴으로 거르는 topic이 있다. 이 구조는 AMQP 개념 문서에 정리되어 있다.

메시지 하나 단위로 다룰 수 있는 게 강점이다. 처리에 실패한 것만 골라 nack하고 재시도 횟수를 넘긴 건 dead letter 큐로 따로 보낸다. 우선순위를 매겨 급한 것부터 꺼낼 수도 있다.

브로커가 죽어도 메시지를 잃지 않으려면 quorum queue를 쓴다. Raft로 여러 노드에 복제해두는 큐인데, 예전에 쓰던 mirrored queue는 4.0에서 빠졌다.

읽어도 남는다 - 카프카

카프카는 LinkedIn에서 나왔고 출발점부터 큐가 아니라 로그였다. Jay Kreps님이 그 발상을 길게 쓴 The Log가 지금 읽어도 이 도구가 왜 이렇게 생겼는지 설명해준다. LinkedIn 쪽 원문은 내려가서 아카이브 링크다.

메시지는 파일 끝에 계속 덧붙는다. 소비자가 읽어도 지워지지 않고 정해둔 기간이 지나야 사라지는데, 기본값이 7일이다.

그래서 소비자가 하는 일은 “꺼내기”가 아니라 “어디까지 읽었는지 기억하기”다. 이 위치를 offset이라고 부른다. 며칠 전 지점으로 offset을 되감으면 그때부터 다시 읽는다.

배포한 소비자에 버그가 있어서 지난 이틀치를 잘못 처리했다면, RabbitMQ에서는 원본이 이미 사라졌으니 다른 곳에서 복구해와야 한다. 카프카는 offset만 돌리면 된다. 이게 실제로 가장 크게 갈리는 지점인 것 같다.

여러 팀이 같은 데이터를 각자 읽는 것도 된다. 주문 이벤트 하나를 배송 팀과 정산 팀과 데이터 팀이 서로 상관없이 자기 속도로 읽는다. 큐였다면 먼저 가져간 쪽이 이겨버린다.

카프카 용어는 파티션에서 갈라져 나온다

카프카 용어 다섯 개를 따로 외우기보다 파티션을 가운데 두고 보는 게 빠른 듯 싶다. 공식 문서 소개에도 이 순서로 나온다.

Topic은 메시지를 담는 이름이다. orders, payments처럼 종류별로 만든다.

Partition은 그 토픽을 실제로 나눠 담은 로그 파일이다. 토픽 하나가 파티션 여러 개로 쪼개져 있고 여기서 나머지가 전부 따라 나온다.

Broker는 파티션을 들고 있는 서버다. 파티션이 브로커들에 흩어져 있어서 서버를 늘리면 처리량이 늘어난다. 파티션마다 복제본을 두고 그중 하나가 리더가 되어 읽기와 쓰기를 받는다.

Producer는 넣는 쪽이다. 메시지에 키를 붙이면 그 키의 해시로 파티션이 정해지고 같은 키는 항상 같은 파티션으로 간다. 키가 없으면 적당히 흩어진다.

Consumer는 읽는 쪽인데 보통 그룹으로 묶어 쓴다. 그룹 안에서 파티션 하나는 소비자 하나가 맡는다. 파티션이 6개면 소비자 6대까지는 나눠 갖고 7대째부터는 놀게 된다.

여기서 두 가지가 나온다. 하나는 소비자를 늘리는 상한이 파티션 개수라는 것. 다른 하나는 순서 보장이 토픽 전체가 아니라 파티션 안에서만 된다는 것이다.

주문 이벤트를 주문 ID로 키를 잡으면 같은 주문의 생성과 취소는 같은 파티션에 순서대로 들어간다. 다른 주문끼리의 순서는 보장되지 않는데, 대개는 그걸로 충분하다.

파티션을 몇 개로 잡을지는 2015년에 나온 계산식이 아직도 많이 인용된다. 다만 파티션은 늘릴 수만 있고 줄이지 못하는 데다 늘리는 순간 키가 가던 파티션이 바뀌어서 순서가 어긋난다. 숫자를 맞추는 문제라기보다 되돌릴 수 없는 결정이라 조심하는 문제에 가까운 것 같다.

요즘은 경계가 좀 흐리다

RabbitMQ에도 Stream이 3.9부터 들어왔다. 덧붙이기만 하는 로그에 offset으로 읽는 방식이라 카프카가 하던 일을 그쪽에서도 한다.

반대로 카프카를 작업 큐처럼 쓰는 것도 물론 된다. 대신 메시지 하나만 골라 재시도하거나 우선순위를 매기는 건 로그 구조상 잘 맞지 않아서 실패한 것만 따로 토픽에 다시 넣는 식으로 직접 만들어야 한다.

운영 비용도 다르다. RabbitMQ는 노드 하나로도 시작하지만 카프카는 브로커를 여러 대 두고 복제를 거는 게 전제라 처음부터 덩치가 있다. 4.0부터 ZooKeeper가 빠지고 KRaft로 합쳐져서 예전보다는 나아졌다고 한다.

결론. 다시 읽을 일이 있느냐로 갈린다

고를 때 물어볼 건 하나인 듯 싶다. 이 메시지를 나중에 다시 읽을 일이 있는가.

한 번 처리하면 끝나는 일감이고 실패한 것만 골라 다루고 싶다면 RabbitMQ. 여러 소비자가 같은 데이터를 각자 읽고 되감아 다시 처리할 일이 있다면 카프카.

처리량이 크니까 카프카라는 설명도 흔한데, 개인 프로젝트나 작은 서비스 규모에서 그 차이가 체감될 일은 아무래도 별로 없을 것 같다. 그보다는 로그로 남겨두는 게 필요한 데이터인지를 먼저 보는 편이 맞아 보인다.

다음은 카프카에서 메시지를 잃지 않으려면 뭘 건드려야 하는지 정리해보려고 한다. acks만 올리면 된다는 말을 자주 보는데 문서를 읽어보니 그것만으로는 안 되는 것 같더라.

Filed under 개발

ShareXFacebookLinkedIn