애플리케이션 서버의 조회 성능을 끌어올리기 위한 방법 중 캐싱이 있습니다. 제가 스프링 기반 프로젝트를 진행하면서 왜 로컬 캐싱을 적용했고 글로벌 캐싱으로 변경했는지 알아보겠습니다.
캐싱이란?
캐싱은 어떤 요청에 대한 결과를 임시적으로 저장하는 행위를 말합니다.
예를 들어, A라는 매개 변수로 메소드를 호출했을 때 그 결과를 임의의 공간에 임시 저장하고 똑같은 A라는 매개 변수로 메소드 호출이 들어왔을 때 실제 작업이 수행되지 않고 임시 저장 중인 결과를 반환하는 형태입니다.
주목적은 성능 향상과 동시에 부하 방지라고 말씀드릴 수 있습니다.
나의 프로젝트에 캐싱이 필요한 이유는?
저의 프로젝트에는 캠핑용품 유형 데이터가 존재합니다. 유형에는 텐트, 의자, 코펠, 버너와 같은 여러 가지가 될 수 있는데요. 캠핑용품을 등록하기 위해서는 어떤 유형인지 선택이 필요하고 그 정보를 관리자가 DB에서 관리하도록 하고 있습니다. 그래서 저장되어 있는 모든 유형 데이터를 조회할 필요가 있었는데, 이 말은 유형 데이터가 저장되어 있는 테이블을 전체 스캔하는 풀 테이블 스캔이 필요하다는 의미입니다.
하지만 용품 등록 시마다 풀 테이블 스캔이 일어나면 DB에 비효율적인 부하가 지속될 것이고 유형 데이터는 계속해서 증가할 가능성이 있는 데이터임과 동시에 잘 변경되지 않는 데이터라 캐싱하여 사용하면 부하를 줄일 수 있었습니다.
다음으로 스프링 프로젝트에 캐싱을 적용하기 위해선 어떠한 개념이 필요했는지 알아보겠습니다.
스프링에서 캐싱이란?
스프링 공식문서에 따르면 스프링에서 캐싱은 트랜잭션과 비슷하게 추상화된 형태로 제공해줍니다. CacheManager라는 인터페이스로 추상화되어 있고 사용자는 CacheManager의 구현체를 빈으로 등록만 하면 쉽게 사용할 수 있습니다. 그리고 기본적으로 애노테이션 방식과 AOP를 기반으로 동작하기 때문에 기존 메소드에 끼치는 영향 없이 캐싱 적용이 가능합니다.
로컬 캐싱 적용
저는 스프링에서는 제공하는 캐시 추상화를 사용하여 가장 손쉽게 캐싱을 구현할 수 있는 ConcurrentHashMap 기반을 고려하였습니다. 하지만 캐시 정책을 설정할 수 없어 개별적 캐시 정책 설정이 가능한 ehcache를 적용하였습니다.
ehcache는 스프링에서 지원하는 캐시 저장소 구현체 중 하나인데 캐시되는 내용별로 캐시 정책을 설정할 수 있고 로컬 메모리를 사용하기 때문에 속도면에서도 매우 뛰어났습니다. 그리고 ehcache에 대한 국내 자료가 많아 접근성 또한 높았습니다.
로컬 캐싱의 한계
실제 ehcache 기반의 로컬 캐싱을 적용해보니 각 서버의 메모리 별로 캐싱이 되는 구조이고 저의 애플리케이션 서버는 Scale-out 구조를 기반하고 있습니다. 그래서 캐시 결과를 삭제하거나 변경할 때 해당 서버에만 적용이 되고 다른 애플리케이션 서버는 캐시 정책에 따라 만료될 때까지 정합성이 맞지 않는 경우가 발생했습니다. 특히나 정합성 문제는 캠핑용품이 등록되는 시점에 유형 데이터가 변경이 되면 이후의 검색 조건에서 제외될 수 있기 때문에 반드시 이 문제를 해결할 필요가 있었습니다.
Redis를 이용한 글로벌 캐싱
로컬 캐싱의 한계를 극복하기 위해 사용한 방법입니다. 글로벌 캐싱은 캐시 서버를 따로 분리하여 캐시 결과를 저장하고 캐시가 필요할 때 하나의 서버에 요청하기 때문에 캐시 결과의 변경 사항을 동기화할 필요가 없었습니다. 그래서 캐시 결과가 변경되더라도 글로벌 서버에 요청만 하면 모든 애플리케이션 서버에서 동일한 결과를 얻는 게 가능해졌습니다.
저는 로컬 캐싱을 적용하여 발생했던 문제를 글로벌 캐싱을 통해 해결할 수 있었습니다. 제가 직면한 문제는 정합성이 중요한 상황이었기 때문에 글로벌 캐싱이 필요했지만 정합성 오차를 어느 정도 허용할 수 있다면 로컬 캐싱을 통해 성능을 극대화하는 것도 괜찮은 방법이라 생각합니다.
'Database > Redis' 카테고리의 다른 글
Redis Session 병목 해결 및 최적화하기 (0) | 2021.06.03 |
---|---|
Scale out 확장 구조에서 Session 불일치 문제와 해결 방법 (0) | 2021.02.11 |