cache-asideを実装しヒット率を測る
한국어 원문으로 표시합니다.
목표
cache-aside 를 직접 구현해 미스와 히트의 비용 차이를 숫자로 확인하고, 네거티브 캐싱과 버전 키 무효화까지 붙인다.
왜 중요한가
cache-aside 는 가장 흔한 패턴이라 코드는 어디서든 복사할 수 있습니다. 정작 어려운 것은 세부입니다. 쓰기 시 캐시를 갱신할지 삭제할지, 없는 데이터를 캐시할지, 그리고 전체를 한 번에 무효화해야 할 때 무엇을 할지입니다. 첫 번째 — 갱신이 아니라 삭제가 정답인 이유는 두 쓰기가 엇갈릴 때 갱신은 옛 값을 남길 수 있지만 삭제는 그렇지 않기 때문입니다. 두 번째 — 존재하지 않는 키를 반복 조회하는 트래픽은 캐시를 통과해 원본을 그대로 때립니다. 이것을 캐시 관통이라고 하고, 짧은 TTL 의 네거티브 캐싱으로 막습니다. 세 번째 — 상품 카테고리가 통째로 바뀌었을 때 관련 키를 전부 찾아 지우는 것은 의존성 추적 지옥입니다. 버전 키는 그것을 버전 숫자 하나 올리는 일로 바꿉니다.
단계
/opt/app/slowdb.py를 127.0.0.1:8151 에 띄운다.GET /item/1이 200 을 주고GET /stats가{"queries":<n>}를 준다./root/ca/cache.py에get_item(id)를 만든다. 캐시 미스면 원본을 조회해item:<id>키에 300초로 저장한다./root/ca/miss.out에source=origin ms=<정수>를 적고 ms 는 100 이상이어야 한다.- 같은 항목을 다시 조회한다.
/root/ca/hit.out에source=cache ms=<정수> origin_queries_delta=0을 적고 ms 는 50 미만이어야 한다. /root/ca/update.py는 원본을 갱신한 뒤 캐시 키를 삭제한다. 소스에 갱신값을 캐시에 다시 쓰는 코드가 있으면 안 된다. 실행 후EXISTS item:1이 0 이어야 한다.- 존재하지 않는
item:9999를 조회하면 원본이 404 를 주고, 그 결과를 60초 수명으로 캐시한다. 두 번째 조회에서 원본 조회 수가 늘지 않아야 하고TTL item:9999가 1 이상 60 이하여야 한다. /root/ca/bench.py로 100회 조회(고유 키 20개)를 돌리고/root/ca/hitratio.txt에hits=<n> misses=<n> ratio=<소수>를 적는다. ratio 는 0.75 이상이어야 한다.- 캐시 키를
v<버전>:item:<id>형태로 바꾸고 버전을ver:item키로 관리한다. 버전을 올린 뒤 조회하면 원본 조회 수가 늘어야 한다./root/ca/version.out에old_ver=<n> new_ver=<n> refetched=true를 적는다.
참고
- 원본 조회 수 확인:
curl -s http://127.0.0.1:8151/stats - 네거티브 캐싱의 TTL 은 정상 캐시보다 훨씬 짧게 둡니다 — 데이터가 곧 생길 수 있기 때문입니다.
- 흔한 실수 1: 쓰기 시 캐시를 갱신하는 것 — 동시 쓰기에서 옛 값이 남을 수 있습니다.
- 흔한 실수 2: 네거티브 캐싱을 안 해 존재하지 않는 ID 를 던지는 요청이 원본을 그대로 때리는 것(캐시 관통).
느린 원본 띄우기
/opt/app/slowdb.py 를 127.0.0.1:8151 에 띄운다. GET /item/1 이 200 을 주고 GET /stats 가 {"queries":<n>} 를 준다.
/opt/app/slowdb.py 는 조회마다 일부러 시간을 씁니다. 자기가 받은 조회 수도 알려 줍니다.
캐시 미스 경로 만들기
/root/ca/cache.py 에 get_item(id) 를 만든다. 캐시 미스면 원본을 조회해 item:<id> 키에 300초로 저장한다. /root/ca/miss.out 에 source=origin ms=<정수> 를 적고 ms 는 100 이상이어야 한다.
캐시에 없으면 원본을 다녀와 캐시에 넣고 돌려줍니다. 첫 조회는 느릴 수밖에 없습니다.
두 번째 조회를 캐시로 처리하기
같은 항목을 다시 조회한다. /root/ca/hit.out 에 source=cache ms=<정수> origin_queries_delta=0 을 적고 ms 는 50 미만이어야 한다.
같은 키를 다시 조회하면 원본에 안 가야 합니다. 원본 조회 수가 늘지 않는지 확인하세요.
갱신 시 캐시를 삭제하기
/root/ca/update.py 는 원본을 갱신한 뒤 캐시 키를 삭제한다. 소스에 갱신값을 캐시에 다시 쓰는 코드가 있으면 안 된다. 실행 후 EXISTS item:1 이 0 이어야 한다.
갱신이 아니라 삭제입니다. 왜 그런지는 읽기 자료에 있습니다. 다음 조회가 알아서 최신을 채웁니다.
없는 키를 네거티브 캐싱하기
존재하지 않는 item:9999 를 조회하면 원본이 404 를 주고, 그 결과를 60초 수명으로 캐시한다. 두 번째 조회에서 원본 조회 수가 늘지 않아야 하고 TTL item:9999 가 1 이상 60 이하여야 한다.
존재하지 않는 키를 반복 조회하면 매번 원본에 갑니다. 짧은 수명으로 '없음'도 캐시하세요.
적중률 측정하기
/root/ca/bench.py 로 100회 조회(고유 키 20개)를 돌리고 /root/ca/hitratio.txt 에 hits=<n> misses=<n> ratio=<소수> 를 적는다. ratio 는 0.75 이상이어야 한다.
히트와 미스를 각각 세어 비율을 냅니다. 요청 분포에 따라 값이 달라집니다.
버전 키로 전체 무효화하기
캐시 키를 v<버전>:item:<id> 형태로 바꾸고 버전을 ver:item 키로 관리한다. 버전을 올린 뒤 조회하면 원본 조회 수가 늘어야 한다. /root/ca/version.out 에 old_ver=<n> new_ver=<n> refetched=true 를 적는다.
키 이름에 버전을 심으면 버전만 올려도 옛 키를 아무도 안 찾습니다. 삭제를 직접 하지 않아도 됩니다.