[실전 서버리스 19] 캐시 계층 설계, 엣지 캐시와 여러 층을 다루기

앞서 데이터베이스 읽기를 줄이는 캐시를 다뤘는데, 서버리스에서 캐시는 그보다 넓은 이야기다. 엣지에는 여러 종류의 캐시가 있고, 이걸 층층이 잘 설계하면 응답이 빨라지고 비용이 준다. 이번 편은 서버리스 관점에서 캐시 계층을 어떻게 설계하는지를 정리한다.


캐시는 서버리스 성능의 핵심이자, 잘못 다루면 낡은 데이터를 보여주는 함정이기도 하다. 여러 층을 이해하고 각 층을 알맞게 쓰는 게 중요하다.

1. 엣지 캐시라는 첫 번째 층

엣지 플랫폼에는 기본으로 제공되는 캐시가 있다. 사용자와 가장 가까운 엣지 위치에서 응답을 저장해두는 것이다. 같은 요청이 그 위치로 또 오면, 코드를 실행하지 않고 저장된 응답을 바로 돌려준다. 코드 실행 없이 응답하니 가장 빠르고 비용도 안 든다.


이 엣지 캐시는 정적 자산에 특히 효과적이다. 이미지나 스타일시트처럼 자주 안 바뀌는 파일은 엣지에 캐시되어, 사용자마다 그 위치에서 빠르게 받는다. 앞서 다룬 정적 자산 서빙이 이 엣지 캐시와 만나면, 자산이 전 세계 어디서나 빠르게 전달된다.


동적 응답도 엣지 캐시에 담을 수 있다. 자주 안 바뀌는 동적 응답이라면, 잠깐 캐시해두고 그동안 오는 요청에 재사용한다. 다만 동적 응답은 캐시 유효 시간을 잘 정해야 한다. 너무 길면 낡은 데이터를, 너무 짧으면 캐시 효과를 잃는다. 데이터 성격에 맞춰야 한다.


엣지 캐시가 코드 실행 없이 응답한다는 게 성능의 핵심이다. 저장된 응답을 그대로 돌려주니 가장 빠르고 CPU 시간도 안 쓴다. 사용자와 가장 가까운 곳에서 바로 응답하니 지연도 최소다. 그래서 캐시할 수 있는 건 최대한 엣지 캐시에 담는 게 성능과 비용 양쪽에 이롭다. 코드를 안 거치는 응답이 가장 저렴하다.


조금 더 배경을 주면, 캐시는 서버리스 성능의 절반이라 할 만큼 중요하다. 엣지에서 아무리 빠르게 코드를 실행해도, 캐시로 아예 코드를 안 거치고 응답하는 것보다는 느리다. 그래서 캐시할 수 있는 걸 최대한 캐시하는 게 성능 최적화의 핵심이다. 다만 캐시는 낡은 데이터라는 함정도 있어, 신중한 설계가 필요하다.

2. 엣지 캐시의 한계

엣지 캐시에는 한계가 있다. 엣지 위치마다 캐시가 따로라는 점이다. 전 세계에 흩어진 각 위치가 자기만의 캐시를 갖는다. 그래서 한 위치에서 캐시된 게 다른 위치에는 없다. 봇이 여러 위치에서 오면, 각 위치에서 처음엔 캐시가 없어 코드를 실행하게 된다.


이 위치별 격리가 봇 트래픽에서 문제가 된다. 봇이 세계 곳곳에서 오니, 각 위치마다 첫 요청은 캐시를 못 쓰고 데이터베이스까지 간다. 위치가 많을수록 이 첫 요청도 많아진다. 앞 편에서 다룬 데이터베이스 읽기 폭증이 여기서도 이어진다. 엣지 캐시만으로는 이 빈틈을 다 못 막는다.


그래서 두 번째 층이 필요하다. 여러 위치가 공유하는 캐시다. 앞서 데이터베이스 편에서 다룬 것처럼, 데이터베이스에 캐시 전용 공간을 두어 모든 위치가 그걸 공유한다. 어느 위치에서 채우면 다른 위치들도 읽어 쓴다. 이 공유 캐시가 위치별 격리의 빈틈을 메운다.


엣지 캐시가 위치마다 격리된다는 한계를 이해하는 게 중요하다. 한 위치에서 캐시된 게 다른 위치엔 없어서, 봇이 여러 위치에서 오면 각 위치의 첫 요청은 캐시를 못 쓴다. 위치가 많을수록 이 첫 요청도 많아진다. 그래서 엣지 캐시만으로는 봇 트래픽의 빈틈을 다 못 막고, 공유 캐시라는 두 번째 층이 필요해진다.

3. 여러 층을 조합하기

캐시 설계의 핵심은 이 여러 층을 잘 조합하는 것이다. 가장 빠른 엣지 캐시를 먼저 확인하고, 없으면 공유 캐시를, 그것도 없으면 데이터베이스를 읽는다. 각 층이 앞 층의 빈틈을 메우며, 대부분의 요청이 데이터베이스까지 가기 전에 캐시로 처리된다.


이 계층 구조가 효율적인 이유는 각 층이 자기 강점을 발휘하기 때문이다. 엣지 캐시는 가장 빠르지만 위치별로 격리돼 있고, 공유 캐시는 조금 느리지만 모든 위치가 공유한다. 이 둘을 겹치면 빠름과 넓음을 함께 취한다. 어느 위치에서 와도 대부분 캐시로, 그것도 가능한 한 빠른 층에서 처리된다.


실제로 이 계층을 갖추니 데이터베이스 원본 읽기가 극히 일부로 줄었다. 대부분의 요청, 특히 봇 트래픽이 캐시 층에서 흡수되고, 데이터베이스는 캐시가 만료될 때 잠깐씩만 읽혔다. 여러 층의 캐시가 함께 작동한 결과다. 한 층으로는 못 하는 걸 여러 층을 쌓아 해낸 것이다.


여러 캐시 층을 조합하는 게 설계의 묘미다. 가장 빠른 엣지 캐시를 먼저, 없으면 공유 캐시를, 그것도 없으면 데이터베이스를. 각 층이 앞 층의 빈틈을 메우며 대부분의 요청이 데이터베이스까지 가기 전에 처리된다. 빠름과 넓음을 함께 취하는 이 계층 구조가, 한 층으로는 못 하는 걸 해낸다.


조금 더 실무적으로, 캐시 층마다 유효 시간을 다르게 둘 수 있다. 엣지 캐시는 짧게, 공유 캐시는 조금 더 길게 하는 식으로 각 층의 특성에 맞춘다. 이렇게 하면 각 층이 자기 역할에 맞게 동작한다. 층마다 무엇을 얼마나 캐시할지를 세밀하게 조정하는 게 캐시 계층 설계의 깊이다.

4. 캐시 무효화

캐시의 어려운 부분이 무효화다. 데이터가 바뀌면 캐시된 옛 데이터를 없애거나 갱신해야 하는데, 이걸 언제 어떻게 할지가 까다롭다. 유효 시간에 맡겨 자연히 만료되게 할 수도 있고, 데이터가 바뀔 때 능동적으로 캐시를 지울 수도 있다.


유효 시간에 맡기는 방식은 단순하지만, 그 시간 동안은 낡은 데이터가 보인다. 짧은 유효 시간이면 이 지연이 짧아 대체로 괜찮다. 반면 능동적 무효화는 데이터가 바뀌는 즉시 캐시를 지워 항상 최신을 보장하지만, 구현이 복잡하고 여러 층의 캐시를 다 지워야 해서 관리가 어렵다.


나는 대부분 짧은 유효 시간에 맡기는 방식을 썼다. 목록 같은 건 몇십 초 정도의 지연이 무의미하니, 능동적 무효화의 복잡함을 감수할 이유가 없었다. 대신 게시판 이동처럼 즉시 반영이 필요한 경우엔 해당 캐시를 능동적으로 지웠다. 데이터의 성격에 따라 방식을 나눈 것이다.


캐시 무효화가 캐시의 가장 어려운 부분이다. 데이터가 바뀌면 옛 캐시를 없애야 하는데, 유효 시간에 맡길지 능동적으로 지울지가 고민이다. 나는 대부분 짧은 유효 시간에 맡기고, 즉시 반영이 꼭 필요한 경우만 능동적으로 지웠다. 데이터의 성격에 따라 방식을 나누는 게 복잡함과 최신성 사이의 균형이다.

5. 캐시 버스팅

정적 자산에는 캐시 버스팅이라는 기법을 쓴다. 자산을 오래 캐시하되, 자산이 바뀌면 그 주소를 바꿔 새 자산을 받게 하는 것이다. 예를 들어 스타일시트 주소에 버전 표시를 붙이고, 바뀔 때마다 그 표시를 갱신한다. 그러면 브라우저가 새 주소를 새 자산으로 인식해 다시 받는다.


이게 필요한 이유는 정적 자산을 오래 캐시하면서도 갱신을 보장하기 위해서다. 오래 캐시하면 빠르지만, 자산을 고쳤을 때 옛것이 계속 나올 위험이 있다. 주소를 바꾸는 캐시 버스팅이 이 딜레마를 푼다. 안 바뀌면 캐시로 빠르게, 바뀌면 새 주소로 확실히 갱신하는 것이다. 앞서 SEO 시리즈에서도 다룬 기법이다.


정리하면 서버리스 캐시는 엣지 캐시와 공유 캐시 같은 여러 층으로 이뤄지고, 이 층들을 조합해 각 빈틈을 메우며, 무효화와 캐시 버스팅으로 낡은 데이터를 관리한다. 다음 편에서는 서버리스의 한계, 즉 CPU 시간과 서브리퀘스트, 번들 크기 같은 제약들을 정리한다. 서버리스로 무엇을 할 수 있고 무엇은 어려운지의 경계다.


캐시 버스팅은 정적 자산을 오래 캐시하면서도 갱신을 보장하는 영리한 기법이다. 자산 주소에 버전 표시를 붙여, 바뀔 때마다 주소를 갱신한다. 그러면 안 바뀌면 캐시로 빠르게, 바뀌면 새 주소로 확실히 갱신된다. 오래 캐시하되 갱신을 놓치지 않는 이 방식이, 정적 자산 관리의 딜레마를 푼다.


또 하나, 캐시 계층은 처음부터 다 갖출 필요는 없다. 트래픽이 적을 땐 단순한 캐시로 시작하고, 봇 트래픽이 늘어 데이터베이스 부담이 커지면 그때 층을 더 쌓으면 된다. 나도 처음엔 단순하게 시작해, 문제가 생기면서 층을 하나씩 더했다. 필요에 따라 캐시를 키워가는 게, 처음부터 복잡하게 짜는 것보다 낫다.


다음 편에서는 서버리스의 한계, 즉 CPU 시간과 서브리퀘스트, 번들 크기 같은 제약들을 정리한다. 서버리스로 무엇을 할 수 있고 무엇은 어려운지의 경계를 알면, 그 안에서 자유롭게 설계할 수 있다.