Chapter 12

캐싱, 마이크로서비스, 컨트롤 플레인

  • 12.1 Policies
  • 12.2 Local cache
  • 12.3 External cache
  • 12.4 Caveats
  • 12.5 API gateway
  • 12.6 Scale imbalance
  • 12.7 Control theory

수평 확장의 인프라를 갖췄으면, 이제 애플리케이션 수준의 확장성 패턴이야. 핫 데이터를 캐싱하고, 모놀리스를 마이크로서비스로 분해하고, 컨트롤 플레인과 데이터 플레인을 분리하는 것 — 이 세 가지가 대규모 시스템에서 반복적으로 나타나는 패턴이지.

캐싱부터 가자. 데이터 스토어 앞에 캐시를 두는 건 기만적으로 단순해 보이지만, 미묘한 일관성 문제가 숨어있어. 캐시는 메모리가 유한하니까 **퇴거 정책(eviction policy)**이 필수야. **LRU(Least Recently Used)**가 가장 보편적이고, LFU, TTL 기반도 있지. 바운드 없는 캐시는 결국 모든 메모리를 소비해 — 반드시 한계를 정해야 해. 인프로세스(로컬) 캐시는 네트워크 홉이 없으니까 가장 빠르지만 프로세스 메모리에 제한되고 인스턴스 간 공유가 안 돼. 외부 캐시(Redis, Memcached)는 모든 인스턴스가 공유하지만 네트워크 지연이 추가되고, 독립적으로 스케일링할 수 있어. 캐시 패턴도 알아둬 — Cache-aside는 캐시에 없으면 DB에서 읽어서 저장, Write-through는 캐시와 DB에 동시 반영, Write-behind는 캐시에 먼저 쓰고 비동기로 DB 반영이야. 단순해 보여도 오래된 데이터, 캐시 만료 시 대량 요청이 몰리는 thundering herd, cache stampede 같은 문제가 따라와. 캐시 무효화가 가장 어려운 부분이지.

서비스가 커지면 마이크로서비스로 분해하게 돼 — 독립 배포, 기술 다양성, 팀 자율성이 장점이야. 근데 공짜가 아니거든. 서비스 간 호출 실패에 대한 재시도/타임아웃/서킷 브레이커, 분산 트랜잭션, 교차 서비스 쿼리의 복잡성, 분산 모니터링이라는 운영 오버헤드가 전부 따라와. 서비스 경계를 잘못 잡으면 과도한 통신과 비싼 리팩터링이 기다리고 있어. 경계는 DDD의 바운디드 컨텍스트(bounded context), 비즈니스 능력 기준으로 나눠야 하지 기술 레이어로 나누면 안 돼. 실용적 조언은 모놀리스로 시작하고 필요할 때 추출하라는 거야. 분해 후에는 API 게이트웨이가 필수 — 외부 클라이언트가 내부 토폴로지를 모르게 하는 단일 진입점으로, 라우팅, 인증, rate limiting, 프로토콜 변환, 응답 집계를 집중 처리하지.

마지막으로 대규모 시스템에서 반복적으로 나타나는 근본 패턴 — 컨트롤 플레인과 데이터 플레인의 분리야. 데이터 플레인은 실제 사용자 요청을 처리하는 빠른 경로이고, 컨트롤 플레인은 시스템 동작을 결정하는 느린 경로지 — 구성, 오케스트레이션, 라우팅 규칙, 헬스 모니터링을 담당해. 둘 사이의 스케일 불균형이 핵심이야. K8s에서 API 서버가 컨트롤, kubelet/pod가 데이터 플레인이고, 서비스 메시에서 Istio가 컨트롤, Envoy 사이드카가 데이터 플레인이잖아. 중요한 원칙이 있어 — 컨트롤 플레인 장애가 데이터 플레인을 즉시 멈추면 안 돼. 데이터 플레인은 마지막으로 알려진 구성으로 계속 동작해야 해. 각각 독립적으로 스케일링하고, 데이터 플레인은 성능/가용성, 컨트롤 플레인은 정확성/일관성에 최적화하는 거야.


정리

12장 읽고 기억할 거 세 가지:

  1. 캐싱은 단순해 보이지만 퇴거 정책, 일관성, thundering herd 같은 문제가 따라와 — 캐시 무효화가 가장 어려운 부분이야
  2. 마이크로서비스의 대가는 분산 시스템의 전체 복잡성이야 — 서비스 경계는 비즈니스 능력 기준, 모놀리스에서 시작하는 게 실용적이지
  3. 컨트롤/데이터 플레인 분리는 K8s, CDN, 서비스 메시 어디서든 나타나는 패턴이야 — 컨트롤이 죽어도 데이터 플레인은 마지막 구성으로 계속 동작해야 해