인턴에서 백오피스 개발을 하던 도중, 같은 데이터를 계속 조회하는 상황이 빈번하게 발생했다.
데이터 수도 꽤 많았기 때문에 매우 비효율적이라고 판단했고, Cache 를 적용해 이전에 조회한 데이터를 다시 데이터베이스에서 조회하지 않게 설계하였다. JDBC를 사용해 커넥션을 연결하고, 쿼리를 날린 후, 데이터베이스에서 다시 가져와 커밋하는 코스트가 매우 높기 때문이다.
캐시를 사용해야겠다고 마음 먹은 후, 요구사항에 맞는 캐시전략을 사용해야했다.
이번 프로젝트의 요구사항은 서버는 하나이며, 캐시의 키 값으로 구분지을 데이터 종류가 많지 않았다. 대시보드의 각 서브 메뉴마다 데이터가 존재했기 때문에, 서브메뉴 * 쿼리 파라미터 개수만큼 데이터 종류가 나왔기 때문이다.
Redis, EHcache, HashMap 의 특징들을 알아보고 HashMap 컬렉션을 사용하여 캐시를 구현한 이유에 대해서 포스팅하겠다.
Redis
우선 Redis 는 Global Cache 이다. Global Cache 는 외부 캐시 저장소에 접근하여 데이터를 가져온다. 때문에 네트워크 I/O 비용이 발생할 수 있다. 그러나 유지보수 비용이 낮기 때문에 고도화 되는 서버일수록 높은 효율을 가진다.
또한 여러대의 서버가 있다고 가정할 때, 모든 서버가 같은 캐시 저장소에 접근하기 때문에 데이터 일관성을 보장해준다.
때문에 서버가 여러대고, 여러대 서버간의 빠른 데이터 공유가 필요한 경우에 주로 사용된다. 본 프로젝트 캐시 전략과는 맞지 않았기 때문에 후보에서 제외됐다.
EHcache
EHcache 는 Local Cache 로 캐시 데이터를 서버의 메모리에 적재해둔다. 때문에 매우 빠르게 접근할 수 있다. 서버 메모리에 적재되기 때문에, 서버마다 독립적인 캐시 메모리를 가지고 있다. 때문에 데이터 일관성이 깨질 수 있으며, 동기화를 한다고 하더라도 코스트가 높다. 스프링에서 xml 파일로 쉽게 생성하고 수정할 수 있다. 다만 형식이 정해져 있으므로, 커스텀 하기가 어렵다. SpringBoot 에서 EHcache 를 xml 파일을 통해 손쉽게 사용하는 예제 블로그이다. 참고하면 좋을 것 같다.
https://oingdaddy.tistory.com/384
서버가 한대일 때 주로 사용하고 빠르다는 장점이 있지만, 본 프로젝트에서는 동적으로 각 메뉴마다 캐시 메모리 적재 유지 시간이 달라져야했다. EHcache는 xml 설정 파일로 정적으로 관리하기 때문에 동적으로 변경하기가 어려웠다. 불가능한 건 아니지만, 멘토님께서 EHcache 공식 레퍼런스를 최소 10회독은 해야지 커스텀 할 수 있을 거라고 하셨다.
러닝커브가 너무 높다고 판단했기 때문에 후보에서 제외되었다.
HashMap
사실 HashMap은 자바의 컬렉션 중 하나일 뿐, 캐시와는 관련이 없다. 그러나 많은 캐시 정책이 HashMap 으로 구현된 건 사실이다. HashMap이 캐시 메모리 역할을 할 수 있게 오픈소스 사용 없이 하드 코딩한 것에 불과하다. 하지만 직접 코딩하는 만큼 커스텀 할 수 있다는 장점이 있기 때문에 HashMap 을 사용해서 캐시 메모리를 구현하였다.
추가로 데이터 종류 (key-value 쌍)이 많지 않고, 데이터 양도 서버가 서비스를 운영하는데 치명적일만큼 메모리를 잡아먹지 않기 때문에 선택할 수 있었다.
정확히 HashMap 을 사용해 구현하지 않았다. 정확히는 Concurrent HashMap 컬렉션을 사용하였다. 이유는 HashMap 은 기본적으로 Thread Safe 을 지원하지 않기 때문이다. 스프링부트 웹 프레임워크는 내장으로 톰캣 웹서버를 사용하고 있기 때문에 멀티 쓰레드 환경을 지원해준다. 톰캣에 Thread Pool 을 만들고 요청이 들어오면 Thread 가 해당 요청을 담당해서 로직을 수행한다. 동일한 ip 주소를 사용한다면 얘기가 달라질 수 있지만, 보통 하나의 유저에 하나의 Thread 가 담당된다. 이러한 환경에서 동시에 같은 메모리 주소에 접근하게 될 경우, 서버가 장애가 생길 수 있기 때문에 문제로 판단하였다.
이러한 이유 때문에 Thread Safe 할 수 있는 HashMap 컬렉션을 사용해야했고, Concurrent HashMap 이 Thread Safe 를 synchronized (동기화) 를 통해 지원해주기 때문에 편리하게 Concurrent HashMap 컬렉션을 사용하였다.
다음 포스팅은 실제로 Concurrent HashMap 를 사용하여 어떻게 Cache 메모리를 만들었는지 코드 레벨에서 검증하겠다.
'개인 공부 > Spring' 카테고리의 다른 글
[Spring] Multi Thread (2) | 2022.11.21 |
---|---|
[Spring] HashMap 으로 Cache 구현하기 (0) | 2022.11.14 |
[Spring] @Transactional 파해치기 (0) | 2022.11.08 |
[Spring] JDBC 에서 Transaction 관리하는 법 (0) | 2022.11.08 |
[Spring] #9 JPA 엔티티 매핑 (0) | 2022.04.18 |