Redis 자료 구조 - List
- Development/Hadoop, NoSQL, BigData
- 2021. 5. 10.
개요
Redis에서 List는 여러 개의 데이터를 저장하기 위한 자료구조이다. 쉽게 생각해서 Linked List를 생각하게 될텐데, 지원하는 기능을 바탕으로 보면, 오히려 Dequee(Double Ended Queue)에 가깝다. Qeueue의 인터페페이스와 Stack의 인터페이스를 동시에 지원하기 때문이다. Stack 혹은 Queue의 말단 연산은 비용이 닞지만, 중간 데이터를 처리하는 연산은 비용이 상대적으로 높다. 따라서, 내가 수행해야 하는 오퍼레이션의 종류 및 데이터 성격이 어떤지 알고 쓰면 좋을 것이다.
명령어
List가 제공하는 전체 명령어 중 주요 기능은 아래와 같다.
LRANGE
127.0.0.1:6379> help lrange
LRANGE key start stop
summary: Get a range of elements from a list
since: 1.0.0
group: list
List에 저장된 값을 조회하는 명령어이다. 전체를 조회하려면 start에 0을, stop에 -1을 지정한다.
127.0.0.1:6379> lrange l1 0 -1
1) "e3"
2) "e2"
3) "e1"
LPUSH, RPUSH
127.0.0.1:6379> help lpush
LPUSH key element [element ...]
summary: Prepend one or multiple elements to a list
since: 1.0.0
group: list
127.0.0.1:6379> help rpush
RPUSH key element [element ...]
summary: Append one or multiple elements to a list
since: 1.0.0
group: list
List의 왼쪽으로 데이터를 넣고 빼는 기능이다.
// l1에 아이템 하나 넣기
127.0.0.1:6379> lpush l1 e1
(integer) 1
// l1에 아이템 여러 개 넣기
127.0.0.1:6379> lpush l1 e2 e3
(integer) 3
// l1 데이터 조회
127.0.0.1:6379> lrange l1 0 -1
1) "e3"
2) "e2"
3) "e1"
위에서 수행한 작업은 왼쪽에 계속 push를 했으므로 다음과 같은 형태가 된다.
e3 -> e2 -> e1
이번에는 오른쪽에 데이터를 넣어보면,
// l1에 아이템 하나 넣기
127.0.0.1:6379> rpush l1 e4
(integer) 4
// l1에 아이템 여러 개 넣기
127.0.0.1:6379> rpush l1 e5 e6
(integer) 6
// l1 데이터 조회
127.0.0.1:6379> lrange l1 0 -1
1) "e3"
2) "e2"
3) "e1"
4) "e4"
5) "e5"
6) "e6"
따라서, 전체 데이터는 다음과 같은 모양이 된다.
e3 -> e2 -> e1 -> e4 -> e5 -> e6
LPOP, RPOP
127.0.0.1:6379> help lpop
LPOP key [count]
summary: Remove and get the first elements in a list
since: 1.0.0
group: list
127.0.0.1:6379> help rpop
RPOP key [count]
summary: Remove and get the last elements in a list
since: 1.0.0
group: list
데이터가 미리 다음과 같이 존재하는 상태에서, LPOP과 RPOP을 각각 수행해 본다.
// 초기 데이터
127.0.0.1:6379> lrange l1 0 -1
1) "e3"
2) "e2"
3) "e1"
4) "e4"
5) "e5"
6) "e6"
// lpop 1회 수행
127.0.0.1:6379> lpop l1
"e3"
// lpop에 카운트 지정 (2)
127.0.0.1:6379> lpop l1 2
1) "e2"
2) "e1"
// 중간 데이터 확인 (rpop 수행전)
127.0.0.1:6379> lrange l1 0 -1
1) "e4"
2) "e5"
3) "e6"
// rpop에 카운트 지정 (2)
127.0.0.1:6379> rpop l1 2
1) "e6"
2) "e5"
// 최종 데이터 확인
127.0.0.1:6379> lrange l1 0 -1
1) "e4"
LLEN
127.0.0.1:6379> help llen
LLEN key
summary: Get the length of a list
since: 1.0.0
group: list
위의 테스트를 수행하고 남은 데이터로 길이를 확인해 보자.
// 데이터 확인
127.0.0.1:6379> lrange l1 0 -1
1) "e4"
// 데이터 길이 확인
127.0.0.1:6379> llen l1
(integer) 1
LPUSHX, RPUSHX
127.0.0.1:6379> help lpushx
LPUSHX key element [element ...]
summary: Prepend an element to a list, only if the list exists
since: 2.2.0
group: list
127.0.0.1:6379> help rpushx
RPUSHX key element [element ...]
summary: Append an element to a list, only if the list exists
since: 2.2.0
group: list
lpush, rpush를 수행하는데 list가 존재할 경우에만 각각 push를 수행한다.
// l2라는 list는 존재하지 않는 상태
127.0.0.1:6379> lrange l2 0 -1
(empty array)
// lpushx 시도
127.0.0.1:6379> lpushx l2 e1
(integer) 0
// l2가 생성되지 않음
127.0.0.1:6379> llen l2
(integer) 0
rpushx도 lpushx와 동일한 결과를 보인다.
LINSERT, LINSERTX
127.0.0.1:6379> help linsert
LINSERT key BEFORE|AFTER pivot element
summary: Insert an element before or after another element in a list
since: 2.2.0
group: list
127.0.0.1:6379> help linsertx
list내 존재하는 값을 기준으로 insert를 수행한다. before 혹은 after를 지정하여 삽입할 위치를 구체화한다.
// 기본 list 준비
127.0.0.1:6379> rpush l2 e1 e2 e3
(integer) 3
127.0.0.1:6379> lrange l2 0 -1
1) "e1"
2) "e2"
3) "e3"
// e1 뒤에 e1.5 추가
127.0.0.1:6379> linsert l2 after e1 e1.5
(integer) 4
127.0.0.1:6379> lrange l2 0 -1
1) "e1"
2) "e1.5"
3) "e2"
4) "e3"
// e3앞에 e2.5 추가
127.0.0.1:6379> linsert l2 before e3 e2.5
(integer) 5
127.0.0.1:6379> lrange l2 0 -1
1) "e1"
2) "e1.5"
3) "e2"
4) "e2.5"
5) "e3"
// e2를 하나 더 추가하여, list내 e2가 여러개 존재하도록 변경
127.0.0.1:6379> rpush l2 e2
(integer) 6
127.0.0.1:6379> lrange l2 0 -1
1) "e1"
2) "e1.5"
3) "e2"
4) "e2.5"
5) "e3"
6) "e2"
// 중복된 값이 여러개인 상황에서 insert 시도
127.0.0.1:6379> linsert l2 before e2 e1.8
(integer) 7
// 앞에서부터 먼저 발견된 element를 기준으로 insert 수행
127.0.0.1:6379> lrange l2 0 -1
1) "e1"
2) "e1.5"
3) "e1.8"
4) "e2"
5) "e2.5"
6) "e3"
7) "e2"
LINDEX, LPOS
127.0.0.1:6379> help lindex
LINDEX key index
summary: Get an element from a list by its index
since: 1.0.0
group: list
127.0.0.1:6379> help lpos
LPOS key element [RANK rank] [COUNT num-matches] [MAXLEN len]
summary: Return the index of matching elements on a list
since: 6.0.6
group: list
인덱스로 값을 조회하는 명령어(index)와, 값으로 인덱스를 조회하는 명령어(pos)이다.
// 기초 데이터
127.0.0.1:6379> lrange l2 0 -1
1) "e1"
2) "e1.5"
3) "e1.8"
4) "e2"
5) "e2.5"
6) "e3"
7) "e2"
// index = 3 (4번째) 요소 조회
127.0.0.1:6379> lindex l2 3
"e2"
// e2의 index 확인 (e2가 두 개 있는 상태)
127.0.0.1:6379> lpos l2 e2
(integer) 3
// e2의 전체 index 확인 (일치하는 데이터 여러 개)
// count 값에 원하는 숫자를 쓰면, 그 갯수만큼 리턴한다.
// 0을 지정하면, 전체 일치하는 index를 리턴한다.
127.0.0.1:6379> lpos l2 e2 count 0
1) (integer) 3
2) (integer) 6
// 순위를 지정하여 조회하기
127.0.0.1:6379> lpos l2 e2 rank 1
(integer) 3
127.0.0.1:6379> lpos l2 e2 rank 2
(integer) 6
// list의 검색 범위를 지정하여 시스템 보호하기: 지정된 갯수내에서만 찾기
// 5개 내에서 e2의 pos 찾아보기
127.0.0.1:6379> lpos l2 e2 maxlen 5
(integer) 3
LSET
127.0.0.1:6379> help lset
LSET key index element
summary: Set the value of an element in a list by its index
since: 1.0.0
group: list
list내 특정 요소(인덱스로 대상지정)의 값을 다른 값으로 변경한다.
// index = 4 (5번째)의 값을 변경한다.
127.0.0.1:6379> lset l2 4 e2.3
OK
127.0.0.1:6379> lrange l2 0 -1
1) "e1"
2) "e1.5"
3) "e1.8"
4) "e2"
5) "e2.3"
6) "e3"
7) "e2"
LTRIM
127.0.0.1:6379> help ltrim
LTRIM key start stop
summary: Trim a list to the specified range
since: 1.0.0
group: list
trim이라는 단어 자체가 불필요한 부분을 쳐내는 것이다. java에서 trim()는, 좌우의 공백을 제거해 주는 함수인데, Redis에서는 공백을 지워주는 함수는 아니다. 의도하는 기능과 파라미터의 의미를 주의깊게 살펴봐야 하는 명령어이다. start ~ stop 외의 데이터를 불필요한 데이터로 보고 지워버리는 명령어이다.
주의-1) start~stop까지를 지우는 것이 아니다.
주의-2) 일치하지 않는 범위의 start-stop을 지정하면 전체 데이터가 삭제된다. (위험)
// list 신규 생성
127.0.0.1:6379> lrange l3 0 -1
(empty array)
// 기본 데이터 생성
127.0.0.1:6379> rpush l3 e1 e2 e3 e4 e5
(integer) 5
// 5개 데이터 생성 확인
127.0.0.1:6379> lrange l3 0 -1
1) "e1"
2) "e2"
3) "e3"
4) "e4"
5) "e5"
// start = 1, stop = 3 (index 기준으로 1~3을 제외하고 삭제: 즉 0, 4가 삭제됨)
127.0.0.1:6379> ltrim l3 1 3
OK
// 기존 index 1-3만 존재하는 것를 확인
127.0.0.1:6379> lrange l3 0 -1
1) "e2"
2) "e3"
3) "e4"
// 데이터가 위의 세 개 밖에 없는 상황에서 다른 index의 데이터를 삭제 시도
127.0.0.1:6379> ltrim l3 4 5
OK
// 망함: 4, 5를 지우라고 했는데 그 앞에 것들까지 다 지워져 버렸음
127.0.0.1:6379> lrange l3 0 -1
(empty array)
인덱스를 음수로 주면, 방향이 바뀌게 된다.
- 양의 인덱스: 0 1 2 3 4
- 음의 인덱스: -5 -4 -3 -2 -1
따라서, 아래와 같이 start = -2, stop = -1를 지정하면, 맨 우측의 두 개를 제외하고 나머지를 삭제하는 결과를 기대할 수 있다.
// ltrim시 start, stop을 음수로 줄 경우
127.0.0.1:6379> lrange l3 0 -1
1) "e1"
2) "e3"
3) "e4"
4) "e5"
5) "e6"
127.0.0.1:6379> ltrim l3 -2 -1
OK
127.0.0.1:6379> lrange l3 0 -1
1) "e5"
2) "e6"
LREM
127.0.0.1:6379> help lrem
LREM key count element
summary: Remove elements from a list
since: 1.0.0
group: list
list에서 어떤 요소를 삭제하는 명령어이다. 같은 값을 갖는 아이템을 기준으로 count는 삭제할 갯수를 의미한다.
- count > 0: list의 왼쪽부터 count만큼 삭제
- count = 0: list내에서 일치하는 값을 갖는 모든 아이템을 삭제
- count < 0: list의 오른쪽부터 count만큼 삭제
e1 -> e2 -> e3 -> e2 -> e4 -> e5 -> e2 -> e2 -> e6
라는 list가 있다고 가정했을 때,
// 초기화
127.0.0.1:6379> lrange l3 0 -1
(empty array)
// 기초 데이터 생성
127.0.0.1:6379> rpush l3 e1 e2 e3 e2 e4 e5 e2 e2 e6
(integer) 9
// 데이터 상태 확인
127.0.0.1:6379> lrange l3 0 -1
1) "e1"
2) "e2"
3) "e3"
4) "e2"
5) "e4"
6) "e5"
7) "e2"
8) "e2"
9) "e6"
// 왼쪽으로부터 한 개 삭제
127.0.0.1:6379> lrem l3 1 e2
(integer) 1
127.0.0.1:6379> lrange l3 0 -1
1) "e1"
2) "e3"
3) "e2"
4) "e4"
5) "e5"
6) "e2"
7) "e2"
8) "e6"
// 오른쪽으로부터 한 개 삭제
127.0.0.1:6379> lrem l3 -1 e2
(integer) 1
127.0.0.1:6379> lrange l3 0 -1
1) "e1"
2) "e3"
3) "e2"
4) "e4"
5) "e5"
6) "e2"
7) "e6"
// 일치하는 요소 전체 삭제
127.0.0.1:6379> lrem l3 0 e2
(integer) 2
127.0.0.1:6379> lrange l3 0 -1
1) "e1"
2) "e3"
3) "e4"
4) "e5"
5) "e6"
RPOPLPUSH
127.0.0.1:6379> help rpoplpush
RPOPLPUSH source destination
summary: Remove the last element in a list, prepend it to another list and return it
since: 1.2.0
group: list
기능 설명에 나와 있는 것처럼, source list로부터 rpop을 하고, destination list에 lpush를 해준다. 이름이 시사하는 바와 같이 rpop + lpush의 복합 명령이지만, 하나의 명령어로 두 개의 오퍼레이션을 처리할 수 있는 atomic한 명령어이다. source와 destination이 모두 list이다.
// 두 list 초기화
127.0.0.1:6379> rpush l10 e10 e11 e12 e13 e14
(integer) 5
127.0.0.1:6379> rpush l20 e20 e21 e22 e23 e24
(integer) 5
127.0.0.1:6379> lrange l10 0 -1
1) "e10"
2) "e11"
3) "e12"
4) "e13"
5) "e14"
127.0.0.1:6379> lrange l20 0 -1
1) "e20"
2) "e21"
3) "e22"
4) "e23"
5) "e24"
// l10으로부터 rpop을 하고, 그 아이템을 l20에 lpush를 한다.
127.0.0.1:6379> rpoplpush l10 l20
"e14"
127.0.0.1:6379> lrange l10 0 -1
1) "e10"
2) "e11"
3) "e12"
4) "e13"
127.0.0.1:6379> lrange l20 0 -1
1) "e14"
2) "e20"
3) "e21"
4) "e22"
5) "e23"
6) "e24"
source와 destination을 같은 list로 지정하면, 원형 리스트를 사용하는 것과 같은 효과를 줄 수 있다.
LMOVE
127.0.0.1:6379> help lmove
LMOVE source destination LEFT|RIGHT LEFT|RIGHT
summary: Pop an element from a list, push it to another list and return it
since: 6.2.0
group: list
위의 rpoplpush의 보다 범용적인 버전이다. 추가된 시점도 6.2.0 버전에서부터 포함되었다. rpoplpush의 경우는, source의 끝과 destination의 앞의 관계밖에 없지만, 이 명령어는 보다 다양한 조합 지원을 한다.
- LEFT LEFT: head -> head
- LEFT RIGHT: head -> tail
- RIGHT LEFT: tail -> head
- RIGHT RIGHT: tail -> tail
Zip List, Quick List
성능 최적화를 위해, 기존의 Linked List만을 사용했던 방식으로부터 데이터량이 적을 때는 Zip List를 사용하고, 많을 떄는 Linked List를 쓰는 방식으로 혼용되었으나, 이를 버전 3.2부터 Quick List 하나로 통합하여 사용하게 되었다. Zip List를 사용하는 옵션 설정은 redis.conf에 표기되어 있다. Advanced Config 세션에 이 옵션이 기술되어 있다.
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
따라서, 이 임계값보다 작으면 ziplist가 채택된다.
// List에서의 테스트
127.0.0.1:6379> rpush l1 a
(integer) 1
127.0.0.1:6379> object encoding l1
"quicklist"
127.0.0.1:6379> rpush l1 0123456789012345678901234567890123456789012345678901234567890123456789
(integer) 2
127.0.0.1:6379> object encoding l1
"quicklist"
// Set에서의 테스트
127.0.0.1:6379> hset h1 a 0123456789012345678901234567890123456789012345678901234567890123456789
(integer) 1
127.0.0.1:6379> object encoding h1
"hashtable"
127.0.0.1:6379> hset h2 a aaa
(integer) 1
127.0.0.1:6379> object encoding h2
"ziplist"
'Development > Hadoop, NoSQL, BigData' 카테고리의 다른 글
Redis Geo - Geospatial 명령어 (0) | 2021.06.07 |
---|---|
Redis 자료 구조 - ZSet (Sorted Set) (0) | 2021.06.02 |
Redis 자료 구조 - Hash (0) | 2021.05.27 |
Redis 자료 구조 - Set (0) | 2021.05.12 |
Redis 자료 구조 - String (0) | 2021.05.06 |
Memcached vs. Redis - 특징 비교 (0) | 2021.05.03 |
Redis 개요 (0) | 2021.04.28 |
Avro 개요 (0) | 2021.04.23 |