백엔드 면접 학습 노트 목차

메시지 큐·비동기 — 백엔드 면접 (실제 기출 기반·심화)

"메시지 큐를 왜 쓰나", "KafkaRabbitMQ 차이", "exactly-once 가능한가", "순서 보장", "중복 처리", "dual-write(트랜잭셔널 아웃박스)" — MSA·비동기 설계 단골이다. 기업 기술블로그(우아한형제들·올리브영)가 실질 1차 소스다.

함정형 단골: ① Kafka가 Exactly-Once 보장? → end-to-end는 사실상 멱등 컨슈머 ② Kafka는 순서를 항상 보장? → 파티션 단위만 ③ MQ 쓰면 유실 0? → acks·복제·outbox 없이는 유실.


1. 왜 메시지 큐인가 (★ 단골)

실제 질문: "메시지 큐를 왜 쓰나요?" · "왜 메일을 동기로 안 보내고 큐로?"

동기 호출은 상대가 느리거나 죽으면 나도 막힌다(강결합). MQ는 사이에 버퍼를 둬:

  • 비동기(보내고 바로 응답)·디커플링(서로 몰라도 됨)·부하 평탄화(폭주를 큐가 흡수)·확장(소비자 추가).

꼬리질문: "왜 메일을 큐로?" → 외부 SMTP 지연·장애가 주문 트랜잭션을 막음 → 이벤트 발행 후 비동기. / "단점은?" → 복잡도·결과적 일관성·중복·순서·운영 부담.


2. Kafka vs RabbitMQ (★★★ 단골)

실제 질문: "KafkaRabbitMQ의 차이는?" · "컨슈머가 죽으면 메시지는 어떻게 되나요?"

KafkaRabbitMQ
모델로그 기반 pub/sub(덤 브로커·스마트 컨슈머·pull)전통 브로커(스마트 브로커·푸시)
저장로그 보존·재생(replay)큐(소비되면 사라짐)
라우팅토픽·파티션Exchange(direct/fanout/topic/headers)
오프셋컨슈머가 추적(여러 그룹 독립)브로커가 ack·전달 상태 관리
강점이벤트 스트리밍·고처리량·재처리작업 분배·복잡한 라우팅·낮은 지연

깊이: Kafka는 데이터를 쌓아 두고 여러 컨슈머 그룹각자 오프셋으로 독립 소비·재생 → 스트리밍·로그. RabbitMQ는 스마트 브로커가 Exchange로 유연하게 라우팅·즉시 소비 → 작업 큐.

꼬리질문: "컨슈머가 죽으면?" → Kafka는 오프셋만큼 보존(다시 읽음), RabbitMQ는 ack 안 하면 재큐. / "Kafka가 빠른 이유?" → 순차 디스크 쓰기·페이지 캐시·zero-copy·배치.


3. 전달 시맨틱·Exactly-Once (★★★ 단골·함정)

실제 질문: "Kafka의 전달 시맨틱은?" · "Exactly-Once를 정말 보장할 수 있나요?" · "중복 메시지를 어떻게 처리하나요?" (medium 김기현)

의미위험
at-most-once처리 전 오프셋 커밋유실
at-least-once처리 후 커밋중복
exactly-once정확히 1번복잡
  • Kafka 기본 = at-least-once(처리 후 오프셋 커밋 전에 죽으면 재처리 → 중복 정상) → 소비자가 멱등해야.
  • EOS(Exactly-Once Semantics): ① 멱등 프로듀서(enable.idempotence=true — 브로커가 PID+시퀀스로 재전송 dedup) + ② 트랜잭션(다중 파티션 원자적·read-process-write).
  • ⚠️ 진짜 EOS 되나?(★): Kafka 내부(read-process-write)는 보장. 하지만 DB 커밋 성공 후 오프셋 커밋 실패 같은 외부(DB·API)까지면 깨질 수 있어 → 결국 **소비자 멱등(idempotent consumer)**이 현실 해법.

깊이 — 중복 제거 4전략: ① Upsert ② Unique Key 제약(DataIntegrityViolationException) ③ 인메모리 캐시(초기화 시 재발) ④ Redis Bloom Filter(false positive).

꼬리질문: "중복을 어떻게?" → 메시지 ID dedup(DB unique·Redis)·upsert. / "유실을 막으려면?" → 프로듀서 acks=all·복제·DB outbox·소비자 ack는 처리 후.

함정: ❌ "Kafka가 Exactly-Once를 완벽 보장" → 외부까지는 멱등 컨슈머가 정석.


4. 순서 보장 (★★ 단골·함정)

실제 질문: "Kafka는 메시지 순서를 보장하나요?" · "순서가 중요한데 처리량도 필요하면?"

  • Kafka는 파티션 단위로만 순서 보장(오프셋 순). 전역 순서는 X(파티션 늘리면 라운드로빈으로 깨짐).
  • 같은 키같은 파티션키 기준 순서(예: userId로 파티셔닝 → 한 사용자 이벤트 순서대로).

꼬리질문: "전역 순서 필요?" → 파티션 1개(병렬 포기) 또는 앱에서 정렬. / "처리량+순서?" → 메시지 키로 파티셔닝(키 단위 순서 + 파티션 병렬).

함정: ❌ "Kafka는 순서를 항상 보장" → 파티션 단위만.


5. Kafka 내부 — 파티션·ISR·acks·리밸런싱 (★ 심화)

  • 토픽→파티션으로 나눠 병렬·확장. 각 메시지는 파티션 안 오프셋(순번).
  • 컨슈머 그룹: 한 그룹 안에서 파티션을 나눠 소비(병렬·파티션 수가 상한). ⚠️ 리밸런싱(멤버 변동 시 파티션 재배분 — 그동안 처리 멈춤·중복 가능 → 멱등).
  • 복제·ISR(In-Sync Replicas): 파티션을 여러 브로커에 복제(leader/follower)·ISR=리더와 동기화된 집합.
  • acks: 0(안 기다림·유실)·1(리더만)·all/-1(ISR 전부 — 유실 방지).

꼬리질문: "컨슈머 lag이 쌓이면?" → 소비자 스케일아웃(파티션 수까지)·처리 최적화·백프레셔. / "파티션 수를 늘리면?" → 병렬↑·순서 단위↓·리밸런싱 비용.


6. dual-write·Transactional Outbox·DLQ (★★ 심화)

실제 질문: "Transactional Outbox 패턴이 무엇이고 왜 쓰나요?" · "DLQ는 언제 쓰나요?" (우아한형제들·올리브영)

  • dual-write 문제: "DB 저장 + 메시지 발행"을 각각 하면 하나만 성공 시 불일치(DB는 저장됐는데 발행 실패·또는 반대).
  • Transactional Outbox: 비즈니스 데이터와 메시지를 같은 트랜잭션으로 outbox 테이블에 쓰고, **릴레이/CDC(Debezium)**가 outbox를 읽어 발행 → 원자성 확보.
다이어그램 로딩 중…
  • DLQ(Dead Letter Queue): N번 재시도 후 실패 메시지 격리(독약 메시지가 전체를 막지 않게). 재시도: 지수 백오프·재시도 토픽. 백프레셔: 소비자 못 따라오면 prefetch 제한·스케일아웃.

꼬리질문: "outbox에서도 중복 발행은?" → CDC가 같은 행을 두 번 읽을 수 있어 소비자 멱등 필요. / "DLQ 메시지는?" → 원인 분석 후 수동/자동 재처리.

함정: ❌ "DB 저장과 발행을 그냥 둘 다 하면 됨" → dual-write 불일치 → outbox.


7. 이벤트 스키마 진화·계약 (★ 채용공고 보강·2026-07)

실제 질문: "이벤트 스키마가 자주 바뀌고 필드가 누락/의미 변경됩니다. 생산 환경에서 스키마 진화를 안전하게 하려면 어떤 계약·검증·배포 체계를 두겠습니까?" (prime-career 현대차·CJ대한통운 공고)

프로듀서와 컨슈머는 독립 배포되므로, 한쪽 변경이 상대를 깨지 않게 호환성 계약이 필요하다.

  • 스키마 레지스트리(Confluent Schema Registry 등) + Avro/Protobuf/JSON Schema로 스키마를 중앙 관리하고, 이벤트에 schema_version 을 포함.
  • 호환성 종류(★): backward(새 스키마로 데이터 읽기 가능 → 소비자 먼저 배포), forward(옛 스키마로 데이터 읽기 가능 → 생산자 먼저 배포), full(양쪽). 어느 쪽을 먼저 배포하느냐로 선택.
  • 안전 규칙: 새 필드는 optional + 기본값, 필드 삭제·타입 변경 금지(deprecate만), 의미가 바뀌면 같은 필드 재활용 말고 새 필드 추가.
  • Tolerant Reader(Postel의 법칙): 파서는 관대하게 — 모르는 필드는 무시, 누락은 기본값 → 사소한 변경에 파이프라인이 죽지 않게.
  • 배포 체계: ① CI에서 스키마 등록 시 호환성 검사 통과해야 머지, ② 소비자 주도 계약 테스트(CDC, Pact) 로 생산자 변경이 소비자를 깨는지 사전 검출, ③ 필수 필드 검증 실패분은 격리 큐(quarantine) 로.

꼬리질문: "소비자를 먼저 배포? 생산자를 먼저?" → backward 호환이면 소비자 먼저(새 코드가 옛·새 데이터 다 읽음), forward면 생산자 먼저. / "outbox 이벤트에도 스키마 진화가?" → 동일 적용, CDC(Debezium)가 읽는 테이블 변경도 계약.

함정: ❌ "필드 타입만 바꾸면 되지" → 소비자 역직렬화 깨짐. ❌ "스키마는 코드에만 두면 됨" → 레지스트리·계약테스트 없이는 배포 순서 사고.

(출처: Confluent — Schema Evolution·Compatibility·Pact — consumer-driven contracts·Kleppmann DDIA 4장(Encoding & Evolution) 교차검증. 실무 프레이밍: docs/04·05.)


8. KRaft·Kafka 4.0 — 버전을 아는지 가르는 질문 (★★ 신규)

나오는 질문: "Kafka에서 ZooKeeper는 무슨 역할인가요?" · "지금도 쓰나요?" · "KRaft가 뭔가요?"

이건 어느 시점 자료로 공부했는지가 그대로 드러나는 질문이다. "ZooKeeper가 브로커 메타데이터를 관리합니다"까지만 답하면 2024년에 멈춘 지식이다.

ZooKeeper가 하던 일: 어느 브로커가 어느 파티션의 리더인지, 토픽·설정·ACL이 무엇인지 같은 클러스터 메타데이터 관리와 컨트롤러 선출. 즉 Kafka를 운영한다는 건 분산 시스템 두 개를 운영하는 일이었다.

KRaft(Kafka Raft, KIP-500)는 그 일을 Kafka 안으로 가져왔다. 발상은 단순하다 — 메타데이터도 로그로 관리하면 된다. Kafka가 제일 잘하는 게 로그니까.

다이어그램 로딩 중…

깊이 — 왜 장애 복구가 빨라지나(★★). ZooKeeper 시절엔 컨트롤러가 죽으면 새 컨트롤러가 ZooKeeper에서 전체 상태를 다시 읽어 들여야 했고, 파티션이 많을수록 그 로딩이 길어졌다. KRaft에서는 팔로워 컨트롤러가 같은 메타데이터 로그를 이미 따라오며 메모리에 상태를 들고 있어서 거의 즉시 이어받는다. 로그가 무한정 커지지 않도록 주기적으로 **스냅샷**을 남기고, 브로커는 그 로그를 컨슈머처럼 당겨(pull) 읽는다. 결과적으로 파티션 수 한계도 크게 늘었다.

Kafka 4.0(2025-03)은 ZooKeeper 모드를 아예 제거했다. 실무에서 중요한 건 두 가지다.

항목내용
마이그레이션 경로ZooKeeper 모드에서 4.0으로 직행 불가. 3.x(권장 3.9)에서 먼저 KRaft로 전환한 뒤 4.0으로
자바 요구 버전클라이언트·Streams는 Java 11, 브로커·Connect·도구는 Java 17 이상

같은 4.0에서 KIP-848 차세대 리밸런스 프로토콜도 정식(GA)이 됐다. 5번에서 본 리밸런싱의 후속이다 — 예전에는 그룹 멤버 중 리더 컨슈머가 파티션 배정을 계산해서 멤버가 들고 날 때마다 그룹 전체가 동기화 지점을 거쳤는데, 새 프로토콜은 브로커의 그룹 코디네이터가 계산해 점진적으로 내려보낸다. 컨슈머가 많을수록 차이가 크고, group.protocol=consumer로 참여시킨다. 그리고 KIP-932 share groups(얼리 액세스)가 들어와, Kafka 토픽 위에서 큐처럼 나눠 소비하는 패턴도 가능해졌다.

꼬리질문: "KRaft를 쓰면 뭐가 좋아지나요?" → 운영 대상이 하나로 줄고, 컨트롤러 페일오버가 거의 즉시이며, 파티션 확장 한계가 올라간다. / "그럼 ZooKeeper는 이제 어디에 쓰나요?" → Kafka에서는 빠졌지만 분산 락·서비스 디스커버리 용도로는 여전히 쓰인다(etcd와 같은 자리).

함정: ❌ "Kafka는 ZooKeeper가 필요하다" → 4.0부터 아니다. ❌ "ZooKeeper 클러스터를 4.0으로 바로 올리면 된다" → 3.9를 거쳐야 한다.


9. 메시지는 언제까지 남나 — 리텐션·로그 컴팩션 (★★ 심화)

나오는 질문: "Kafka는 소비해도 메시지가 남는다고 했는데, 언제까지 남나요?" · "__consumer_offsets는 계속 커지지 않나요?" · "특정 사용자 데이터만 지우려면요?"

"로그라서 재처리가 된다"는 답에는 언제까지라는 조건이 빠져 있다. 그걸 정하는 게 cleanup.policy 다.

delete(기본) — 오래된 세그먼트 파일을 통째로 버린다. retention.ms(기본 7일)나 retention.bytes 기준이다. 재처리가 가능한 창이 곧 이 값이라, "3개월 전 데이터를 다시 흘려 주세요"는 리텐션이 7일이면 애초에 불가능하다.

compact — 지우는 기준이 나이가 아니라 키다. 같은 키의 최신 값만 남긴다.

컴팩션 전:  (u1,"서울") (u2,"부산") (u1,"대전") (u3,"광주") (u1,"제주")
                 ↓  같은 키의 옛 값 제거 (오프셋 번호는 그대로)
컴팩션 후:  (u2,"부산") (u3,"광주") (u1,"제주")

그래서 컴팩션 토픽은 "이벤트 기록"이 아니라 "현재 상태 스냅샷" 이 된다. 처음부터 다 읽으면 모든 키의 최신 상태가 복원되므로 캐시 워밍·설정 배포·CDC 스냅샷에 쓴다. 질문에 나온 __consumer_offsets가 바로 컴팩션 토픽이다 — 필요한 건 "이 그룹의 현재 오프셋"이지 그 이력이 아니니까.

깊이 — 두 가지 전제(★).키가 없는 메시지는 컴팩션 대상이 될 수 없다. 무엇의 최신인지 판단할 근거가 없기 때문이다. ② 삭제를 표현하려면 툼스톤(tombstone)키는 있고 값이 null 인 메시지 — 를 보낸다. 컨슈머들이 "이 키는 지워졌다"를 볼 시간을 준 뒤 delete.retention.ms가 지나면 툼스톤 자체도 정리된다. GDPR 같은 개인정보 삭제 요구가 이 방식으로 처리된다.

cleanup.policy=compact,delete둘을 함께 걸면 먼저 컴팩션하고 그다음 리텐션으로 오래된 것을 버린다.

꼬리질문: "컴팩션 토픽에서 특정 키를 완전히 지우려면?" → 툼스톤 발행 후 컴팩션·delete.retention.ms 경과를 기다린다. / "리텐션을 늘리면 되지 않나요?" → 디스크 비용이 선형으로 늘고, 정말 장기 보관이 필요하면 별도 저장소(레이크)로 싱크하는 게 정석이다.

함정: ❌ "Kafka는 메시지를 영구 보관한다" → 기본 7일이다. ❌ "컴팩션을 켜면 중복이 사라진다" → 같은 키의 옛 값만 정리하는 것이지 중복 제거가 아니다.


10. 컨슈머 랙 — 운영 1순위 지표 (★★ 실무)

나오는 질문: "메시지 처리가 밀리는 걸 어떻게 감지하나요?" · "컨슈머 랙이 뭔가요?" · "랙이 쌓이면 어떻게 대응하죠?"

정의는 뺄셈 하나다.

랙 = LEO(Log End Offset — 파티션에 쌓인 마지막 오프셋)
   − 그룹이 커밋한 오프셋

예: LEO 10,000 / 커밋 9,500 → 랙 500 (읽어야 할 게 500건 남았다)
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
  --describe --group order-consumer
# TOPIC   PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG
# orders  0          9500            10000           500

깊이 — 랙은 "느리다"가 아니라 "관계"다(★★). 랙이 말해 주는 건 생산 속도를 소비가 못 따라간다는 사실이고, 어디에 쌓이는지가 원인을 가른다.

랙 패턴원인대응
특정 파티션에만 쏠림키 분포 치우침(핫 파티션)키 설계 변경·파티션 재분배
모든 파티션에서 고르게 증가컨슈머 부족 또는 처리 로직이 느림컨슈머 스케일아웃(파티션 수가 상한)·처리 최적화
리밸런스·배포 직후 급증 후 회복정상대응 불필요
랙이 갑자기 0오프셋이 리셋됐거나 리텐션으로 데이터가 날아감오히려 경보 대상

마지막 두 줄 때문에 순간값으로 알림을 걸면 오탐이 쏟아진다. 배포마다 랙이 튀었다가 따라잡히기 때문이다. LinkedIn의 Burrow 같은 도구가 임계값 대신 시간에 따른 추세로 판단하는 이유가 이것이다 — 줄고 있으면 정상, 계속 늘면 경보.

꼬리질문: "컨슈머를 늘렸는데 랙이 안 줄어요" → 파티션 수가 상한이다. 남는 컨슈머는 논다. 파티션을 늘려야 한다. / "랙을 건수 대신 시간으로 보면?" → 더 유용하다. "500건 밀림"보다 "3분 뒤처짐"이 SLA와 직접 연결된다.

함정: ❌ "랙 = 지연 시간" → 건수다. 메시지 처리 속도에 따라 같은 랙도 체감 지연이 다르다.


11. RabbitMQ 내부 — AMQP 모델·prefetch·쿼럼 큐 (★ 심화)

나오는 질문: "RabbitMQ는 어떻게 라우팅하나요?" · "prefetch가 뭔가요?" · "고가용성은 어떻게 확보하죠?"

2번의 비교표를 실제로 설명할 수 있어야 하는 지점이다. Kafka와 모델 자체가 다르다 — Kafka에서 프로듀서는 토픽·파티션을 직접 겨냥하지만, AMQP에서 발행자는 큐를 모른다. 익스체인지(exchange) 에 보낼 뿐이고, 어느 큐로 갈지는 바인딩(binding) 규칙이 정한다.

다이어그램 로딩 중…
익스체인지라우팅 방식
direct라우팅 키가 바인딩 키와 정확히 일치할 때만
fanout라우팅 키 무시, 바인딩된 모든 큐에 복사(브로드캐스트)
topic점(.)으로 나뉜 패턴 매칭 — *단어 하나, #0개 이상의 단어
headers라우팅 키 대신 헤더 속성으로 매칭(`x-match=any

이 간접 계층이 RabbitMQ의 강점이다 — 발행자 코드를 안 고치고 바인딩만 추가해 새 구독자를 붙인다.

깊이 — prefetch는 왜 중요한가(★★). 소비자가 basic.ack를 보내야 브로커가 메시지를 지우고, 확인 없이 죽으면 다른 소비자에게 재전달된다. prefetch(QoS)확인 안 된 메시지를 소비자당 몇 개까지 미리 밀어 줄지의 상한이다.

prefetch = 1     안전하지만 매 건 왕복 → 처리량 손해
prefetch = 1000  빠른 소비자가 1,000건을 쥔 채 느리게 처리
                 → 다른 소비자는 놀고 부하 분산이 무너진다

즉 prefetch는 처리량과 공평한 분배 사이의 손잡이다. Kafka에는 이에 정확히 대응하는 개념이 없다 — 파티션이 이미 소비 단위를 나눠 두기 때문이다.

깊이 — 고가용성은 이제 쿼럼 큐뿐(★). RabbitMQ 4.0에서 classic mirrored queue가 제거돼, 복제 큐는 쿼럼 큐(quorum queue) 가 유일한 선택지다. Raft 합의 알고리즘으로 복제해 데이터 안전성과 처리량이 모두 낫고(같은 조건에서 미러링 큐의 약 3배), 항상 durable이다. 대신 exclusive 큐·global QoS는 지원하지 않는다. 실무에 바로 와닿는 건 delivery-limit(기본 20) — 배달 횟수를 헤더로 세다가 한계를 넘기면 버리거나 DLX(Dead Letter Exchange) 로 보내 무한 재큐 루프를 구조적으로 막는다. 6번의 DLQ가 RabbitMQ에서는 이 DLX로 구현된다.

꼬리질문: "메시지가 무한히 재시도되며 큐를 막습니다" → 쿼럼 큐의 delivery-limit + DLX. 클래식 큐라면 재시도 횟수를 헤더로 직접 세야 한다. / "fanout과 Kafka의 컨슈머 그룹 여럿은 같은 건가요?" → 결과는 비슷하지만 시점이 다르다. fanout은 발행 시점에 복사하고, Kafka는 하나의 로그를 그룹마다 각자 오프셋으로 읽는다.

함정: ❌ "RabbitMQ는 발행자가 큐를 지정한다" → 익스체인지에 보낸다. ❌ "미러링 큐로 HA를 구성한다" → 4.0에서 제거됐다.


12. 스프링에서의 구현 — 발행·소비·실패 처리 (★ 코드)

나오는 질문: "실패한 메시지는 어떻게 처리하셨나요?" · "재시도는 몇 번이고 그다음은요?"

// 발행 — 키를 주면 같은 키가 같은 파티션으로 간다(4번의 순서 보장)
@Service
class OrderEventPublisher {
    private final KafkaTemplate<String, OrderPlaced> template;

    void publish(OrderPlaced event) {
        template.send("orders", event.orderId(), event);   // (토픽, 키, 값)
    }
}

// 소비 — concurrency는 이 인스턴스가 띄울 컨슈머 스레드 수
@KafkaListener(topics = "orders", groupId = "order-consumer", concurrency = "3")
void onOrderPlaced(OrderPlaced event) {
    orderService.handle(event);     // 예외가 나면 아래 에러 핸들러로
}

실패 처리가 답변의 핵심이다. 그냥 두면 같은 메시지에서 무한 재시도하며 그 파티션 전체가 막힌다(독약 메시지).

@Bean
DefaultErrorHandler errorHandler(KafkaTemplate<String, Object> template) {
    var recoverer = new DeadLetterPublishingRecoverer(template);   // "<원본토픽>.DLT" 로
    return new DefaultErrorHandler(recoverer, new FixedBackOff(1000L, 3L));  // 1초 간격 3회 후 DLT
}

깊이 — 재시도하면 안 되는 예외가 있다(★). 역직렬화 실패는 재시도해도 절대 성공하지 않는다. 본문이 애초에 깨졌거나 스키마가 안 맞는 것이라(7번의 스키마 진화와 연결된다), 재시도는 시간 낭비이고 그동안 파티션이 막힌다. 이런 예외는 재시도 없이 즉시 DLT로 분류해야 한다. 반대로 일시적 네트워크 오류·DB 데드락은 재시도가 맞다. "재시도가 의미 있는 실패인가" 로 나누는 게 기준이다.

꼬리질문: "DLT에 쌓인 메시지는 어떻게 하나요?" → @DltHandler로 받아 로깅·알림하고, 원인 수정 후 원본 토픽으로 되돌려 재처리한다. 자동 재투입은 같은 이유로 다시 실패할 수 있어 보통 수동 승인 단계를 둔다. / "재시도 중에 그 파티션의 다음 메시지는요?" → 막힌다. 그래서 재시도 횟수·간격을 짧게 잡거나, 지연이 긴 재시도는 별도 재시도 토픽으로 뺀다.


흔한 오답·함정 정리

  • KafkaExactly-Once 완벽 보장 → 외부까지는 멱등 컨슈머.
  • Kafka는 순서를 항상 보장 → 파티션 단위만.
  • MQ 쓰면 유실 0acks=all·복제·outbox 없이는 유실.
  • DB 저장+발행을 그냥 둘 다 → dual-write 불일치(outbox).
  • 컨슈머 그룹에 컨슈머를 무한정 늘리면 빨라진다 → 파티션 수가 상한.
  • Kafka는 ZooKeeper가 필요하다4.0부터 아니다(KRaft 전용). ZK 모드에서 4.0 직행도 불가(3.9 경유).
  • Kafka는 메시지를 영구 보관retention.ms 기본 7일. 재처리 창이 곧 그 값.
  • 컴팩션을 켜면 중복이 사라진다같은 키의 옛 값만 정리. 중복 제거가 아니다.
  • 랙 = 지연 시간 → 건수다. 시간으로 환산해야 SLA와 연결된다.
  • RabbitMQ는 발행자가 큐를 지정한다익스체인지에 보내고 바인딩이 경로를 정한다.
  • 미러링 큐로 HA 구성 → RabbitMQ 4.0에서 제거. 쿼럼 큐뿐이다.
  • 실패하면 계속 재시도하면 된다 → 역직렬화 실패는 영원히 실패하고 그동안 파티션이 막힌다.

한국 면접 단골 Q&A (답변 골격)

질문핵심 답
MQ 왜 쓰나비동기·디커플링·부하 평탄화·확장
Kafka vs RabbitMQ로그 스트리밍 vs 작업 큐(Exchange)
전달 시맨틱Kafka 기본 at-least-once·중복→멱등
Exactly-Once멱등 프로듀서+트랜잭션·외부는 멱등 컨슈머
순서 보장파티션 단위만·같은 키→같은 파티션
acks0/1/all(ISR 전부·유실 방지)
리밸런싱멤버 변동 시 재배분·멈춤·중복
중복 처리ID dedup·upsert·unique·Bloom
outboxdual-write 불일치→DB 트랜잭션+outbox+CDC
DLQ반복 실패 격리·후처리
ZooKeeper 역할메타데이터·컨트롤러 선출 — 4.0에서 제거
KRaft컨트롤러 쿼럼 + __cluster_metadata 로그·즉시 페일오버
Kafka 4.0 업그레이드ZK→4.0 직행 불가·3.9에서 KRaft 전환 후
KIP-848브로커가 배정 계산·점진 적용·group.protocol=consumer
리텐션retention.ms 기본 7일 = 재처리 가능 창
로그 컴팩션키별 최신 값만·상태 스냅샷·키 필수·툼스톤으로 삭제
컨슈머 랙LEO − 커밋 오프셋·추세로 판단(순간값은 오탐)
랙이 안 줄어듦파티션 수가 상한 → 파티션을 늘려야
AMQP 라우팅발행자는 큐를 모름 → 익스체인지 + 바인딩(direct/fanout/topic/headers)
prefetch미확인 메시지 상한 — 처리량 vs 공평 분배의 손잡이
쿼럼 큐Raft 복제·4.0부터 HA 유일·delivery-limit 기본 20
실패 처리재시도 N회 후 DLT·역직렬화 실패는 즉시 DLT

꼬리질문 대비 (상 난이도)

  • "메시지 유실 막으려면?" → acks=all·복제·DB outbox·소비자 ack는 처리 후.
  • "전역 순서 필요?" → 파티션 1개(처리량 희생) 또는 키 파티셔닝.
  • "컨슈머 lag?" → 스케일아웃(파티션 수까지)·처리 최적화·백프레셔.
  • "Exactly-Once를 진짜?" → Kafka 내부만·외부는 멱등 컨슈머.
  • "Kafka가 빠른 이유?" → 순차 디스크·페이지 캐시·zero-copy·배치.
  • "KRaft로 바꾸면 뭐가 좋아지나요?" → 운영 대상이 하나로 축소·컨트롤러 페일오버가 거의 즉시(팔로워가 메모리에 상태 보유)·파티션 확장 한계 상승.
  • "3개월 전 이벤트를 재처리해 달라는 요청이 왔습니다" → 리텐션이 그보다 짧으면 Kafka에는 이미 없다. 장기 보관은 별도 저장소로 싱크해 두는 게 정석.
  • "컴팩션 토픽에서 특정 사용자 데이터를 지우려면?" → 툼스톤(키 있고 값 null) 발행 → 컴팩션 → delete.retention.ms 경과.
  • "랙 알림을 걸었는데 배포마다 울립니다" → 순간값이 아니라 추세로 판단(Burrow 방식). 또는 회복 시간을 조건에 넣는다.
  • "역직렬화 실패 메시지가 파티션을 막습니다" → 재시도 대상에서 제외하고 즉시 DLT. 스키마 계약(7번)으로 재발 방지.
  • "RabbitMQ에서 특정 소비자만 메시지를 독점합니다" → prefetch가 과도하다. 낮춰서 분배를 회복시킨다.

한 줄 요약 — MQ는 비동기·디커플링·부하 평탄화. Kafka(로그·스트리밍·재생)↔RabbitMQ(Exchange·작업 큐). Kafka 기본 at-least-once(중복→소비자 멱등), EOS=멱등 프로듀서(PID+시퀀스)+트랜잭션이나 외부까지는 멱등 컨슈머, acks=all+ISR로 유실 방지. 순서는 파티션 단위(같은 키→같은 파티션). dual-write는 Outbox+CDC, 반복 실패는 DLQ. 클러스터 메타데이터는 KRaft(컨트롤러 쿼럼 + __cluster_metadata 로그)가 관리하고 Kafka 4.0은 ZooKeeper를 제거했다(3.9 경유 필수·브로커 Java 17). 리밸런스는 KIP-848로 브로커가 계산해 점진 적용. 메시지 수명은 cleanup.policydelete면 리텐션 기간이 재처리 창(기본 7일), compact면 키별 최신 값만 남아 상태 스냅샷(키 필수·삭제는 툼스톤). 운영 1순위 지표는 컨슈머 랙(LEO − 커밋 오프셋)이고 순간값이 아니라 추세로 본다. RabbitMQ는 발행자가 큐를 모르고 익스체인지+바인딩이 경로를 정하며(direct/fanout/topic/headers), prefetch가 처리량과 분배의 손잡이, HA는 4.0부터 쿼럼 큐(Raft·delivery-limit 20)뿐이다.

(출처 — 한국 면접 기출·교차검증 2026-06: medium 김기현 — Kafka 중복 메시지·Exactly-Once·우아한형제들 — 카프카+Transactional Outbox·올리브영 — Kafka 중복/유실·velog — Kafka vs RabbitMQ · Confluent — Delivery Semantics·Idempotent Producer·RabbitMQ — Exchanges·microservices.io(Transactional Outbox) 교차검증.)

*(8~12번 보강 2026-08 — 1차 자료: Apache Kafka 4.0 릴리스 공지·Confluent — KRaft·Confluent — Log Compaction·RabbitMQ — AMQP 0-9-1 Concepts·RabbitMQ — Quorum Queues·Spring for Apache Kafka — Handling Exceptions 교차검증.)

캐싱·Redis — 전략·eviction·스탬피드·자료구조시스템 설계·분산 — CAP·복제·샤딩·Saga·서킷브레이커