[스터디] 데이터 중심 애플리케이션 설계 3장, 4장 기반 스터디

DDIA 3~4장 토론 주제 정리 (1~4)
데이터 중심 애플리케이션 설계(마틴 클레프만) 3장(저장소와 검색), 4장(부호화와 발전) 기반
목적: 사수와 토론을 위한 핵심 개념 + 예시 답안 + 토론 결과 기록용 템플릿
1. 로그 구조 저장소(LSM / append-only) vs B-Tree
핵심 개념
- B-Tree 계열(InnoDB 등)
- 디스크 상에서 정렬된 구조를 유지하며 제자리 업데이트(in-place update)를 많이 수행
- 장점: 범위 조회(range scan), 정렬/인덱스 탐색이 안정적
- 단점: 쓰기 시 페이지 분할/랜덤 I/O 등으로 비용이 커질 수 있음(워크로드/스토리지에 따라 편차)
- LSM(Log-Structured Merge) 계열(RocksDB 등)
- 쓰기를 먼저 append로 쌓고(메모리/로그), 나중에 정렬된 파일(SSTable)로 내린 뒤 컴팩션(compaction)으로 병합
- 장점: 대량 쓰기/삽입에 유리(순차 I/O 친화적), 높은 쓰기 처리량
- 단점
- 컴팩션으로 인한 write amplification(같은 데이터가 여러 번 다시 써짐)
- 읽기가 여러 레벨을 거칠 수 있고, 범위 조회 패턴에 따라 손해 가능
- 백그라운드 컴팩션이 p99 지연에 영향을 줄 수 있음
요약: 쓰기 폭주/대량 ingest는 LSM이 매력적일 수 있고, 범위 조회/정렬 기반 조회는 B-Tree가 안정적인 경우가 많음.
(단, SSD/HDD, 캐시, 튜닝, 워크로드가 더 큰 변수)
예시 답안(토론용)
- “우리 서비스는 쓰기 트래픽이 순간적으로 몰리거나 이벤트 로그를 대량 적재하는 구간이 있나요? 있다면 LSM 기반(RocksDB류)이 쓰기 처리량 측면에서 유리할 수 있어요.”
- “반대로 ‘최근 1주일 주문 내역’처럼 범위 조회가 많다면 B-Tree 기반(InnoDB)의 범위 스캔 성능이 운영적으로 더 안정적일 수 있습니다.”
- “LSM은 컴팩션 때문에 백그라운드 I/O가 튀는 구간이 생길 수 있어서, 실시간 API처럼 p99 지연이 중요하면 그 리스크까지 같이 봐야 할 것 같습니다.”
토론 결과(기록용)
- 우리 서비스 워크로드 특징(읽기/쓰기 비율, 범위조회, 핫키 등):
- 현재 선택(또는 제안)되는 스토리지/엔진과 이유:
- 리스크/운영 포인트(컴팩션, p99, 비용 등):
- 액션 아이템(검증 실험/지표/튜닝 계획):
2. 캐시 vs 머티리얼라이즈드 뷰 vs 세컨더리 인덱스
핵심 개념
세 가지 모두 “조회 성능 향상”이 목표지만 일관성/유지비용/운영 방식이 다름.
(1) 캐시(Cache)
- 앱/Redis 같은 별도 계층에 결과를 저장
- 장점: 매우 빠르고 유연(쿼리 결과, 객체, 페이지 등)
- 단점: 최신성(일관성) 문제 + 무효화(invalidation)가 어렵다
- 흔한 패턴:
cache-aside,read-through,write-through
(2) 머티리얼라이즈드 뷰(Materialized View)
- 자주 쓰는 조회(특히 조인/집계) 결과를 DB가 테이블처럼 저장
- 장점: 조회가 단순해지고 반복 계산 감소(리포트/집계 화면에 유리)
- 단점: 갱신 전략(즉시/주기/증분)과 비용이 핵심, 최신성(지연 허용치) 합의 필요
(3) 세컨더리 인덱스(Secondary Index)
- 원본 테이블 외에 “다른 컬럼 기준의 빠른 탐색”을 위한 구조
- 장점: DB 내부 기능이라 비교적 자연스럽게 정합성 유지, 조건 검색이 빠름
- 단점: 인덱스가 늘수록 쓰기 비용/저장공간 증가
요약
- 캐시: 빠르지만 최신성은 애플리케이션이 책임
- MV: 자주 쓰는 조인/집계를 저장, 갱신(지연) 정책이 핵심
- 인덱스: DB가 검색 성능을 제공, 대신 쓰기 비용 증가
예시 답안(토론용)
- “느린 엔드포인트가 ‘단순 조회인데 호출이 매우 잦음’이라면 캐시가 1순위일 수 있어요.”
- “조인+집계가 무거운 리포트성 화면이면 머티리얼라이즈드 뷰나 집계 테이블이 운영 측면에서 편할 수 있습니다.”
- “특정 필터 검색이 느린 거라면(예:
status + created_at) 세컨더리 인덱스가 정석이고요.” - “결국 기준은 ‘최신성이 얼마나 중요한가’와 ‘쓰기 트래픽을 얼마나 감당 가능한가’라고 생각합니다.”
토론 결과(기록용)
- 성능 병목이 있는 화면/쿼리(Top N):
- 최신성 요구(즉시/수초/수분/수시간 단위 지연 허용):
- 선택한 전략(캐시/MV/인덱스/혼합)과 이유:
- 운영 정책(TTL, 무효화, 갱신 주기, 인덱스 관리 기준):
- 액션 아이템(지표/실험/적용 범위):
3. 트랜잭션 격리 수준 (정합성 vs 성능)
핵심 개념
격리 수준은 “동시성 상황에서 어떤 이상 현상을 허용할지”를 정한다.
대표 이상 현상
- Dirty read: 커밋 안 된 값을 읽음
- Non-repeatable read: 같은 행을 두 번 읽었는데 값이 달라짐
- Phantom read: 조건 조회 결과에 “새 행이 끼어듦”
- Write skew: 서로 다른 행을 보고 각각 업데이트해 제약이 깨지는 현상(실무에서 중요)
대표 격리 수준(개념적)
- Read Committed: 커밋된 것만 읽음(기본으로 많이 사용)
- Repeatable Read: 트랜잭션 동안 읽은 행의 일관성을 더 강하게 보장(phantom 등은 DB 구현 따라 상이)
- Serializable: 동시에 실행해도 순서대로 실행한 것과 같게 보이도록(가장 강하지만 비용 증가)
실무 포인트: 격리 수준을 무작정 올리기보다,
비즈니스 불변조건(invariant)을 정의하고 이를 깨는 동시성 버그를 어떤 방식(락/원자적 업데이트/재시도)으로 막을지 설계하는 게 중요.
실전 예시 힌트
- 재고/결제처럼 치명적 오류는 보통:
SELECT ... FOR UPDATE(락)- 조건부 원자 업데이트(예:
WHERE stock > 0) - 멱등키(idempotency key)
와 함께 고려
예시 답안(토론용)
- “격리 수준을 ‘무조건 높이는 게 답’이라기보다, 우리 서비스에서 반드시 지켜야 하는 불변조건이 뭔지부터 정리해야 한다고 생각해요.”
- “결제/재고는 중복이나 음수 같은 치명적 오류가 나면 안 되니 락이나 조건부 업데이트로 원자성을 확보하고 필요하면 더 강한 격리를 적용하는 게 합리적일 것 같습니다.”
- “피드/조회성 트래픽은 Read Committed + 재시도 같은 방식으로 처리량을 우선할 수도 있을 것 같고요.”
- “또, 겪는 문제가 phantom인지 write skew인지 유형을 먼저 분류하면 격리/락 설계를 더 정확히 고를 수 있을 것 같습니다.”
토론 결과(기록용)
- 우리 서비스에서 반드시 지켜야 하는 불변조건(예: 재고 음수 금지, 중복 결제 금지 등):
- 현재/목표 격리 수준(또는 DB 기본값)과 이유:
- 락/원자적 업데이트/재시도/멱등키 적용 여부:
- 과거에 겪은 동시성 이슈 사례(있다면):
- 액션 아이템(테스트 시나리오, 모니터링, 적용 계획):
4. 메시지 큐/이벤트 기반 도입 시 ‘최소 합의 규칙’
핵심 개념
동기 호출을 “이벤트 발행 → 비동기 처리”로 바꾸면, 분산 환경의 기본 전제(중복/순서/재처리/스키마)가 중요해진다.
최소 합의 규칙 체크리스트
1) 전달 보장(Delivery semantics)
- 흔한 기본값: at-least-once(적어도 한 번 전달)
→ 중복 메시지가 올 수 있다.
2) 멱등성(Idempotency)
- 중복 메시지를 받아도 결과가 한 번 처리한 것과 같아야 함
- 대표 전략:
event_id기반 처리 기록(소비자 측 dedup 테이블)- 비즈니스 키 기반 upsert(예:
order_id)
3) 순서(Ordering)
- 완전한 전역 순서 보장은 어렵고 비용이 큼
- 현실적 합의: 같은 키(user_id/order_id) 단위 순서 보장
4) 재처리(Replay) & 보상(Compensation)
- 과거 이벤트 재처리가 필요할 수 있음
- 다시 계산 가능한 것(집계/통계) vs 보상 로직 필요한 것(결제/포인트/재고) 구분
5) 스키마 버전/호환성(Schema evolution)
- 필드 추가는 optional로
- 제거/의미 변경은 매우 신중
schema_version명시, 구/신버전 공존 기간 합의
예시 답안(토론용)
- “메시지 큐를 도입하면 ‘중복 전달이 올 수 있다’를 기본 전제로 잡아야 할 것 같아요. 그래서
event_id를 두고 소비자에서 처리 기록으로 멱등 처리하는 걸 최소 규칙으로 합의하면 좋겠습니다.” - “순서는 전체 보장보다
order_id같은 키 단위로 보장하는 식의 합의가 운영 난이도를 낮출 것 같습니다.” - “이벤트 스키마는 한번 퍼지면 되돌리기 어렵기 때문에
schema_version을 명시하고 필드 추가는 optional로 해서 구/신버전을 공존 처리하는 원칙이 안전할 것 같습니다.” - “재처리(replay) 시 어떤 이벤트는 다시 돌려도 되는지, 어떤 건 보상 트랜잭션이 필요한지(결제/포인트)는 도입 전에 꼭 정해야 할 것 같습니다.”
토론 결과(기록용)
- 도입 목적(왜 비동기/이벤트가 필요한가):
- 전달 보장 가정(at-least-once 등) 및 중복 처리 전략:
- 순서 보장 범위(키 단위? 전역? 필요 없음?):
- 재처리 정책(가능/불가, 보상 필요 영역):
- 스키마 버전 관리 방식(버전 필드, 호환 기간, 책임자):
- 액션 아이템(POC, 위험요소, 운영 룰 문서화):
Leave a comment