CCDAK 학습 도구
함정 사전 — 헷갈리는 40쌍
CCDAK에서 실점은 "모르는 것" 때문보다 "비슷한 두 개를 뒤바꾼 것" 때문에 생깁니다.
session.timeout.ms와 max.poll.interval.ms,
acks와 min.insync.replicas,
map과 mapValues —
각각은 알아도 시험장에서 어느 쪽인지 헷갈립니다.
이 페이지는 그 40쌍을 같은 5축으로 비교해 정리합니다.
시험 3일 전부터 이 페이지만 반복해도 됩니다.
읽는 방법
- 모든 표가 같은 5개 행으로 되어 있습니다 — 무엇인가 / 어디 설정인가 / 언제 발동하는가 / 혼동 시 결과 / 시험에서의 출제 형태.
- "어디 설정인가" 행을 가장 먼저 보세요. 출제에서 가장 많이 바꿔치기되는 정보가 소속(프로듀서·컨슈머·브로커·토픽)입니다.
- 1회독은 전체를, 2회독부터는 "혼동 시 결과" 행만 훑으면 5분에 끝납니다.
- 마지막에 오답 유도 패턴 분석이 있습니다. 선택지 문장의 형태만 보고 함정을 배제하는 방법입니다.
session.timeout.ms vs max.poll.interval.ms,
acks vs min.insync.replicas, retention vs compaction 등 대표 6쌍을
소속(프로듀서·컨슈머·브로커·토픽)과 발동 시점으로 갈라 배치한 그림입니다.
컨슈머 — 1번부터 9번
1. session.timeout.ms vs max.poll.interval.ms
| 축 | session.timeout.ms | max.poll.interval.ms |
|---|---|---|
| 무엇인가 | 하트비트가 끊긴 뒤 코디네이터가 기다리는 시간. 기본 45000 | 두 poll() 호출 사이 허용 시간. 기본 300000 |
| 어디 설정인가 | 컨슈머 | 컨슈머 |
| 언제 발동하는가 | 백그라운드 하트비트 스레드가 멈췄을 때 | 애플리케이션 스레드의 처리가 길어졌을 때 |
| 혼동 시 결과 | 처리 지연 문제에 session.timeout.ms를 올려 증상은 안 잡히고 장애 감지만 늦어집니다 | |
| 출제 형태 | "프로세스가 죽은 것을 감지하는 설정은?" | "프로세스는 살아 있는데 그룹에서 빠지는 이유는?" → 1차 처방은 max.poll.records 축소 |
2. heartbeat.interval.ms vs session.timeout.ms
| 축 | heartbeat.interval.ms | session.timeout.ms |
|---|---|---|
| 무엇인가 | 하트비트를 보내는 주기. 기본 3000 | 하트비트를 기다리는 한계. 기본 45000 |
| 어디 설정인가 | 컨슈머 | 컨슈머 (브로커의 group.min/max.session.timeout.ms 범위 안이어야 함) |
| 언제 발동하는가 | 주기적으로 계속 | 마지막 하트비트로부터 이 시간이 지나면 |
| 혼동 시 결과 | 권장 관계는 heartbeat ≤ session ÷ 3입니다. 이를 어기면 하트비트 한 번만 놓쳐도 사망 판정이 납니다 | |
| 출제 형태 | "session.timeout.ms=9000일 때 heartbeat.interval.ms 권장값은?" → 3000 이하 | |
3. assign() vs subscribe()
| 축 | subscribe(topics) | assign(partitions) |
|---|---|---|
| 무엇인가 | 토픽을 구독하고 할당을 받음 | 파티션을 직접 지정 |
| 어디 설정인가 | 컨슈머 코드 (API) | 컨슈머 코드 (API) |
| 언제 발동하는가 | 리밸런스 시 파티션이 재배정됨 | 호출 즉시 고정. 리밸런스 없음 |
| 혼동 시 결과 | 둘을 같은 인스턴스에서 섞으면 IllegalStateException. assign()은 파티션 증가를 감지하지 않아 새 파티션이 조용히 방치됩니다 | |
| 출제 형태 | "group.id 없이 동작하는 것은?" → assign() / "리밸런스를 피하려면?" → assign()(단 확장성 포기) | |
4. auto.offset.reset — earliest vs latest vs none
| 축 | earliest | latest (기본값) | none |
|---|---|---|---|
| 무엇인가 | 로그의 처음부터 읽음 | 지금 이후에 오는 것만 읽음 | 예외를 던짐 (NoOffsetForPartitionException) |
| 어디 설정인가 | 컨슈머 | ||
| 언제 발동하는가 | 커밋된 오프셋이 없을 때, 또는 커밋된 오프셋이 로그 범위를 벗어났을 때만. 정상 커밋이 있으면 아무 일도 하지 않습니다 | ||
| 혼동 시 결과 | 재시작마다 전량 재처리한다고 오해 | offsets.retention.minutes(7일) 경과로 커밋이 만료되면 그 사이 데이터를 조용히 건너뜁니다 | 운영 중 새 파티션이 생겼을 때 애플리케이션이 죽음 |
| 출제 형태 | "기존 그룹에서 earliest로 바꾸면 처음부터 읽는가?" → 아니오. 새 group.id나 오프셋 리셋이 필요합니다 | ||
5. enable.auto.commit vs 수동 커밋
| 축 | enable.auto.commit=true (기본값) | 수동 커밋 |
|---|---|---|
| 무엇인가 | auto.commit.interval.ms(5000)마다 자동 커밋 | commitSync() / commitAsync()를 직접 호출 |
| 어디 설정인가 | 컨슈머 설정 | 컨슈머 코드 (설정은 false로) |
| 언제 발동하는가 | poll()이 호출될 때 직전 반환분을 커밋 | 개발자가 정한 시점 |
| 혼동 시 결과 | 자동 커밋은 처리 완료를 기다리지 않습니다. 처리 중 크래시하면 이미 커밋된 레코드가 유실됩니다. 트랜잭션을 쓸 때 자동 커밋이 켜져 있으면 원자성이 깨집니다 | |
| 출제 형태 | "at-least-once를 보장하려면?" → 자동 커밋 끄고 처리 후 커밋 | |
6. commitSync() vs commitAsync()
| 축 | commitSync() | commitAsync() |
|---|---|---|
| 무엇인가 | 커밋 응답을 기다림 | 커밋을 보내고 즉시 반환 (콜백으로 결과 수신) |
| 어디 설정인가 | 컨슈머 코드 (API) | |
| 언제 발동하는가 | 호출 시 블로킹. 실패하면 자동 재시도 | 호출 시 논블로킹. 자동 재시도 없음 |
| 혼동 시 결과 | 루프마다 호출하면 처리량이 떨어짐 | 실패가 조용히 넘어가 재시작 시 중복 처리가 늘어남 |
| 출제 형태 | 권장 패턴: 루프에서는 commitAsync(), 종료 직전 finally에서 commitSync() | |
7. eager 리밸런스 vs cooperative 리밸런스
| 축 | eager | cooperative (incremental) |
|---|---|---|
| 무엇인가 | 리밸런스 시작 시 모든 파티션 반납 (stop-the-world) | 옮겨야 할 파티션만 두 라운드에 걸쳐 회수·재할당 |
| 어디 설정인가 | 컨슈머 partition.assignment.strategy (classic 프로토콜) | |
| 언제 발동하는가 | 멤버 변경 시 전체 중단 후 재할당 | 멤버 변경 시 영향 없는 파티션은 계속 소비 |
| 혼동 시 결과 | eager에서 cooperative로 옮길 때 두 단계 롤링 배포가 필요합니다. 한 번에 바꾸면 프로토콜 협상이 실패합니다 | |
| 출제 형태 | "리밸런스 중에도 일부 파티션을 계속 소비하는 방식은?" → cooperative. KIP-848(group.protocol=consumer)은 전면 증분 설계입니다 | |
8. Range vs RoundRobin vs Sticky vs CooperativeSticky
| 축 | RangeAssignor | RoundRobinAssignor | StickyAssignor | CooperativeStickyAssignor |
|---|---|---|---|---|
| 무엇인가 | 토픽별로 연속 구간 분배 | 전체 토픽-파티션을 돌려 담기 | 균등 + 기존 할당 유지 | Sticky + 증분 재할당 |
| 어디 설정인가 | 컨슈머 partition.assignment.strategy. 기본값은 [RangeAssignor, CooperativeStickyAssignor] | |||
| 언제 발동하는가 | 리밸런스 시. 그룹 멤버 전원이 공통으로 지원하는 전략이 협상으로 선택됩니다 | |||
| 혼동 시 결과 | 토픽이 여러 개일 때 불균등해질 수 있음 | 토픽 간 균등하지만 기존 할당이 다 바뀜 | 여전히 eager라 stop-the-world 발생 | 기본값 목록의 첫 항목이 Range이므로, 쓰려면 이것만 단독 지정 |
| 출제 형태 | "리밸런스 영향을 최소화하는 전략은?" → CooperativeStickyAssignor | |||
9. group.protocol=classic vs consumer (KIP-848)
| 축 | classic (기본값) | consumer (KIP-848) |
|---|---|---|
| 무엇인가 | 클라이언트가 할당을 계산하는 기존 프로토콜 | 브로커가 할당을 계산하는 새 프로토콜 |
| 어디 설정인가 | 컨슈머 group.protocol. 서버 쪽은 4.0부터 자동 활성 | |
| 언제 발동하는가 | 기본값이므로 명시하지 않으면 이것 | consumer로 명시해야 활성 |
| 혼동 시 결과 | consumer에서는 session.timeout.ms·heartbeat.interval.ms·partition.assignment.strategy가 클라이언트에서 무시됩니다. 이 설정으로 튜닝하려다 아무 효과를 못 봅니다 (할당자는 group.remote.assignor) | |
| 출제 형태 | "KIP-848은 4.0 GA다. 컨슈머 기본값은?" → classic. 시중 자료가 가장 많이 틀리는 지점 | |
프로듀서 — 10번부터 16번
10. acks vs min.insync.replicas
| 축 | acks | min.insync.replicas |
|---|---|---|
| 무엇인가 | 응답을 몇 곳에서 받을지. 기본 all | 쓰기를 성립시키는 최소 ISR 수. 기본 1 |
| 어디 설정인가 | 프로듀서 | 브로커 · 토픽 |
| 언제 발동하는가 | 매 전송 | acks=all일 때만. acks=0/1이면 검사되지 않음 |
| 혼동 시 결과 | min.insync.replicas=2를 걸어도 프로듀서가 acks=1이면 아무 보호가 없습니다. 반대로 acks=all이어도 min.insync.replicas=1이면 ISR이 1로 줄었을 때 유실됩니다 | |
| 출제 형태 | "RF=3, min.insync.replicas=2일 때 브로커 몇 대까지 죽어도 쓰기가 되는가?" → 1대 (RF − minISR) | |
11. batch.size vs linger.ms
| 축 | batch.size | linger.ms |
|---|---|---|
| 무엇인가 | 파티션별 배치의 크기 상한. 기본 16384 바이트 | 배치를 더 모으기 위해 기다리는 시간. 기본 5 |
| 어디 설정인가 | 프로듀서 | 프로듀서 |
| 언제 발동하는가 | 둘 중 먼저 충족되는 쪽에 전송됩니다. AND가 아니라 OR입니다 | |
| 혼동 시 결과 | batch.size만 올려도 트래픽이 적으면 배치가 안 참 | 기본값이 0이라고 외우면 틀립니다. 4.0에서 0→5로 변경되었습니다 |
| 출제 형태 | "지연을 최소화하려면?" → linger.ms=0 명시. "처리량을 올리려면?" → 둘 다 상향 + 압축 | |
12. buffer.memory vs max.block.ms
| 축 | buffer.memory | max.block.ms |
|---|---|---|
| 무엇인가 | 전송 대기 레코드를 담는 전체 메모리. 기본 33554432(32MB) | 버퍼·메타데이터 대기의 블로킹 상한. 기본 60000 |
| 어디 설정인가 | 프로듀서 | 프로듀서 |
| 언제 발동하는가 | 브로커가 느려 배치가 쌓이면 소진됨 | 버퍼가 소진되어 send()가 블로킹될 때부터 카운트 |
| 혼동 시 결과 | 버퍼가 가득 차면 send()가 블로킹되고, max.block.ms를 넘기면 TimeoutException이 발생합니다. "비동기니까 절대 블로킹되지 않는다"는 오해가 여기서 깨집니다 | |
| 출제 형태 | "send()가 블로킹될 수 있는가?" → 예, 버퍼 고갈과 메타데이터 미확보 시 | |
13. request.timeout.ms vs delivery.timeout.ms
| 축 | request.timeout.ms | delivery.timeout.ms |
|---|---|---|
| 무엇인가 | 한 번의 요청에 대한 응답 대기. 기본 30000 | send()부터 성공/실패 확정까지 전체. 기본 120000 |
| 어디 설정인가 | 프로듀서 (컨슈머·Admin에도 동명 설정 존재) | 프로듀서 |
| 언제 발동하는가 | 매 요청마다. 초과하면 재시도 대상 | 배치 대기 + 모든 재시도의 합이 이 값을 넘으면 최종 실패 |
| 혼동 시 결과 | linger.ms + request.timeout.ms ≤ delivery.timeout.ms가 성립해야 합니다. 어기면 설정 검증에서 걸립니다 | |
| 출제 형태 | "전송이 최대 얼마나 걸릴 수 있는가?" → delivery.timeout.ms | |
14. retries vs delivery.timeout.ms
| 축 | retries | delivery.timeout.ms |
|---|---|---|
| 무엇인가 | 재시도 횟수. 기본 2147483647 | 재시도에 쓸 수 있는 시간. 기본 120000 |
| 어디 설정인가 | 프로듀서 | 프로듀서 |
| 언제 발동하는가 | 기본값이 사실상 무한이라 거의 발동하지 않음 | 실질적인 재시도 종료 조건 |
| 혼동 시 결과 | retries=0으로 두면 중복이 사라진다고 오해합니다. 실제로는 멱등성이 중복을 막고, retries=0은 유실 위험만 늘립니다. 멱등성을 켜면 retries>0이 강제됩니다 | |
| 출제 형태 | "재시도 동작을 제어하는 4.x 권장 방법은?" → 시간 예산(delivery.timeout.ms) 조절 | |
15. retriable 예외 vs non-retriable 예외
| 축 | retriable | non-retriable |
|---|---|---|
| 무엇인가 | LeaderNotAvailableException, NotLeaderOrFollowerException,
NotEnoughReplicasException, NetworkException,
TimeoutException, CoordinatorNotAvailableException |
RecordTooLargeException, SerializationException,
AuthorizationException, InvalidTopicException |
| 어디 설정인가 | 설정이 아니라 예외 계층. 프로듀서가 자동 판별 | |
| 언제 발동하는가 | 클러스터 상태가 일시적으로 나쁠 때 | 데이터·설정·권한이 잘못되었을 때 |
| 혼동 시 결과 | non-retriable을 재시도로 해결하려 하면 영구히 실패하는 레코드가 파티션을 막습니다. 코드·설정·권한을 고쳐야 합니다 | |
| 출제 형태 | matching 유형(예외 ↔ 원인)의 단골. RecordTooLargeException은 재시도해도 안 됩니다 | |
16. 키 있는 메시지 vs 키 없는 메시지
| 축 | 키 있음 | 키 없음 (null) |
|---|---|---|
| 무엇인가 | murmur2(key) % numPartitions — 결정적 | 기본 파티셔너의 sticky batching |
| 어디 설정인가 | ProducerRecord의 key 인자. 파티셔너는 partitioner.class(기본 null = 내장 로직) | |
| 언제 발동하는가 | 매 전송 시 해시 계산 | 배치가 마감되면 다음 파티션으로 전환 |
| 혼동 시 결과 | 파티션 수를 늘리면 같은 키가 다른 파티션으로 가 순서가 깨집니다 | 같은 엔티티의 이벤트가 여러 파티션에 흩어져 순서 보장이 사라집니다 |
| 출제 형태 | "키 없는 메시지는 어떻게 분배되는가?" — 큰 그림은 "여러 파티션에 나눠 담김"이 맞고, 세부는 레코드 단위 라운드로빈이 아니라 배치 단위 sticky입니다 (2.4 이전 DefaultPartitioner는 라운드로빈) | |
전달 보장 — 17번부터 21번
17. 멱등 프로듀서 vs 트랜잭션
| 축 | 멱등 프로듀서 | 트랜잭션 |
|---|---|---|
| 무엇인가 | PID + 파티션별 시퀀스 번호로 재시도 중복 제거 | 여러 파티션·토픽 쓰기와 오프셋 커밋의 원자성 |
| 어디 설정인가 | 프로듀서 enable.idempotence=true (기본값) | 프로듀서 transactional.id + API 호출. 컨슈머 isolation.level도 필요 |
| 언제 발동하는가 | 재시도로 같은 배치가 다시 도착할 때 브로커가 폐기 | commitTransaction() 시점에 일괄 가시화 |
| 혼동 시 결과 | 멱등성만으로 EOS가 된다고 오해합니다. 멱등성은 한 세션 × 한 파티션만 커버하고, 애플리케이션이 두 번 send()한 것이나 프로듀서 재시작은 막지 못합니다 | |
| 출제 형태 | "EOS에 필요한 것을 모두 고르시오" — 멱등성만 고르면 오답 | |
18. at-least-once vs exactly-once
| 축 | at-least-once (기본 동작) | exactly-once |
|---|---|---|
| 무엇인가 | 유실 없음, 중복 가능 | Kafka 경계 안에서 정확히 한 번 |
| 어디 설정인가 | 프로듀서 acks=all + 컨슈머 처리 후 커밋 | 프로듀서 transactional.id + 컨슈머 read_committed + sendOffsetsToTransaction. Streams는 processing.guarantee=exactly_once_v2 |
| 언제 발동하는가 | 재시도·리밸런스 시 중복이 발생할 수 있음 | 트랜잭션 커밋 단위로 |
| 혼동 시 결과 | EOS를 켰다고 컨슈머 멱등성을 생략하면, 외부 시스템에는 여전히 중복이 갑니다. 그리고 EOS는 처리량·지연 비용이 있습니다 | |
| 출제 형태 | "기본 설정에서 얻는 보장은?" → at-least-once | |
19. read_uncommitted vs read_committed
| 축 | read_uncommitted (기본값) | read_committed |
|---|---|---|
| 무엇인가 | high watermark까지 전부 읽음 (중단된 트랜잭션 포함) | LSO(Last Stable Offset)까지, 커밋된 것만 |
| 어디 설정인가 | 컨슈머 isolation.level. 프로듀서 설정이 아닙니다 | |
| 언제 발동하는가 | 항상 | 진행 중 트랜잭션이 있으면 그 뒤에 커밋된 메시지까지 대기 |
| 혼동 시 결과 | 프로듀서만 트랜잭션을 쓰고 컨슈머를 기본값으로 두면 abort된 데이터가 하위 시스템으로 갑니다. 반대로 read_committed에서 긴 트랜잭션이 열려 있으면 지연이 폭증합니다 | |
| 출제 형태 | "트랜잭션을 썼는데 취소된 데이터가 들어갔다" → 컨슈머 설정 누락. "컨트롤 레코드는 애플리케이션에 보이는가?" → 아니오(오프셋은 소비) | |
20. Kafka 내부 EOS vs 외부 시스템
| 축 | Kafka → Kafka | Kafka → 외부 DB · API |
|---|---|---|
| 무엇인가 | consume-transform-produce가 한 트랜잭션 | 외부 호출은 트랜잭션 밖 |
| 어디 설정인가 | 프로듀서·컨슈머·Streams 설정으로 달성 | 설정으로 해결 불가. 애플리케이션 코드가 멱등해야 함 |
| 언제 발동하는가 | 커밋 시점에 원자적으로 가시화 | 재시도할 때마다 외부에 다시 호출 |
| 혼동 시 결과 | "EOS를 켰으니 결제가 두 번 되지 않는다"고 믿고 멱등 키를 넣지 않아 이중 결제가 발생합니다 | |
| 출제 형태 | "Kafka EOS로 막을 수 없는 것은?" → 외부 시스템 중복. 대응은 멱등 키 · UPSERT · dedup 저장소 | |
21. high watermark vs LEO vs committed offset
| 축 | committed offset | high watermark | LEO |
|---|---|---|---|
| 무엇인가 | 컨슈머 그룹이 커밋한 위치 | ISR 전원이 복제를 마친 상한 | 리더가 기록한 마지막 다음 위치 |
| 어디 설정인가 | 설정 아님 — __consumer_offsets에 저장 | 설정 아님 — 리더가 ISR 최소 LEO로 계산 | 설정 아님 — 로그의 끝 |
| 언제 발동하는가 | 커밋할 때 갱신 | ISR 복제가 진행될 때 전진 | 리더가 append할 때 전진 |
| 혼동 시 결과 | lag = high watermark − committed offset입니다. LEO로 계산하면 아직 복제 중이라 읽을 수도 없는 레코드를 lag에 포함시켜 과대 계산합니다 | ||
| 출제 형태 | "컨슈머가 읽을 수 있는 상한은?" → high watermark. HW와 LEO 사이 구간은 컨슈머에게 보이지 않습니다 | ||
크기와 스토리지 — 22번부터 26번
22. max.request.size vs message.max.bytes vs fetch.max.bytes
| 축 | max.request.size | message.max.bytes | fetch.max.bytes |
|---|---|---|---|
| 무엇인가 | 프로듀서 요청 크기 상한. 1048576 | 브로커가 허용하는 배치 크기. 1048588 | 컨슈머 fetch 응답 상한. 52428800 |
| 어디 설정인가 | 프로듀서 | 브로커 (토픽은 max.message.bytes) | 컨슈머 |
| 언제 발동하는가 | 전송 직전, 클라이언트에서 | 브로커가 배치를 받을 때 (압축 후 기준) | fetch 요청 크기 산정 시 |
| 혼동 시 결과 | 큰 메시지를 허용하려면 4곳을 모두 올려야 합니다 — 토픽 max.message.bytes, 프로듀서 max.request.size, 컨슈머 max.partition.fetch.bytes, 브로커 replica.fetch.max.bytes. 마지막을 빼먹어도 복제가 멈추거나 ISR이 줄지는 않습니다 — fetch 계열은 하드 상한이 아니라 큰 배치도 진행 보장을 위해 그대로 반환되기 때문입니다. 대신 fetch 응답당 배치 1건으로 떨어져 복제 처리량이 무너집니다. 하드 게이트는 프로듀서 max.request.size와 브로커 message.max.bytes(토픽 max.message.bytes) 둘뿐입니다 | ||
| 출제 형태 | matching(설정↔소속)과 "1.5MB를 보내려면 무엇을 바꿔야 하는가(모두 고르시오)". 1048576과 1048588의 12바이트 차이도 출제 대상 | ||
23. retention vs compaction (cleanup.policy)
| 축 | delete (기본값) | compact |
|---|---|---|
| 무엇인가 | 기간·크기를 넘긴 세그먼트를 삭제 | 키별 최신 값만 유지 |
| 어디 설정인가 | 토픽 cleanup.policy (브로커 기본값도 존재). compact,delete 조합 가능 | |
| 언제 발동하는가 | retention.ms/retention.bytes 초과 + 세그먼트 마감 후 | dirty ratio가 min.cleanable.dirty.ratio(0.5)를 넘으면 백그라운드 클리너가. 활성 세그먼트는 제외 |
| 혼동 시 결과 | 상태 토픽에 delete가 남아 상태가 사라집니다 | 이벤트 토픽에 compact를 걸어 이력이 사라집니다. 그리고 키가 없으면 컴팩션이 동작하지 않습니다 |
| 출제 형태 | "컴팩션 토픽인데 같은 키의 옛 값이 보인다" → 활성 세그먼트 + 백그라운드 동작. "즉시 하나만 남는다"는 오답 | |
24. retention.ms vs segment.ms
| 축 | retention.ms | segment.ms |
|---|---|---|
| 무엇인가 | 보관 기간. 기본 604800000(7일) | 세그먼트를 마감하는 주기. 기본 604800000(7일) |
| 어디 설정인가 | 토픽 | 토픽 (segment.bytes는 1GiB) |
| 언제 발동하는가 | 마감된 세그먼트에 대해서만 판정 | 이 시간이 지나면 활성 세그먼트를 닫고 새로 만듦 |
| 혼동 시 결과 | retention.ms=3600000(1시간)으로 줄여도 segment.ms가 7일이면 최대 7일치가 남습니다. 삭제 단위가 세그먼트이기 때문입니다 | |
| 출제 형태 | "짧은 보관을 실제로 적용하려면?" → segment.ms(또는 segment.bytes)도 함께 줄여야 합니다 | |
25. retention.ms vs delete.retention.ms
| 축 | retention.ms | delete.retention.ms |
|---|---|---|
| 무엇인가 | 일반 레코드의 보관 기간. 604800000 | tombstone(value=null)의 보관 기간. 86400000(1일) |
| 어디 설정인가 | 토픽 (cleanup.policy=delete에서 의미) | 토픽 (cleanup.policy=compact에서 의미) |
| 언제 발동하는가 | 세그먼트 마감 후 기간 초과 시 | 컴팩션이 tombstone을 만난 뒤 이 시간이 지나면 tombstone도 제거 |
| 혼동 시 결과 | tombstone이 너무 빨리 사라지면, 오래 멈췄던 컨슈머가 삭제 사실을 보지 못하고 옛 값을 계속 유지합니다 (삭제가 하위로 전파되지 않음) | |
| 출제 형태 | "컴팩션 토픽에서 삭제가 반영되지 않은 컨슈머가 있다" → tombstone 수명 문제 | |
26. 컨슈머 그룹 vs Share Group (KIP-932)
| 축 | 컨슈머 그룹 | Share Group (Queues) |
|---|---|---|
| 무엇인가 | 파티션을 배타적으로 나눠 소비 | 한 파티션을 여러 컨슈머가 함께 소비 |
| 어디 설정인가 | 컨슈머 group.id + subscribe() | share consumer API (별도 그룹 종류) |
| 언제 발동하는가 | 컨슈머 수 ≤ 파티션 수까지만 병렬성 증가 | 컨슈머 수가 파티션 수를 넘어도 병렬 처리 |
| 혼동 시 결과 | Share Group은 레코드 단위 ack(ACCEPT / RELEASE / REJECT)과 전달 시도 카운트를 쓰며, 파티션 단위 순서 보장을 제공하지 않습니다. 순서가 필요한 스트림을 Share Group으로 옮기면 순서가 깨집니다 | |
| 출제 형태 | "처리 시간 편차가 큰 작업 큐에 적합한 것은?" → Share Group. 버전 사실: 4.1 preview → 4.2 production-ready | |
스키마 — 27번부터 29번
27. BACKWARD vs FORWARD 호환성 (배포 순서)
| 축 | BACKWARD (기본값) | FORWARD |
|---|---|---|
| 무엇인가 | 새 스키마로 옛 데이터를 읽을 수 있다 | 옛 스키마로 새 데이터를 읽을 수 있다 |
| 어디 설정인가 | Schema Registry의 subject 단위 호환성 설정 (전역 기본값도 존재) | |
| 언제 발동하는가 | 새 스키마 등록 시 검사 | 새 스키마 등록 시 검사 |
| 혼동 시 결과 | 컨슈머 먼저 배포. 순서를 뒤바꾸면 컨슈머가 모르는 스키마를 받아 전량 실패 | 프로듀서 먼저 배포 |
| 출제 형태 | "BACKWARD에서 필드를 추가하려면?" → default가 있어야 허용. FORWARD는 default 없는 필드 추가도 허용, 대신 default 없는 필드 삭제는 거부 | |
28. TRANSITIVE vs 비-TRANSITIVE
| 축 | BACKWARD / FORWARD / FULL | *_TRANSITIVE |
|---|---|---|
| 무엇인가 | 직전 버전과만 대조 | 등록된 모든 이전 버전과 대조 |
| 어디 설정인가 | Schema Registry subject 호환성 설정 | |
| 언제 발동하는가 | v3 등록 시 v2만 검사 | v3 등록 시 v2, v1 모두 검사 |
| 혼동 시 결과 | 비-TRANSITIVE에서 v1→v2→v3로 조금씩 바꾸면 v1과 v3가 호환되지 않을 수 있습니다. 오래된 데이터를 재처리할 때 터집니다 | |
| 출제 형태 | "v1 데이터를 v3 컨슈머로 재처리해야 한다. 어느 모드가 필요한가?" → BACKWARD_TRANSITIVE | |
29. TopicNameStrategy vs RecordNameStrategy
| 축 | TopicNameStrategy (기본값) | RecordNameStrategy |
|---|---|---|
| 무엇인가 | subject = {topic}-key / {topic}-value | subject = 레코드 풀네임 |
| 어디 설정인가 | 직렬화기 설정 (key.subject.name.strategy / value.subject.name.strategy) | |
| 언제 발동하는가 | 스키마 등록·조회 시 subject 계산 | 같음 |
| 혼동 시 결과 | 한 토픽에 여러 종류의 레코드를 넣으면 호환성 검사가 매번 실패합니다 | 같은 레코드 타입이 토픽을 넘어 스키마를 공유하므로, 한 토픽만 바꾸려 해도 전체가 영향받습니다 |
| 출제 형태 | "한 토픽에 여러 이벤트 타입을 담으려면?" → RecordName 또는 TopicRecordName 전략. 절충안이 TopicRecordNameStrategy({topic}-{레코드 풀네임}) | |
Kafka Connect — 30번부터 33번
30. source 커넥터 vs sink 커넥터의 오프셋 저장 위치
| 축 | Source | Sink |
|---|---|---|
| 무엇인가 | 외부 시스템 → Kafka. 소스 시스템의 위치를 기록 | Kafka → 외부 시스템. Kafka 컨슈머 오프셋을 기록 |
| 어디 설정인가 | 워커 offset.storage.topic (standalone은 offset.storage.file.filename) | __consumer_offsets — 일반 컨슈머와 동일. 그룹 이름은 connect-{커넥터 이름} |
| 언제 발동하는가 | offset.flush.interval.ms 기본 60000(1분)마다 | |
| 혼동 시 결과 | 복구 시 엉뚱한 곳을 초기화해 전량 재수집 또는 재적재가 일어납니다 | |
| 출제 형태 | matching(커넥터 종류 ↔ 저장 위치). "sink 커넥터의 lag을 보는 명령은?" → kafka-consumer-groups --describe --group connect-{name} | |
31. standalone vs distributed Connect
| 축 | standalone | distributed |
|---|---|---|
| 무엇인가 | 단일 프로세스, 로컬 상태 | 워커 클러스터, Kafka 토픽에 상태 저장 |
| 어디 설정인가 | 워커 properties + 커넥터 properties 파일 | 워커 properties + REST API. group.id가 클러스터 이름 |
| 언제 발동하는가 | 기동 시 커넥터가 즉시 시작 | REST로 등록할 때 시작. 워커 변경 시 태스크 재분배 |
| 혼동 시 결과 | distributed에 커넥터 properties를 커맨드라인으로 주면 무시됩니다. standalone은 프로세스가 죽으면 오프셋 파일 외에 모든 상태가 사라집니다 | |
| 출제 형태 | "워커를 재시작하니 커넥터가 사라졌다" → standalone. 내부 토픽 3종은 모두 cleanup.policy=compact이고 config.storage.topic은 파티션 1개 | |
32. SMT vs Kafka Streams
| 축 | SMT (Single Message Transform) | Kafka Streams |
|---|---|---|
| 무엇인가 | 레코드 1건 단위의 stateless 변환 | 상태를 갖는 스트림 처리 애플리케이션 |
| 어디 설정인가 | 커넥터 설정 transforms=… (체인, 순서가 결과를 바꿈) | 별도 애플리케이션 코드 |
| 언제 발동하는가 | source는 converter 앞, sink는 converter 뒤 | 토폴로지 실행 시 |
| 혼동 시 결과 | SMT로 집계·조인을 하려다 막힙니다 — stateless라서 불가능합니다. 반대로 필드 하나 바꾸려고 Streams 앱을 띄우는 것은 과잉입니다 | |
| 출제 형태 | "두 토픽을 조인해 적재하려면?" → Streams(또는 ksqlDB). 대표 SMT: InsertField, MaskField, ValueToKey, RegexRouter, Cast, TimestampConverter, Filter | |
33. errors.tolerance vs errors.retry.timeout
| 축 | errors.tolerance | errors.retry.timeout |
|---|---|---|
| 무엇인가 | 실패 레코드를 건너뛸지 여부. 기본 none | 재시도에 쓸 시간 예산. 기본 0(재시도 없음), -1은 무한 |
| 어디 설정인가 | 커넥터 설정 | |
| 언제 발동하는가 | 변환·컨버터·sink 쓰기 실패 시 | 재시도 가능한 실패 시 (지수 백오프, 상한은 errors.retry.delay.max.ms 60000) |
| 혼동 시 결과 | all만 켜면 일시 장애로도 데이터가 조용히 버려집니다. DLQ는 세 조건이 필요합니다 — errors.tolerance=all + errors.deadletterqueue.topic.name 지정 + sink 커넥터. errors.deadletterqueue.context.headers.enable=true가 없으면 원인 헤더가 없습니다 | |
| 출제 형태 | "일시 장애는 재시도, 영구 불량은 격리하려면?" → 둘을 함께 설정 + total-records-skipped 알림 | |
Kafka Streams — 34번부터 38번
34. KStream vs KTable
| 축 | KStream | KTable |
|---|---|---|
| 무엇인가 | 레코드 스트림 — append-only 사실의 연속 | changelog 스트림 — 키별 최신 상태(upsert) |
| 어디 설정인가 | builder.stream("topic") | builder.table("topic") |
| 언제 발동하는가 | 같은 키가 또 오면 별개 이벤트 | 같은 키가 또 오면 갱신, value=null이면 삭제 |
| 혼동 시 결과 | 상태를 스트림으로 읽어 중복 집계 | 이벤트를 테이블로 읽어 이벤트 유실 (합계가 실제보다 작아짐) |
| 출제 형태 | 판단 기준: "같은 키가 반복될 때 두 값이 모두 의미가 있는가". 결제·클릭·센서 = KStream, 프로필·재고·잔액 = KTable | |
35. KTable vs GlobalKTable
| 축 | KTable | GlobalKTable |
|---|---|---|
| 무엇인가 | 파티션 단위로 인스턴스에 분산된 테이블 | 모든 인스턴스가 전체 사본을 가진 테이블 |
| 어디 설정인가 | builder.table(...) | builder.globalTable(...) |
| 언제 발동하는가 | 조인 시 co-partitioning 필요 | 조인 시 co-partitioning 불필요. KeyValueMapper로 키가 아닌 값으로도 조인 가능 |
| 혼동 시 결과 | 큰 테이블을 GlobalKTable로 만들면 인스턴스마다 전체를 적재해 메모리와 시작 시간이 폭증합니다 | |
| 출제 형태 | "파티션 수가 다른 참조 데이터와 조인하려면?" → GlobalKTable. 적합한 데이터는 작고 자주 안 바뀌는 마스터 | |
36. map vs mapValues
| 축 | map | mapValues |
|---|---|---|
| 무엇인가 | 키와 값을 모두 바꿀 수 있는 stateless 변환 | 값만 바꾸는 stateless 변환 |
| 어디 설정인가 | Streams DSL 토폴로지 코드 | |
| 언제 발동하는가 | 키가 바뀔 수 있으므로 스트림을 "리파티션 필요"로 표시. 실제 리파티션 토픽은 뒤에 키 기반 연산(집계·조인)이 올 때 생성 | 표시하지 않음 → 리파티션 없음 |
| 혼동 시 결과 | 값만 바꾸면 되는데 map을 쓰면 불필요한 리파티션 토픽이 생겨 네트워크·디스크·지연이 증가합니다 | |
| 출제 형태 | "리파티션을 유발하는 연산을 모두 고르시오" → map, flatMap, selectKey, groupBy, repartition. mapValues·flatMapValues·filter·groupByKey는 아닙니다 | |
37. tumbling vs hopping vs sliding vs session 윈도우
| 축 | Tumbling | Hopping | Sliding | Session |
|---|---|---|---|---|
| 무엇인가 | 고정 크기, 겹치지 않음 | 고정 크기, advance 간격으로 겹침 | 레코드 기준 고정 간격 구간 | 비활성 gap으로 구분, 크기 가변 |
| 어디 설정인가 | TimeWindows.ofSizeWithNoGrace | TimeWindows…advanceBy(…) | SlidingWindows.ofTimeDifferenceAndGrace | SessionWindows.ofInactivityGap… |
| 언제 발동하는가 | 한 레코드가 1개 윈도우에 | 한 레코드가 size÷advance 개 윈도우에 | 데이터가 있는 구간만 | gap 안에 이벤트가 오면 세션이 계속 늘어나고, 세션 병합도 일어남 |
| 혼동 시 결과 | Hopping을 Tumbling으로 착각하면 집계가 중복 계산됩니다. …WithNoGrace를 쓰면 지연 도착 레코드가 버려집니다 | |||
| 출제 형태 | "10분 크기, 5분 advance면 한 레코드는 몇 개 윈도우에?" → 2개. "사용자 활동 구간을 묶으려면?" → session | |||
38. co-partitioning vs repartition()
| 축 | co-partitioning | repartition() |
|---|---|---|
| 무엇인가 | 조인의 전제 조건 — 공식 요건은 ① 파티션 수 동일 ② 파티셔너 동일 두 개입니다. “키 동일”은 요건이 아니라 equi-join 자체의 전제 | 키 기준으로 다시 분배하는 연산 (리파티션 토픽 생성) |
| 어디 설정인가 | 토픽 설계 · 프로듀서 partitioner.class · StreamPartitioner | 토폴로지 코드 |
| 언제 발동하는가 | 런타임 파티션 할당 단계에서 파티션 수만 검증 | 호출 즉시 |
| 혼동 시 결과 | 파티션 수가 다르면 TopologyException이 나지만, 파티셔너가 다른 것은 감지되지 않아 조인이 조용히 누락됩니다. KStream-GlobalKTable 조인과 KTable-KTable 외래키 조인은 co-partitioning이 필요하지 않습니다 | |
| 출제 형태 | "파티션 3개와 6개 토픽을 조인하려면?" → 적은 쪽을 6으로 리파티션. KStream-KTable이면 KStream을 리파티션하는 편이 낫습니다(KTable은 상태 저장소가 하나 더 생김) | |
보안 — 39번과 40번
39. SASL_PLAINTEXT vs SASL_SSL
| 축 | SASL_PLAINTEXT | SASL_SSL |
|---|---|---|
| 무엇인가 | SASL 인증 + 암호화 없음 | SASL 인증 + TLS 암호화 |
| 어디 설정인가 | 클라이언트 security.protocol · 브로커 listeners / listener.security.protocol.map | |
| 언제 발동하는가 | 연결 수립 시. 나머지 두 값은 PLAINTEXT(둘 다 없음)와 SSL(암호화 + 선택적 mTLS 인증) | |
| 혼동 시 결과 | SASL 메커니즘 이름 PLAIN과 헷갈려 비밀번호를 평문으로 네트워크에 흘립니다. SASL_PLAINTEXT + PLAIN 조합이 가장 위험합니다 | |
| 출제 형태 | "프로덕션에서 인증과 암호화를 모두 원하면?" → SASL_SSL. SASL 메커니즘: PLAIN, SCRAM-SHA-256/512, GSSAPI, OAUTHBEARER | |
40. 인증(authentication) vs 인가(authorization / ACL)
| 축 | 인증 (authentication) | 인가 (authorization / ACL) |
|---|---|---|
| 무엇인가 | 연결한 주체가 누구인지 확인 | 그 주체가 무엇을 할 수 있는지 결정 |
| 어디 설정인가 | 브로커 listeners · sasl.mechanism · SSL 설정 / 클라이언트 security.protocol | 브로커 authorizer 설정 + kafka-acls로 부여하는 ACL. super.users는 우회 |
| 언제 발동하는가 | 연결 수립 시 | 매 요청마다 (읽기·쓰기·토픽 생성·그룹 참여 각각) |
| 혼동 시 결과 | 인증만 설정하고 ACL을 빠뜨리면 TopicAuthorizationException이 발생합니다. 반대로 인증 없이 ACL만 걸면 주체를 특정할 수 없어 의미가 없습니다 | |
| 출제 형태 | "로그인은 되는데 읽기가 거부된다" → 인가(ACL) 문제. 컨슈머는 Topic Read + Group Read 두 ACL이 모두 필요합니다 | |
오답 유도 패턴 분석
위 40쌍은 지식입니다. 이 절은 기술입니다 — 내용을 모르는 상태에서도 선택지의 형태만 보고 함정을 배제하는 방법입니다. 시험 당일 전략의 요약판을 여기서 확장합니다.
패턴 1 — 절대적 표현이 들어간 선택지
"항상", "절대", "모든 경우에", "무조건", "완전히 보장한다", "결코 ~하지 않는다"가 들어간 선택지는 대개 오답입니다. Kafka에는 조건 없는 보장이 거의 없기 때문입니다.
| 함정 선택지 | 빠진 조건 |
|---|---|
"acks=all이면 절대 유실되지 않는다" | min.insync.replicas가 1이면 ISR이 1로 줄었을 때 유실됩니다 |
| "Kafka는 항상 순서를 보장한다" | 순서 보장은 파티션 단위이며 키가 있어야 합니다 |
| "EOS를 켜면 모든 중복이 사라진다" | Kafka 경계 안에서만. 외부 시스템은 별개입니다 |
| "컴팩션 토픽에는 키당 레코드가 항상 하나다" | 활성 세그먼트 제외 + 백그라운드 동작이므로 옛 값이 남습니다 |
| "파티션을 늘리면 무조건 빨라진다" | 컨슈머 수·파일 핸들·리밸런스 비용·키 분포가 함께 작용합니다 |
패턴 2 — 설정의 소속을 바꿔치기
이 사이트 전체에서 가장 강조하는 패턴입니다. 설정 이름은 정확한데 소속만 틀려 놓습니다. 아는 이름이 나오면 안심하게 되므로 잘 걸립니다.
| 설정 | 진짜 소속 | 함정으로 붙이는 소속 |
|---|---|---|
acks | 프로듀서 | 브로커 · 토픽 |
min.insync.replicas | 브로커 · 토픽 | 프로듀서 |
isolation.level | 컨슈머 | 프로듀서 · 브로커 |
transactional.id | 프로듀서 | 컨슈머 |
max.request.size | 프로듀서 | 브로커 |
message.max.bytes | 브로커 | 프로듀서 |
max.message.bytes | 토픽 | 프로듀서 |
fetch.max.bytes · max.partition.fetch.bytes | 컨슈머 | 브로커 |
replica.fetch.max.bytes | 브로커 | 컨슈머 |
cleanup.policy · retention.ms | 토픽 | 컨슈머 |
max.poll.interval.ms · max.poll.records | 컨슈머 | 브로커 |
offsets.retention.minutes | 브로커 | 컨슈머 |
errors.tolerance · DLQ 설정 | Connect 커넥터 (sink 전용) | 브로커 · source 커넥터 |
processing.guarantee | Kafka Streams | 프로듀서 |
transaction.state.log.min.isr | 브로커 | Streams · 프로듀서 |
패턴 3 — 기본값을 미묘하게 변형
3.x 기준 값이거나 몇 바이트만 다른 값을 섞습니다. 시중 자료 대부분이 3.x 기준이라 이 함정이 특히 잘 통합니다.
| 설정 | 4.3 실제 | 함정으로 나오는 값 | 함정의 출처 |
|---|---|---|---|
linger.ms | 5 | 0 | 3.x 기본값 |
acks | all | 1 | 2.x 기본값 |
enable.idempotence | true | false | 2.x 기본값 |
session.timeout.ms | 45000 | 10000 | 예전 기본값 |
group.protocol | classic | consumer | "KIP-848이 GA니까"라는 오추론 |
message.max.bytes | 1048588 | 1048576 | "1MB"로 반올림 |
isolation.level | read_uncommitted | read_committed | "트랜잭션이 기본이니까"라는 오추론 |
processing.guarantee | at_least_once | exactly_once_v2 | 같은 오추론 |
num.stream.threads | 1 | CPU 코어 수 | "당연히 코어만큼"이라는 추측 |
num.standby.replicas | 0 | 1 | "기본적으로 대비되어 있겠지" |
errors.retry.timeout | 0 | 양수 | "재시도가 기본이겠지" |
unclean.leader.election.enable | false | true | 아주 오래된 기본값 |
패턴 4 — 사실이지만 질문에 답하지 않는 선택지
"가장 적절한(BEST/MOST appropriate)"류 문항에 주로 나옵니다. 선택지 문장 자체는 옳아서 지우기 어렵습니다. 질문의 목적어를 다시 읽는 것이 해법입니다.
| 질문 | "옳지만 답이 아닌" 선택지 | 정답 방향 |
|---|---|---|
| "같은 주문의 상태 변화 순서를 보장하려면?" | "acks=all과 멱등성을 켠다" (내구성·중복에 대한 사실) |
주문 ID를 키로 지정한다 |
| "컨슈머가 계속 그룹에서 빠진다. 1차 처방은?" | "session.timeout.ms를 올린다" (하트비트 축의 사실) |
max.poll.records를 줄인다 |
| "sink 커넥터의 처리량을 올리려면?" | "워커를 추가한다" (확장 방법 중 하나) | tasks.max와 입력 파티션 수를 먼저 본다 |
| "값만 변환하는데 리파티션을 피하려면?" | "topology.optimization=all로 켠다" (도움은 됨) |
mapValues를 쓴다 |
패턴 5 — 과잉 조치 선택지
증상은 사라지지만 데이터 유실이나 큰 부작용이 따르는 선택지입니다. 시험은 대체로 최소 변경으로 원인을 제거하는 답을 정답으로 둡니다.
- "토픽을 삭제하고 다시 만든다" — 데이터 전량 유실. 거의 항상 오답
- "커넥터를 지우고 재생성한다" — 오프셋이 초기화되어 재적재 위험. 409는 기다리면 됩니다
- "
unclean.leader.election.enable=true로 켠다" — 가용성을 얻고 데이터를 잃습니다 - "
errors.tolerance=all로 두고 넘어간다" — DLQ와 skipped 알림 없이는 조용한 유실 - "
max.poll.interval.ms를 아주 크게 올린다" — 진짜로 죽은 컨슈머를 감지하지 못하게 됩니다 - "
retries=0으로 중복을 막는다" — 중복은 멱등성이 막고, 이건 유실만 늘립니다
패턴 6 — 보장의 경계나 동작 시점을 넓혀 놓은 선택지
| 함정 선택지 | 실제 경계 |
|---|---|
| "EOS를 켜면 외부 DB 중복도 막힌다" | Kafka 토픽 경계 안에서만 |
"멱등 프로듀서는 애플리케이션의 이중 send()도 걸러낸다" | 재시도 중복만. 서로 다른 레코드로 봅니다 |
| "컴팩션은 중복을 즉시 제거한다" | 백그라운드 + dirty ratio 조건 + 활성 세그먼트 제외 |
"cleanup.policy를 바꾸면 이미 지워진 데이터가 돌아온다" | 돌아오지 않습니다 |
"tasks.max를 올리면 항상 빨라진다" | sink는 입력 파티션 수가, source는 커넥터 구현이 상한 |
| "오프셋은 토픽 전체에서 유일하다" | 파티션 안에서만 의미가 있습니다 |
"auto.offset.reset은 재시작할 때마다 적용된다" | 커밋 오프셋이 없거나 범위를 벗어났을 때만 |
"records-lag-max가 0이면 안전하다" | 컨슈머가 죽으면 메트릭이 사라져 0처럼 보입니다 |
"TopologyTestDriver로 리밸런스도 테스트할 수 있다" | 브로커가 없으므로 불가 |
패턴 7 — 버전에 따라 답이 달라지는 문항 식별법
| 선택지에 함께 등장하면 | 묻는 것 | 4.3 기준 정답 |
|---|---|---|
0과 5 | linger.ms의 버전 차이 | 5 |
1과 all | acks의 버전 차이 | all |
10000과 45000 | session.timeout.ms | 45000 |
| ZooKeeper와 KRaft | 메타데이터 저장소 | KRaft 전용 (ZK는 4.0에서 제거) |
--zookeeper와 --bootstrap-server | CLI 옵션 | --bootstrap-server (--zookeeper는 없는 옵션) |
exactly_once와 exactly_once_v2 | Streams 보장 값 | exactly_once_v2 |
| "2.13"이 Kafka 버전으로 서술 | 버전 표기 이해 | 오답 — 2.13은 Scala 버전입니다 |
이어서 볼 곳
공식 문서 출처
40쌍의 설정 이름·기본값·소속은 모두 Apache Kafka 4.3 공식 문서에서 확인했습니다. 오답 유도 패턴 절은 문항 형태에 대한 분석이며 공식 문서의 서술이 아닙니다.
- Producer Configs
- Consumer Configs
- Broker Configs
- Topic Configs
- Connect Configs · Error Reporting in Connect
- Streams Configs · Streams DSL (리파티션, co-partitioning, 윈도우)
- Message Delivery Semantics · Transactions
- Log Compaction · Replication
- Security (
security.protocol4종, ACL) - Upgrading Apache Kafka (4.x 변경 사항, Share Groups 4.2 production-ready)