Redis 자료 구조 - List

    개요

    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 자료 구조 - Hash  (0) 2021.05.27
    Redis 자료 구조 - Set  (0) 2021.05.12
    Redis 자료 구조 - List  (0) 2021.05.10
    Redis 자료 구조 - String  (0) 2021.05.06
    Memcached vs. Redis - 특징 비교  (0) 2021.05.03
    Redis 개요  (0) 2021.04.28

    댓글(0)

    Designed by JB FACTORY