캐시란?
캐시는 애플리케이션의 성능을 대폭 상승시켜 주는 대표적인 기술입니다. 조회 성능이 더 느린 계층에서 발생하는 병목 현상을 줄이기 위해 조회 성능이 더 빠른 계층에 임시로 데이터를 저장하는 방법입니다.
컴퓨터 구조에서의 메모리 계층에서 캐시라는 개념을 처음 접할 수 있지만, 저같은 백엔드 개발자는 DB(스토리지)에 저장된 것을 RAM으로 저장하여 조회 성능을 상승 키시는 것이 생각나네요.
조회 성능이 뛰어나게 된다면, 모든 정보를 캐싱하면 우리의 서비스는 기존보다 더 많은 트래픽에 대응할 수 있는 서비스로 성장할 수 있겠죠. 하지만 모든 정보를 캐싱하는 것을 불가능에 가깝습니다.
바로 돈 때문입니다. 우리가 컴퓨터를 구매하려고 할 때, SSD가 더 빠르지만 HDD가 훨씬 싸기때문에, 대량의 디스크 공간이 필요하면 HDD를 구매하죠? RAM 역시 가격이 SSD나 HDD보다 비싸기 때문에 우리는 모든 데이터를 캐싱할 수 없습니다.
그럼 `어떤 정보`를 저장해야 할까요? 생각해보면 간단합니다. `가장 많이 조회`되는 데이터를 저장하면 됩니다.
1~100 페이지의 공지사항이 있다고 예를 들어 본다면, 공지사항 1페이지가 이 공지사항 기능의 대부분의 트래픽을 차지할 것입니다. 이런 경우를 캐싱하면 될 것입니다.
이와 반대로 100번째 페이지 공지사항은 초당 10000개의 요청이 들어오는 경우 평균 응답 속도가 3초가 걸려서 사용자에게 굉장히 불편함을 줄 수 있습니다. 하지만 100번째 페이지에 초당 1만 개의 요청이 들어올 일이 있을까요? 아마 없을 겁니다.
이런 상황을 `파레토 법칙`에 대입할 수 있을 겁니다.
파레토 법칙( - 法則, 영어: Pareto principle, law of the vital few, principle of factor sparsity)[1][2] 또는 80 대 20 법칙(영어: 80–20 rule)은 '전체 결과의 80%가 전체 원인의 20%에서 일어나는 현상을 가리킨다.[3] 예를 들어, 20%의 고객이 백화점 전체 매출의 80%에 해당하는 만큼 쇼핑하는 현상을 설명할 때 이 용어를 사용한다. 2 대 8 법칙라고도 한다.
- 위키백과 파레토 법칙 (https://ko.wikipedia.org/wiki/%ED%8C%8C%EB%A0%88%ED%86%A0_%EB%B2%95%EC%B9%99)
이 내용을 보면 우리가 제공하는 `20%의 API가 80%의 사용율`을 보인다고 이해하면 되겠네요. 개발을 할 때, 어떤 API를 사용하는지에 대한 카운트 로그를 남겨 확인한 후 적절하게 캐싱을 적용하면 좋겠습니다.
Local Cache와 Global Cache
캐시가 어떤 개념인지에 대해서 알아봤으니, 웹 애플리케이션에서 사용하는 캐시에서 빠질 수없는 개념인 Local Cache와 Global Cache에 대해서 알아보겠습니다. 이 둘의 차이는 캐시 데이터를 `어디에 저장하냐`에서 발생합니다. 캐시 저장 위치에 따라서 둘의 장점과 단점이 갈리게 됩니다.
Local Cache
먼저 Local Cache에 대해서 알아보겠습니다. Local Cache는 웹 어플리케이션 서버에 저장하는 캐시라고 생각하면 편합니다. Spring을 사용해서 개발한다면 하나의 프로세스에 저장, 즉 같은 `JVM 메모리`를 사용합니다 (EhCache의 경우 Disk에도 저장할 수 있다고 합니다).
아래처럼 동작하여 DB와 같은 스토리지에 저장된 정보 혹은 외부 DB 서버에 저장되어 네트워크 통신이 발생하지 않아 훨씬 빠른 성능을 제공합니다.
Local Cache의 경우 더 빠른 성능을 제공하면서도, Global Cache처럼 별도의 캐시를 위한 서버를 증설하지 않아도 되기 때문에, 비용적인 측면에서 저렴할 수 있습니다.
하지만 서버 인스턴스가 여러 개가 존재하는 경우, 서로 다른 캐시 정보가 저장되어 특정 인스턴스에는 정보가 없거나, 서로 불일치한 정보를 지니고 있는 문제가 발생할 수 있어 `캐시 동기화`를 시켜줘야 한다는 점이 단점입니다. 추가로 여러 서버에서 동일한 정보를 저장하여 자원 낭비가 발생할 수 있다는 점도 단점입니다.
Global Cache
글로벌 캐시의 경우는 외부 서버나 다른 프로세스에 캐시를 저장합니다. 주로 `Redis`나 `Memcached`와 같은 솔루션을 별도의 서버에 설치하여 사용합니다.
글로벌 캐시의 경우 로컬 캐시의 특징을 반대로 갖는다고 생각하시면 됩니다. 여러 서버 인스턴스가 동일한 캐시 저장소를 통해 일관된 정보를 사용합니다. 이를 통해 캐시의 일관성이 틀어지지 않아 캐시 동기화 작업을 하지 않아도 됩니다.
또한, 로컬 캐시의 동일한 데이터를 여러 번 저장하여 발생하는 자원 낭비 역시 발생하지 않습니다.
하지만 캐시 솔루션을 동작시킬 추가 컴퓨팅 자원을 구매해야 한다는 점은 부담으로 다가올 수 있습니다. 그리고 캐시 서버와의 통신으로 인해 로컬 캐시보다 더욱 느린 성능을 보여줍니다. 마지막으로 캐시 전략에 따라서 캐시 서버가 동작하지 않을 경우 서비스가 정상적으로 동작하지 않을 수 있고, DB에 많은 요청이 몰리는 `Thundering Herd` 문제가 로컬 캐시보다 더 심각하게 발생할 수 있다는 단점이 있습니다.
로컬 캐시와 글로벌 캐시의 특징을 정리하면 다음과 같습니다.
Local Cache | Global Cache | |
장점 | 더욱 빠른 성능 추가 컴퓨팅 자원 구매 X |
인스턴스 간 일관된 캐시 정보 제공 메모리 낭비 X |
단점 | 인스턴스 간 다른 캐시 저장 가능 데이터 중복 저장으로 인한 메모리 낭비 |
서버와 캐시 솔루션간 통신으로 인한 성능 저하 추가 서버 자원 구매 필요 |
마무리
지금까지 캐싱에 대한 개념과 로컬 캐시와 글로벌 캐시의 특징에 대해서 알아봤습니다. 처음 말했듯이 캐싱은 성능을 높이는 대표적인 기술이고, 적용하는 것 자체는 어려움이 없기 때문에, 한번 적용해 보고 성능의 변화를 느껴보시면 좋겠습니다.
캐시에 대해서는 이 포스팅에서도 언급한 `캐시 동기화`나 `캐시 전략`같은 개념도 있기 때문에, 추후에 공부해서 다뤄보도록 하겠습니다.
마지막으로 제가 프로젝트에서 로컬 캐시와 글로벌 캐시를 적용해서 성능 테스트를 한 결과를 보여드리고 글을 마무리하겠습니다.
참고 자료
지마켓 기술블로그 - Java Application 성능개선에 대해 알아보자 - Local Cache 편