백엔드 공부를 하면서 성능을 더 높이기 위해 빠르게 조회용으로 쓰는 NoSQL에 대해서 많이 들어봤습니다. 그 중에서 대표적인 몽고 DB와 Redis가 유명한데 Redis가 무엇인지 궁금하여 찾아봤습니다.
Redis는 In-Memory 기반의 키-값
형식의 비정형 데이터 구조를 가졌습니다. 따라서 별도의 쿼리 없이도 데이터를 간단히 조회할 수 있습니다.
Redis는 크게 String, Set, Sorted Set, Hash, List 자료구조를 지원하고, 서비스의 특성에 따라서 캐시로도 사용가능하고, Persistence Data Storage로 사용할 수도 있습니다.
서비스 요청이 증가할수록, 특히 DB에서 데이터를 조회하는 경우에는 많은 부하를 줄 수 있습니다. 이런 상황에서 나중에 요청된 결과를 미리 저장해두었다가 빨리 제공하기 위해 캐시를 사용합니다.
Redis는 메모리 기반이기때문에 디스크에 비해서 용량은 적지만 접근 속도는 빠릅니다.
Look aside cache 방식
Write Back
Redis는 Collection을 제공합니다. 주로 게임에서 사용자가 많은 랭킹을 산출할 경우에도 많이 사용하고, Sorted Set
을 사용하면 랭킹 서버를 쉽게 구현가능합니다.
Redis Transaction
은 한번의 딱 하나의 명령만 수행할 수 있습니다. 이에 더하여 single-thread
특성을 유지하고 있기 때문에 다른 스토리지 플랫폼보다는 이슈가 덜합니다. 하지만 이러한 특징때문에 더블클릭 같은 동작으로 같은 데이터가 2번씩 들어가게 되는 불상사는 막을 수 없기 때문에 별도 처리가 필요합니다.
주로 인증 토큰을 저장하거나 유저 API limit을 두는 상황 등에서 Redis를 많이 사용하고 있습니다.
간단하게 어떻게 Redis를 설치하고 Spring Boot와 연계하는지 실습을 해봤습니다.
먼저, Redis를 Docker를 사용하여 설치하였습니다. 도커는 URL을 통해 설치하시면 됩니다.
api("org.springframework.boot:spring-boot-starter-data-redis")
application.yml 설정 파일에 연동할 Redis의 접속정보를 기술합니다.
spring:
redis:
host: localhost
port: 6379
스프링 부트는 기본적으로 Redis 포트를
6379
로 설정하고 있습니다.
저는 Kotlin
을 사용하여 Redis의 명령어들을 사용하였습니다. 그 중 strings
타입의 데이터를 저장하는 StringRedisTemplate
을 Spring Boot에서 사용하는 예제입니다.
@Component
class RedisRunner(val redisTemplate: StringRedisTemplate): ApplicationRunner {
override fun run(args: ApplicationArguments?) {
val values = redisTemplate.opsForValue();
values.set("junyoung", "30")
values.set("hobby", "coding study")
values.set("dream", "take a walking with my puppy")
}
}
StringRedisTemplate을 의존성으로 주입받아서 set으로 key, value
를 저장하였습니다.
opsForValue() 메서드는 value관련된 operation들을 제공하는 객체를 받아옵니다.
@EnableJpaAuditing
@SpringBootApplication
class DokotlinApplication(var boardRepository: BoardRepository) {
fun main(args: Array<String>) {
runApplication<DokotlinApplication>(*args)
}
스프링 부트 어플리케이션을 실행하면 아래와 같이 key-value
가 들어간것을 Redis Client에서 확인이 가능합니다.
keys *
명령어는 Redis에 저장된 key 값들을 전부 조회하는 명령어로 테스트를 위해서 사용하였지만, 실무에서는 사용하지 않을 것을 권고하고 있습니다.
get hobby 명령어를 실행하면 hobby에 매칭되는 value인 "coding study"가 나오는 것을 확인할 수 있었습니다.
이번에는 Entity를 Redis에 저장하는 예제를 살펴보겠습니다.
@RedisHash(value = "fund")
class Fund(
@Id
val id: Long? = null
) {
val name = "name-$id"
}
interface FundRedisRepository: CrudRepository<Fund, Long>
Redis 엔티티 Fund를 정의하였습니다.
@RedisHash("fund")
으로 해당 엔티티가 Redis 엔티티임을 명시하였습니다.
@Id 어노테이션이 적용된 맴버변수에 값이 실제 hash_id가 됩니다. 만약 null 값을 주게 된다면 Redis 내부적으로 random한 값을 넣어주게 됩니다.
앞으로 Fund라는 엔티티 데이터들을 Redis에 무수히 저장이 될텐데, 이 엔티티들만 보관하는 하나의 Hash 키 값이 @RedisHash("fund")
이고, 이 Hash 공간에서 각 엔티티들이 fund:hash_id라는 키 값을 가지게 됩니다.
fund:hash_id Key와 하나 이상의
Field/Element
값으로 저장할 수 있으며, Value에는 기본적으로 strings 타입의 데이터를 저장할 수 있습니다.
즉, HashMap<String,HashMap<String,Fund>> 구조로 구성되어 있습니다.
이제 테스트 코드를 작성하여 실제 Fund 엔티티들을 Redis에 저장해봤습니다.
@SpringBootTest
class FundRedisRepositoryTests {
@Autowired
lateinit var fundRedisRepo: FundRedisRepository
@Test
@DisplayName("펀드 엔티티들을 Redis에 저장한다")
fun saveFundWithRedis() {
val funds = (1..10).map {
Fund(
id = it.toLong()
)
}
fundRedisRepo.saveAll(funds)
}
}
Redis를 GUI로 쉽게 접근할 수 있는 Medis
라는 Tool을 사용해서 결과를 확인해봤습니다.
짜잔... Fund 엔티티들이 Redis에 저장되었습니다. 아래 이미지를 보면 Redis에 총 11개의 key 값이 존재합니다.
fund
는 위에서 Redis 엔티티를 정의할 때 작성한 @RedisHash("fund")
를 의미합니다. 그 외에 뒤에 붙은 1 ~ 10까지의 숫자들은 hash_id를 의미합니다.
타입을 살펴보면 fund key는 Set
타입이고,
fund:hash_id key는 Hash
타입입니다.
Hash 타입의 데이터를 처리할 때는 hmset, hget, hgetall, hkey, hlen
명령어를 사용합니다.
대표적으로 아래 3개의 명령어를 찾아봤습니다.
hget hash_id field: 해당 key에 대한 필드의 value를 검색
hgetall hash_id: 해당 key에 대한 모든 field와 value들을 검색
hexists hash_id field: 해당 key에 대한 field가 존재하는지 여부 확인
참조 사이트: https://velog.io/@max9106/Spring-Boot-Redis,https://coding-start.tistory.com/130, https://cheese10yun.github.io/redis-getting-started/