본문으로 바로가기

15

캐시 — cache-aside 패턴

캐시를 넣는다는 건 코드에 네 줄을 쓴다는 뜻이다

먼저 보면 좋은 강의: 지연 시간과 처리량, 캐시는 왜 필요한가

단계별로 보기

스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.

대기 중1 / 4 단계 — 1·2단계 — 먼저 메모지를 본다 (HIT)

서버가 데이터베이스에 바로 묻지 않습니다. 먼저 캐시에 물어봅니다. "상품 42번 있어?" 있습니다. 여기서 끝입니다. 오른쪽 데이터베이스는 이 요청의 존재조차 모릅니다. 1밀리초 걸렸습니다.

캐시 적중률95.0%

이 강의를 마치면

  • 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.5ms

30밀리초가 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번

이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.