Cloud Modernization 17

캐싱 전략 이해: 성능 계층과 데이터 갱신 흐름

캐시를 빠른 저장소 정도로만 보면 무엇을 캐시하고 언제 갱신해야 하는지 판단하기 어렵다.

근거 · 교안 p67-p71

캐싱 아키텍처와 Lazy Loading, Write-through 전략 ?? ???

1장: 캐시, 단순한 빠르기를 넘어: 성능 계층의 의미

1. 캐시, 단순한 빠르기를 넘어: 성능 계층의 의미

솔라의 시선은 모니터 속 아키텍처 다이어그램의 한 상자에 머물러 있었다. ‘Cache’라고 적힌 네모. 그 옆에는 짧은 설명이 붙어 있었다.

“캐싱 아키텍처는 애플리케이션 서버와 데이터베이스 사이에 캐시 클러스터를 두어 자주 사용하는 데이터를 빠르게 가져오도록 한다.”

솔라는 의자 등받이에 몸을 기댔다. “음, 생각보다 간단하네.” 혼잣말이었다. 애플리케이션이 무언가 필요할 때마다 멀리 있는 데이터베이스까지 가는 건 번거롭다. 그러니 자주 쓰는 물건은 가까운 서랍에 넣어두는 것처럼, 자주 쓰는 데이터를 더 빠른 임시 저장소에 두는 것. 그게 캐시였다. 그냥 빠른 길, 지름길 같은 거구나. 솔라는 그렇게 결론 내리고 고개를 끄덕였다.

“언니, 캐시라는 거, 그냥 빠른 DB라고 생각하면 되는 거 맞지?”

거실에서 책장을 정리하던 루나가 솔라의 목소리에 뒤를 돌아보았다. 솔라는 자신의 발견이 자랑스럽다는 듯 말을 이었다.

“매번 저 멀리 있는 창고(데이터베이스)까지 가지 않고, 바로 옆에 있는 작은 선반(캐시)에서 데이터를 꺼내 쓰는 거잖아. 훨씬 빠르니까. 당연히 성능이 좋아지는 거고.”

루나는 잠시 책을 정리하던 손을 멈추고 솔라를 바라보았다. “빠르다는 건 맞아. 하지만 ‘어떻게’ 빨라지는지에 대해 생각해 보면 조금 다른 그림이 보일 거야.”

루나는 솔라를 책장 앞으로 불렀다. “솔라, 네가 저기 방 끝에 있는 책장에서 ‘푸른 용의 전설’이라는 책을 가져와야 한다고 해보자. 지금 한번 가져와 볼래?”

솔라는 영문도 모른 채 자리에서 일어나 방 끝으로 걸어갔다. 여러 책 사이에서 ‘푸른 용의 전설’을 찾아 꺼내 들고 루나에게 돌아왔다. 꽤나 번거로운 과정이었다.

“자, 이제 그 책을 다시 제자리에 꽂아두고, 내가 다시 달라고 하면 또 가져와야 해. 매번 똑같은 시간이 걸리겠지?”

“당연하지. 길이나 책장 위치가 바뀌는 게 아니니까.” 솔라가 대답했다.

“좋아. 그럼 이번에는 책을 가져온 뒤에, 여기 바로 옆 작은 탁자 위에 올려둬.” 루나가 거실 중앙의 작은 탁자를 가리켰다. “그리고 내가 다시 ‘푸른 용의 전설’을 달라고 하면, 어디로 갈 거야?”

“여기 탁자 위에서 바로 집으면 되지. 책장까지 갈 필요 없이.”

솔라의 대답에 루나는 고개를 끄덕였다. “바로 그거야. 결과적으로 책을 훨씬 빨리 가져왔지. 하지만 여기서 중요한 건 탁자가 책장보다 ‘빨라서’가 아니야.”

“응? 그래도 더 가까우니까 빠른 거잖아.” 솔라가 반문했다.

“맞아. 하지만 근본적인 변화는 다른 데 있어.” 루나는 종이 한 장을 가져와 간단한 그림을 그리기 시작했다. 첫 번째 그림에는 ‘솔라’와 ‘책장’만 있고, 둘을 잇는 화살표가 그려져 있었다. 요청 → 책장(DB)

“이게 처음 네가 움직인 경로야. 책을 달라는 요청이 오면 무조건 책장으로 가는, 단 하나의 길이지.”

루나는 그 아래에 두 번째 그림을 그렸다. 이번에는 ‘솔라’와 ‘탁자’, 그리고 ‘책장’이 있었다. 화살표는 ‘솔라’에서 ‘탁자’로 먼저 향했다.

“하지만 탁자를 사용하면서부터 네 동선이 바뀌었어. 일단 탁자부터 확인하잖아. 요청 → 탁자(캐시). 책이 탁자에 있으면 거기서 끝나. 만약 없으면, 그때야 비로소 책장으로 가지. 탁자에 없음 → 책장(DB). 캐시가 한 일은 단순히 빠른 공간을 제공한 게 아니라, 데이터에 접근하는 경로 자체를 바꾼 거야.

솔라는 두 그림을 번갈아 보았다. 화살표의 흐름이 명확하게 달랐다. 첫 번째는 직선 도로였지만, 두 번째는 중간에 갈림길이 있는 새로운 경로였다. 탁자라는 존재가 새로운 길을 만들어낸 것이다.

“아…! 그러니까 캐시는 그냥 ‘빠른 저장소’라는 물건이 아니라, 데이터로 가는 길을 새로 설계하는 ‘성능을 위한 계층’ 같은 거구나. 요청을 중간에 가로채서 ‘여기서 처리할 수 있으면 하고, 안 되면 원래 길로 보내줄게’라고 말하는 중간 관리자처럼.”

솔라의 눈이 반짝였다. ‘빠르다’는 현상에만 집중했던 시야가 ‘경로를 바꾼다’는 구조적인 설계로 옮겨가는 순간이었다. 캐시를 도입한다는 건, 시스템의 데이터 흐름도에 새로운 분기점을 만드는 행위였다.

“맞아. 그래서 ‘어떤 데이터를 캐시에 둘 것인가’라는 질문이 중요해지는 거야. 그건 곧, 이 새로운 지름길을 어떤 데이터에게 허용할 것인지 결정하는 규칙을 만드는 일이니까.”

솔라는 다시 모니터 속 다이어그램을 보았다. ‘Cache’라고 적힌 상자가 이제 다르게 보였다. 서버와 데이터베이스 사이에 놓인 단순한 상자가 아니었다. 모든 데이터 요청이 가장 먼저 거쳐가는 첫 번째 관문. 데이터베이스로 향하는 길목을 지키며 전체 시스템의 흐름을 조율하는 성능의 조절판이었다.

“그럼… 이제 알겠어. 캐시는 데이터 접근 경로를 바꾸는 성능 계층이라는 거. 그래서 ‘자주 사용하는 데이터’를 이 새로운 빠른 길로 다니게 해서 전체 속도를 높이는 거구나.”

솔라는 잠시 생각에 잠겼다. 한 가지 의문이 꼬리를 물고 떠올랐다.

“그런데 언니, 이 새로운 길이 항상 좋은 것만은 아닐 것 같아. 만약 책장에서 누군가 ‘푸른 용의 전설’ 내용을 새로 고쳐 썼는데, 내 탁자 위에는 아직 옛날 책이 그대로 있으면 어떡하지? 언제 이 탁자 위 책을 새것으로 바꿔줘야 하는 거야?”

2장: 읽기 전략의 핵심: 요청 시 필요한 데이터만 가져오는 Lazy Loading

루나는 솔라의 질문에 말없이 행동으로 답했다. 솔라가 가져다 놓은 ‘푸른 용의 전설’ 책을 탁자 위에서 집어 들더니, 다시 방 끝에 있는 책장에 꽂아 넣었다. 탁자는 다시 텅 비었다. 지름길이 사라져 버렸다.

솔라는 언니의 행동을 멍하니 바라보았다. “어? 왜 다시 갖다 놔? 그럼 매번 책장까지 가야 하잖아. 캐시를 쓰는 의미가 없잖아.” 그녀의 목소리에는 당혹감이 묻어났다. 원래 데이터베이스의 책 내용이 바뀌면 탁자 위의 책을 어떻게 할지 물었는데, 언니는 아예 탁자를 비워버렸다. 이것은 문제의 해결이 아니라 문제 자체를 없애버리는 것처럼 보였다.

“네 질문에 대한 답을 찾아보는 과정이야.” 루나는 차분하게 말했다. “‘언제 이 탁자 위 책을 새것으로 바꿔줘야 하는가?’라고 물었지. 그건 캐시 데이터를 어떻게 최신으로 유지할 것인가, 즉 ‘갱신’에 대한 질문이야. 그 전략 중 하나를 지금부터 네가 직접 해보는 거야.”

루나는 소파에 앉으며 솔라에게 첫 번째 요청을 했다. “솔라, ‘은빛 늑대의 노래’라는 책 좀 가져다줄래?”

솔라는 잠시 망설였다. 탁자는 비어 있었다. 선택지는 하나뿐이었다. 그녀는 자리에서 일어나 방 끝 책장으로 향했다. 책을 찾아 돌아오는 길은 여전히 번거로웠다. 솔라는 책을 루나에게 건네주지 않고, 먼저 작은 탁자 위에 올려놓았다. 그러고 나서야 루나에게 책을 밀어주었다.

“좋아. 다음은 ‘푸른 용의 전설’을 가져다줘.”

이번엔 달랐다. ‘푸른 용의 전설’은 아직 탁자 위에 없었다. 솔라는 다시 책장으로 걸어가 책을 찾아왔고, 이번에도 탁자 위에 책을 먼저 올려둔 뒤에야 루나에게 건넸다. 탁자 위에는 이제 두 권의 책이 놓였다.

“마지막으로, ‘은빛 늑대의 노래’ 다시 한번 줘.”

솔라는 의자에서 엉덩이만 살짝 떼어 탁자 위의 책을 집어 루나에게 건넸다. 책장까지 갈 필요가 없었다. 1초도 걸리지 않았다.

루나는 책을 받으며 물었다. “방금 네가 한 행동에 규칙이 있었어. 알아챘니?”

“규칙?” 솔라는 고개를 갸웃거렸다. “그냥 요청한 책을 가져다준 것뿐인데. 아, 탁자 위에 없으면 책장에서 가져오고, 가져온 건 일단 탁자 위에 뒀어. 다음에 또 찾을 때 편하려고.”

“바로 그거야. ‘탁자 위에 없으면’이라는 조건. 그게 핵심이야.” 루나는 종이에 어제 그렸던 경로 그림을 다시 그리기 시작했다. 이번에는 좀 더 상세했다.

  1. 요청: '은빛 늑대의 노래'
  2. → 탁자(캐시) 확인: 비어 있음 (Cache Miss)
  3. → 책장(DB)으로 이동
  4. → '은빛 늑대의 노래' 가져오기
  5. → 탁자(캐시)에 책 저장
  6. → 솔라에게 책 전달

“이게 네가 첫 번째 책을 가져온 경로야. 탁자에 책이 없는 상황, 이걸 ‘캐시 미스(Cache Miss)’라고 불러. 이때 넌 책장에서 데이터를 가져와서 캐시, 즉 탁자를 채웠지. 그리고 요청에 응답했어.”

루나는 두 번째 요청의 경로도 그렸다. 과정은 똑같았다. 하지만 세 번째 요청은 달랐다.

  1. 요청: '은빛 늑대의 노래'
  2. → 탁자(캐시) 확인: 책 있음 (Cache Hit)
  3. → 즉시 책 전달

“세 번째 요청에서는 책장까지 갈 필요가 없었지. ‘캐시 히트(Cache Hit)’가 일어났으니까. 이 모든 과정에서 중요한 건, 네가 책을 탁자 위에 올려놓는 시점이야. 넌 요청을 받은 ‘후에’, 그리고 탁자에 책이 ‘없을 때만’ 책장에서 책을 가져와 탁자를 채웠어.”

솔라의 눈이 동그래졌다. “그러고 보니 그렇네! 나는 미리 모든 책을 탁자에 가져다 놓지 않았어. 필요하다고 할 때, 게으름 피우다가 마지못해 움직이는 것처럼….”

“맞아. 그래서 이 전략의 이름이 ‘게으른 로딩’, 즉 Lazy Loading이야.”

그 단어를 듣는 순간, 모든 조각이 맞춰졌다. 캐시를 갱신하는 건, 무작정 최신 데이터를 덮어쓰는 행위가 아니었다. Lazy Loading은 ‘읽기’ 요청에 대한 아주 구체적이고 효율적인 대응 전략이었다. 처음 요청이 왔을 때만 진짜 저장소인 데이터베이스에 접근하고, 그 결과를 캐시에 저장해둔다. 그러면 다음번 똑같은 요청부터는 캐시가 빠르게 응답할 수 있다. 데이터베이스의 부하를 줄이고, 응답 속도를 높이는 아주 영리한 방법이었다.

“아! 그럼 내가 처음에 했던 질문, ‘책장 내용이 바뀌었는데 탁자 위 책은 옛날 거면 어떡하냐’는 문제는… Lazy Loading 방식에선 바로 해결되진 않겠네. 일단 탁자에 있는 걸 먼저 줄 테니까.”

“정확해. Lazy Loading은 읽기가 아주 많은 서비스에 효과적이야. 자주 바뀌지 않는 데이터를 빠르게 제공하는 데 최적화되어 있지. 하지만 데이터가 바뀌었을 때 최신 정보를 즉시 반영하지는 못할 수도 있다는 단점이 있어. 그건 또 다른 전략이 필요한 문제지.”

솔라는 탁자 위에 놓인 두 권의 책과 책장을 번갈아 보았다. 이제 그녀는 ‘캐시 갱신’이라는 막연한 개념 대신 ‘Lazy Loading’이라는 구체적인 도구를 손에 쥐었다. 읽기 요청이 들어왔을 때, 데이터가 캐시에 없다면 그때 비로소 데이터베이스에서 가져와 캐시를 채운다. 이 간단한 규칙 하나가 데이터 접근 흐름을 얼마나 효율적으로 바꾸는지 직접 경험한 것이다.

솔라는 잠시 생각에 잠겼다. 읽기 흐름은 이제 명확해졌다. 하지만 자연스럽게 다음 질문이 떠올랐다.

“알겠어, 언니. 읽기 요청이 많을 땐 이렇게 ‘게으르게’ 데이터를 채우는 게 효율적이겠네. 그런데 만약에 내가 작가라서 책 내용을 새로 ‘쓰는’ 상황이라면 어떡하지? 예를 들어, ‘푸른 용의 전설’의 결말을 새로 써서 저장해야 한다면 말이야. 그때는 책장과 탁자, 어디에 먼저 알려줘야 해?”

3장: 쓰기 전략의 핵심: DB와 캐시를 함께 갱신하는 Write-through

3. 쓰기 전략의 핵심: DB와 캐시를 함께 갱신하는 Write-through

루나는 대답 대신, 솔라의 책상에서 펜 한 자루와 깨끗한 메모지 한 장을 가져왔다. 이전까지 데이터를 ‘읽던’ 상황을 정리하듯, 그녀는 탁자 위에 놓인 ‘푸른 용의 전설’ 책 옆에 메모지와 펜을 나란히 놓았다. 책은 더 이상 단순한 읽기 대상이 아니었다. 빈 메모지와 펜이 옆에 놓이자, 이제는 새로운 내용이 쓰이기를 기다리는 원고처럼 보였다.

이 작은 변화만으로 솔라의 역할은 데이터를 ‘읽어오는’ 소비자에서 데이터를 ‘만들어내는’ 창작자로 바뀌었다. 이전 장에서 남았던, 데이터를 ‘쓸’ 때에 대한 질문이 눈앞의 구체적인 사물들로 재구성된 것이다. 루나가 입을 열었다. “네가 작가가 되어 새로운 결말을 ‘쓴다’고 가정해 보자. 이 종이에 아주 짧게 써봐.”

솔라는 잠시 고민하더니 펜을 들어 몇 문장을 적어 내려갔다. 용이 공주를 구하는 대신, 둘이 함께 새로운 왕국을 건설한다는 내용이었다. 만족스러운 미소를 지으며 솔라가 펜을 놓았다.

루나가 물었다. “좋아. 그 새로운 결말, 즉 새로운 데이터를 이제 저장해야 해. 어디에 보관할 거지? 진짜 원본이 있는 저기 책장에? 아니면 지름길인 이 탁자에?”

솔라는 망설였다. 너무나 당연한 질문 같았지만, 답은 명확하지 않았다.

“음… 탁자(캐시)에만 저장하면, 책장(데이터베이스)의 원본과 내용이 달라져 버려. 그럼 나중에 다른 사람이 책장에서 원본을 읽으면 옛날 결말을 보게 되잖아. 데이터가 사라진 거나 마찬가지네.” 그녀는 잠시 말을 멈추고 다른 가능성을 생각했다. “그럼 책장에만 저장하면? 탁자에 있는 책은 여전히 옛날 결말이겠지. 그럼 다음 사람이 탁자에서 책을 집으면 옛날 내용을 보게 될 거고… 캐시를 쓰는 의미가 퇴색되네. 결국… 둘 다 바꿔야 하는 거 아닐까? 그런데 두 군데에 동시에 저장하려면 시간도 두 배로 걸리고 번거로울 것 같은데…”

솔라의 고민은 ‘쓰기’ 작업이 ‘읽기’와는 전혀 다른 문제라는 사실을 보여주고 있었다. 읽기는 데이터가 있다는 전제하에 어떻게 효율적으로 가져올지의 문제지만, 쓰기는 원본 데이터 자체를 변경하고 그 변경 사항을 어떻게 모든 곳에 일관되게 전파할지의 문제였다.

루나는 솔라의 고민을 지켜보다가, 작은 포스트잇 두 장을 가져와 각각 ‘책장 저장’, ‘탁자 저장’이라고 적었다. 그리고 솔라가 쓴 새 결말 메모지 옆에 나란히 붙였다.

“이제부터 규칙을 하나 만들자. ‘저장’이라는 행동은 이 두 버튼을 ‘항상 함께’ 누르는 거야. 하나만 누르는 건 허용되지 않아.”

솔라는 그 규칙의 의미를 되새기며 고개를 끄덕였다. 그녀는 새로 쓴 결말 메모지를 들고 일어섰다. 먼저 책장으로 걸어가 ‘푸른 용의 전설’ 책갈피에 새 메모지를 끼워 넣었다. (데이터베이스 업데이트) 그리고 자리로 돌아와, 탁자 위에 있던 기존의 ‘푸른 용의 전설’ 책 위에도 새 결말 메모지 사본을 올려두었다. (캐시 업데이트) 두 작업이 모두 끝나자, 비로소 ‘저장 완료’라고 말할 수 있었다.

루나는 그 과정을 종이에 그렸다.

  1. 쓰기 요청: '새 결말 저장'
  2. → 책장(DB)에 새 결말 저장
  3. → 탁자(캐시)에 새 결말 저장
  4. → 저장 성공 응답

“방금 네가 한 행동의 가장 큰 장점이 뭘까?”

“음… 언제나 최신 정보가 보장된다는 거?” 솔라가 답했다. “이제 누가 책을 찾든, 책장에서 꺼내든 탁자에서 집든 무조건 새로운 결말을 읽게 될 거야. 데이터가 일치하니까.”

“정확해. 그럼 단점은?”

“쓰는 데 시간이 좀 더 걸린다는 거. 책장에도 저장하고, 탁자에도 저장해야 하니까. 두 작업이 모두 끝날 때까지 기다려야 하잖아. 만약 책장에 저장하는 데 시간이 오래 걸린다면, 전체 쓰기 작업이 느려지겠지.”

바로 그 순간, 솔라는 깨달았다. 이것 역시 하나의 ‘전략’이라는 것을. 루나가 말했다. “그게 바로 Write-through(쓰기 관통) 전략이야. 이름 그대로, 캐시를 ‘관통해서’ 데이터베이스까지 데이터를 곧장 써넣는 방식이지. 모든 쓰기 작업이 캐시와 데이터베이스에 동시에 반영돼.”

“아… 캐시를 거쳐서 데이터베이스까지 쭉 쓰는 거구나.”

Write-through. 데이터를 쓸 때 캐시와 데이터베이스의 일관성을 최우선으로 생각하는, 매우 직관적이고 안정적인 패턴이었다. 쓰기 속도가 조금 희생되더라도, 데이터가 절대 구버전으로 읽히는 일이 없도록 보장하는 강력한 규칙이었다.

이제 솔라의 머릿속에는 두 개의 확실한 도구가 자리 잡았다. 읽기 요청이 폭주하고 데이터 변경이 잦지 않은 곳에는 Lazy Loading. 쓰기 작업 후 즉각적인 데이터 일관성이 무엇보다 중요한 곳에는 Write-through. 캐시는 더 이상 단순한 ‘빠른 저장소’가 아니었다. 데이터의 읽기/쓰기 흐름이라는 맥락에 맞춰 적절한 전략을 선택해야 하는 정교한 성능 계층이었다.

루나는 마지막으로 하얀 종이 한 장을 더 꺼내 솔라 앞에 놓았다.

“자, 마지막 문제. 네가 인기 뉴스 웹사이트를 설계한다고 상상해봐. 이 사이트에는 크게 두 가지 기능이 있어.” 루나는 종이에 간단한 도표를 그렸다.

1. 실시간 인기 뉴스 TOP 10

  • 1분에 수천 번씩 조회됨.
  • 내용은 1시간에 한 번만 업데이트됨.

2. 기사 댓글 달기

  • 사용자들이 수시로 댓글을 작성함.
  • 새 댓글은 작성 즉시 모든 사람에게 보여야 함.

“솔라, 너라면 이 두 기능에 각각 어떤 캐싱 전략을 적용할 거야? 그리고 그 이유는?”

솔라는 펜을 들었다. 더 이상 망설임은 없었다. 그녀는 ‘실시간 인기 뉴스 TOP 10’ 항목을 동그라미 치고 그 옆에 적었다.

Lazy Loading. 읽기 요청이 압도적으로 많고, 데이터가 자주 바뀌지 않으니까. 처음 조회한 사용자가 캐시를 채우고 나면, 나머지 수많은 사용자는 데이터베이스까지 갈 필요 없이 엄청나게 빠른 속도로 인기 뉴스를 볼 수 있어. 데이터 일관성이 약간 늦어도 큰 문제가 안 돼.”

그리고 그녀는 ‘기사 댓글 달기’ 항목을 동그라미 쳤다.

“여기는 Write-through. 사용자가 댓글을 썼는데 저장이 안 되거나 다른 사람이 못 보면 안 되잖아. 쓰기 속도가 약간 느려지더라도, 데이터베이스와 캐시에 동시에 기록해서 데이터의 정확성과 일관성을 보장하는 게 훨씬 중요해.”

솔라가 적은 글자는 명확했다. 그녀는 이제 단순히 캐시의 정의를 읊는 것이 아니었다. 주어진 요구사항에 맞춰 데이터의 흐름을 설계하고, 각기 다른 상황에 맞는 최적의 전략을 선택하고 있었다. 캐시라는 성능의 조절판을 어떻게 다뤄야 하는지 온전히 이해한 것이다.