Redis?
싱글스레드로 동작하는 오픈소스 인 메모리 key - value 데이터 저장소입니다.
Why Redis?
- 주 메모리에 데이터를 상주시킴으로서 디스크에 액세스 할 필요 없이 빠른 I/O 를 제공합니다.
- 다양한 기능 및 다양한 자료구조를 제공함으로서, 다양한 방법으로 활용 할 수 있습니다.
- In-memory 데이터베이스지만, 디스크에 스냅샷을 저장함으로서 안정성을 확보합니다.
- Replication 을 지원함으로서 고 가용성을 확보할 수 있습니다.
When Redis?
몇가지 사용사례를 살펴보고 아래에서 사용사례별 어떤 자료구조를 사용하면 좋은지 알아보겠습니다.
- Cashing: 디스크 기반의 RDBMS 나 NoSQL 앞에 배치해 특정 요청에 대한 디스크 액세스를 최소화 할 수 있습니다.
- Session: JWT 기반의 Sateless 한 어플리케이션이 아닌 경우, 세션 정보를 레디스에 저장함으로서 여러 서버에서 공유할 수 있습니다.
- 대기열: 순서를 보장하는 자료구조를 사용하여 어플리케이션 앞단에서 많은 요청에 대한 작업의 대기열로 활용할 수 있습니다.
그 밖에도 Pub/Sub 을 통한 서버간 메시징 혹은 채팅 서비스 개발등 다양한 방면으로 사용할 수 있습니다.
자료구조
자료구조 설명에 들어가기 전에, 로컬에서 간단히 실습해볼 환경을 세팅하겠습니다.
redis 이미지 설치
docker pull redis
확인
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
sonarqube latest 64f59e1fcc5b 5 months ago 560MB
redis <none> ddcca4b8a6f0 7 months ago 105MB
mysql 8.0.25 5c62e459e087 8 months ago 556MB
redis 컨테이너 데몬으로 실행
docker run -d --name rediscontainer redis
redis-cli 로 redis 컨테이너 접속
docker exec -it rediscontainer redis-cli
127.0.0.1:6379>
String
키와 값이 1:1로 매칭되는 단순 문자열 입니다.
SET O(1)
key 에 String 자료구조를 넣습니다.
이미 존재하는 key 에 SET 할 경우, 값이 대체됩니다.
127.0.0.1:6379> set test "hello world"
OK
GET O(1)
key 에 존재하는 String 값을 가져옵니다.
127.0.0.1:6379> get test
"hello world"
DEL O(1)
해당 key 에 매칭되는 값을 제거합니다. String 자료구조 뿐만이 아닌 모든 자료구조에서 사용 가능합니다.
127.0.0.1:6379> del test
(integer) 1
127.0.0.1:6379> get test
(nil)
Expire
해당 키 값의 만료시간을 지정합니다. 만료시간이 다 될 경우, 자동으로 삭제됩니다.
ttl 명령어로 남은 만료 시간을 조회할 수 있습니다. 역시 다른 자료구조에도 적용 가능합니다.
127.0.0.1:6379> set test "hello world"
OK
127.0.0.1:6379> expire test 5 (= set test "hello world" ex 5)
(integer) 1
127.0.0.1:6379> ttl test
(integer) 2
List
순서를 보장하는 배열 형태의 자료구조입니다.
흔히 생각하기 좋은 List 보다는 Dequeue (deck) 과 가까운 기능을 제공합니다.
LPUSH, RPUSH O(1)
리스트의 좌 우에서 데이터를 넣어줍니다.
해당하는 키에 이미 생성된 리스트가 없을 경우 새로 생성하고 데이터를 넣습니다.
RPUSH list A
LPUSH list B
LRANGE O(S+N)
특정 인덱스에서 특정 인덱스 까지의 요소를 가져옵니다. -1 은 모든 요소를 뜻합니다.
127.0.0.1:6379> LRANGE list 0 -1
1) "B"
2) "A"
LPOP, RPOP O(1)
리스트의 좌우에서 데이터를 꺼내고 삭제합니다.
127.0.0.1:6379> LPOP list
"B"
127.0.0.1:6379> LRANGE list 0 -1
1) "A"
LSET O(N)
리스트의 특정 인덱스 값을 변경합니다. O(N)
127.0.0.1:6379> LRANGE list 0 -1
1) "E"
2) "D"
3) "C"
127.0.0.1:6379> LSET list 3 KKK
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "E"
2) "D"
3) "C"
4) "KKK"
LTRIM O(N)
리스트의 특정 인덱스부터 특정 인덱스 까지의 데이터만 남기고 모두 제거합니다.
LTRIM list 1 2
BLPOP, BRPOP
리스트에 요소가 없으면 지정한 타임아웃만큼 Blocking 대기 있을시 바로 POP 합니다.
127.0.0.1:6379> LPUSH list 1 2
(integer) 2
127.0.0.1:6379> BLPOP list timeout 5
1) "list"
2) "2"
127.0.0.1:6379> BLPOP list timeout 5
1) "list"
2) "1"
127.0.0.1:6379> BLPOP list timeout 5
(nil)
(5.04s)
대략 살펴본 명령어들의 시간복잡도로 알 수 있지만, 레디스의 List 는 앞 혹은 뒤가 아닌 중간의 데이터에 접근하는 명령어들은 상당히 느립니다. 그러므로 Queue 나 Stack 역할을 하는 자료구조가 필요한 경우 유용하게 사용할 수 있습니다.
예를들면 특정 갯수의 최근 방문한 페이지 목록을 뿌려주는 기능등을 구현하는데 이용할 수 있겠습니다.
또한 BLPOP, BRPOP 명령어를 이용하여 메시지를 리스닝하는 메시징 큐도 간단히 구현할 수 있습니다.
Hash
Redis Hash 자료구조는 하나의 키에 여러개의 값을 저장할 수 있는 Field - Value 의 집합 쌍입니다.
같은 키에 존재한다고 해서 서브 키 요소간 필드가 일치할 필요는 없어 비정형 데이터를 저장할 수도 있습니다.
HMSET O(1)
Key 에 Hash 를 저장합니다.
127.0.0.1:6379> hmset user:1 name 김개똥 birth 1993
OK
HGET, HMGET, HGETALL O(1)
HGET 은 Key 에 저장된 Hash 의 특정 필드를 가져옵니다.
127.0.0.1:6379> hget user:1 birth
"1993"
HMGET 은 다수의 필드를 한번에 가져올 수 있습니다.
127.0.0.1:6379> hmget user:1 birth name
1) "1993"
2) "김개똥"
HGETALL 은 해당 키의 모든 필드를 가져옵니다.
127.0.0.1:6379> hgetall user:1
1) "name"
2) "김개똥"
3) "birth"
4) "1993"
Redis Hash 의 개별 필드에 개별 명령을 줄 수도 있습니다.
해당 명령들은 https://redis.io/commands#hash를 참조합시다.
해시는 키 하나에 여러 데이터를 함께 저장할 수 있으므로 정형화 되지 않은 데이터를 순서에 상관없이 키에 바로 접근하여 사용하고 싶을때 사용하면 유용할 것 같습니다. 예를들면 로그인 중에만 유효한 장바구니를 구현하고 싶다면, 생명주기가 짧은 데이터를 위해 RDBMS 에 접근하는게 부담스러울 경우가 있습니다. 이런 경우 Hash 를 이용하여 장바구니 페이지를 위한 상품정보를 담아 구현할 수도 있을 것 같습니다.
SET
SET 은 중복을 허용하지 않는 순서가 없는 STRING 의 집합입니다.
내부 엘리먼트 수와 상관없이 어떤 특정 데이터에 접근하는 시간은 O(1) 로 동일합니다.
SADD O(1)
Key 에 엘리먼트를 추가합니다. Key 에 이미 존재하는 SET이 없다면, 생성합니다.
127.0.0.1:6379> SADD set 1
(integer) 1
127.0.0.1:6379> SADD set A
(integer) 1
SMEMBERS O(1)
KEY의 전체 엘리먼트를 조회합니다.
127.0.0.1:6379> SMEMBERS set
1) "1"
2) "A"
SISMEMBER O(1)
Key에 해당 엘리먼트가 존재하는지 확인합니다.
127.0.0.1:6379> SISMEMBER set A
(integer) 1
127.0.0.1:6379> SISMEMBER set B
(integer) 0
SCARD O(1)
Key에 존재하는 엘리먼트 수를 확인합니다.
127.0.0.1:6379> SCARD set
(integer) 2
SREM O(1)
Key에서 특정 엘리먼트를 제거합니다.
127.0.0.1:6379> SREM set A
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "1"
SPOP, SRANDMEMBER O(1)
둘 모두 Key 에서 랜덤으로 특정 엘리먼트를 반환합니다. 다만 POP 은 반환후 제거합니다.
SMOVE O(1)
특정 KEY 에서 다른 KEY 로 엘리먼트를 이동시킵니다.
이외에도
SDIFF O(N) 다수 키중 중복되는 엘리먼트를 제외하고 반환
SDIFFSTORE O(N) 다수 키 중 중복되는 엘리먼트를 제외한 것을 새로운 키에 저장
SINTER O(N) 다수 키에서 중복되는 멤버만을 반환
SUNION O(N) 다수 키의 모든 엘리먼트를 반환 (중복 엘리먼트는 한번만 조회)
등 여러 기능을 제공합니다.
Set 은 중복불가와 순서가 중요하지 않은 데이터와
여러개의 자료구조의 연산으로 중복을 추출, 제거 하는 기능에 사용하면 적절합니다.
한번만 누를 수 있는 코멘트의 좋아요 처리나, 일일 사용자의 방문 집계를 예로 들 수 있습니다.
SortedSet
SortedSet 은 Set 과 동일하게 중복을 허용안하는 집합이지만
Set 과는 다르게 score 라는 가중치에 따른 정렬이 가능하다는 차이점이 있습니다.
ZADD O(log(N))
SortedSet 에 요소를 추가하는 명령어입니다. ZADD [키] [가중치] [값] 으로 입력합니다.
정렬 과정이 필요하기 때문에 O(log(N)) 의 시간복잡도를 지닙니다.
이미 존재하는 값에 대하여 명령어를 입력하면, 가중치를 변경할 수 있습니다.
127.0.0.1:6379> ZADD sset 10 A 7 B 15 C 3 D
(integer) 4
127.0.0.1:6379> ZADD sset 8 A # A 가중치 8 로 변경
ZSCORE O(1)
특정 요소의 가중치를 조회합니다.
127.0.0.1:6379> ZSCORE sset A
"8"
ZRANK O(log (N))
특정 요소의 가중치의 랭킹을 조회합니다.
127.0.0.1:6379> ZRANK sset A
(integer) 2
127.0.0.1:6379> ZRANK sset D
(integer) 0
ZRANGE [key] O (log(N) + M) {N = 요소수, M = 반환된 요소 수}
특정 등수의 인덱스를 가진 요소로부터 가중치순으로 조회합니다.
127.0.0.1:6379> ZRANGE sset 1 1
1) "B"
127.0.0.1:6379> ZRANGE sset 1 2
1) "B"
2) "A"
127.0.0.1:6379> ZRANGE sset 1 -1
1) "B"
2) "A"
3) "C"
이외에도 ZRANGEBYSCORE, ZRANGEBYLEX 등의 반환 명령어들이 있습니다.
ZREM O(M * log (N))
키에 저장된 특정 엘리먼트를 제거합니다
127.0.0.1:6379> ZREM sset A
(integer) 1
127.0.0.1:6379> ZREM sset B D
(integer) 2
SortedSet 의 강력함은 가중치에 의한 정렬에 있습니다. 가중치를 어떤 데이터로 선택하느냐에 따라 다양한 방법으로 활용될 수 있습니다.
가장 간단한 예로는 게임이나 이벤트 페이지의 포인트 랭킹보드를 구현하는데 사용할 수 있겠습니다. 포인트를 가중치로 유저아이디를 요소로 넣으면 동일 유저에 대한 중복제거와 포인트에 대한 정렬을 별도의 구현없이 수행할 수 있습니다.
레디스는 빠른 데이터 액세스와 다양한 자료구조를 제공함이 장점입니다.
하지만 메모리 기반의 저장소며, 싱글스레드로 구현되어있어 한 요청에서 병목이 생기면 전체적인 장애로 이어질 수 있습니다.
각 요청의 시간복잡도를 이해하고 있고, O(N) 이상의 요청을 할 때에는 요청량이 얼마나 될 것인지 데이터양은 얼마정도로 유지될 것인지를 면밀히 파악해볼 필요가 있습니다.
'Database' 카테고리의 다른 글
[REAL MYSQL 8.0] InnoDB 스토리지 아키텍쳐 (0) | 2022.04.04 |
---|---|
RDS Slow Query Log 세팅 및 쿼리 개선 (0) | 2022.03.22 |
[REAL MYSQL 8.0] MySQL 엔진 아키텍쳐 - (1) (0) | 2022.03.14 |
[Real MySQL 8.0] 계정과 역할 (0) | 2022.03.08 |
[Real MySQL 8.0] MySQL 서버 설정과 시스템 변수 (0) | 2022.03.05 |