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

  • Read-after-write와 monotonic read는 각각 어떤 사용자 경험을 보호하는가?
  • replica read, cache, read model을 쓰면서 방금 쓴 값을 보여주려면 무엇이 필요한가?
  • version token, primary routing, lag wait 중 어떤 전략을 선택해야 하는가?

개요

Read-after-write는 사용자가 방금 쓴 값을 다음 읽기에서 볼 수 있게 하는 보장이다. Monotonic read는 사용자가 한 번 본 값보다 더 오래된 값을 나중에 보지 않게 하는 보장이다. 둘은 비슷해 보이지만 다르다. 주문 직후 상세 화면에 주문이 보여야 하는 것은 read-after-write다. 주문 상태가 배송 준비로 보였다가 다시 결제 완료로 내려가면 monotonic read가 깨진 것이다. Replica, cache, read model을 쓰면 이 두 경험이 쉽게 깨진다.

이 문서가 바꾸는 설계 축

바뀌는 내용
성능primary routing이나 lag wait는 latency와 primary 부하를 늘린다.
정합성사용자가 쓴 값과 이미 본 version을 기준으로 읽기 경로를 보정한다.
장애 격리replica lag가 커져도 핵심 화면만 primary로 우회할 수 있다.
운영 복잡도version token, session hint, lag metric, fallback 정책을 관리한다.

두 보장을 구분한다

보장예시깨졌을 때
Read-after-write프로필 수정 직후 내 프로필에서 새 이름이 보인다.사용자는 저장 실패로 오해한다.
Monotonic read주문 상태가 배송중으로 보인 뒤 다시 준비중으로 보이지 않는다.사용자는 시스템을 믿지 못한다.
Consistent prefix채팅 메시지 순서가 앞뒤로 뒤집히지 않는다.대화 흐름이 깨진다.
Causal read댓글 작성 후 알림/답글이 원인보다 먼저 보이지 않는다.인과 관계가 이상해진다.
실무에서는 모든 읽기에 linearizable read를 요구하기보다, 필요한 화면에 필요한 보장을 붙인다.

대표 시나리오

사용자가 배송지를 수정한 뒤 주문 상세와 주문 목록을 본다.

화면기대허용 전략
배송지 수정 완료 응답새 배송지 version 반환write result
주문 상세방금 쓴 배송지가 즉시 보여야 함primary read 또는 version wait
주문 목록몇 초 지연 가능replica read 가능
관리자 통계수 분 지연 가능projection read
이때 주문 상세까지 replica에서 읽으면 사용자가 이전 배송지를 볼 수 있다.
목록과 통계는 지연을 허용할 수 있지만 상세는 read-after-write가 필요하다.

Version token 전략

쓰기 응답에 version을 반환하고, 다음 읽기에서 최소 version을 요구한다.

PUT /shipping-address
200 OK
{
  "addressId": "addr-10",
  "version": 42
}

다음 읽기:

GET /orders/order-1
X-Min-Version: address:42

서버는 replica가 version 42 이상이면 replica를 읽고, 아니면 primary를 읽거나 잠깐 기다린다. 이 방식은 모든 요청을 primary로 보내지 않으면서 read-after-write를 보정한다.

Primary routing 전략

가장 단순한 방법은 쓰기 직후 일정 시간 primary를 읽는 것이다.

public OrderView getOrder(String orderId, ReadHint hint) {
    if (hint.afterWrite()) {
        return orderReader.readFromPrimary(orderId);
    }
    return orderReader.readFromReplica(orderId);
}

이 방식은 구현이 쉽다. 하지만 쓰기 직후 사용자가 여러 화면을 빠르게 이동하면 primary 부하가 늘어난다. afterWrite hint를 몇 초 유지할지, 어떤 API에만 적용할지 정해야 한다.

Replica lag wait 전략

Replica lag가 짧고 측정 가능하면 일정 시간 기다릴 수 있다.

required_version = 42
replica_visible_version = 41
max_wait = 200ms

200ms 안에 replica가 version 42에 도달하면 replica를 읽는다. 도달하지 못하면 primary read나 PROCESSING 응답으로 전환한다. 이 방식은 latency budget이 있어야 한다. 무한 대기는 availability를 해친다.

Monotonic read 전략

Monotonic read는 사용자가 본 최대 version을 기억해야 한다.

GET /orders/order-1
X-Seen-Version: order:57

서버는 최소 57 이상을 보여줘야 한다. 더 낮은 version밖에 없으면 primary로 가거나, 잠시 기다리거나, refreshing 상태를 준다. 사용자별 seen version을 client token, session, local storage 중 어디에 둘지도 결정해야 한다. 보안 민감 데이터라면 client가 임의로 조작해도 안전한 방식으로 검증해야 한다.

Cache와 read model

Cache는 read-after-write를 쉽게 깨뜨린다. 사용자가 프로필을 바꿨는데 cache TTL이 5분이면 이전 이름이 계속 보일 수 있다. 해결 방식은 여러 가지다.

방식장점비용
write-through invalidation즉시 반영이 쉽다invalidation 실패 처리 필요
user-specific bypass방금 쓴 사용자만 primary/cache bypass요청 경로가 복잡해진다
short TTL단순하다miss 증가와 DB 부하
response overlayclient가 방금 쓴 값을 임시 표시server truth와 차이 관리
read model도 마찬가지다.
projection lag를 숨길지, 화면에 processing을 보여줄지 결정해야 한다.

API 응답 계약

보장을 못 지킬 때도 계약이 필요하다.

200 OK
{
  "orderId": "order-1",
  "status": "PAYMENT_COMPLETED",
  "freshness": "STALE",
  "visibleVersion": 56,
  "expectedVersion": 57
}

또는 사용자가 기다려야 한다면:

202 Accepted
{
  "status": "PROCESSING",
  "retryAfterMillis": 500
}

무조건 오래된 값을 200으로 주면 사용자는 저장 실패로 오해한다.

개인 프로젝트 기준

  • 쓰기 직후 바로 보여야 하는 화면을 API별로 표시한다.
  • replica나 cache를 쓴다면 read-after-write 예외 경로를 하나 둔다.
  • version 컬럼이나 updatedAt으로 최소 version 기준을 만들 수 있는지 확인한다.
  • 목록과 상세의 일관성 요구를 다르게 적는다.
  • stale 응답을 숨기지 말고 간단한 상태 필드로 드러낸다.

기업 운영 기준

  • read-after-write miss rate와 replica lag를 함께 본다.
  • primary routing 비율이 늘면 primary capacity와 비용을 재검토한다.
  • version token, session hint, client overlay 정책을 API 문서에 둔다.
  • projection lag가 사용자 SLO를 넘으면 alert를 발생시킨다.
  • support/CS가 stale과 processing의 의미를 이해하도록 문구를 통일한다.

위험 신호

  • 모든 조회를 replica로 보내면서 주문 직후 상세 화면을 검증하지 않는다.
  • cache TTL만 줄이고 invalidation 실패나 사용자별 bypass를 설계하지 않는다.
  • 사용자가 본 상태가 뒤로 가도 괜찮다고 가정한다.
  • version 없이 timestamp만으로 순서를 추정한다.
  • stale 상태를 숨기고 저장 실패처럼 보이게 만든다.
  • primary 우회 비율을 측정하지 않아 비용 증가를 모른다.

확인 질문

확인 질문

  • 확인 질문: Read-after-write와 monotonic read의 차이는 무엇인가?
    • 답변: read-after-write는 방금 쓴 값이 보여야 하는 보장이고, monotonic read는 이미 본 값보다 오래된 값을 나중에 보지 않게 하는 보장이다.
  • 확인 질문: replica read를 쓰면서 read-after-write를 지키는 방법은 무엇인가?
    • 답변: 일정 시간 primary routing, version token 기반 lag wait, 사용자별 cache bypass 같은 보정 경로가 필요하다.
  • 확인 질문: stale 응답을 숨기면 왜 위험한가?
    • 답변: 사용자가 저장 실패나 상태 후퇴로 오해해 재시도, 중복 요청, 문의가 늘어날 수 있기 때문이다.