15강
캐시 — cache-aside 패턴
캐시를 넣는다는 건 코드에 네 줄을 쓴다는 뜻이다
먼저 보면 좋은 강의: 지연 시간과 처리량, 캐시는 왜 필요한가
단계별로 보기
스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.
대기 중1 / 4 단계 — 1·2단계 — 먼저 메모지를 본다 (HIT)
서버가 데이터베이스에 바로 묻지 않습니다. 먼저 캐시에 물어봅니다. "상품 42번 있어?" 있습니다. 여기서 끝입니다. 오른쪽 데이터베이스는 이 요청의 존재조차 모릅니다. 1밀리초 걸렸습니다.
이 강의를 마치면
- cache-aside(옆에 두는 캐시)의 네 단계 절차를 순서대로 설명한다.
- 캐시 미스가 원본 조회보다 오히려 느리다는 사실과, 그럼에도 전체가 빨라지는 이유를 계산한다.
- 여러 서버가 캐시 하나를 공유해야 하는 이유를 설명한다.
- 빈 캐시로 시작할 때(cold start) 벌어지는 일과 그 대비책을 설명한다.
캐시를 "어디에" 두는가
앞 강의에서 우리는 캐시를 넣으면 데이터베이스가 산다는 것을 봤습니다. 이번에는 그 캐시가 실제로 어떻게 동작하는지 봅니다.
가장 널리 쓰이는 방식의 이름은 cache-aside, 우리말로 옮기면 "옆에 두는 캐시"입니다. 이름이 곧 설명입니다. 캐시는 데이터베이스로 가는 길목을 막고 서 있는 게 아니라, 옆에 놓인 메모지입니다. 물어볼지 말지는 애플리케이션이 정합니다.
네 단계
1. 캐시에 물어본다 "상품 42번 있어?"
2. 있으면(HIT) → 그걸 준다. 끝.
3. 없으면(MISS) → 데이터베이스에 물어본다
4. 받은 답을 캐시에 적어두고 → 사용자에게 준다이게 전부입니다. 코드로도 정말 네 줄 정도입니다. 다음 사람이 상품 42번을 물으면 2번에서 끝납니다.
미스는 원래보다 느리다
여기서 사람들이 자주 놓치는 사실이 있습니다. 미스는 손해입니다.
| 걸리는 시간 | |
|---|---|
| 캐시 없이 데이터베이스 조회 | 30ms |
| 캐시 적중 | 1ms |
| 캐시 미스 | 1ms + 30ms = 31ms |
미스일 때는 캐시에 헛걸음한 1밀리초를 손해 봅니다. 그런데도 전체는 압도적으로 빨라집니다. 적중률 95%로 계산해 보면
0.95 × 1ms + 0.05 × 31ms = 0.95 + 1.55 = 2.5ms30밀리초가 2.5밀리초가 됐습니다. 적중률이 충분히 높으면 미스의 손해는 묻힙니다. 뒤집으면, 적중률이 낮은 캐시는 아무 도움도 안 되면서 복잡도만 늘립니다. 적중률 20%짜리 캐시라면 빼는 게 낫습니다.
왜 캐시는 서버 밖에 있어야 하나
11강의 세션 이야기와 똑같은 구조입니다.
서버 네 대가 각자 자기 메모리에 캐시를 들고 있다고 합시다.
- 서버 1이 상품 42번을 캐시했습니다. 그런데 다음 요청은 서버 3으로 갑니다.
서버 3은 미스입니다. 같은 항목을 네 번 캐시해야 합니다.
- 상품 42번의 가격이 바뀌면 네 곳을 다 지워야 합니다. 그럴 방법이 마땅치 않습니다.
- 서버를 하나 끄면 그 서버의 캐시가 통째로 날아가고, 그만큼 미스가 늘어납니다.
그래서 캐시도 세션처럼 밖으로 빼서 공유합니다. 모든 서버가 같은 캐시를 봅니다. 그러면 적중률이 서버 수와 무관해지고, 지우는 것도 한 번이면 됩니다.
cache-aside의 좋은 점
캐시가 죽어도 서비스는 돌아갑니다.
1번에서 캐시가 대답이 없으면 애플리케이션은 그냥 미스로 취급하고 3번으로 갑니다. 캐시는 길목을 막고 있는 게 아니라 옆에 있는 것이므로, 없어도 길은 남아 있습니다.
이건 설계상 아주 큰 장점입니다. 캐시 장애가 서비스 장애로 곧장 번지지 않습니다.
cache-aside의 나쁜 점
방금 그 문장에는 큰 별표가 붙습니다.
캐시가 죽어도 서비스는 논리적으로는 돌아간다. 다만 부하를 못 견딘다.
캐시가 비면 모든 요청이 3번으로 갑니다. 앞 강의에서 봤듯이 그건 데이터베이스에 초당 150건이 통째로 쏟아진다는 뜻입니다. 데이터베이스는 66건이 한계입니다.
그래서 실무에서는 이런 대비를 합니다.
- 캐시를 한 대만 두지 않는다. 캐시 자체를 이중화합니다.
- 미리 채워 둔다. 배포 전에 인기 항목을 미리 넣어 두는 것(warm-up)입니다.
- 동시 미스를 합친다. 같은 항목에 대한 미스가 100개 동시에 나면 데이터베이스에
100번 묻지 말고 한 번만 묻고 나머지는 그 답을 나눠 씁니다. 이 시뮬레이터의 캐시는 이 동작을 기본으로 켜 두고 있습니다.
마지막 항목이 없으면 캐시가 비는 순간 데이터베이스가 즉사합니다. 이걸 cache stampede(캐시 쇄도)라고 부릅니다.
직접 해보세요
아래 과제는 지금까지 배운 것을 한 번에 씁니다. 서버도 모자라고 데이터베이스도 모자랍니다. 서버는 늘려서 풀고, 데이터베이스는 캐시로 지키세요. 두 병목의 처방이 다르다는 것이 이 과제의 요점입니다.
이 강의가 단순화한 것
교육용 모형은 언제나 무언가를 생략합니다. 무엇을 생략했는지 아는 것도 학습의 일부입니다.
전제한 것
- 이 시뮬레이터의 캐시는 동시 미스 합치기(coalescing)를 기본으로 켜 두고 있다. 끄면 캐시가 비는 순간 데이터베이스가 즉시 무너진다.
- 캐시의 유효 시간이 실행 시간보다 길어서 이 시뮬레이션 동안 만료가 일어나지 않는다.
- 캐시 자체는 죽지 않고 용량도 충분하다고 본다. 실제로는 캐시도 이중화 대상이다.
- 이 과제의 트래픽은 전부 읽기다. 쓰기가 섞이면 무효화 문제가 생기며 뒤의 강의에서 다룬다.
다루지 않은 것
- read-through 캐시(캐시가 스스로 원본을 조회하는 방식)와의 비교
- 직렬화 형식과 캐시 키 설계
- 캐시 키의 네임스페이스 관리와 버전 태그
- 네거티브 캐싱(없다는 사실을 캐시하기)
더 읽을거리: Scaling Memcache at Facebook · AWS — Caching Best Practices
이제 직접 만들어 보세요
읽어서 아는 것과 만들어서 아는 것은 다릅니다. 컴포넌트를 배치하고 트래픽을 흘려서 실제로 동작하는지 확인해 보세요.
이 강의에 물어보기
하루 5번이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.