← 문제로

17. 읽기 복제본 지연과 read-your-writes 일관성 위반 (C#)

난이도 하
내 리뷰 · C#
해설 · C#

해설 — 읽기 복제본 지연과 read-your-writes 일관성 위반 (C#)

난이도: 하

요약

쓰기는 프라이머리에, 읽기는 비동기 복제본에 보내는데, 방금 자기가 쓴 값을 곧바로 복제본에서 읽는다. 복제 지연(replication lag) 때문에 (D)의 조회는 (C)의 UPDATE 가 아직 전파되지 않은 옛 잔액을 돌려줄 수 있다. 사용자는 충전했는데 잔액이 그대로거나 덜 오른 화면을 본다(read-your-writes 일관성 위반).

문제점

1) [복제 지연] 방금 쓴 값을 복제본에서 읽음 (증상: stale 잔액)

  • 증상: (C) ChargeGold가 프라이머리에 UPDATE 를 커밋해도, 비동기 복제는 수십~수백 ms(부하 시 더) 지연된다. 그 사이 (D) GetBalanceForResponse가 복제본을 조회하면 충전 전 값을 읽는다. 클라이언트는 "충전이 안 됐다"고 오인해 재충전·문의를 유발한다.
  • 재현조건: 쓰기 직후(복제 지연 시간 이내)의 읽기, 특히 복제 지연이 큰 순간.
  • 근본원인: "자기가 쓴 세션의 후속 읽기"를 복제본으로 보냈다. read-your-writes 보장이 필요한 읽기와, 일반 조회(약한 일관성 허용)를 구분하지 않았다.

2) [설계] 일관성 요구가 라우팅에 반영되지 않음 (증상: 산발적/재현난)

  • 증상: 지연은 항상 나는 게 아니라 부하·네트워크에 따라 가변적이라 QA 에서 안 잡히고 프로덕션에서 간헐 발생한다. 어떤 읽기가 강한 일관성을 요구하는지가 코드에 표현되지 않았다.

수정안

방금 쓴 값을 즉시 보여줘야 하는 읽기는 프라이머리로 라우팅(또는 반환값 활용).

public async Task<long> ChargeAndReturnBalance(long userId, long amount)
{
    // 방법 1: 쓰기와 같은 커넥션(프라이머리)에서 바로 최신값을 돌려받는다.
    long newBalance = await _primary.QueryScalarAsync(
        "UPDATE users SET gold = gold + @amt WHERE id = @id RETURNING gold", // PG: RETURNING
        amount, userId);
    return newBalance;
}

// 강한 일관성이 필요한 읽기는 명시적으로 프라이머리로
public Task<long> GetBalanceStrong(long userId) =>
    _primary.QueryScalarAsync("SELECT gold FROM users WHERE id=@id", userId);

// 일반(약한 일관성 허용) 조회만 복제본
public Task<long> GetBalanceEventual(long userId) =>
    _replica.QueryScalarAsync("SELECT gold FROM users WHERE id=@id", userId);
  • 가장 견고: UPDATE ... RETURNING(또는 트랜잭션 내 재조회)으로 쓰기 결과를 그 자리에서 받으면 별도 읽기 자체가 불필요.
  • 일관성 요구를 API 로 분리(...Strong vs ...Eventual)해 호출자가 라우팅 의도를 명시.

더 나은 설계 (트레이드오프)

  • write-then-read 세션 고정(sticky): 쓰기 후 짧은 시간(복제 지연 상한) 동안 그 사용자의 읽기를 프라이머리로 고정. 트레이드오프: 프라이머리 부하 소폭 증가, 지연 상한 추정 필요.
  • 복제 위치(LSN/GTID) 대기: 쓰기 시 얻은 복제 위치를 읽기 시 복제본이 그 지점까지 따라잡았는지 확인 후 조회(예: PostgreSQL pg_wait_for_replay_lsn 류, MySQL GTID). 강한 read-your-writes 를 복제본에서도 보장. 트레이드오프: 지연·구현 복잡도.
  • 읽기 후 자기 보정: 응답에 클라이언트가 계산한 예상 잔액을 쓰고, 복제 반영 후 정정. 트레이드오프: UX 복잡.

면접 포인트

  • 비동기 복제의 read-your-writes/monotonic-read 일관성 문제와 복제 지연의 가변성.
  • 어떤 읽기가 강한 일관성을 요구하고 어떤 읽기가 eventual 을 허용하는지 구분해 라우팅하는 설계.
  • UPDATE ... RETURNING, 세션 고정, 복제 위치(LSN/GTID) 대기 같은 해법과 각 트레이드오프.