Spring Boot + Kotlin 환경에서 Redis Transaction 처리에 대해 알아보고, 적용 과정에서 해결해야 했던 것들을 공유합니다.
1. 테스트 환경
- Spring Boot Version : 2.3.1
2. Redis Transaction 처리
스프링 공식문서에는 Redis Transaction 처리 관련해서 2가지 방법을 소개합니다.
- @Transactional Support
- RedisTemplate의 multi, exec, discard 이용
* 스프링 공식문서
docs.spring.io/spring-data/data-redis/docs/2.3.4.RELEASE/reference/html/#tx
2.1. @Transactional Support
스프링 문서의 일부분입니다.
@Transactional을 사용하려면 3가지 설정을 해야 합니다.
- Redis configure에 @EnableTransactionsManagement 추가
- RedisTemplate에 transaction support 기능을 true로 변경 (기본값 : false)
- PlatformTransactionManager를 bean으로 등록
Redis의 PlatformTransactionManager는 redisson으로 사용 가능하다고 합니다.
dzone.com/articles/how-to-manage-transactions-in-redis-on-java
여기서는 다른 방법인 RedisTemplate의 multi, exec, discard 이용하여 transaction 처리를 해봤습니다.
2.2 RedisTemplate의 multi, exec, discard 이용
MULTI, EXEC, DISCARD, WATCH는 Redis transaction의 기본 요소들입니다.
- MULTI : transaction의 시작입니다.
- EXEC : transaction의 종료입니다. commit과 같다고 할 수 있습니다.
- DISCARD : transaction을 중단합니다.
- WATCH : Optimistic locking 기능을 제공합니다.
각 요소들의 자세한 사항은 redis 문서를 보시면 됩니다.
위 기능들을 사용하려면 redisTemplate의 execute 메서드를 이용하면 됩니다.
인터넷에 검색하면 대부분 JAVA로 된 예제 코드들입니다. 이 코드를 IntelliJ에서 Kotlin으로 변경하면 Generic type 때문에 오류가 발생합니다. 여기서는 아래와 같이 하여 에러를 해결했습니다.
redisTemplate.execute(object : SessionCallback<Any?> {
@Throws(DataAccessException::class)
override fun <K : Any?, V : Any?> execute(operations: RedisOperations<K, V>): Any? {
try {
operations.watch(key as K) // WATCH: Optimistic Locking
operations.multi() // MULTI: 트랜잭션 경계 설정
operations.opsForHash<K, V>().putAll(key as K, map as MutableMap<K, V>)
} catch (e: Exception) {
operations.discard()
return null
}
return operations.exec() // EXEC: COMMIT
}
})
Redis에 HMSET으로 저장하려면 operations.opsForHash <K, V>(). putAll()을 이용해야 합니다.
이때, 다음과 같이 의도하지 않은 값들이 key와 value에 저장될 수 있습니다.
위와 같은 값이 나오면 redisTemplate에 serializer을 설정하여 해결할 수 있습니다.
val stringSerializer: RedisSerializer<String> = StringRedisSerializer()
redisTemplate.stringSerializer = stringSerializer
redisTemplate.keySerializer = stringSerializer
redisTemplate.hashKeySerializer = stringSerializer
redisTemplate.hashValueSerializer =stringSerializer
3. 그 외 실험
3.1 WATCH를 설정하고 안하고의 차이?
watch를 설정하고 안 하고 어떠한 차이가 날지 테스트해봤습니다.
테스트는 TTL update를 동시에 3번 시도하는 것으로 했습니다.
다음은 redis-cli에서 monitor를 캡처한 것입니다.
- WATCH 미설정 시
PEXPIRE는 millisecond 만큼 TTL을 설정하겠다는 의미입니다. 여기서는 7,200,000 밀리초이니 2시간입니다.
watch를 설정하지 않으면 3번의 transaction이 시간차로 실행됩니다.
- WATCH 설정시
watch를 설정하기 되면 TTL 설정은 한 번으로 끝나게 됩니다.