Redis 서버에 붙어서 키가 제대로 들어갔는지, 만료시간이 걸렸는지, 큐에 뭐가 쌓였는지 확인할 때 결국 손이 가는 건 redis-cli다. 명령어가 많아 보이지만 규칙이 있다. 앞 글자가 어떤 자료구조를 다루는지 알려준다.

이 글에서는 접속 옵션, 서버 상태 확인, 기본 타입인 String 명령어를 먼저 보고, Hash·List·Set·Sorted Set 네 자료구조를 차례로 정리한다. 운영 서버에서 조심해야 할 명령어는 마지막에 따로 모았다.

접속은 옵션 다섯 개로 끝난다

먼저 Redis가 포트를 열고 있는지 확인한다.

$ netstat -nlpt | grep 6379
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 127.0.0.1:6379          0.0.0.0:*               LISTEN      -

접속 옵션은 포트(-p), 호스트(-h), DB 번호(-n), 비밀번호(-a, auth의 약어), 클러스터 모드(-c) 정도면 충분하다.

# 6480번 포트의 redis 서버 접속
$ ./redis-cli -p 6480
127.0.0.1:6480>

# 다른 서버에 있는 6480번 포트의 redis 서버 접속
$ ./redis-cli -h 192.168.124.xxx -p 6480
192.168.124.xxx:6480>

# DB 번호가 0인 redis 서버 접속
$ ./redis-cli -n 0 -p 6480
127.0.0.1:6480>

# 비밀번호가 test인 redis 서버 접속
$ ./redis-cli -a test -p 6480
127.0.0.1:6480>

클러스터에 접속할 때는 -c를 꼭 붙인다. 그래야 get을 했을 때 키가 있는 노드로 알아서 넘어간다.

$ ./redis-cli -p 6480 -c
127.0.0.1:6480> get a
-> Redirected to slot [15495] located at node3:6480
"b"

서버 상태는 info와 monitor로 본다

info는 서버 정보를 섹션별로 보여준다.

$ ./redis-cli -p 6480 info

출력이 길어서 자주 보는 항목에만 설명을 달아 두었다.

# Server
# 레디스 서버에 대한 일반적인 정보

# Clients
# 현재 접속해 있는 클라이언트 수
connected_clients:2
# 대기 중인 클라이언트 수
blocked_clients:0

# Memory
used_memory:3287928
# 현재 사용 중인 메모리
used_memory_human:3.14M
# OS에서 Redis가 실제 차지하는 메모리양
used_memory_rss_human:4.09M
# peak 시 사용한 메모리
used_memory_peak_human:3.14M

# MaxMemory 설정
maxmemory_human:0B
# MaxMemory 초과 시 데이터 처리 정책
maxmemory_policy:noeviction

# Persistence

# Stats
# 서버가 시작된 뒤 받아들인 전체 연결 수
total_connections_received:8
# 현재 처리하고 있는 초당 operation 수
instantaneous_ops_per_sec:0
# 현재 input kbps
instantaneous_input_kbps:0.00
# 현재 output kbps
instantaneous_output_kbps:0.00

# Replication

# CPU

# Modules

# Cluster
cluster_enabled:0

# Keyspace

지금 이 서버에 어떤 명령이 들어오는지 실시간으로 보려면 monitor, 주요 통계를 주기적으로 찍어 보려면 --stat을 쓴다.

$ redis-cli -p 6480 monitor
OK

$ redis-cli -p 6379 --stat

프롬프트에 들어가지 않고 명령을 바로 넘길 수도 있다. 포트가 연달아 있는 여러 서버에 같은 명령을 돌릴 때는 셸 반복문과 묶는다.

$ redis-cli -p 6379 keys "*"

# 6001~6010번 포트 서버의 키 개수를 한 번에 확인
$ for i in {6001..6010}; do src/redis-cli -p $i dbsize; done

가장 기본인 String 명령어

접두어가 없는 명령어는 대부분 String을 다룬다. 저장·조회·삭제부터 보자.

# 키, 값 저장
set {key} {value}

# 특정 키 값 조회
get {key}

# 여러 개의 key, value를 한 번에 저장
mset {key} {value} [{key} {value} ...]

# 여러 개의 key에 해당하는 value를 한 번에 조회
mget {key} [{key} ...]

# 특정 키 삭제 (여러 개를 한 번에 지울 수도 있다)
del {key} [{key} ...]
127.0.0.1:6379> MSET username assu userage 20 usercity seoul
OK

127.0.0.1:6379> MGET username usercity
1) "assu"
2) "seoul"

127.0.0.1:6379> GET usercity
"seoul"

키 자체를 살피는 명령어도 있다.

keys *          # 서버 내 모든 key 조회
keys *te*       # 특정 문자열을 포함하는 key 조회
                # 1) "test1"
                # 2) "test2"
exists {key}    # key 존재 여부
randomkey       # 랜덤으로 key 하나 조회
type {key}      # key의 타입 조회
dbsize          # 서버 내 key 전체 개수
flushdb         # 현재 선택된 DB의 모든 데이터 삭제
flushall        # 서버 내 모든 데이터 삭제

만료시간

ttl로 남은 만료시간을 초 단위로 본다. -1은 만료시간이 없는 키, -2는 키가 없다는 뜻이다(만료되어 사라진 경우 포함).

ttl {key}

# 30초 뒤 만료되도록 저장
set {key} {value} EX 30

# 값을 덮어쓰면서 남아 있던 만료시간은 유지 (Redis 6.0부터)
set {key} {value} KEEPTTL

# 이미 있는 키에 만료시간 설정
EXPIRE tutorialspoint 60
(integer) 1    # 설정 완료
(integer) 0    # 키가 없거나 만료시간을 설정할 수 없는 경우

옵션 없이 set으로 값을 덮어쓰면 걸려 있던 만료시간이 사라진다. 만료시간이 30초로 다시 시작되는 게 아니라 키가 만료되지 않는 상태가 된다. 값을 바꿔도 원래 만료 시점을 지키고 싶으면 KEEPTTL을 붙인다.

조건부 저장과 숫자 증감

set a b nx   # key a에 값이 없을 때만 저장 (있으면 수정하지 않음)
set a b xx   # key a에 값이 있을 때만 수정 (없으면 저장하지 않음)
set a 10
incr a        # 1 증가
get a         # "11"
decr a        # 1 감소

set a 10
incrby a 10   # 10 증가
get a         # "20"
decrby a 10   # 10 감소

# 소수 증감은 INCRBYFLOAT 하나로 처리한다
INCRBYFLOAT a 2.5    # 2.5 증가
INCRBYFLOAT a -2.5   # 2.5 감소

문자열 이어 붙이기와 자르기

append는 현재 값 뒤에 문자열을 붙인다. 키가 없으면 새로 만든다.

127.0.0.1:6480> APPEND greeting Hello  # key가 없으면 생성
(integer) 5

127.0.0.1:6480> GET greeting
"Hello"

127.0.0.1:6480> APPEND greeting World  # 기존 key 뒤에 이어 붙임
(integer) 10

127.0.0.1:6480> GET greeting
"HelloWorld"

getrange, setrange는 각각 substring, replace에 해당한다.

127.0.0.1:6379> SET hello world
OK

127.0.0.1:6379> GETRANGE hello 2 4
"rld"

127.0.0.1:6379> SETRANGE hello 2 ppp
(integer) 5

127.0.0.1:6379> GET hello
"woppp"

명령어 앞 글자가 자료구조를 알려준다

String 말고도 Redis 값은 여러 자료구조가 될 수 있다. 외울 게 많아 보이지만 명령어 앞 글자만 보면 어느 자료구조를 다루는지 바로 알 수 있다.

접두어자료구조예
없음Stringset, get, incr
hHashhset, hget, hgetall
l / rList (왼쪽/오른쪽)lpush, rpop, lrange
sSetsadd, smembers
zSorted Setzadd, zrange

어떤 키가 무슨 자료구조인지 모르겠으면 type {key}로 먼저 확인한다.

Hash

키 하나 아래에 field-value 쌍을 여러 개 담는다.

hset user:1 name An city seoul   # 초기화

# 특정 hash의 특정 field 값 조회
hget user:1 name   # "An"
hget user:1 city   # "seoul"

# 특정 hash의 특정 field 삭제
hdel user:1 name

# 특정 hash의 모든 field-value 조회
hgetall user:1

# 특정 hash의 field만 조회
hkeys user:1

# 특정 hash의 value만 조회
hvals user:1

List

Linked List 형태라서 양 끝에 넣고 빼는 게 자연스럽다. 값이나 인덱스로 데이터를 찾거나 삭제할 수도 있다. 최대 길이는 2^32 - 1(4,294,967,295)이다. 주로 Stack이나 Queue를 구현할 때 쓰고, 백그라운드 작업자 시스템의 대기열이 대표적인 예다.

Stack

Queue

넣는 쪽과 빼는 쪽을 어떻게 고르느냐에 따라 Queue도 되고 Stack도 된다.

# 생성
lpush {queue name} {value 1}
lpush {queue name} {value 1} {value 2} {value 3} ...

# lpush로 넣었다면
rpop {queue name}   # FIFO 추출 (Queue)
lpop {queue name}   # LIFO 추출 (Stack)

rpush는 오른쪽에 값을 넣으므로 오른쪽으로 갈수록 최근에 들어온 값이다. rpush test-queue A B C라고 하면 A부터 차례로 들어가 [A, B, C]가 되고, 여기서 lpop을 하면 먼저 들어온 순서대로 나온다.

rpush test-queue A B C   # [A, B, C]

lpop test-queue   # "A"
lpop test-queue   # "B"
lpop test-queue   # "C"

값을 꺼내지 않고 들여다보기만 하려면 llen과 lrange를 쓴다.

127.0.0.1:6480> rpush queue-name 1 2 3 4 5
(integer) 5

127.0.0.1:6480> llen queue-name   # 리스트 길이 확인
(integer) 5

# 전체 요소 확인
127.0.0.1:6480> lrange queue-name 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"

# 특정 인덱스 값 확인
127.0.0.1:6480> lrange queue-name 0 0
1) "1"

Set

순서가 없고 중복을 허용하지 않는다. 1 1 2 2 3 3 1 1을 넣어도 남는 건 세 개다.

127.0.0.1:6480> sadd set-name1 1 1 2 2 3 3 1 1
(integer) 3

# set 크기 확인
127.0.0.1:6480> scard set-name1
(integer) 3

# 모든 원소 조회
127.0.0.1:6480> smembers set-name1
1) "1"
2) "2"
3) "3"

127.0.0.1:6480> sadd set-name2 1 2 3

# 1이 set에 있는지 확인
127.0.0.1:6480> sismember set-name2 1

# set-name2에서 1 삭제 (remove)
127.0.0.1:6480> srem set-name2 1

# 랜덤하게 원소 하나를 pop
127.0.0.1:6480> spop set-name2

Sorted Set

값마다 가중치(score)를 붙여 정렬해 두는 Set이다. 점수 순으로 줄을 세워 두니 랭킹 기능을 만들 때 먼저 떠올릴 만하다.

# 기본 문법
zadd {SortedSet name} [가중치1] [key1] [가중치2] [key2] ...
zadd test-name 30 a 10 b 15 c
(integer) 3

# 전체 출력 (기본 오름차순)
zrange test-name 0 -1
1) "b"
2) "c"
3) "a"

# 전체 출력 (내림차순, rev 옵션은 Redis 6.2부터)
zrange test-name 0 -1 rev
1) "a"
2) "c"
3) "b"

# 가중치와 함께 내림차순 조회
zrange test-name 0 -1 rev withscores
1) "a"
2) "30"
3) "c"
4) "15"
5) "b"
6) "10"

# zrank: 가중치가 가장 낮은 요소가 0위 (오름차순)
zrank test-name a   # (integer) 2
zrank test-name b   # (integer) 0
zrank test-name c   # (integer) 1

# zrevrank: 가중치가 가장 높은 요소가 0위 (내림차순)
zrevrank test-name a   # (integer) 0
zrevrank test-name b   # (integer) 2
zrevrank test-name c   # (integer) 1

# 가중치가 가장 높은 요소 꺼내기
zpopmax {SortedSet name}

# 가중치가 가장 낮은 요소 꺼내기
zpopmin {SortedSet name}

운영 서버에서는 조심할 명령어

Redis는 명령을 한 스레드에서 하나씩 처리한다. 그래서 오래 걸리는 명령 하나가 그동안 들어온 다른 요청을 전부 기다리게 만든다. 키가 많은 서버의 keys *, 데이터를 통째로 지우는 flushall·flushdb, 필드가 많은 Hash의 hgetall이 그런 명령이다. 로컬에서 연습할 때는 편하지만 운영 환경에서는 쓰기 전에 한 번 더 생각한다. 키를 훑어봐야 한다면 커서로 조금씩 나눠 가져오는 scan이 대안이다.