인메모리 데이터베이스
레디스의 다양한 자료구조
개발의 편의성이 좋아지고 난이도가 낮아진다
In-Memory
Redis
Sorted-Set
NoSQL로서 Key-Value 타입의 저장소인 레디스(Redis, Remote Dictionary Server)
의 주요 특징은 아래와 같습니다.
영속성을 지원하는 인메모리 데이터 저장소
읽기 성능 증대를 위한 서버 측 복제를 지원
쓰기 성능 증대를 위한 클라이언트 측 샤딩(Sharding) 지원
다양한 서비스에서 사용되며 검증된 기술
문자열, 리스트, 해시, 셋, 정렬된 셋과 같은 다양한 데이터형을 지원. 메모리 저장소임에도 불구하고 많은 데이터형을 지원하므로 다양한 기능을 구현
[REDIS] 📚 캐시 데이터 영구 저장하는 방법 (RDB / AOF)
그래서 서버가 꺼진 후 restart되더라도, disk에 저장해놓은 데이타를 다시 읽어서 메모리에 로딩하기 때문에 데이타 유실되지 않는다.
이런 Persistent(영속성) 기능은 휘발성 메모리 DB를 데이터 스토어로서 활용한다는 장점이 있지만 이 기능 때문에 장애의 주 원인이 되기도 한다.
그러므로 이 기능을 잘 알아보고 사용하는게 중요하다.
redis에서는 데이타를 저장하는 방법이 RDB (snapshotting) 방식과 AOF (Append only file) 두가지가 있다.
Atomic
처리 함수를 제공한다(원자성).Persistence
) 명령어로 명시적 삭제, expires를 설정하지 않으면 데이터가 삭제되지 않는다. 스냅샷
기능을 제공해 메모리 내용을 *.rdb 파일로 저장하여 해당 시점으로 복구할 수 있다. AOF
: Redis의 모든 Wirte/Update 연산을 log 파일에 기록 후 서버 재시작 시 순차적으로 재실행, 데이터 복구싱글 쓰레드
로 수행되기 때문에, 서버 하나에 여러개의 Redis Server를 띄울 수 있다.(Master-Slave 구조
) master-slave 간의 복제는 non-blocking
스냅샷과 AOF
기능을 통한 복구 방식을 주의해서 작성해야 데이터 유실에 대비할 수 있다.너무 많은 아이템을 사용하게 되면, 명령어 수행에 걸리는 시간이 증가하여 퍼포먼스가 떨어집니다.
따라서 1만개 이하 수준으로 유지합니다.
레디스 서버가 피지컬 메모리 이상으로 사용하게 되면 장애가 발생한다.
피지컬 메모리 이상으로 사용하게 되면 스왑이 발생하는데, 스왑을 사용한 페이지가 존재하면 디스크를 사용하게 된다고 합니다.
디스크를 사용한 페이지가 존재하면 계속해서 디스크를 사용하게 됨으로 주의해야 합니다.
따라서 메모리 파편화가 발생하므로 모니터링을 통해서 메모리 관리를 해줘야 한다.
Redis를 사용하다보면 필연적으로 포크가 발생하는데, 이때 write 가 헤비한 Redis 는 메모리를 두 배까지 사용할 수 있게 되면서 퍼포먼스가 극단적으로 떨어질 수 있다고 한다.
메모리 파편화 때문인지 유사한 크기의 데이터를 사용하는게 관리에 유용하다고 한다.
메모리 관리에서 유용해지나, 속도는 좀 더뎌지긴 한다. 돈이 많다면 사용해도 문제없다. Redis는 돈빨이다.
하지만 일정 개수까지는 속도를 보장한다. 일정 개수는.. 찾아보고 나중에 수정하겠습니다.
싱글 스레드이므로, 시간이 오래 걸리는 명령어에 주의하라.
KEYS
FLUSHALL, FLUSHDB
Delete Collections
Get All Collections
프로덕션 환경에서 위와 같은 명령어는 자살 행위다. 따라서 명령어를 나눠서 수행할 수 있게 구성하라. 그래서 컬렉션을 나눠서 사용하는것을 권장하나 봅니다. 한번에 모든 데이터를 가져와야 되는 경우, 하나의 콜렉션이 너무 커버리면 O(n) 시간이 너무 커지므로, 패킷 대기 시간이 길어져 타임아웃 에러들이 줄줄줄 발생하게 되니깐 말이죠.
데이터를 호출할때는 하나당 몇천개 이내로만 호출하는 것이 좋습니다.
전체 장애의 99%가 특정 커맨드 사용으로 인해 발생한다고 합니다.
Maxclient 설정 50000
RDB/AOF 설정 off
특정 command disable
데이터의 특성에 따라 선택할 수 있는 방법이 달라집니다.
일반적으로 모듈러 분산을 사용하게 되는 경우 장비의 스케일 아웃 (장비 추가) 에 리밸런스가 발생하는데 이때 장애에 매우 취약합니다.
키 값을 해시해서 자신의 해시값보다 크지만 가장 가까운 서버로 데이터가 배치되는 방식입니다. 따라서 서버가 죽더라도 죽은 서버가 가지고 있는 값들만 변동이 있습니다.
데이터를 어떻게 나눌것인가, 데이터를 어떻게 찾을것인가는 동일한 문제입니다.
상황마다 샤딩 전략이 달라지는게 가장 쉬운 방식은 range 입니다.
범위에 따라서 분산하는데 이는 데이터 집중화 현상이 발생할 수 있습니다.
서버를 확장할 때 서버 개수를 2의 배수로 확장하면, 데이터를 옮기 위치가 바로 결정되는 장점을 가지고 있습니다.
하지만 서버 확장시 2배로 키워야 된다는 돈의 압박이 있습니다.
모든 서버를 인덱스 서버가 관리하는 것인데, 인덱스 서버가 다운되면 답도 없습니다. 이 부분에서도 분명히 인덱스 서버의 레플리카를 두는 방식을 취할수 있겠죠?.. 아마도 학습이 부족합니다.
자체적인 Primary Secondary Failover를 수행해줍니다.
슬록 단위의 데이터를 관리해줍니다.
메모리 사용량이 더 많은게 단점입니다.
마이그레이션 자체를 관리자가 결정해줘야 합니다.
라이브러리 구현이 필요합니다.
장애 처리 방식은 대표적으로 3가지 방식을 이야기하셨습니다.
모니터링 해야될 redis 정보는 rss, used memory, connection number, tps 입니다.
시스템에서는 cpu, disk, network rk 있습니다.
Redis 가 CPU 점유율을 100% 먹는 현상이 종종 발생한다고 합니다.
이 때 명령어 사용 패턴을 분석해서 장애 대응을 해야 합니다.
메모리 사용량이 3/4 이상 넘는다면 장비 증설을 고려해야 합니다.
Write 가 헤비하면 마이그레이션에 주의가 요망합니다.
돈 많이 듭니다.
이 때는 메모리를 절대적으로 여유롭게 사용하는 것을 권장합니다.
정기적인 migration이 요구되며, RDB/AOF 가 필요하면 secondary에서 수행할 수 있도록 해야합니다.
collection은 단순히 key, value 쌍으로 데이터를 저장하는 구조
set "a" HappyKoo
del "a"
localhost:6379> mget (key1) (key2) ...
#예시
localhost:6379> mget "a" "b"
1) "Happy"
2) "Koo"
localhost:6379> keys (pattern)
#예시 (키 전체 조회)
localhost:6379> keys *
1) "key1"
2) "a"
주의해야할 것이 redis server 운영 중에는 전체 키를 조회하는 keys * 명령을 절대로 수행해서는 안됩니다.
keys 명령은 모든 keys를 스캔해서 조회하기 때문에 key 수가 많을 경우 처리시간이 많이 소요되고, 그동안 다른 명령을 처리하지 못하기 때문입니다.
대신 scan 명령으로 일정 수의 키를 반복하여 수행해야합니다. (한번에 약 10개씩 조회)
localhost:6379> scan (cursor) (pattern) (count)
#예시
localhost:6379> scan 0
1) "0" #next cursor이며 모두 조회했을 경우 0
2) "key1"
3) "a"
List Collection은 하나의 key에 여러 value들을 list로 저장하는 구조이며, 명령들은 다음과 같습니다.
localhost:6379> lpush (key1) (value1 value2 ...)
#예시
localhost:6379> lpush "key1" "happy" "koo"
(integer) 2
localhost:6379> lrange (key1) (startIndex) (endIndex)
#예시
localhost:6379> lrange "key1" 0 -1
1) "koo"
2) "happy"
startIndex를 0, endIndex를 -1으로 하면 리스트 전체를 조회합니다.
localhost:6379> rpush (key1) (value1 value2 ...)
#예시
localhost:6379> rpush "key1" "good"
(integer) 3
localhost:6379> lrange "key1" 0 -1
1) "koo"
2) "happy"
3) "good"
localhost:6379> lpop (key1)
#예시
localhost:6379> lpop "key1"
"koo"
localhost:6379> lrange "key1" 0 -1
1) "happy"
2) "good"
localhost:6379> rpop (key1)
#예시
localhost:6379> rpop "key1"
"good"
localhost:6379> lrange "key1" 0 -1
1) "happy"
localhost:6379> llen (key1)
#예시
localhost:6379> llen "key1"
(integer) 1
localhost:6379> ltrim (key1) (startIndex) (endIndex)
#예시
localhost:6379> rpush "key2" "a" "b" "c" "d"
(integer) 4
localhost:6379> lrange "key2" 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
localhost:6379> ltrim "key" 1 2
OK
localhost:6379> lrange "key2" 0 -1
1) "b"
2) "c"
ltrim을 사용할 때 Reverse index를 사용하고 싶으면 음수로 사용하면 되며, 없는 범위를 지정하면 모든 데이터가 삭제되니 주의해야합니다.
Set Collection은 List Collection과 같이 하나의 key에 여러 value를 저장할 수 있으며,
다른 점은 데이터가 입력된 순서와 상관없이 저장되고 중복되지 않습니다.
Sets에서는 value를 member라고 부르며, 주로 데이터가 있는지 없는지 체크하는 용도로 사용됩니다.
localhost:6379> sadd (key1) (value1 value2 value3 ...)
#예시
localhost:6379> sadd "key3" "a" "b" "a" "c"
(integer) 3
localhost:6379> smembers (key1)
#예시
localhost:6379> smembers "key3"
1) "c"
2) "a"
3) "b"
localhost:6379> sismember (key1) (value1)
#예시
localhost:6379> sismember "key3" "a"
(integer) 1
localhost:6379> sismember "key3" "d"
(integer) 0
localhost:6379> srem (key1) (value1)
#예시
localhost:6379> srem "key3" "a"
(integer) 1
localhost:6379> spop (key1) (count)
#예시
localhost:6379> spop "key3" 1
1) "c"
localhost:6379> smembers "key3"
1) "b"
localhost:6379> srandmember (key1) (count)
#예시
localhost:6379> srandmember "key3" 1
1) "b"
localhost:6379> smembers "key3"
1) "b"
Sorted Set collection은 key 하나에 여러 개의 score와 value로 구성되고, value는 score로 sort되며 중복되지 않습니다.
만약 score가 같으면 value로 sort되며, 주로 랭킹을 보여주는 시스템을 구축할 때 사용됩니다.
localhost:6379> zadd (key1) (score1 memeber1 score2 member2 ...)
#예시
localhost:6379> zadd "key4" 0 "Happy" 2 "Koo" 1 "Good"
(integer) 3
localhost:6379> zrange (key) (startIndex) (endIndex) (withscores)
#예시
localhost:6379> zrange "key4" 0 -1
1) "Happy"
2) "Good"
3) "Koo"
localhost:6379> zrangebyscore (key) (min) (max) (withscores) (limit offset count)
#예시
localhost:6379> zrangebyscore "key4" 1 2
1) "Good"
2) "Koo"
localhost:6379> zrevrangebyscore (key) (max) (min) (withscores) (limit offset count)
#예시
localhost:6379> zrevrangebyscore "key4" 2 1
1) "Koo"
2) "Good"
localhost:6379> zscore (key) (member)
#예시
localhost:6379> zscore "key4" Good
"1"
localhost:6379> zrem (key) (member)
#예시
localhost:6379> zrem "key4" Good
(integer) 1
localhost:6379> zremrangebyscore (key) (min) (max)
#예시
localhost:6379> zremrangebyscore "key4" "0" "2"
(integer) 2
Hash collection은 key 하나에 여러 개의 field와 value로 구성됩니다. 명령은 다음과 같습니다.
localhost:6379> hset (key) (field value)
localhost:6379> hmset (key) (field1 value1 field2 value2 ...)
#예시
localhost:6379> hset "key5" "a" "Happy"
(integer) 1
localhost:6379> hmset "key5" "b" "Koo" "c" "Good"
OK
localhost:6379> hget (field)
localhost:6379> hmget (field1 field2 ...)
#예시
localhost:6379> hget "key5" "a"
"Happy"
localhost:6379> hmget "key5" "b" "c"
1) "Koo"
2) "Good"
localhost:6379> hgetall (key)
#예시
localhost:6379> hgetall "key5"
1) "a"
2) "Happy"
3) "b"
4) "Koo"
5) "c"
6) "Good"
localhost:6379> hkeys (key)
localhost:6379> hvals (key)
#예시
localhost:6379> hkeys "key5"
1) "a"
2) "b"
3) "c"
localhost:6379> hvals "key5"
1) "Happy"
2) "Koo"
3) "Good"
localhost:6379> hdel (key) (field1 field2 ...)
#예시
localhost:6379> hdel "key5" "a"
(integer) 1