RDB vs NoSQL
JSP를 공부하면 세트로 따라오는 것이 데이터이다. 웹사이트를 개발하더라도 사이트에서 저장되는 데이터가 있을 것이다. 그 데이터를 어떻게 저장하고 관리할 것이며 조작할 것인지에 대해 알
dawulle.tistory.com
이전 블로그 글 중 NoSQL에 대해 공부하며 포스팅을 한 적이 있다. 그때 NoSQL 중 하나를 사용해보며 공부 후 포스팅 해보기로 했었다. 미뤄두었던 걸 이번에 진행하는 프로젝트 개발에 기능 중 하나인 장바구니 기능에 NoSQL의 종류 중 하나를 적용해보고자 하였다. 그렇다면 각가의 특징에 대해 알아야 한다고 생각하여 첫번째로 Redis에 대해 알아보고 포스팅 해보려고 한다.

레디스(Redis)란?
- Remote Dictionary Server 의 약자로, 오픈소스의 In-Memory 데이터베이스이다.
키-값(key-value) 데이터 저장소로 사용되며, 메모리 기반 데이터베이스라고도 부른다. - 즉, 모든 데이터를 메모리에 저장하고 검색 및 처리할 수 있는 기술을 제공한다.
- NoSQL 데이터베이스이며 주로 캐싱, 세션관리, 메시지 브로커, 실시간 분석 등 다양한 응용프로그램에 사용된다.
- 데이터를 메모리에 저장하기때문에 빠른 읽기 및 쓰기 작업을 지원한다.
하지만, 메모리에 저장하는 이유로 큰 데이터 집합을 다루려면 많은 메모리가 필요할 수 있다. - 다양한 데이터 구조를 지원한다.(ex. 문자열, 리스트, 해시, 셋, 정렬 집합 등)
레디스는 다른 In-Memory DB 중 하나인 Memcache와의 가장 큰 차이점이다. - 데이터베이스 클러스터링을 지원하며, 단일 쓰레드로 동작한다.
병렬처리가 필요한 작업에서는 성능 이슈가 발생할 수 있다. (관련하여 자세한 포스팅하겠다.) - 레디스는 Java언어를 지원하지 않지만 Redis 클라이언트 라이브러리를 사용하여 통합할 수 있다.
레디스 클라이언트
간단히 종류에 대해 알아보았다.
- 레디스 서버에 연결하여 레디스 데이터 구조를 조작할 수 있게 해준다.
- Java용 Redis 라이브러리로는 Lettuce, Redisson, Jedis 등이 있다.
- Lettuce: 동기/비동기 및 논블로킹 I/O를 지원한다. 레디스 클러스터 지원한다.
- Redisson: 비동기 및 논블로킹 I/O를 지원한다.
- Jedis: 동기 지원
Cache Manager
캐시 추상화에서는 캐시 기술을 지원하는 캐시 매니저를 빈으로 등록해야 한다. 종류는 아래와 같다.
- ConcurrentMapCacheManager
- 캐시 기능을 구현하는 간단한 캐시 매니저로, 메모리 상에 캐시를 저장하여 빠른 접근이 가능하다.
- 하지만, 용량이 제한적으로 큰 데이터를 다루거나 장기간 보관하는 경우에는 지양한다.
- ConcurrentMap 기반의 캐시 맵을 사용한다. 동시성이 지원되는데 그래서 다중 스레드가 동시에 캐시에 접근하더라도 안전하게 동작 가능하다.
- 하지만, 단일 애플리케이션의 메모리 내의 캐시를 다루기 때문에 여러 컴퓨터 또는 장치들이 연결되어 분산하고 처리하는 방식에서의 캐시공유는 적합하지 않다.
- SimpleCacheManager
- 기본적으로 제공하는 캐시가 없다.
- 사용할 캐시를 직접 등록하여 사용하기 위한 캐시 매니저이다.
- EhCacheCacheManager
- Spring에서 간단하게 사용할 수 있는 Java 기반 오픈 소스 캐시 라이브러리로,
데몬을 가지고 있지 않고 Spring 내부적으로 동적하여 캐싱처리를 한다. - 캐시를 캐시저장소에 만료시간과 함께 저장하고, 사용자 요청이 있을 경우 만료 시간 전이라면 해당 저장소에
있는 데이터를 사용하는 방식이다. - 메모리 기반 및 디스크 기반 캐시를 지원하며 분산 환경에서도 사용 가능하다.
- 서버 애플리케이션과 라이프사이클을 같이 하므로 사용하기 간편하다.
- Spring에서 간단하게 사용할 수 있는 Java 기반 오픈 소스 캐시 라이브러리로,
- CaffeineCacheManager
- Caffeine Cache Github에서 Java를 위한 최적의 캐싱 라이브러리라고 소개하고 있다.
- Google 오픈 소스 Guava Cache와 ConcurrentHashMap을 개선한 ConcurrentLinkedHashMap을 바탕으로 개선되어 성능을 개선하였다고 하여 고성능으로 최근 많이 사용한다.
- 또한 properties 나 yml 설정이 쉽다.
- CompositeCacheManager
- 한 개 이상의 캐시 매니저를 사용하도록 지원해주는 혼합 캐시 매니저이다.
- JCacheCacheManager
- JSR-107 기반의 캐시를 사용하는 캐시 매니저이다.
- JSR-107은 Java Community Process(JCP)의 일부로 개발된 자바 스펙의 한 종류로,
Java Temporary Caching API를 정의하는 스펙이다.
이 스펙을 간단히 설명하면, 캐싱을 위한 표준 Java API를 제공하며 Java 애플리케이션에서 캐시를 사용하고 관리한다.
의존성 추가
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
@EnableCaching
Spring에서 @Cacheable과 같은 어노테이션 기반의 캐시기능을 사용하기 위해서는 먼저 별도의 선언이 필요하다.
그래서 해당 어노테이션을 설정 클래스에 추가해주어야한다.
// 예제 코드
@EnableCaching
@SpringBootApplication
public class FashionCommerceServerApplication {
public static void main(String[] args) {
SpringApplication.run(FashionCommerceServerApplication.class, args);
}
}
공통 Optional Element
| Element | Description | Type |
| cacheName | 캐시 이름(설정 메서드 리턴값이 저장) | String[] |
| value | cacheName의 alias | String[] |
| key | 동적인 키 값을 사용하는 SpEL표현식 동일한 cacheName을 사용하지만 구분될 필요가 있을 경우 사용되는 값 |
String |
| condition | SpEL 표현식이 참일 경우에만 캐싱 적용 - or, and 등의 조건식, 논리연산이 가능 |
String |
| cacheManager | 사용할 CacheManager 지정 (위의 CacheManager 자료 확인) |
String |
@Cacheable
적용된 메서드의 리턴값을 기준으로 캐시에 값을 저장한다.
적용할 메소드에 @Cacheable 어노테이션을 붙여주면 캐시에 데이터가 없을 경우에는 기존의 로직을 실행한 후에 캐시에 데이터를 추가하고, 캐시에 데이터가 있으면 캐시의 데이터를 반환한다.
// 예제코드
@Cacheable(cacheNames = "cartList", key = "#userId", sync = false)
public List<ProductDto> putCartList(int userId, List<ProductDto> orderProductList) {
List<ProductDto> productDtoList = new ArrayList<>();
Optional Element
| Element | Description | Type |
| unless | 캐싱을 막기 위해 사용되는 SpEL 표현식 condition과 반대로 참일 경우에만 캐싱이 적용되지 않음 |
String |
| sync | 여러 스레드가 동일한 키에 대한 값을 로드하려고 할 경우, 기본 메서드의 호출을 동기화한다. 즉, 캐시 구현체가 Thread safe 하지 않는 경우, 캐시에 동기화를 걸 수 있는 속성 |
boolean |
@CacheEvict
Cache를 비우고 싶거나, DB의 데이터 삭제가 있을 때 Redis Cache에 데이터를 삭제한다.
DB에서 DELETE와 같은 삭제에서 사용한다.
// 예제코드
@CacheEvict(cacheNames = "cartList", key = "#userId", beforeInvocation = false, allEntries = false)
public void deleteCartList(int userId) {
//...
}
Optional Element
| Element | Description | Type |
| allEntries | 캐시 내의 모든 리소스를 삭제할지의 여부 | boolean |
| beforeInvocation | true : 메서드 수행 이전 캐시 리소스 삭제 false : 메서드 수행 후 캐시 리소스 삭제 |
boolean |
@CachePut
DB의 데이터 업데이트가 있을 때 Redis Cache에 데이터를 업데이트한다.
DB에서 PUT/PATCH와 같은 업데이트에서 사용한다.
// 예제코드
@CachePut(cacheNames = "cartList", key = "#userId")
public void updateCartList(int userId) {
//...
}
Optional Element
| Element | Description | Type |
| unless | 캐싱을 막기 위해 사용되는 SpEL 표현식 condition과 반대로 참일 경우에만 캐싱이 적용되지 않음 |
String |
@Caching
하나의 메서드를 호출 할 때 Cacheable, CacheEvict 등 여러 개의 캐싱 동작을 수행해야 할 때 사용한다.
// 예제 코드: @Caching을 사용해 여러개의 @CacheEvict을 묶음.
@Caching(evict = {
@CacheEvict("addresses"),
@CacheEvict(value = "directory", key = "#customer.name")
})
public String getAddress(Customer customer {
//...
}
Optional Element
| Element | Description | Type |
| cacheable | 적용 될 @Cacheable array를 등록 | Cacheable[] |
| evict | 적용 될 @CacheEvict array를 등록 | CacheEvict[] |
| put | 적용 될 @Cacheput array를 등록 | CachePut[] |
@CacheConfig
클래스 단위로 캐시 설정을 동일하게 하고 싶을 때 사용한다.
// 예제 코드
@CacheConfig(cacheNames = {"addresses"}, cacheManager = "cacheManager")
public class CustomerDataService {
// ..
@Cacheable
public String getAddress(Customer customer) {
//...
}
}
Optional Element
| Element | Description | Type |
| cacheNames | 해당 클래스 내 정의된 캐시 작업에서의 default 캐시 이름 | String[] |
| cacheManager | 사용할 CacheManager 지정 | String |
간단히 Redis에 대해 알아보며 프로젝트에 활용해 보았다. 자주 조회되는 데이터에 대해 사용하였지만 레디스 설정과 어노테이션을 활용하여 쉽게 사용할 수 있었다. 캐시 기술을 지원하는 캐시 매니저의 다양한 종류로 특징에 맞게 다음 프로젝트 개발 시 활용할 기회를 만들어봐야겠다. 또한 다른 NoSQL 종류를 알아보고 그에 관련하여 포스팅 해보겠다.
[출처 및 참고]