이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Stateful 컴포넌트를 도입할 때 어떤 복구 비용을 함께 계산해야 하는가?
  • DB, Redis, broker, 검색 색인은 상태의 성격이 어떻게 다른가?
  • 장애 후 재처리 가능한 상태와 유실되면 안 되는 상태를 어떻게 구분하는가?

개요

Stateful 컴포넌트는 데이터를 갖는 순간 장애 복구 책임을 가진다. DB, Redis, message broker, object storage, 검색 색인은 모두 상태를 갖지만 같은 상태가 아니다. 어떤 상태는 유실되면 돈이나 권한이 틀어진다. 어떤 상태는 원본에서 다시 만들 수 있다. 어떤 상태는 잠깐 사라져도 사용자 경험만 나빠진다. 설계에서 중요한 것은 “무엇을 도입했는가”보다 “그 상태를 잃었을 때 어디서 복구할 수 있는가”다.

이 문서가 바꾸는 설계 축

바뀌는 내용
성능stateful store를 붙이면 빠른 조회나 비동기 처리가 가능하지만 write path가 늘어난다.
정합성원본 상태와 파생 상태의 관계를 정해야 한다.
장애 격리특정 저장소 장애가 핵심 기능 전체로 번지지 않게 경계를 둔다.
운영 복잡도backup, failover, replay, rebuild, data validation 절차가 필요하다.

상태를 네 종류로 나눈다

상태 종류예시유실 영향복구 방법
Source of Truth주문, 결제, 포인트 원장치명적backup, replication, 수동 검증
Derived State검색 색인, read model, 랭킹지연 또는 조회 오류원본에서 rebuild
Ephemeral State로그인 세션, rate limit counter재로그인, 일시 제한 오류TTL 만료, 재로그인, 재생성
Processing Statequeue offset, DLQ, idempotency key중복, 누락, 순서 문제replay, dedup, 운영 재처리
같은 Redis라도 역할에 따라 복구 비용이 다르다.
cache Redis는 비워져도 DB에서 다시 읽으면 된다.
session Redis는 비워지면 사용자가 로그아웃된다.
idempotency Redis가 비워지면 결제 중복 방어가 깨질 수 있다.

대표 시나리오

주문 시스템에서 다음 상태를 갖는다고 하자.

컴포넌트상태요구
RDB주문, 결제, 재고 원장유실 불가
Redis세션, 상품 cache, 주문 idempotency key일부 유실 허용 여부가 다름
Kafka주문 생성 이벤트, 결제 완료 이벤트재처리 가능해야 함
Search Index상품 검색 문서원본 DB에서 재생성 가능
Object Storage영수증 PDF재생성 비용 큼
이 표를 만들면 같은 “장애”라도 대응이 달라진다.
검색 색인이 깨지면 검색 품질이 나빠진다.
주문 원장이 깨지면 비즈니스 정합성이 깨진다.
idempotency key가 사라지면 retry 요청이 중복 주문으로 이어질 수 있다.

RTO와 RPO를 붙인다

복구 비용은 RTO와 RPO로 설명한다.

용어질문
RTO장애 후 얼마 안에 기능을 회복해야 하는가?
RPO장애 시점에서 얼마만큼의 데이터 유실을 허용하는가?
예를 들어 주문 원장의 RPO는 거의 0이어야 한다.
반면 추천 read model은 최근 몇 분의 지연을 허용할 수 있다.
order_db_rpo = 0
order_db_rto = 15 minutes
search_index_rpo = 10 minutes
search_index_rto = 2 hours
session_store_rpo = 30 minutes TTL 안에서 일부 유실 허용

이렇게 적어야 multi-AZ, backup 주기, replay 절차의 우선순위가 정해진다.

Redis 역할별 판단

Redis를 쓴다는 말만으로는 설계가 부족하다.

Redis 역할실패 시 영향보호 수준
Cachemiss 증가, DB 부하 증가eviction과 stampede 방지
Session Store재로그인, 인증 실패replication, memory sizing, failover
Lock중복 처리, lock 유실TTL, fencing token, DB 제약 보완
Idempotency Store중복 결제/주문 가능성TTL, unique constraint, 원장 검증
Rate Limit Counter제한 우회 또는 과도한 제한fail-open/closed 정책
idempotency key를 cache처럼 취급하면 위험하다.
중복 실행되면 안 되는 작업은 DB unique constraint나 원장 검증으로 보강해야 한다.

Broker 상태와 재처리

broker의 상태는 메시지와 offset이다. 메시지가 남아 있어도 consumer가 중복 처리 가능해야 한다.

{
  "eventId": "order-created-983",
  "eventType": "OrderCreated",
  "aggregateId": "order-1024",
  "occurredAt": "2026-07-01T10:00:00+09:00"
}

eventId는 중복 처리 방어의 기준이다. consumer는 같은 eventId를 다시 받아도 결과가 달라지지 않아야 한다. DLQ는 실패를 숨기는 통이 아니라 재처리 기준을 보관하는 운영 상태다. DLQ가 쌓이면 어떤 owner가 어떤 순서로 확인하고 재처리할지 정해야 한다.

Search Index와 read model

검색 색인과 read model은 원본이 아니다. 하지만 사용자 화면은 이 데이터를 통해 움직인다. 색인이 오래 지연되면 사용자는 상품이 없다고 느낄 수 있다.

product_db_count = 10,000,000
index_rebuild_rate = 20,000 docs/sec
full_rebuild_time = 10,000,000 / 20,000
                  = 500 seconds

전체 rebuild가 8분 정도라면 장애 중 read model 재생성이 현실적이다. 하지만 10시간이 걸리면 incremental replay, snapshot, dual write 검증이 필요하다. 파생 상태도 복구 시간이 길면 핵심 의존성이 된다.

Stateful 컴포넌트 격리

모든 상태 저장소를 한 장애 도메인으로 묶지 않는다. 예를 들어 session Redis와 cache Redis를 같은 cluster에 두면 cache 폭증이 session eviction으로 이어질 수 있다.

격리 대상이유
session과 cachecache eviction이 로그인 유실로 번지지 않게 한다.
online DB와 batch DB대량 집계가 사용자 요청 latency를 잠식하지 않게 한다.
critical queue와 marketing queue부가 작업 적체가 주문 처리에 영향을 주지 않게 한다.
read model과 write DBrebuild 작업이 원장 write를 막지 않게 한다.

운영 지표

Stateful 컴포넌트는 기능 지표보다 복구 지표가 중요하다.

컴포넌트지표
DBreplication lag, backup age, lock wait, restore test result
Redismemory used, eviction, hit ratio, failover count
Brokerconsumer lag, oldest message age, DLQ count
Searchindexing lag, failed documents, rebuild duration
Object Storagewrite error, lifecycle policy, restore duration
복구 절차를 문서로만 두면 약하다.
정기적으로 restore test나 replay test를 해 봐야 실제 RTO를 알 수 있다.

개인 프로젝트 기준

  • DB와 Redis가 각각 어떤 상태를 갖는지 표로 나눈다.
  • cache처럼 유실되어도 되는 상태와 idempotency처럼 유실되면 안 되는 상태를 구분한다.
  • backup을 설정했다면 실제 복구 명령을 한 번 검증한다.
  • queue를 쓰면 DLQ와 중복 처리 기준을 남긴다.
  • 검색 색인이나 read model은 원본에서 다시 만들 수 있는지 적는다.

기업 운영 기준

  • stateful 컴포넌트마다 owner team, RTO, RPO, backup, failover 절차를 둔다.
  • 복구 테스트는 배포 전 체크리스트나 정기 운영 훈련에 포함한다.
  • session, cache, lock, idempotency를 같은 Redis cluster에 둘 때 격리 위험을 리뷰한다.
  • DLQ, replay, rebuild는 수동 운영 절차와 권한을 명확히 한다.
  • 상태 저장소 비용은 저장량뿐 아니라 복구 시간과 장애 영향 범위로 평가한다.

위험 신호

  • Redis를 cache, session, lock, idempotency에 함께 쓰면서 실패 정책을 나누지 않는다.
  • DB backup은 있지만 restore 테스트가 없다.
  • broker consumer가 중복 메시지를 처리할 수 없다.
  • 검색 색인이 원본인지 파생 상태인지 문서에 없다.
  • DLQ가 쌓여도 owner와 재처리 기준이 없다.
  • stateful 컴포넌트 장애가 어떤 사용자 기능을 멈추는지 모른다.

확인 질문

확인 질문

  • 확인 질문: Stateful 컴포넌트 도입 전에 반드시 물어야 할 질문은 무엇인가?
    • 답변: 이 상태가 유실되면 어떤 사용자 영향이 생기고, 어디서 얼마 안에 복구할 수 있는지 물어야 한다.
  • 확인 질문: Redis cache와 Redis idempotency store는 왜 다르게 봐야 하는가?
    • 답변: cache 유실은 성능 문제지만 idempotency 상태 유실은 중복 결제나 중복 주문 같은 정합성 문제로 이어질 수 있기 때문이다.
  • 확인 질문: 파생 상태도 중요하게 다뤄야 하는 이유는 무엇인가?
    • 답변: 원본에서 재생성 가능하더라도 rebuild 시간이 길면 실제 사용자 기능이 오래 깨질 수 있기 때문이다.