[redis] 레디스 Redis

Published: by Creative Commons Licence

참고 문서

테스트 환경 정보

  • 테스트 환경: Windows WSL-Ubuntu 20.x
  • Redis 7.2.x

개요

레디스는 인메모리 데이터베이스다. 그러니까 흔히들 메모리라 부르는 주기억 장치에 데이터를 저장하여 입출력 속도가 빠르지만, 전력이 끊기면 모조리 지워지는 특징이 있는 데이터베이스다.

그러나 레디스는 디스크에 별도로 데이터를 보관하는 기능을 제공하기 때문에 (설정에 따라) 데이터 보존을 기대할 수 있다.

BSD 라이선스라서 저작권자 표기, 보증 부인을 지키는 한 개작/배포 제한이 없다.

빈 값에 대한 표현은 null이 아닌 (nil)이다.

설치

Redis | Install Redis on Windows

url -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list

sudo apt-get update
sudo apt-get install redis

서버 시작/종료

sudo service redis-server start

# CLI 터미널 접속
redis-cli

# 살아있는 지 확인
ping

# 종료
shutdown

원격 접속

Redis CLI 클라이언트로 원격 서버에 접속해보자.

redis-cli -h HOST_NAME -p PORT_NUMBER

# 접속 후 인증
auth USERNAME PASSWORD

redis-cli 명령에 아이디 비번을 붙여서 사용하는 방법이 있긴 한데 보안문제로 권장하지 않는다 함. (그러면 옵션을 왜 만들어놨어 😒)

TODO 스프링 부트의 내장 레디스 로컬 서버에는 접속이 안 되는데 확인 필요. (데이터그립으론 잘 되는데)

클라이언트 라이브러리

Redis | Clients

리눅스 터미널의 CLI 클라이언트 외에 제공되는 각 언어별 클라이언트. 예로 Java용 클라이언트는 Jedis, JRedisBloom가 있고 JavaScript용 클라이언트는 rebloom이 있음.

보안 강화

레디스의 모든 인터페이스는 기본적으로 인증 절차를 필요로 하지 않는다. 따라서 보안 강화를 위해 다음 절차를 권장하고 있다:

  1. 레디스로 통하는 포트를 외부에서 연결할 수 없도록 방화벽을 설정할 것(레디스의 통신 포트는 6379, 클러스터 모드에선 16379, 센티널(?)에선 26379번이 기본값임).
  2. bind 디렉티브로 특정 네트워크와의 통신만 허용하도록 설정할 것.
  3. requirepass 옵션으로 클라이언트가 AUTH 명령을 사용해 인증 단계를 수행하도록 설정할 것.
  4. spiped 같은 SSL 터널링 소프트웨어를 사용해 클라이언트와 서버 간 통신을 암호화 할 것.

설정 바꾸기

Redis | Redis configuration

로컬 서버 포트 변경

/etc/redis/redis.conf 파일의 port 항목을 변경하고 재시작하면 된다.

클러스터링

레디스 서버 2대 이상을 클러스터링으로 구성하면 RDB 옵션이어도 디스크 저장 없이 재난에 대처할 수 있다. 죽었다 살아난 서버가 다른 클러스터의 데이터를 불러오도록 할 수 있기 때문.

TODO

데이터셋을 디스크에 저장하기

Redis | Redis persistence

save 명령으로 덤프 파일을 만드는 것을 의미한다. 저장 옵션 RDB, AOF, No persistence, RDB + AOF 하나를 선택할 수 있다.

RDB는 저장 옵션(persistence options)의 기본값이며 주기적으로 스냅샷을 파일로 저장하는 방식이다. 이렇게만 말하면 자동으로 될 것 같지만 백업 주기는 수동으로 설정해줘야 한다. (아마도)

AOF는 로그 방식이라고 부르며, 수신된 모든 쓰기 작업을 디스크에 저장한다. RDB에 비해서 처리가 느리지만 정전 등의 재난에 대처하기 쉽다는 장점이 있다.

불러오기

TODO 이게 자동으로 되는건지 모르겠네…

데이터 타입 Data Types (자료형)

Redis | Understand Redis data types

Keys

Redis | Understand Redis data types#keys

데이터의 키가 되는 타입. 레디스는 'binary safe' 하다고 한다. 뭔 소리냐면, 일반적인 문자열(빈 문자열 포함)부터 JPEG 파일까지의 모든 바이너리 시퀀스를 키로 사용할 수 있다.

너무 긴 데이터는 성능에 악영향을 끼치니 적당히 짧으면서 가독성 있는 값을 사용하라고 한다. 권장되는 키의 모양은 아래와 같다:

  • object-type:id
  • user:1000
  • comment:4321:reply.to
  • comment:4321:reply-to

여기선 그냥 이름의 일부로 사용했지만, 콜론:으로 나눠지는 부분을 keyspace라고 한다. 이걸 이용한 알림 기능이 있는 모양이다. Redis | Redis keyspace notifications

키 타입의 최대 크기는 512 MB다.

TODO 근데 바이너리 시퀀스가 뭘까… (01로 이뤄진 연속된 값이 아닐까?)

문자열 Strings

일반적인 문자열을 생각하면 되는데, set 명령은 공백으로 인자를 구분하기 때문에 값이 공백이 있는 경우 따옴표로 감싸야 함:

set mykey 'qwer asdf'
OK

get mykey
"qwer asdf"

set mykey2 qwer asdf
(error) ERR syntax error

작은따옴표와 큰따옴표의 차이는 없다. Redis | Redis CLI#string-quoting-and-escaping

JSON

리스트 Lists

스택과 큐를 구현한 데이터 타입. JavaScript의 배열과 비슷하다. 도움말에선 문자열 값으로 연결된 리스트(linked lists of string values)라고 한다.

셋 Sets

중복을 허용하지 않는 값들의 집합. 이 자료형의 구성 요소(값)는 멤버(members)라고 부른다. 도움말에선 순서가 지정되지 않은 고유 문자열 모음(unordered collection of unique strings)이라 설명한다.

해시 Hashes

특정 키 아래에 필드와 값을 쌍으로 갖는 데이터 타입으로 다른 언어의 map 계열 데이터 타입과 유사하다.

정렬된 셋 Sorted Sets

스트림 Streams

Geospatial

비트맵 Bitmaps

비트필드 Bitfields

Probabilistic

Time series

자주 쓰는 명령어

Redis | Commands

참고: 레디스의 명령어는 대소문자를 구분하지 않는다.

MONITOR

레디스 서버에서 처리되는 모든 명령을 다시 스트리밍한다. 어떤 명령이 실행되고 있는지 디버깅할 때 쓴다. 관리자 기능이나 QUIT은 기록되지 않는다.

⚠️ MONITOR는 서버 부하를 크게 증가시키니 운영 환경에서는 주의해서 사용할 것

# 스프링 부트 서버가 시작될 때의 레디스 로그
> monitor
OK
1766146681.448195 [0 127.0.0.1:41184] "HELLO" "3"
1766146681.459792 [0 127.0.0.1:41184] "CLIENT" "SETINFO" "lib-name" "Lettuce"
1766146681.459806 [0 127.0.0.1:41184] "CLIENT" "SETINFO" "lib-ver" "6.3.2.RELEASE/8941aea"
1766146684.002978 [0 127.0.0.1:41184] "HMSET" "spring:session:sessions:0dce35b1-9cd3-4ded-9675-82e56b27baa4" "lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x9b6\x8b\xb4#" "creationTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x9b6\x8b\xb4#" "maxInactiveInterval" "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x0e\x10"
1766146684.006092 [0 127.0.0.1:41184] "PEXPIREAT" "spring:session:sessions:0dce35b1-9cd3-4ded-9675-82e56b27baa4" "1766150283939"
1766146684.017743 [0 127.0.0.1:41184] "EXISTS" "spring:session:sessions:0dce35b1-9cd3-4ded-9675-82e56b27baa4"

INFO

info
info section
info section section2 section3 ...

서버에 대한 정보와 통계를 반환하는 명령어. info만 입력하면 모든 정보가 출력된다. section은 일종의 카테고리다. 가령 info server라고 입력해 레디스 버전과 시스템 OS 정보만 출력할 수 있다.

section으로 입력 가능한 키워드는 아래와 같다:

  • server: General information about the Redis server
  • clients: Client connections section
  • memory: Memory consumption related information
  • persistence: RDB and AOF related information
  • stats: General statistics
  • replication: Master/replica replication information
  • cpu: CPU consumption statistics
  • commandstats: Redis command statistics
  • latencystats: Redis command latency percentile distribution statistics
  • sentinel: Redis Sentinel section (only applicable to Sentinel instances)
  • cluster: Redis Cluster section
  • modules: Modules section
  • keyspace: Database related statistics
  • errorstats: Redis error statistics

TODO 번역

CONFIG

설정값을 변경하거나 가져온다.

config get dir
1) "dir"
2) "/var/lib/redis"

# databases 설정 조회
config get databases

# user가 포함된 모든 설정 조회
config get *user*

SELECT

데이터베이스를 선택하는 명령이다.

# 첫 번째 데이터베이스 선택
select 0

# 두 번째 데이터베이스 선택
select 1

레디스에는 (건드린 게 없다면) 16개의 데이터베이스가 있고, 접속 시 첫 번째(index=0) 데이터베이스가 기본값으로 선택된다.

어떤 개발자의 말에 따르면 데이터베이스는 테스트하고 놀 때나 쓰고 차라리 인스턴스를 여러 개 만들라고 한다. (트랜잭션 때문이라나 뭐라나…)

SAVE, BGSAVE

데이터를 즉시 디스크에 저장(dump the dataset to disk)하도록 하는 명령어. 저장된 파일의 경로는 설정을 건드리지 않았으면 /var/lib/redis/dump.rdb이다.

# 즉시 저장(완료될 때까지 기다림)
save

# 백그라운드 저장(안기다림)
bgsave

예약을 걸 수도 있는데, 다음 명령은:

save 60 1000

매 60초 마다 천 건의 키가 변경된 경우 자동으로 디스크에 저장한다.

FLUSHALL

현재 선택한 데이터베이스를 포함한 모든 데이터베이스의 키를 전부 삭제한다.

flushall

KEYS

주어진 패턴과 일치하는 모든 키 반환한다.

KEYS pattern
  • pattern: 찾으려는 키의 글롭 패턴(glob patterns). 생략할 수 없고 모든 키를 반환하게 하려면 * 라고 작성한다.
keys *
1) "qwe"
2) "asd"

그런데 이 명령은 제한적으로 사용해야 하고, 대신 scan을 사용하라고 한다. 아래를 보자.

SCAN

Redis | SCAN

SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
  • cursor: 순회중인 데이터 집합의 현재 위치(여기까지 검색했다는 뜻). 0은 시작지점을 의미한다. 그러니 처음부터 검색하려면 0으로 지정하면 됨.
  • pattern: 찾으려는 키의 글롭 패턴. 생략하면 모든 데이터count만큼 반환된다. (글롭 패턴 요약: 모든 글자*, 한 글자?, 문자 범위 지정[])
  • count: 반환되는 데이터의 수를 지정하는 옵션. scancount로 지정된 만큼 반환하려고 하지만 내부 알고리즘과 데이터베이스의 상태에 따라 근사치로 반환될 수 있다. 이 옵션을 생략하면 대략 10개 씩 가져옴.
  • type: TODO
# 커서 위치 10부터 모든 키 스캔
scan 10

# 이름이 정확히 'aaa'인 키를 커서 0부터 20개 스캔
scan 0 match aaa count 20

# 이름이 'mykey:'로 시작하는 모든 키를 커서 0부터 스캔
scan 0 match mykey:*

# 이름이 세 글자이고 두 번째 문자가 'a'인 키를 커서 5부터 스캔
scan 5 match ?a?

점진적 탐색 알고리즘:

> scan 0
1) "7"
2)  1) "If you ask me."
    2) "qwe"
    3) "test"
    4) "I'm waldo"
    5) "asd"
    6) "Mighty fine morning."
    7) "bar"
    8) "q:w:e:r"
    9) "foo"
   10) "123:/home:456"

SCAN은 검색 패턴과 일치하는 키를 일정량 만큼 반환하며, 현재 위치를 의미하는 커서 값을 반환한다. 위 예시에서는 7이 반환됐는데, 아직 탐색할 데이터가 남았으니 7부터 다시 찾으라는 말로 이해하면 된다.

> scan 7
1) "0"
2) 1) "Hello there!"
   2) "qwe:zxc"

더 이상 찾을 데이터가 없을 때 반환되는 커서 값은 0이다.

검색은 0이 나올 때까지

범위가 좁은 패턴을 입력했다 해도 SCAN의 특성상 빈 배열과 커서 위치만 알려주는 경우가 있다:

> scan 0 match aaa
1) "5"
2) (empty array)

> scan 5 match aaa
1) "0"
2) 1) "aaa"

이럴 때는 반환된 커서 위치만 바꿔서 이전과 같은 검색 패턴으로 다시 검색해야 한다. 언제까지? 0이 나올 때까지. 뭔가 일반적이지 않지만, 레디스가 원래 이렇게 설계되었다. '대규모 데이터베이스 환경에서 성능을 보호하고 데이터를 효과적으로 탐색하기 위함'이라나 뭐라나

SET

문자열 값을 지정한 키로 메모리에 새로 저장한다.

set mykey 'qwer'

set q:w:e:r 1234

만료시간을 지정하려면 ex 혹은 px 옵션을 사용함:

# 만료시간 5초 설정(초 단위)
set qwe 'asd' ex 5

# 만료시간 5초 설정(밀리초 단위)
set foo 'bar' px 5000

GET

지정한 키로 저장된 문자열 값을 반환한다.

get mykey
"qwer"

get q:w:e:r
"1234"

TYPE

특정 키의 데이터 타입을 문자열로 반환한다.

type mykey

키가 없으면 none, 키가 있으면 string, list, set, zset, hash, stream 중에 하나를 반환한다.

DEL

특정 키를 삭제한다.

del mykey
del mykey1 mykey2 mykey3

삭제에 성공하면 1, 지정한 키를 찾을 수 없으면 0을 반환한다.

이 명령은 키 삭제와 메모리 회수를 블로킹 방식으로 처리하기 때문에, 운영 환경에서는 사용하지 않는 게 좋다.

DEL처럼 지정한 키를 삭제한다.

unlink mykey
unlink mykey1 mykey2 mykey3

DEL과 다르게 메모리 회수를 백그라운드에서 수행한다.

EXPIRE

특정 키에 저장된 데이터의 만료시간을 설정하고, 시간이 지나면 자동으로 삭제하게 하는 명령어. 초 단위로 지정한다.

# mykey의 만료시간을 30초로 설정
expire mykey 30

없는 키를 지정하면 0이 반환된다.

Options

expire의 옵션은 모두 만료시간을 설정할지 말지를 결정하는 조건으로 쓰인다.

  • nx: 설정된 만료시간이 없을 때만
  • xx: 설정된 만료시간이 있을 때만
  • gt: 새 만료시간이 기존보다 클 경우에만
  • lt: 새 만료시간이 기존보다 작은 경우에만

TTL

TTL key

특정 키에대한 만료시간을 반환한다. 만약 키가 존재하지 않으면 -2를, 키가 존재하나 만료시간이 없으면 -1을 반환한다.

LPUSH

리스트(Lists)의 앞에 요소(element)를 추가한다.

RPUSH

리스트의 뒤에 요소를 추가한다. lpush의 반대격

LPOP

리스트의 맨 앞 요소를 꺼낸다.

RPOP

리스트의 맨 뒤 요소를 꺼냄.

LLEN

리스트의 길이 반환

LMOVE

element의 위치를 변경하는 명령어

LRANGE

주어진 offset의 모든 요소를 반환한다. offset은 시작과 끝의 인덱스로 지정하는 방식이며, 인덱스는 음수일 수도 있는데 이 경우 끝에서부터 몇 번째인지를 의미함.

offset이 -3 2면 끝에서 3번째 요소부터 처음에서 3번째 요소까지를 출력하라는 뜻이다. 그러니까 그냥 0 2와 같음.

LTRIM

주어진 offset의 모든 요소를 삭제한다.

SADD

지정한 키의 셋(Sets)에 멤버(member, 셋의 구성 요소를 의미함)를 추가한다.

sadd foo var
sadd foo var2

SMEMBERS

지정한 키로 저장된 셋의 모든 멤버를 반환한다.

smembers foo
1) "var2"
2) "var"

SISMEMBER

특정 멤버가 있는지 확인

SINTER

둘 이상의 셋 사이에 존재하는 공통 멤버를 반환함

SCARD

특정 셋의 멤버 수를 반환

SSCAN

셋의 멤버를 대상으로 패턴 검색을 수행한다. 특정 키 내부에서 검색한다는 점만 빼면 scan과 나머지는 같다.

SSCAN key cursor [MATCH pattern] [COUNT count]
> sscan foo 0 MATCH var*
1) "0"
2) 1) "var1"
   2) "var2"
   3) "var3"

HSET

해시(Hashes) 데이터 타입의 지정된 필드와 값을 설정한다. 한 번에 여러 필드를 설정할 수 있다.

HSET key field value
HSET key field value field2 value2
...
hset map a 'aaa'
hset map b 'bbb' c 'ccc'

HGET

특정 키와 필드에 해당하는 값을 출력한다.

HGET key field
> hset map a 'aaa'

> hget map a
"aaa"

HKEYS

지정된 해시 키에 존재하는 모든 필드 이름을 반환한다.

HKEYS key

HGETALL

지정된 해시 키에 존재하는 모든 필드 이름과 값을 반환한다.

HGETALL key
> hset map a 'aaa' b 'bbb'

> hgetall map
1) "a"
2) "aaa"
3) "b"
4) "bbb"

HSCAN

scan의 해시 버전. 다른 점은 특정 해시 키의 필드 이름이나 필드 값이 패턴과 일치하면 모두 출력한다는 것.

HSCAN key cursor [MATCH pattern] [COUNT count]
> hset map a 'aaa'

> hscan map 0 match a
1) "0"
2) 1) "a"
   2) "aaa"

ZSCAN

sorted sets 타입 패턴 검색 명령어

ACL(Access Control List) 시스템

ACL은 레디스 6.0 버전에 추가된 권한 관리 기능이다. ACL을 통해 각각 다른 권한을 가진 여러 사용자를 생성할 수 있으며, 특정 명령어 또는 키에 대한 액세스 권한을 제어할 수 있다.

# 모든 키에 대한 set 명령어 사용 권한이 있는 verginia 사용자 생성
# 이 사용자는 활성상태가 되며, 이미 존재하는 사용자면 활성상태로 바꾸고 set 권한을 부여한다
acl setuser user_name on allkeys +set

# 사용자에게 get 명령어 사용 권한을 부여한다
# 하지만 어떤 키에도 접근할 수 없어서 get 사용은 불가
acl setuser user_name +get

# 사용자 활성화(활성화 전에는 로그인 불가)
acl setuser USER_NAME on

# 사용자 비활성화
acl setuser USER_NAME off

# 사용자 삭제
acl deluser user_name

# 현재 연결이 인증된(로그인한) 사용자의 이름을 반환한다
acl whoami

# 사용자의 비밀번호를 1234로 설정한다. 
# `>` 기호가 비밀번호를 나타낸다.
acl setuser USER_NAME >1234

# USER_NAME 사용자의 연결을 인증(= 로그인)한다. 이 때 비밀번호로 1234를 사용한다.
auth USER_NAME 1234

# 비밀번호가 없는 사용자 로그인 방법
auth default nopass

TODO

Redis Pub/sub

PUBLISHSUBSCRIBE 명령으로 실시간으로 메시지를 주고받는 기능. 이 기능으로 레디스가 일종의 메시지 브로커를 수행하도록 할 수 있다. 구조가 매우 단순하고 빠른게 장점이지만, 메시지 전달 보장이 안되며 과거 메시지를 조회하는 기능 같은 건 없다.

사용 방법 예시

우선 SUBSCRIBEtest.channel을 구독한다:

> SUBSCRIBE test.channel

1) "subscribe"
2) "test.channel"
3) (integer) 1
Reading messages... (press Ctrl-C to quit or any key to type command)

이제 이 터미널은 그대로 두고 새 터미널을 열어서, PUBLISH로 메시지를 발행한다:

> PUBLISH test.channel hello

(integer) 1

문제가 없다면, 구독중이던 터미널에서 다음처럼 출력된다:

1) "message"
2) "test.channel"
3) "hello"