캐시: 아이디어의 반복

Seoki·2026년 3월 12일

캐시를 처음 만난 건 싱글톤 패턴 글에서 volatile을 공부할 때였다.
한쪽이 바뀌었는데, 다른 쪽은 아직 그 변화를 모를 수 있다는 걸 알았다.

그런데 로컬 캐시에서 같은 일이 벌어졌다.
Redis에서도 같은 일이 벌어졌다.
전혀 다른 기술인데, 어디선가 본 것 같은 구조가 계속 나왔다.

계층만 바뀌고, 구조는 똑같았다.
캐시는 아이디어의 반복이었다.


원본은 느리다

속도의 비밀은 단순하다. 가까우면 빠르다.

CPU는 메모리에 접근할 때 캐시처럼 가까운 저장소를 먼저 확인한다.
애플리케이션도 비슷하다. 프로세스 메모리 안의 로컬 캐시를 보고,
그다음 Redis를 보고, 마지막에 DB를 본다.

이유는 같다. 원본을 매번 직접 읽는 건 느리기 때문이다.

그러면 전부 가까운 곳에 두면 되지 않나?
안 된다. 빠른 저장소는 작고, 큰 저장소는 느리다.

그래서 계층을 만든다.
작고 빠른 걸 가까이 두고, 크고 느린 걸 멀리 둔다.

[ CPU 세계 ]
L1 캐시      ~1ns     코어 전용, 수십 KB
L2 캐시      ~4ns     코어 전용, 수백 KB
L3 캐시      ~10ns    전 코어 공유, 수 MB
RAM         ~100ns   전체 시스템 공유

[ 애플리케이션 세계 ]
로컬 캐시 (Caffeine)  ~0.1ms   서버 전용, JVM 힙
Remote 캐시 (Redis)  ~1ms     전 서버 공유, 네트워크 1홉
데이터베이스 (MySQL)   ~10ms    전체 시스템 공유, 디스크/네트워크

L1 대비 RAM이 100배 느린 것처럼, 로컬 캐시 대비 DB는 100~500배 느리다.
비율이 거의 같다. 해법도 닮아 있다.

원본을 매번 보러 가는 건 비싸니까, 복사본을 가까이 둔다.
이게 캐시의 출발점이다.

그리고 여기서 하나 더 눈여겨볼 게 있다.
두 세계 모두 "전용 vs 공유" 구분이 있다.

CPU에서 L1은 각 코어가 개별적으로 갖고 있고, L3는 모든 코어가 공유한다.
애플리케이션에서 Caffeine은 각 서버 JVM이 개별적으로 갖고 있고, Redis는 모든 서버가 공유한다.

바로 이 "전용 저장소가 있다"는 게 성능의 핵심이자 문제의 시작이다.


복사본의 함정

여기까지 보면 캐시는 거의 마법 같다.
가까이 두면 빠르고, 계층만 잘 나누면 된다.

대가 없는 마법이 있을까

캐시(cache)의 어원은 프랑스어 cacher, "숨기다"라는 뜻이다.
17세기 사냥꾼들은 원정 중 물자를 중간 지점에 숨겨뒀다.
기지까지 돌아가는 건 너무 멀었기 때문이다.

원본에 매번 가는 건 비싸니까, 복사본을 가까이 숨겨둔다.
300년 전에도 지금도, 이유는 같다.

하지만 숨겨둔 것은 변화를 모른다.
기지에서 뭐가 바뀌어도, 땅에 묻힌 물자는 그대로다.
원본이 바뀌었을 때, 복사본은 그 사실을 모를 수 있다.

이 문제는 계층이 달라져도 반복된다.


1층 - CPU 캐시

여러 코어는 각자 가까운 캐시를 사용한다.

코어 A: x = 10 을 보고 있다
코어 B: x = 10 을 보고 있다

코어 A: x = 20 으로 변경
코어 B: 아직 10 으로 볼 수 있다

한 주체가 값을 바꿨는데, 다른 주체는 아직 그 변화를 못 볼 수 있다.
하드웨어는 이 문제를 cache coherence protocol로 관리한다.

대표적으로 MESI 프로토콜이 작동하는 방식은 이렇다.

- 이해를 돕기 위해 MESI의 대표적 흐름으로 단순화했다

MESI = Modified, Exclusive, Shared, Invalid

코어 A가 x를 수정하는 순간:
  1. 버스에 "나 x 바꿨다" 시그널을 보낸다
  2. 코어 B의 L1에 있는 x가 Invalid 상태로 바뀐다
  3. 코어 B가 x를 읽으려 하면 Invalid → 캐시 미스
  4. 코어 A의 최신 값을 가져온다

즉, 아래 계층에서는 복사본 간의 일관성을 하드웨어가 상당 부분 관리한다.

하지만 하드웨어의 관리가 모든 걸 보장하지는 않는다.
애플리케이션 레벨로 올라오면, 스레드 간 가시성과 재정렬처럼 하드웨어만으로 해결되지 않는 문제를 만난다.
Java의 volatile이 존재하는 이유다.

복사본이 생기면, 누군가는 일관성을 지켜야 한다.


2층 - 로컬 캐시

로컬 캐시는 빠르다.
애플리케이션 프로세스 안에 있으니 네트워크 비용도 없다.

하지만 서버가 여러 대가 되면 문제가 생긴다.

서버 A: 상품 가격 = 10,000원
서버 B: 상품 가격 = 10,000원

관리자가 DB에서 가격을 15,000원으로 변경

서버 A: 여전히 10,000원
서버 B: 여전히 10,000원

이건 CPU 캐시에서 보던 구조와 닮아 있다.
차이는 하나다.

CPU에서는 하드웨어가 일관성을 관리한다.
로컬 캐시에서는 아무도 자동으로 관리해주지 않는다.

CPU의 MESI가 시그널을 보내서 다른 코어의 캐시를 무효화했다면,
애플리케이션에서는 메시지를 보내서 다른 서버의 캐시를 무효화해야 한다.

TTL이라는 보험이 필요한 이유도 여기에 있다.
아래 계층에서 시스템이 대신하던 일이, 위 계층으로 오면 개발자의 책임이 된다.


3층 - Redis

Redis를 쓰면 로컬 캐시처럼 서버마다 따로 다른 값을 들고 있는 문제는 줄어든다.
여러 서버가 같은 외부 캐시를 보기 때문이다.

하지만 문제가 전부 사라지는 건 아니다.
이번에는 Redis와 DB 사이가 어긋난다.

1. 애플리케이션 → Redis 조회 → 10,000원
2. 관리자 → DB에서 15,000원으로 변경
3. 애플리케이션 → Redis 조회 → 여전히 10,000원

로컬 캐시에서는 "서버마다 복사본이 따로 있다"가 문제였다면,
Redis에서는 "공유된 복사본이 원본보다 늦다"가 문제가 된다.

구조는 또 같다.

원본은 바뀌었는데, 복사본은 아직 그걸 모른다.


반복된 건 기술이 아닌 구조

CPU 캐시:  한 주체가 바꿨는데 다른 주체는 아직 못 본다
로컬 캐시:  원본이 바뀌었는데 서버는 아직 못 본다
Redis:    원본이 바뀌었는데 캐시는 아직 못 본다

CPU 캐시, 로컬 캐시, Redis 같은 기술이 반복된 게 아니었다.

반복된 건 구조였다.

  1. 원본은 느리다
  2. 그래서 가까운 곳에 복사본을 둔다
  3. 그 순간 복사본의 원본과 같다는 보장은 사라진다
  4. 그래서 복사본의 최신성(freshness) 을 감당할 장치가 필요해진다

계층만 바뀌고, 구조는 똑같았다.


최신성의 대가

복사본이 원본과 어긋나는 건 피할 수 없다.
문제는 그 어긋남을 얼마나 오래 허용할 것인가다.

가장 단순한 감당은 TTL. 시간이 지나면 버리는 것이다.
단순하고 운영이 쉽다. 대신 만료 전까지는 옛날 값을 보여준다.

원본이 바뀌는 순간, 캐시를 바로 반영하고 싶다면?

대표적으로 두 가지 선택지가 있다.
복사본을 갱신하거나, 삭제하거나.

직관적으로는 갱신이 나아 보인다.
최신 값을 바로 넣으면 다음 조회가 미스 없이 읽을 수 있으니까.

하지만 두 요청이 거의 동시에 원본을 갱신하는 상황을 생각해보자.

삭제는 순서가 상관없다.
누가 먼저 삭제하든 결과는 같다.
캐시가 비어있으니 다음 조회가 원본에서 최신 값을 가져온다.

이게 Cache Aside에서 갱신보다 삭제/무효화가 선호되는 이유다.

CPU에서도 같은 갈림길이 있었다. 대표적으로 MESI는 무효화 쪽을 택했다.

복사본을 믿지 않는 것이 가장 안전하다는 판단은, 계층이 달라져도 반복된다.

최신성을 더 높이려는 시도도 닮아 있다.

같은 구조, 다른 전달 수단.
네트워크 위에서는 대가가 커진다.

감당하다 보면 생기는 문제까지 닮아 있다.

물론 이 둘이 정확히 같은 문제는 아니다.
원인도, 규모도, 해결 수단도 다르다.
하지만 관련 없는 데이터가 같은 단위에 묶이면 불필요한 비용이 생기고,
여러 주체가 같은 대상에 몰리면 중재가 필요해지는 구조는 닮아 있다.

결국 방향은 하나다.

복사본을 오래 믿으면 단순해진다.
정확도를 높일수록 무효화와 동기화 비용이 커진다.

어긋남만 반복되는 게 아니었다.
그 어긋남을 감당하는 방법도, 감당하다 생기는 문제도 반복되고 있었다.


규모가 1000배 커지면

CPU에서 애플리케이션으로 올라와도 구조가 반복되는 걸 봤다.
그러면 규모가 1000배 커지면 어떨까.
초당 수억 읽기, 수천~수만 대 노드, 여러 지역에 퍼진 복사본.

새로운 아이디어가 필요할까.

Uber는 초당 1.5억 읽기를 Redis 캐시로 처리한다.
문제는 TTL만으로는 stale 데이터가 너무 오래 남았다는 거다.
그래서 Flux라는 시스템이 MySQL binlog를 비동기로 감시하고,
DB가 바뀌는 순간 캐시를 무효화한다.

원본이 바뀌면, 전달 장치가 감지하고, 복사본을 무효화한다.
MESI가 버스에 시그널을 보내서 다른 코어의 캐시를 무효화하는 것과 닮아 있다.

Netflix의 EVCache는 22,000대 서버가 글로벌 멀티 리전에 흩어져 있다.
문제는 한 리전에서 캐시가 바뀌어도 다른 리전은 모른다는 거다.
그래서 리전 간 캐시를 복제하고 무효화하는 경로를 만든다.

MESI가 같은 칩 안에서 버스 시그널로 코어 간 캐시를 무효화했다면,
Netflix는 대륙을 넘어 리전 간 복사본을 동기화한다.
전달 거리만 달라졌을 뿐, 구조는 닮아 있다.

규모가 폭발적으로 커져도, 새로운 본질이 생기는 건 아니다.
같은 문제를 더 먼 거리와 더 큰 실패 비용 위에서 다루게 될 뿐이다.

차이는 여기서 나온다.
하드웨어에서는 신호 전달이 빠르고 강하게 통제된다.
네트워크 위에서는 지연이 생기고, 유실을 막아야 하고, 순서를 고민해야 하고, 부분 장애도 감당해야 한다.

하지만 감당해야 하는 것의 정체는 변하지 않는다.
복사본의 최신성이다.


아이디어는 반복된다

나노초 단위 CPU 캐시에서 벌어지던 일이, 밀리초 단위의 애플리케이션에서 반복됐고, 초 단위의 글로벌 시스템에서 또 반복됐다.

확대해도 같았다. 축소해도 같았다.

원본은 느리고 그래서 더 가까운 곳에 복사본을 둔다.
그 순간부터 복사본의 최신성은 공짜가 아니게 된다.
CPU에서 먼저 발생했고 같은 해법이 애플리케이션으로 올라왔다.

캐시는 아이디어의 반복이었다.


기술이 숨기고 있는 레이어를 하나씩 벗겨냈다.
CPU 캐시, 로컬 캐시, Redis, Cache Aside, Write Through ...
마지막에 남은 건 하나의 아이디어였다.

새로운 기술이 나오면 이름에 먼저 눈이 간다.
이름이 다르면 다른 기술이라고 생각하기 쉽다.

이제 먼저 묻는다.
이건 어떤 구조로 생겼는가
어디서 본 적 있는 구조인가

새로운 캐시 전략을 만나면, 나는 이렇게 물을 것이다.

"이 구조는 복사본의 최신성을 어떻게 감당하는가?"


참고

profile
물이 없어도 노 젓기

4개의 댓글

comment-user-thumbnail
2026년 3월 12일

"계층이나 환경은 다를지라도, 내부에 구현되어있는 구조적 본질은 같다"를 점을 파악하는 과정이 재밌었습니다.
seoki님처럼 이렇게 본질을 먼저 고민하는 습관을 들인다면, 나중에 완전히 새로운 기술을 마주하더라도 훨씬 수월하게 접근할 수 있을 것 같아요!!!! 잘 읽었씁니다. 저 개인적으로 RT 드립니다!!!!

답글 달기
comment-user-thumbnail
2026년 3월 12일

글 잘 읽었습니다.
특히 무효화 시그널과 Redis의 캐시 무효화가 구조적으로 닮아있다는 관찰이 인상 깊었습니다!
MESI가 무효화를 택한 이유와 Cache-Aside에서 갱신보다 삭제가 선호되는 이유가 같은 구조라는 연결이 좋았습니다.

저도 Redis 캐시 설계하면서 갱신이냐 삭제냐 고민했는데, 동시 갱신 시 순서 꼬이는 거 때문에 삭제를 택했거든요. 그때는 그냥 실용적 판단이었는데, 글 읽고 보니 MESI도 같은 이유였던거 같습니다!
장바구니에 캐시를 안 건 것도, 지금 생각하면 "이 복사본의 최신성을 감당할 수 있나?"에 대한 답이 아니오였던 거고요. 그리고 새로운 캐시 전략을 만나면 "이 구조는 복사본의 최신성을 어떻게 감당하는가?"를 묻겠다는 마지막 문장이 좋았습니다! 저도 seoki님 처럼 앞으로 그 질문을 먼저 던져보겠습니다!
좋은 글 공유해주셔서 감사합니다.

답글 달기
comment-user-thumbnail
2026년 3월 17일

글의 논리가 명확해서 너무 잘 읽힙니다!

답글 달기
comment-user-thumbnail
2026년 3월 17일

글의 논리가 명확해서 너무 잘 읽힙니다!

답글 달기