msa
주니어와 토론할 질문들
msa / msa-design / kafka
주니어와 한 호흡에 한 섹션씩 갈 수 있게 구성. 각 질문은 정답 하나가 있는 게 아님 — 트레이드오프 토론이 목적.
§1. MSA / 아키텍처 전반
Q1.1 — 모놀리스 vs MSA
- 같은 도메인을 모놀리스로 짜는 vs MSA로 짜는 vs Modular Monolith로 짜는 차이는?
- "MSA는 만들기 어려운 게 아니라 운영하기 어렵다" 무슨 뜻?
- 어느 규모에서 모놀리스가 MSA보다 합리적인가?
- 우리 티켓팅 도메인을 모놀리스로 짠다면 가장 먼저 깨질 부분은?
Q1.2 — Bulkhead
- 대기열과 예매를 같은 서버군이 받으면 어떤 시나리오에서 무너지나?
- Circuit Breaker와 Bulkhead의 관계는? 둘 다 필요한가?
- 자원을 격리하면 활용률이 떨어진다. 어떻게 균형 잡나? (auto scaling? warm pool?)
- 다이어그램에서 Queue와 Booking이 자원 격리되어 있는데, Redis는 왜 두 개로 분리되어 있나?
Q1.3 — Gateway
- API Gateway 없이 클라이언트가 직접 각 서비스 호출하면 어떤 문제?
- BFF (Backend for Frontend) 패턴과 Gateway의 차이는?
- Service Mesh와 Gateway의 책임 경계는?
- Gateway가 단일 장애점이 되면 어떻게 막나?
§2. CQRS / 데이터
Q2.1 — 분리의 정도
- ticket-command/query 분리. 같은 DB → Primary/Replica → 완전 별도 DB 셋 중 어느 단계가 "진짜 CQRS"인가?
- 같은 DB일 때 query에
hikari.read-only: true거는 게 무슨 의미? (지금은 효과 없어 보이는데) - 두 서비스가 같은 엔티티 클래스를 중복 정의하는 게 좋은가 나쁜가? 왜?
Q2.2 — Replica lag
- write 직후 read에서 안 보이는 시간(replica lag)이 1초라면 UI는 어떻게 대응?
- "내가 방금 산 티켓이 내 예약 목록에 안 보임"을 막는 패턴 몇 가지?
- 힌트: read-your-writes consistency, session affinity, sticky read, write-through cache
- replica 여러 대 중 어느 replica에서 읽을지 어떻게 정하나?
Q2.3 — 동시성 제어
- 비관락 vs 낙관락 vs Redis SETNX vs Kafka 파티션 큐잉. 좌석 예약에 각각 언제 적절한가?
- 시나리오 B 측정에서 비관락이 잡혔는데도 latency가 오히려 줄었다. 왜?
- 힌트: JIT warm-up, JVM cold start, HikariCP pool warm-up, 응답 바디 크기
- @Version (낙관락)을 비관락과 함께 쓰는 게 의미가 있나?
§3. Kafka
Q3.1 — 기본 개념
- "Kafka는 메시지 큐" vs "Kafka는 distributed commit log". 어느 게 정확하고 왜?
- Topic / Partition / Offset 셋 중 "병렬성의 단위"는?
- 같은 메시지를 두 그룹이 동시에 받으려면? 한 그룹 안 여러 consumer가 받으려면?
Q3.2 — 우리 적용
- 티켓팅 도메인에 partition을 몇 개로 잡을까? 그 근거는?
- partition key를
seatId로 했을 때 장단점? userId로 하면? eventId로 하면? - partition 수를 부족하게 잡으면 나중에 늘릴 수 있지만, 어떤 함정이 있나?
- Worker 인스턴스 수와 partition 수의 관계?
Q3.3 — 정확성
- Worker가 메시지 처리 중에 죽으면? offset commit 시점이 중요한 이유?
- "같은 메시지를 두 번 처리해도 안전한 코드 (idempotent consumer)"는 어떻게?
- exactly-once 보장이 어디까지 가능하고 어디부터 application 책임인가?
Q3.4 — Outbox
- command가 "DB INSERT + Kafka publish" 둘 다 해야 할 때, 하나만 성공하면?
- outbox 패턴이 그 문제를 어떻게 해결하나?
- 우리 다이어그램에서 Worker가 DB INSERT를 한다. 그럼 outbox가 어디서 필요해질까? (힌트: 환불 알림, 통계 이벤트)
Q3.5 — 직접 구현 (MyKafka)
- 1×1000 batch가 1000×single 대비 61배 빠른 이유 분석해보자.
- sparse offset index의 trade-off. dense index와 비교.
- 우리 MyKafka가 안 만든 것 중 가장 먼저 만들어야 할 건? (Replication? KRaft? Log compaction? Exactly-once?)
- Zero-copy(
sendfile())가 어디서 비용을 절약하나?
§4. 부하 테스트 / 측정
Q4.1 — 시나리오 설계
시나리오 A (경쟁 X) → B (경쟁 O). 다음에 C로 갈지 D로 갈지 어떻게 정하나?
"한 번에 한 변수" 원칙이 시나리오 B에서 깨졌다. 어떻게 깨졌고 어떻게 복구할까?
부하 테스트로 측정해야 할 4가지 layer:
- API (RPS, latency 분포)
- DB (CPU, connections, lock waits)
- JVM (heap, GC, thread pool)
- OS (CPU steal, IO wait, network)
각 layer가 먼저 한계 도달하면 다음 처방은? (DB가 먼저 터지면 → ?, JVM이 먼저 터지면 → ?)
Q4.2 — knee point
- 점진 증가 부하 테스트에서 "어디서 무너지는가" 를 어떻게 판단?
- error rate가 1% 넘는 지점 vs p99 latency가 SLA 넘는 지점 — 어느 게 더 의미 있나?
- soak test (장시간 일정 부하)와 spike test의 목적 차이?
Q4.3 — Worker 도입 트리거
- 우리 부하 테스트에서 어떤 지표가 임계 넘으면 "Worker를 도입해야겠다"고 결정?
- 그 측정을 어떤 시나리오로 잡아야 하나?
§5. 메타 — 학습 방법론
Q5.1 — 다이어그램의 함정
- 다이어그램(LOAD_TEST_LOG와 ARCHITECTURE에서)을 보자마자 "전부 구현해야 한다" 모드에 빠지면 뭐가 안 좋나?
- "동작하는 최소 → 측정 → 진화" 사이클의 가치?
Q5.2 — "한 번에 한 변수"
- 시나리오 A에서 B로 갈 때 의도된 유일 변수는 "경쟁 추가" 였는데 JVM warm-up 이라는 숨은 변수가 같이 들어왔다.
- 이런 숨은 변수를 미리 차단하려면 측정 protocol에 어떤 것이 들어가야 하나?
- warm-up runs
- 같은 시나리오 N회 반복
- JVM flag (
-XX:+PrintCompilation등으로 가시화) - 측정 순서 무작위화
Q5.3 — 직접 구현 vs 매니지드
- MyRedis, MyKafka를 직접 구현한 이유. 매니지드를 안 쓴 학습 가치는?
- 그렇다고 production에 MyKafka 쓰면 안 되는 이유는?
- "직접 구현 vs 매니지드 사용 vs 직접 운영 (docker로)" 세 옵션 중 언제 무엇을 택하나?
Q5.4 — 진화 순서
- "Worker → Replica → Queue API → Gateway" 순서로 가는 근거.
- 만약 이걸 "Queue API → Worker → Replica → Gateway" 순으로 바꾸면 뭐가 깨지나?
§6. "만약 X면?" 시나리오 게임
가상 상황에 답해보기. 답이 한 개가 아닌 게 핵심.
S1
좌석 100개를 한 명의 사용자가 한 번에 예약 신청한다.
- 비관락이 100개 row 다 잡음. 그 사이 다른 사용자는?
- 좌석 100개 중 90개는 AVAILABLE, 10개는 RESERVED라면 어떻게 응답?
- "all or nothing" vs "부분 성공" — 어느 게 맞나?
S2
Kafka publish에는 성공했는데 Worker가 DB INSERT에 실패 (예: DB 일시 장애).
- Worker는 offset commit을 안 한다. 다음 polling에서 같은 메시지 다시 받음.
- 메시지를 무한 재시도하면? Dead Letter Queue가 필요한가?
S3
replica lag이 갑자기 30초로 증가.
- query 서비스의 응답이 30초 옛 데이터가 됨. 사용자가 방금 예약한 좌석이 안 보임.
- 어떻게 감지? 어떻게 대응? (서킷브레이커? primary fallback? 사용자 안내?)
S4
Booking API가 정상인데 Queue API가 죽음.
- 이미 입장한 사용자는 계속 예매 가능 (Bulkhead 효과). 그런데 새 입장자는?
- 어떻게 graceful degradation?
S5
시즌 종료 후 traffic 1/1000으로 감소.
- 서버 인스턴스 다 줄여야 함. partition 수도 줄여야 하나?
- partition 줄이기가 어려운 이유는? 어떻게 대처?
S6
Worker가 메시지를 너무 빨리 처리해서 DB가 못 따라옴.
- back-pressure는 어디서? consumer 측에서 pull rate를 줄이나?
- 또는 DB connection pool로 자연스럽게 throttling?
§7. 다음 학습 거리 (이 토론 이후)
세션 끝나기 전에 주니어에게 줄 만한 follow-up 학습 거리:
책 / 문서
- Designing Data-Intensive Applications (Kleppmann) — 분산 시스템 교과서
- Microservices Patterns (Richardson) — 패턴 카탈로그
- Kafka 공식 문서 — 특히 "Consumer Groups", "Offsets", "Replication"
- Spring Cloud Gateway 공식 가이드
직접 해볼 것
- 우리 ticket-command/query를 docker-compose로 띄워보기
- k6 시나리오 C (점진 증가) 작성해서 knee point 찾기
- MyKafka client SDK 직접 짜보기 (Producer만이라도)
- Postgres streaming replication 셋업 (
pg_basebackup)
더 깊은 토픽
- Event Sourcing (CQRS와 짝)
- Saga 패턴 (분산 트랜잭션)
- Service Mesh (Istio, Linkerd)
- Backpressure 패턴 (Reactive Streams, RxJava/Reactor)
- Idempotency Key 패턴
토론 진행 팁
- 답을 먼저 주지 말기. 주니어가 답하다가 막히면 힌트 한 줄만.
- "왜?" 를 두 번 더. 첫 답이 맞으면 "왜 그래?", 그것도 맞으면 "그럼 반대 경우는?".
- 트레이드오프 강제하기. "이게 다 좋다"는 답이 나오면 "그럼 단점은?" 질문.
- 숫자를 묻기. "조금 빨라져요" → "얼마나? 어떻게 측정?"
- 한 호흡에 한 섹션. 다 다루려 하지 말고 깊이 가기.