In-Memory Computing
인-메모리 컴퓨팅이란 Application이 운영을 위한 데이터를 하드 디스크가 아닌 메인 메모리에서 수행하는 것을 말한다.
즉, 연산을 위한 영역으로만 여겨졌던 메모리 영역을 대량의 데이터를 저장하여 처리할수 있는 공간으로 사용하는 것이다.
기술적/비용적 측면에서는
과거 Main Memory의 가격이 상대적으로 높았기 때문에 Memory 영역을 사용하는 것이 불가능/비효율적이었지만,
Memory의 가격이 낮아진 오늘 날에는 가능해지고, 보편화된 기술이 되었다.
In-Memory 컴퓨팅/아키텍쳐는 데이터의 절대적인 양과 종류가 폭발적으로 증가한 빅데이터 시대에 데이터 처리 속도를 향상시킬 수 있는 핵심 기술로 평가받고 있다.
Cache(캐시)란 데이터의 원본이나 원본 데이터를 통해 연산된 값을 미리 저장(복사)해두는 임시 저장소를 의미한다.
즉, 데이터의 읽기(Read) 성능을 개선시키기 위해 DB와 같은 영구 저장소로부터 로드된 데이터를 빠르게 읽어올 수 있는 Memory 영역에 저장해두는 방식인 것이다.
스프링 부트 캐시 적용하는 가장 쉬운 방법
@EnableCaching, @Cacheable, @CacheEvict
spring-boot-starter-cache
라이브러리 불러온다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 캐시 기능을 사용하고 싶은 프로젝트에 @EnableCaching을 쓴다.
@EnableCaching
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- 캐시하고 싶은 메서드에
@Cacheable
, 캐시를 제거하고자하는 메서드에는@CacheEvict
를 쓴다.
@Service
public class TestService {
private List<String> list;
@PostConstruct
public void init() {
list = new ArrayList<>();
}
@Cacheable(value="test")
public String getInformation(String info) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return list.stream().filter(x->x.equals(info)).findFirst().get();
}
@CacheEvict(value="test")
public void createInformation(String info) {
list.add(info);
}
}
spring boot에서 캐시 기능을 추상화 시켜놨기 때문에 spring-boot-starter-cache 라이브러리를 불러오기만하면 아주 쉽게 사용이 가능하다. 즉, Anotation을 적용하는 것만으로도 캐시 기능을 사용할 수 있게 했다.
특히 스프링부트에서 자주 쓰는 캐시엔진(?)에 대해서는 자동으로 적용되게 해놨다. (Redis, Encache, ConcurrentMap, …)
spring-boot-starter-cache 라이브러리를 불러왔으면 @EnableCaching으로 해당 애플리케이션에서 캐시를 사용할 것임을 알린다. 그 후 캐시가 적용될 메서드에는 @Cacheable(value=”XXXX”)를 적용한다.
그러면 해당 메서드가 최초 1회 이후 호출될때마다 메서드의 파라미터가 같은 값이면 메서드를 호출하지 않고 결과값을 캐시에서 꺼내 리턴해준다.
redis 적용한 cache
- dispatcher-servlet 설정 ```xml
## RedisTemplate
```java
//spring-boot parent 2.2이상 버전에서는 자동 설정에 의해서 redis template 빈이 4개가 생성됩니다.
@Autowired RedisTemplate redisTemplate;
@Autowired StringRedisTemplate stringRedisTemplate;
@Autowired ReactiveRedisTemplate reactiveRedisTemplate;
@Autowired ReactiveStringRedisTemplate reactiveStringRedisTemplate;
@EnableAutoConfiguration(exclude={RedisReactiveAutoConfiguration.class})로 reactive redis template은 자동 설정을 disable시킬 수 있습니다.
redisTemplate와 stringRedisTemplate의 차이는 Serializer(직렬화)입니다. redisTemplate의 key, value serializer는 JdkSerializationRedisSerializer이고 stringRedisTemplate의 serializer StringRedisSerializer입니다. 직렬화 방식이 다르기 때문에 혼용해서 사용하는 건 안 됩니다.
@Test
public void test() {
redisTemplate.opsForValue().set("key", "hello");
log.debug("redisTemplate get value => {}", redisTemplate.opsForValue().get("key"));
//==> "hello"
log.debug("stringRedisTemplate get value => {}", stringRedisTemplate.opsForValue().get("key"));
//==> null
- 만약 다른 직렬화 방식을 사용한다면 RedisTemplate bean을 생성해야 합니다. (RedisConnectionFactory은 자동 설정에 의해 생성된 빈을 주입받아 사용)
@Bean public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); redisTemplate.setKeySerializer(RedisSerializer.string()); redisTemplate.setValueSerializer(RedisSerializer.java()); redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setHashValueSerializer(RedisSerializer.java()); return redisTemplate; }
- bean 네임을 “redisTemplate”로 하면 자동 설정(RedisAutoConfiguration)이 redisTempate은 생성하지 않는다.
public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); ....
<?xml version="1.0" encoding="UTF-8"?>
<ehcache
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir" />
<sizeOfPolicy maxDepth="100000" maxDepthExceededBehavior="continue"/>
<!-- 캐시 정의 -->
<!--
name : 캐시의 이름이다. @Cacheable("캐시의 이름") 와 일치시켜줘야한다.
diskExpiryThreadIntervalSeconds : 디스크에 저장된 캐시들에 대해 만료된 항목을 제거하기 위한 스레드를 실행할 주기 설정
diskSpoolBufferSizeMB : 디스크 캐시에 쓰기 모드로 들어갈 때 사용될 비동기 모드의 스폴 버퍼 크기 설정 (OutOfMemory 발생시 수치 낮추도록 함)
diskPersistent : VM이 재기동할 때 캐싱된 객체를 계속 유지할지 여부
eternal : 한번 캐시하면 영원히 유지할 것인지의 여부
maxElementsInMemory : 메모리에 캐싱될 최대 객체 수
maxEntriesLocalHeap : 힙메모리 최대량
overflowToDisk : 메모리저장공간이 부족할때 Disk 사용여부
memoryStoreEvictionPolicy : 최대 개수에 도달할 때, 제거 알고리즘
- FIFO : 먼저 저장된 데이터를 우선 삭제
- LFU : 데이터의 이용 빈도 수를 기준으로 이용 빈도가 가장 낮은 것부터 삭제
- LRU : 데이터의 접근 시점을 기준으로 최근 접근 시점이 오래된 데이터부터 삭제
overflowToDisk : maxElementsInMemory이 옴계량에 가까우면 오버플로우되는 객체들을 디스크에 저장할지 결정
timeToIdleSeconds : 다음 시간동안 유휴상태 후 갱신할지 결정 (데이터가 지정된 시간(초단위)동안 재호출되지 않으면 휘발됨)
timeToLiveSeconds : 한번 저장된 데이터의 최대 저장 유지 시간(초단위)
maxBytesLocalHeap : 최대 로컬 힙메모리 사용량 설정 (사용 시 maxEntriesLocalHeap 사용 불가)
maxBytesLocalDisk : maxBytesLocalHeap에 설정된 캐시 사용 이후 디스크 스토리지 한계 설정 -->
<!--
maxElementsInMemory="1000" // 메모리에 유지되는 최대 캐시 수
name="INTERFACE_CACHE" // 캐시 이름
eternal="false" // 캐시 항목 유지 true:캐시 만료 안됨, false:설정에 따라 만료됨
timeToIdleSeconds="120" // 캐시가 마지막으로 사용된지 얼마 후에 소멸? default:0
timeToLiveSeconds="120" // 캐시 만료 시간, default : 0 (영원히 안 지움)
overflowToDisk="false" // 메모리가 꽉 찼을때 디스크에 저장 여부
diskSpoolBufferSizeMB="100"
diskPersistent="false" // VM 종료 대비 캐시를 디스크에 저장(백업 개념), default:120초
diskExpiryThreadIntervalSeconds="120" // 자바 GC랑 비슷한 역할, default:120초
-->
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskSpoolBufferSizeMB="30"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<!-- /frontweb/src/main/resources/sql/display/displayCategorySql.xml -->
<cache
name="displayCategorySql.getDisplayCategoryList"
eternal="false"
maxEntriesLocalHeap="100000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="86400"
memoryStoreEvictionPolicy="LFU" />
</ehcache>
/* pom.xml*/
<!-- redis cache -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.23.RELEASE</version>
</dependency>
<dependency>
<groupId>biz.paluch.redis</groupId>
<artifactId>lettuce</artifactId>
<version>4.5.0.Final</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>redis information</comment>
<entry key="redis.maxRedirects">2</entry>
<entry key="redis.host1">hostip</entry>
<entry key="redis.port1">port</entry>
</properties>