MSA2026년 5월 27일8분 읽기

MSA·CQRS·Kafka 토론 질문 60선: 주니어와 한 호흡씩 가는 가이드

선착순 티켓팅 MSA 프로젝트를 주니어와 토론할 때 쓰는 질문 모음. MSA / CQRS / Kafka / 부하 테스트 / 메타 학습 / '만약 X면?' 시나리오 게임까지. 정답이 하나가 아닌, 트레이드오프 토론을 목적으로 한다.

#MSA#CQRS#Kafka#Discussion#Mentoring#Architecture#Learning

MSA · CQRS · Kafka 토론 질문 60선

주니어와 한 호흡에 한 섹션씩 갈 수 있게 구성했다. 각 질문은 정답 하나가 있는 게 아니다 — 트레이드오프 토론이 목적이다.


들어가며

이 글은 선착순 티켓팅 MSA 시리즈토론용 질문 모음이다.

원래는 주니어와 함께 1시간씩 잡고 한 섹션을 깊이 파헤치려고 만든 노트였다. 공유하기로 한 이유는 두 가지다.

  1. 답이 하나가 아닌 질문이 학습 가속도가 가장 빠르다. 정답 있는 객관식 문제는 외우면 끝이지만, 트레이드오프 질문은 "둘 다 맞는 경우" → "조건이 바뀌면 답도 바뀐다" → "그 조건이 뭔지 알아내는 게 시니어의 일"이라는 흐름으로 자연스럽게 깊어진다
  2. "왜?"를 두 번 더 물어보는 습관이 토론 가이드 없이 잘 안 길러진다. 이 글의 모든 질문은 첫 답이 맞아도 "왜 그래?", 그것도 맞으면 "그럼 반대 경우는?"로 이어진다

자세한 본문은 MSA 설계 결정 9가지, CQRS 깊은 이야기, Kafka 깊은 이야기에 있다. 이 글은 그 본문들 위에서 펴는 질문 사전이다.


목차

  • §1. MSA / 아키텍처 전반
  • §2. CQRS / 데이터
  • §3. Kafka
  • §4. 부하 테스트 / 측정
  • §5. 메타 — 학습 방법론
  • §6. "만약 X면?" 시나리오 게임
  • §7. 다음 학습 거리 (이 토론 이후)
  • 토론 진행 팁

§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:

    1. API (RPS, latency 분포)
    2. DB (CPU, connections, lock waits)
    3. JVM (heap, GC, thread pool)
    4. 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개를 한 명의 사용자가 한 번에 예약 신청한다.

  • 비관락이 100개 row 다 잡음. 그 사이 다른 사용자는?
  • 좌석 100개 중 90개는 AVAILABLE, 10개는 RESERVED라면 어떻게 응답?
  • "all or nothing" vs "부분 성공" — 어느 게 맞나?

S2: Kafka publish 성공, DB INSERT 실패

Kafka publish에는 성공했는데 Worker가 DB INSERT에 실패한다 (예: DB 일시 장애).

  • Worker는 offset commit을 안 함. 다음 polling에서 같은 메시지 다시 받음
  • 메시지를 무한 재시도하면? Dead Letter Queue가 필요한가?

S3: Replica lag 30초

replica lag이 갑자기 30초로 증가.

  • query 서비스의 응답이 30초 옛 데이터가 됨. 사용자가 방금 예약한 좌석이 안 보임
  • 어떻게 감지? 어떻게 대응? (서킷브레이커? primary fallback? 사용자 안내?)

S4: Queue API 사망, Booking 정상

Booking API가 정상인데 Queue API가 죽음.

  • 이미 입장한 사용자는 계속 예매 가능 (Bulkhead 효과). 그런데 새 입장자는?
  • 어떻게 graceful degradation?

S5: 시즌 종료, 트래픽 1/1000

시즌 종료 후 traffic 1/1000으로 감소.

  • 서버 인스턴스 다 줄여야 함. partition 수도 줄여야 하나?
  • partition 줄이기가 어려운 이유는? 어떻게 대처?

S6: Worker가 너무 빠름

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 패턴

토론 진행 팁

  • 답을 먼저 주지 말기. 주니어가 답하다가 막히면 힌트 한 줄만
  • "왜?"를 두 번 더. 첫 답이 맞으면 "왜 그래?", 그것도 맞으면 "그럼 반대 경우는?"
  • 트레이드오프 강제하기. "이게 다 좋다"는 답이 나오면 "그럼 단점은?" 질문
  • 숫자를 묻기. "조금 빨라져요" → "얼마나? 어떻게 측정?"
  • 한 호흡에 한 섹션. 다 다루려 하지 말고 깊이 가기

마치며

이 질문들을 만들면서 한 가지를 느꼈다. 좋은 질문은 내가 답을 알아서 가르치려는 데서 나오기보다, 나 자신도 아직 명쾌하게 못 푼 문제에서 나온다는 점이다.

위 60개 중 상당수는 나도 매번 답이 흔들린다. 비관락 vs 낙관락 vs Redis SETNX, partition 수 결정, replica lag 대응 — 모두 "팀·도메인·트래픽 패턴에 따라 답이 달라진다"는 차원에서 끝난다.

그래서 토론에서 가장 중요한 건 **"답을 외우게 하지 않는 것"**이다. 한 케이스에서의 답이 다른 케이스에서 무너지는 걸 함께 보여줘야 한다. 그래야 "이 결정의 근거는 무엇이었는가"를 매번 다시 묻는 시니어의 습관이 길러진다.

만약 이 글이 누군가의 1-on-1이나 스터디에 쓰인다면, 한 가지 부탁이 있다. 답을 먼저 주지 말아달라. 주니어가 막히면 힌트 한 줄, 그러고도 막히면 또 한 줄. 답이 한 번에 나오는 토론은 다음 번에 똑같은 질문이 들어와도 같은 답을 외워서 말할 뿐, 사고 회로가 안 자란다.

본문 시리즈로 돌아가기:

#MSA#CQRS#Kafka#Discussion#Mentoring#Architecture#Learning

황호민

Backend Engineer · Java/Kotlin · Spring Boot · Next.js