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

ddia

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, 위험요소, 운영 룰 문서화):

Categories:

Updated:

Leave a comment