Foundry
캐시/Redis
심화
핵심

캐시 일관성 문제

DB는 바뀌었는데 캐시는 옛 값, 정합성이 깨지는 순간

캐시 일관성 문제

불일치가 만들어지는 순간

쓰기 A: DB UPDATE (v2 커밋)
읽기 B: 캐시 미스 -> DB SELECT (v1 시점 값 획득)
쓰기 A: 캐시 DEL
읽기 B: 캐시 SET (v1)   <- 옛 값이 캐시에 남는다

경합 자체는 드물지만, TTL이 길면 그 옛 값이 오래 살아남는다.

완화 순서

  • TTL을 항상 준다: 무효화가 실패해도 시간이 지나면 스스로 복구된다
  • 커밋 후 삭제: 트랜잭션 커밋 이후에 DEL. 커밋 전 삭제는 롤백과 어긋난다
  • 삭제 실패 재처리: DB 커밋은 되돌릴 수 없으므로 삭제 실패는 로그나 큐로 남겨 재시도한다
  • 쓰기 직후 읽기: 본인 쓰기 결과를 즉시 보여야 하는 화면은 짧게 캐시를 우회한다

실무 포인트

  • 캐시를 넣는 순간 시스템은 결과적 일관성이 된다. 어느 정도 낡음까지 허용할지 먼저 합의한다
  • 잔액, 재고, 좌석처럼 불일치 비용이 큰 값은 캐시하지 않거나 DB 조건부 갱신으로 최종 검증한다
  • 캐시와 DB에 대한 이중 쓰기는 원자적이지 않다. 둘 다 성공한다는 가정을 코드에 두지 않는다
면접에서 이렇게 나옵니다

Q.DB와 캐시의 정합성을 어떻게 맞추나요?

완전한 일치는 불가능하므로, 어긋나는 시간에 상한을 두는 방향으로 설계합니다.

장치역할
커밋 후 캐시 삭제기본. 새 값으로 덮지 않고 지운다
만료 시간삭제가 실패해도 수렴시킨다
변경 로그 구독애플리케이션이 놓친 경로까지 잡는다
이중 삭제갱신 직후와 짧은 지연 뒤 한 번 더
정합성 점검표본을 비교해 어긋난 건수를 알림으로

즉시 일치가 필요한 값은 아예 캐시하지 않는 것이 답입니다. 잔액과 재고는 캐시로 읽되 결정은 원본에서 조건부로 합니다.

상황어디서 읽나
목록과 상세 표시캐시에서 읽는다
결제 확정원본에서 UPDATE ... WHERE qty > 0 으로 확인한다

흔한 실수: 캐시를 강한 일관성으로 만들려고 애쓰는 것. 캐시는 원본과 사본이 존재하는 구조라 시차가 본질입니다. 없앨 수 없고 얼마나 짧게 만들지를 정하는 문제입니다.

Q.캐시 삭제가 실패하면 어떻게 처리하나요?

그대로 두면 옛 값이 만료까지 남습니다. 실패를 흡수할 장치를 둡니다.

방법내용
재시도짧게 몇 번. 실패하면 로그와 지표로 남긴다
만료 시간최종 안전망. 이것만으로도 수렴한다
삭제 작업을 큐로실패한 삭제를 큐에 넣어 나중에 처리한다
변경 로그 구독DB 변경을 별도 경로로 받아 다시 지운다
버전 키값에 버전을 넣고 조회 시 비교해 옛 값을 무시한다

중요한 것은 삭제 실패를 조용히 넘기지 않는 것입니다. 지표로 남기지 않으면 정합성이 어긋난 채로 오래 지속됩니다.

캐시 서버가 아예 죽은 경우는 오히려 안전합니다. 조회도 실패해 DB 로 넘어가므로 옛 값이 나갈 일이 없습니다. 위험한 것은 조회는 되고 삭제만 실패하는 부분 장애입니다.

흔한 실수: 삭제를 최선 노력으로 두고 만료도 길게 잡는 것. 둘 중 하나는 짧아야 어긋남이 수렴합니다.

Q.캐시 갱신과 DB 갱신 사이의 경합을 설명해주세요

읽기와 쓰기가 겹칠 때, 옛 값이 캐시에 박히는 좁은 창이 생깁니다.

순서동작
1조회 A 가 캐시 미스로 DB 에서 옛 값을 읽는다
2쓰기 B 가 DB 를 갱신하고 캐시를 지운다
3조회 A 가 1번에서 읽어둔 옛 값을 캐시에 채운다

결과적으로 DB 는 새 값이고 캐시는 옛 값입니다. 삭제를 했는데도 어긋납니다.

완화내용
만료 시간가장 값싼 안전망
이중 삭제갱신 직후와 짧은 지연 뒤 한 번 더 지운다
변경 로그 구독애플리케이션 타이밍과 무관하게 다시 지운다
쓰기 경로에 잠금정확성이 돈과 직결될 때만. 처리량을 깎는다

발생 확률은 낮습니다. 미스가 난 뒤 채우기까지의 짧은 구간에 쓰기가 끼어들어야 하기 때문입니다. 그래서 대개 만료와 이중 삭제로 충분합니다.

흔한 실수: 이 경합을 모르고 삭제 방식이 완벽하다고 보는 것. 확률이 낮을 뿐 없지는 않습니다.

Q.캐시하면 안 되는 데이터를 판단하는 기준은?

세 질문으로 걸러냅니다.

질문아니오라면
몇 초 지난 값을 보여줘도 되는가캐시하지 않는다. 잔액, 재고, 좌석
같은 키에 같은 응답이 나가는가개인화 요소를 키에 넣지 않으면 남의 값이 나간다
반복 조회되는가히트가 안 나면 메모리만 쓴다

여기에 두 가지를 더 봅니다.

항목내용
민감도캐시는 접근 제어가 약하다. 공유 캐시에 개인 정보를 두지 않는다
크기큰 값은 전송과 메모리를 크게 먹어 이득이 상쇄된다

첫 질문이 아니오여도 표시용으로만 캐시하는 선택은 가능합니다. 재고를 캐시로 보여주고 결제 시점에 원본에서 조건부로 확인하면, 화면은 빠르고 정확성은 지킵니다.

흔한 실수: 정합성 기준을 데이터 종류로만 정하는 것. 같은 재고 데이터도 목록 화면과 결제 확정에서 요구 수준이 다릅니다. 쓰임새 단위로 판단해야 합니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

캐시/Redis 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.