Redis Sorted Set
나는 과일 장수이다. 그리고 나는 판매하는 과일의 가격을 아래와 같이 손님들에게 보여주고 있다.
이 때, 아래와 같은 조건들을 만족해야 한다.
- 각 과일의 가격은 자주 업데이트된다.
- 각 과일은 유일(unique)하다.
- 가격의 오름차순/내림차순으로 정렬된 과일 목록을 매번 고객에게 보여주어야 한다.
이 모든 조건 안에서 가장 효율적인 자료구조를 이용하여 과일을 데이터로 저장할려고 한다.
단순히 1, 2번을 고려했을 때는 Map, Dictionary와 같은 key/value 형태의 자료구조를 쉽게 떠올릴 수 있다. key를 과일, value를 가격으로 저장하면 1, 2번에 대해 효율적으로 처리할 수 있다.
하지만 3번의 경우를 생각해보면, 매번 저장된 key, value들을 모두 꺼내서 value에 대해 정렬해야하고, 또 정렬된 각 value에 해당하는 key들을 함께 최종적으로 반환해야되므로 시간적, 공간적으로 비용이 많이 든다.
이 때 Redis에서 제공해주는 Sorted Set 자료구조를 이용하면 효율적으로 처리할 수 있다.
Sorted Set이란?
일반적으로 Set 자료구조는 저장된 value들을 unique하게 관리하기 위해 사용된다. 이 때 저장된 value들 사이의 순서는 관리되지 않는다.
하지만 Redis에서 제공해주는 자료구조 중 하나인 Sorted Set(또는 ZSET, 둘다 동일한 말이다)은, 이름 그대로 Set의 특성을 그대로 가지면서 추가적으로 저장된 value들의 순서도 관리해준다. 이 때 이 순서를 위해 각 value에 대해 score를 필요에 맞게 설정할 수 있으며, 이 score를 기반으로 정렬이 된다.
Sorted Set 구조
구조를 보면 우리가 쉽게 접한 단순한 key/value 형태의 자료구조보다 복잡하다. key
, member
, score
라는 새로운 용어가 등장하기 때문에 더 복잡해 보이기도 하다.
여기서 key
는 Redis의 다른 자료구조들과 마찬가지로 단순히 하나의 ZSET
에 부여되는 key 이름이다.
ZSET
은 key/value 형태의 자료구조이고, 여기서 key는 member
, value는 score
라고 부른다. 하나의 ZSET
에서 member
는 unique하고, member
값을 통해 시간복잡도 O(log(N))으로 해당하는 value에 접근할 수 있다. (ZRANK
참고)
score
은 부동 소수점 수만 허용되고, 이 score
값을 기준으로 ZSET
내의 각 원소들이 순서를 가지게 된다.
Sorted Set 명령어
Sorted Set은 다양한 명령어들을 제공해준다. 이 중에서 가장 많이 사용되는 기본 명령어들에 대해서만 한번 살펴보도록 하자. 앞의 과일 장수 예시를 이용하여 각 명령어를 실행해보자.
ZADD
ZADD key score member
먼저 ZSET
에 각 과일의 이름과 가격 데이터를 추가해보자. 과일 데이터를 저장할 ZSET
의 key
는 fruit
라고 정했다.
127.0.0.1:6379> ZADD fruit 2 apple
(integer) 1
127.0.0.1:6379> ZADD fruit 10 strawberry
(integer) 1
복수 개의 데이터를 한번에 넣을 수도 있다.
127.0.0.1:6379> ZADD fruit 8 melon 4 orange 15 pineapple 5 banana
(integer) 4
이미 존재하는 member
값이라면 score
값을 변경한다. 만약 apple의 가격이 20$으로 올랐다면, 아래와 같이 변경하면 된다.
127.0.0.1:6379> ZADD fruit 20 apple
(integer) 0
ZSCORE
ZSCORE key member
banana의 가격은 아래와 같이 조회하면 된다.
127.0.0.1:6379> ZSCORE fruit banana
"5"
ZRANK
ZRANK key member
melon이 몇 번째로 싼 과일인지는 아래와 같이 조회하면 된다.
127.0.0.1:6379> ZRANK fruit melon
(integer) 2
ZRANGE
ZRANGE key start stop
ZRANGE
에서 start
, stop
에는 정렬된 원소들 중에서 내가 출력하고 싶은 원소의 시작 위치와 끝 위치를 넣으면 된다. 한가지 유념해야 할점은 첫번째 원소를 0이라 했을 때의 상대적인 위치값이고, 양수/음수 모두 가능하다.
ZRANGE
를 이용하여 전체 과일을 가격이 낮은 순서대로 모두 출력하고자 한다.
127.0.0.1:6379> ZRANGE fruit 0 -1
1) "orange"
2) "banana"
3) "melon"
4) "strawberry"
5) "pineapple"
6) "apple"
가격이 가장 낮은 과일, 가장 높은 과일만을 출력하고 싶다면,
127.0.0.1:6379> ZRANGE fruit 0 0
1) "orange"
127.0.0.1:6379> ZRANGE fruit -1 -1
1) "apple"
가격이 낮은 순서대로 나열했을 때, 2번째부터 3번째까지의 과일을 출력하고 싶다면,
127.0.0.1:6379> ZRANGE fruit 2 3
1) "melon"
2) "strawberry"
만약 과일의 가격도 함께 출력하고 싶다면, WITHSCORES
옵션을 추가하면 된다.
127.0.0.1:6379> ZRANGE fruit 0 -1 WITHSCORES
1) "orange"
2) "4"
3) "banana"
4) "5"
5) "melon"
6) "8"
7) "strawberry"
8) "10"
9) "pineapple"
10) "15"
11) "apple"
12) "20"
가격이 높은 순서대로 출력하고 싶다면, ZREVRANGE
명령어를 이용하자.
127.0.0.1:6379> ZREVRANGE fruit 0 -1 WITHSCORES
1) "apple"
2) "20"
3) "pineapple"
4) "15"
5) "strawberry"
6) "10"
7) "melon"
8) "8"
9) "banana"
10) "5"
11) "orange"
12) "4"
ZRANGEBYSCORE
ZRANGEBYSCORE key min max
6$ 이상 15$ 이하 가격의 과일들을 가격이 낮은 순서대로 출력하고자 한다.
127.0.0.1:6379> ZRANGEBYSCORE fruit 6 15 WITHSCORES
1) "melon"
2) "8"
3) "strawberry"
4) "10"
5) "pineapple"
6) "15"
5$ 초과 15$ 미만 가격의 가격의 과일들을 가격이 낮은 순서대로 출력하고자 한다면,
127.0.0.1:6379> ZRANGEBYSCORE fruit (5 (15 WITHSCORES
1) "melon"
2) "8"
3) "strawberry"
4) "10"
가격 상관없이 모든 과일들을 가격이 낮은 순서대로 출력하고자 한다면,
127.0.0.1:6379> ZRANGEBYSCORE fruit -inf +inf
1) "orange"
2) "banana"
3) "melon"
4) "strawberry"
5) "pineapple"
6) "apple"
10$ 보다 비싼 모든 과일들을 가격이 높은 순서대로 출력하고자 한다면,
(ZREVRANGEBYSCORE
에서는 min
과 max
의 위치가 ZRANGEBYSCORE
에서와 반대다.)
127.0.0.1:6379> ZREVRANGEBYSCORE fruit +inf (10 WITHSCORES
1) "apple"
2) "20"
3) "pineapple"
4) "15"
ZREM
ZREM key member
pineapple을 더이상 판매하지 않기로 하여 pineapple 데이터를 지울려고 한다.
127.0.0.1:6379> ZREM fruit pineapple
(integer) 1
127.0.0.1:6379> ZRANGE fruit 0 -1
1) "orange"
2) "banana"
3) "melon"
4) "strawberry"
5) "apple"
ZADD
와 마찬가지로 한번에 복수개의 삭제도 가능하다.
127.0.0.1:6379> ZREM fruit strawberry orange
(integer) 2
127.0.0.1:6379> ZRANGE fruit 0 -1
1) "banana"
2) "melon"
3) "apple"