티스토리 뷰
1. Lettuce를 사용하는 경우에 MGET 동작 방식에 대해 이해하기
[ Redis MGET 명령어와 CrossSlot 에러 ]
MGET 이란 Multi-Get의 줄임말로, 여러 개의 key에 대하여 GET 요청을 보내는 방식을 의미한다. 그러면 레디스는 각각의 GET 요청에 대한 응답을 목록으로 반환하여 제공해준다.
MGET key1 key2 key3 key4
1) "value1"
2) "value2"
3) "value3"
4) "value4"
MGET 요청을 보낼 때는 주의할 점이 하나 있는데, 레디스 클러스터 환경에서는 MGET 요청으로 전달되는 key들이 항상 동일한 슬롯을 향해야 한다는 것이다. 레디스 클러스터 환경에는 키를 특정 노드에 배분하는 단위인 슬롯(Slot)이라는 개념이 존재한다. 기본적으로 16384개의 슬롯으로 구성되며, 각각의 클러스터가 슬롯을 다음과 같이 나눠 갖는다.
레디스는 요청 키를 CRC 16 해싱한 값으로 슬롯 번호를 구하고, 해당 슬롯이 존재하는 레디스 클러스터에 요청을 보내게 된다. 따라서 기본적인 레디스 구현에 따르면, MGET 요청이 서로 다른 슬롯을 향하게 될 경우, 다음과 같은 에러를 만나게 된다.
mangkyu@mangkyu-1 ~ % redis-cli mget key1 key2 key3 key4 key5
(error) CROSSSLOT Keys in request don't hash to the same slot
[ Lettuce 클라이언트를 사용하는 경우의 MGET 동작 방식 ]
자바 진영에서는 레디스와 통신하기 위해 레디스 클라이언트(Redis Client) 중 하나인 Lettuce가 많이 사용된다. 그렇다면 Lettuce는 MGET 동작을 어떻게 처리하는지 살펴보도록 하자.
다음과 같이 {0}MangKyu0, {1}MangKyu1, … {98}MangKyu98, {99}MangKyu99 과 같이 문자열을 만들고, Lettuce를 사용하는 redisTemplate을 통해 MGET 요청을 보낸다고 하자. 이러한 요청을 Lettuce는 어떻게 처리할까?
fun mget(): List<Long> {
val set: MutableSet<String> = HashSet()
for (i in 0 until 100) {
val tmp = "{${i}}" + "MangKyu${i}"
set.add(tmp)
}
return redisTemplate.opsForValue().multiGet(set) ?: emptyList<>()<>()
}
Wireshark 도구를 이용해 TCP 패킷을 분석해보면, 다음과 같이 슬롯 단위로 N번 연산을 보내게 됨을 확인할 수 있다.
Redis CLI에서는 CROSSSLOT 에러가 발생한 반면 Lettuce에서는 이러한 에러가 발생하지 않기에, 해당 처리에 대한 부분을 Lettuce가 내부적으로 지원하고 있음을 추측할 수 있다. 실제로 Lettuce의 MGET 구현을 살펴보면, 다음과 같이 요청하는 키들을 슬롯 단위로 구분하고, 레디스에 MGET 요청을 전송하도록 해두었음을 확인할 수 있다.
[ Lettuce 클라이언트에서 MGET 사용 시의 주의 사항 ]
앞서 살펴보았듯 Lettuce 클라이언트는 MGET 요청을 보내는 경우에 슬롯 단위로 MGET을 구분하여 레디스로 요청함을 확인할 수 있었다. 따라서 Lettuce에서 MGET 요청을 보낼 때, 슬롯 관련 처리를 적절하게 하지 않으면 예상치 못한 과도한 요청이 발생할 수 있다.
레디스에는 해시 태그(hash tag)라는 것이 존재하는데, 요청을 보내는 키에 다음과 같이 {} 중괄호로 요청을 처리할 해시 슬롯을 계산할 값을 지정해줄 수 있다.
fun mget(): List<Long> {
val set: MutableSet<String> = HashSet()
for (i in 0 until 100) {
val tmp = "{${i % 10}}" + "MangKyu${i}"
set.add(tmp)
}
return redisTemplate.opsForValue().multiGet(set) ?: emptyList<>()<>()
}
MGET 요청의 키들이 지나치게 별도의 해시 슬롯에 배치되지 않도록 해시 태그를 위와 같이 적절히 연산해주면, 다음과 같이 레디스의 MGET 요청을 어느 정도 제어할 수 있다. 이를 통해 특정 레디스 슬롯으로 MGET 연산의 범위를 좁혀 효율적인 처리 방식으로 구현 할 수 있는 것이다.
'Server' 카테고리의 다른 글
[Server] Redis 메모리 사용량 추정하기(Redis Memory Usage Estimation) (0) | 2025.03.04 |
---|---|
[Server] 데이터 중심의 세상과 객체 지향 프로그래밍(Data-Oriented and Object-Oriented Programming) (2) | 2024.11.12 |
[Server] 객체에게 역할과 책임을 부여하는 객체 지향 프로그래밍(Object-Oriented Programming) (3) | 2024.11.05 |
[Server] 진짜 중복과 가짜 중복의 구분(중복 여부를 판단하는 기준) (4) | 2024.10.22 |
[Server] 비즈니스 정책과 입력 데이터, 서로 다른 데이터 검증 및 유효성 검사(Validation) (7) | 2024.10.01 |