17강
캐시 무효화
적어둔 답은 전부 과거의 답이다
먼저 보면 좋은 강의: 캐시 — cache-aside 패턴, 쓰기 통과와 쓰기 지연
단계별로 보기
스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.
대기 중1 / 4 단계 — 메모지는 원본이 바뀐 걸 모른다
캐시가 상품 42번의 가격을 3,000원으로 적어뒀습니다. 그사이 원본에서 가격이 4,000원으로 바뀌었습니다. 캐시는 이 사실을 모릅니다. 아무도 알려 주지 않았고, 스스로 확인할 방법도 없습니다. 이 순간 사용자는 계속 3,000원을 봅니다. 시스템은 아무 오류도 내지 않습니다.
이 강의를 마치면
- 캐시에 있는 값은 언제나 '과거의 값'이며, 문제는 그 과거가 얼마나 오래됐는가임을 설명한다.
- 유효 시간(TTL)을 정하는 것이 신선도와 부하 사이의 거래임을 계산한다.
- 쓰기가 많아질수록 캐시의 적중률이 떨어지는 이유를 관찰한다.
- '조금 틀려도 되는 데이터'와 '절대 틀리면 안 되는 데이터'를 구분해 정책을 정한다.
캐시에는 항상 과거가 들어 있다
캐시가 하는 일을 다시 봅시다. 어떤 시점의 답을 적어두고 재사용합니다. 그런데 원본은 그 뒤로도 계속 바뀝니다. 그리고 메모지는 그 사실을 모릅니다.
카운터에 붙여 둔 "오늘 배송비 3,000원" 메모지. 사장님이 어제 배송비를 4,000원으로 올렸습니다. 메모지는 그대로입니다.
이것이 캐시의 유일하고 근본적인 문제입니다. 성능 문제가 아니라 진실 문제입니다.
컴퓨터 과학에 유명한 농담이 있습니다.
컴퓨터 과학에서 어려운 문제는 딱 둘이다 — 캐시 무효화와 이름 짓기.
캐시를 넣는 건 쉽습니다. 언제 지울지 정하는 게 어렵습니다.
방법 1 — 시간이 지나면 버린다 (TTL)
가장 단순합니다. 적어둘 때 "5분 뒤 폐기"라고 써 둡니다. 5분이 지나면 그 값은 없는 것으로 칩니다. 다음 요청은 미스가 되고 원본에서 새로 가져옵니다.
누구에게 알릴 필요도, 관리할 목록도 없습니다. 그래서 압도적으로 많이 쓰입니다.
대가는 정직합니다. 최대 5분 동안 틀린 값이 나갑니다. 그리고 TTL은 그대로 거래 조건이 됩니다.
| TTL | 최대 오차 | 원본 부하 |
|---|---|---|
| 10초 | 10초 | 항목당 분당 6번 조회 |
| 5분 | 5분 | 항목당 분당 0.2번 조회 |
| 1시간 | 1시간 | 거의 0 |
TTL을 짧게 하면 캐시가 캐시가 아니게 됩니다. TTL 1초짜리 캐시는 거의 모든 요청을 원본으로 보냅니다. 그러면 애초에 캐시를 왜 넣었나요.
방법 2 — 바뀌면 즉시 지운다
값을 바꾸는 쪽이 캐시에 알려 줍니다. "42번 지워." 그러면 다음 조회는 미스가 되고 최신 값을 가져옵니다. 이걸 무효화라고 합니다.
틀린 값이 나가는 시간이 거의 0이 됩니다. 좋아 보입니다. 그런데 어렵습니다.
- 지울 것을 다 알아야 합니다. 상품 42번이 바뀌면 상품 상세 캐시만
지우면 될까요? 그 상품이 들어간 카테고리 목록은? 검색 결과는? 추천 목록은? 하나를 고쳤는데 지워야 할 게 열 곳입니다. 하나를 빠뜨리면 거기서만 옛날 값이 계속 나갑니다. 그리고 그런 버그는 몇 달 뒤에 발견됩니다.
- 지우는 것도 실패할 수 있습니다. 지우라는 신호가 유실되면 그 값은
TTL이 끝날 때까지 틀린 채로 남습니다.
그래서 현실적인 답은 대개 둘 다 하는 것입니다. 무효화로 대부분을 빠르게 처리하고, TTL을 안전망으로 깔아 둡니다. 무효화가 실패해도 TTL이 언젠가는 정리해 줍니다.
무효화의 숨은 청구서
여기가 이 강의에서 새로 배우는 부분입니다.
값을 지우는 것은 공짜가 아닙니다. 지운 항목은 다음 조회에서 반드시 미스가 되고, 그 미스는 원본까지 갑니다.
쓰기 한 번 → 무효화 한 번 → 나중에 원본 조회 한 번즉 쓰기가 많아질수록 캐시의 적중률이 떨어집니다. 읽기만 있던 14강에서는 적중률이 95%가 넘었습니다. 그런데 쓰기가 20% 섞이면 인기 항목이 계속 지워지므로 적중률이 그만큼 내려갑니다.
이 사실의 결론이 중요합니다.
캐시는 읽기가 압도적으로 많은 데이터에서만 잘 작동한다.
읽기와 쓰기 비율이 1:1인 데이터를 캐시하면 거의 효과가 없습니다. 캐시를 붙이기 전에 읽기/쓰기 비율부터 확인해야 하는 이유입니다.
그래서 얼마나 오래된 값까지 괜찮은가
이건 기술 질문이 아닙니다. 데이터마다 답이 다릅니다.
| 데이터 | 허용 오차 | 왜 |
|---|---|---|
| 계좌 잔액 | 0초 | 틀리면 사고다. 캐시하지 않는다 |
| 재고 수량 | 몇 초 | 오차만큼 초과 판매가 난다 |
| 상품 가격 | 몇 분 | 표시가와 결제가가 다르면 분쟁이 된다 |
| 조회수, 좋아요 수 | 몇 분 | 아무도 신경 쓰지 않는다 |
| 뉴스 목록 | 몇 분 | 조금 늦게 떠도 괜찮다 |
이 표를 채우는 건 업무 담당자입니다. "가격이 3분 늦게 반영돼도 되나요?"는 개발자가 답할 질문이 아닙니다. 그런데 개발자가 답하지 않으면 아무도 답하지 않으므로, 개발자가 TTL을 300초로 적어 넣고 아무도 모르게 됩니다. 정책을 정하지 않으면 정책이 저절로 생깁니다.
직접 해보세요
아래 과제는 14강과 비슷해 보이지만 결정적으로 다릅니다. 쓰기가 20% 섞여 있습니다. 캐시를 넣어 데이터베이스를 살리되, 통과한 뒤 적중률을 14강과 비교해 보세요. 그 차이가 무효화의 청구서입니다.
이 강의가 단순화한 것
교육용 모형은 언제나 무언가를 생략합니다. 무엇을 생략했는지 아는 것도 학습의 일부입니다.
전제한 것
- 이 시뮬레이터의 캐시는 쓰기 우회(write-around) 정책을 쓴다. 쓰기가 오면 해당 항목을 지우고 원본에 기록한다. 즉 모든 쓰기가 곧 무효화다.
- 무효화 신호는 절대 유실되지 않는다고 가정한다. 실제로는 이 신호가 유실되어 옛날 값이 TTL 끝까지 남는 일이 흔하다.
- '틀린 값을 봤다'는 사건을 오류로 세지 않는다. 이 시뮬레이터가 보여주는 것은 무효화가 적중률과 원본 부하에 미치는 영향까지다.
- 관련 항목의 연쇄 무효화(상품 하나가 바뀌면 목록·검색 결과도 무효)는 모델링하지 않는다. 현실에서는 이 연쇄가 무효화를 어렵게 만드는 주범이다.
다루지 않은 것
- TTL 만료 시각이 겹쳐 한꺼번에 미스가 나는 현상과 지터(jitter)
- stale-while-revalidate — 만료된 값을 갱신 중에도 잠시 더 제공하기
- 버전 태그·세대 키로 무효화를 한 번에 처리하는 기법
- 분산된 여러 캐시 사이의 무효화 전파
더 읽을거리: RFC 9111 — HTTP Caching · Scaling Memcache at Facebook
이제 직접 만들어 보세요
읽어서 아는 것과 만들어서 아는 것은 다릅니다. 컴포넌트를 배치하고 트래픽을 흘려서 실제로 동작하는지 확인해 보세요.
이 강의에 물어보기
하루 5번이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.