redis란?
- key-value 형식의 NoSQL 데이터 베이스
- 일반적인 DB는 디스크에 저장을 하는데 redis는 메모리에 저장을 한다.
사용하면서 느꼈었던 점
- 데이터베이스를 다양하게 다뤄보진 않았지만 내가 다뤄본 거 중에는 읽고/쓰기가 제일 빨랐음
- 다양한 데이터 타입이 존재 한다.
설치
Mac의 경우 brew를 통해 설치
1 | brew install redis |
실행할 폴더에서 터미널을 열고 다음의 명령어를 입력 한다.
1 | npm init -y |
redis 데이터 타입
데이터 타입의 명령어들이 엄청 많기 때문에 전부 쓰기는 불가능 해서 최대한 써 본 위주로 작성
공통
redis에 연결하기 위한 코드
1 | const redis = require('redis'); |
strings
key - value를 저장할 수 있는 데이터 타입이다.
모든 데이터(숫자, 문자, JSON, 기타 등등)를 쓸 수 있으나 읽을 때는 데이터 타입이
string으로 되어 있다 그래서 타입 이름이 string인가..? 크기는 512MB까지 가능
set, mset
- set은 하나의 key와 value를 저장을 한다.
- mset은 여러 개의 key, value를 저장할 때 쓴다.
1 | redisClient.set('key', 1, (err, result) => { |
get, mget
- get은 key를 통해 value를 읽는다.
- mget은 여러 개의 key로 value를 읽을 때 사용 한다.
1 | redisClient.get('key', (err, result) => { |
del
key를 삭제 한다.
1 | redisClient.del('key', (err, result) => { |
incrby
해당 숫자 만큼 증,감소가 진행 된다 이 녀석은 원자성을 지원 한다.
예로 0인 상태에서 A에서는 숫자를 2를 증가 시키고 B에서는 4를 증가 하라는 게 비슷한 시기에
일어났을 때 덮어쓰지 않고 하나 하나씩 처리하여 총 6이 된다.
그리고 이런 증,감소는 리턴 타입은 숫자로 받는다.
1 | redisClient.incrby('key', 2, (err, result) => { |
setex
지정한 시간이 지나면 key가 삭제 된다 만료가 필요한 캐시나 세션에 좋은 타입이다.
단위는 초
1 | redisClient.setex('Session:UserA', 10, 'abc', (err, result) => { |
lists
list는 자료구조에 나오는 스택, 큐와 같다고 생각 하면 된다.
리스트에 데이터가 하나도 없을 시 자동으로 삭제가 되고 리스트가 보유할 수 있는 데이터의 개수는
최대 232 - 1 이다.
lpush
스택, 큐 처럼 마지막에 삽입 되고 돌아오는 결과 값은 현재 리스트에 포함된 값의 개수
1 | redisClient.lpush('MyList', 'a', (err, result) => { |
rpop
먼저 들어온 데이터를 꺼내고 삭제, 큐를 생각하면 되겠다.
1 | redisClient.rpop('MyList', (err, result) => { |
lpop
마지막에 들어온 데이터를 꺼내고 삭제, 스택을 생각하면 된다.
1 | redisClient.lpop('MyList', (err, result) => { |
lrange
인덱스로 범위를 지정해서 리스트를 조회 할 수 있다.
0, -1로 하면 전체 조회를 하고 인덱스는 제일 마지막 데이터가 0부터 시작 한다.
1 | redisClient.lrange('MyList', 0, -1, (err, result) => { |
sets
sets은 순서가 없고 똑같은 데이터가 없는 구조이며 하나의 set이 가질 수 있는 데이터의
최대 개수는 232 - 1 이다.
sadd
하나 또는 여러 개의 데이터를 삽입할 수 있고 성공 시 리턴은 삽입한 개수다.
중복된 데이터가 있는 경우에는 0을 받는다.
1 | redisClient.sadd('MySet', 'a', (err, result) => { |
srem
하나 또는 여러 개의 데이터를 삭제할 수 있고 성공 시 삭제된 개수를 리턴 받는다.
1 | redisClient.srem('MySet', 'a', (err, result) => { |
smembers
sets에 속한 데이터들을 모두 볼 수 있다.
1 | redisClient.smembers('MySet', (err, result) => { |
sunion
합집합 처럼 여러 sets들의 데이터들을 합쳐서 볼 수 있다.
1 | redisClient.sunion(['MySet', 'MySet2'], (err, result) => { |
srandmember
sets의 데이터들 중 랜덤으로 하나 가지고 온다.
1 | redisClient.srandmember('MySet2', (err, result) => { |
sscan
데이터가 엄청 큰 sets에서 smembers를 호출 하는 경우 처리 하는 동안 다른 요청에 응답 하질
않으므로 그럴 경우 sscan을 사용하면 된다고 한다.
1 | const sscan = (cursor, arr, cb) => { |
1000000건이 있는 sets에 재귀로 짜서 돌려봤는데 겁나 느리네…
sorted sets
정렬된 sets는 점수로 정렬된, 똑같은 데이터가 없는 구조이다.
쉽게 이해 하자면 하나의 테이블(=key)에 key(=member), value(=score)가 세트로
저장이 되는데 value는 숫자만 되고 정렬을 할 수 있다는 의미 한 가지 유의할 점은 정렬 시
같은 score가 있는 경우에는 이름순으로 정렬 된다.
zadd
하나의 member, score를 저장을 하고 성공 시 1을 리턴 하고 중복된 데이터는 0을 리턴 한다.
1 | redisClient.zadd('MySt', 100, 'a', (err, result) => { |
zincrby
하나의 member의 score를 원하는 값만큼 증,감소 할 수 있고 리턴은 증,감소된 값이다.
1 | redisClient.zincrby('MySt', 30, 'a', (err, result) => { |
zrange, zrevrange
zrange는 지정한 index의 범위만큼 score가 낮은 순으로 정렬된 member들을
조회 할 수 있고 0, -1은 모두 조회이다.
zrevrange는 zrange와 반대로 score가 높은 순으로 정렬된 member들을 조회 할 수 있다.
withscores를 추가하면 score도 결과에 나온다.
1 | redisClient.zrange('MySt', 0, -1, (err, result) => { |
zrank, zrevrank
둘 다 member의 index를 리턴 한다.
차이점은 zrank는 score가 작은 순으로 0, zrevrank는 score가 큰 순으로 0
1 | redisClient.zrank('MySt', 'a', (err, result) => { |
zscore
member의 score를 리턴 한다.
1 | redisClient.zscore('MySt', 'a', (err, result) => { |
zrem
데이터를 삭제 한다.
1 | redisClient.zrem('MySt', 'a', (err, result) => { |
zscan
zscan도 sscan과 비슷한 용도
1 | redisClient.zscan('MySt', 0, (err, result) => { |
hashes
hashes는 하나의 key에 여러개 field, value를 저장 할 수 있는 구조
hset, hmset
- hset은 하나의 field, value를 삽입
- hmset은 여러개의 field, value를 삽입
1 | redisClient.hset('MyHash', 'a', 1, (err, result) => { |
hget, hmget
- hget은 하나의 field로 조회 해서 value를 가지고 온다.
- hmget은 여러 개의 field로 조회를 해서 여러 value를 가지고 온다.
1 | redisClient.hget('MyHash', 'a', (err, result) => { |
hdel
필드를 삭제 한다.
1 | redisClient.hdel('MyHash', 'c', (err, result) => { |
hgetall
해당 key의 모든 field, value를 가지고 온다.
1 | redisClient.hgetall('MyHash', (err, result) => { |
hscan
scan 시리즈들은 모두 동일
1 | redisClient.hscan('MyHash', 0, (err, result) => { |
hyperloglog
hyperloglog는 해당 key의 데이터 개수를 구하기 위한 처음 보는 구조다.
장점은 하나의 key당 아주 작은 메모리(최대12KB)를 사용하고 단점은 이게 100% 정확도를
보장하지 않는다고 한다 표준오차는 0.81%인데(그럼 99%는 맞는다는 소리네..) 사용 예는
해당 사이트나 게임에 고유 방문자 수 같은 케이스
pfadd
키에 하나의 데이터 또는 여러 데이터를 삽입할 수 있다.
1 | redisClient.pfadd('visit:2020-01-01', ['user1', 'user2', 'user3', 'user4'], (err, result) => { |
pfcount
하나의 키 또는 여러 키의 개수를 받을 수 있다.
1 | redisClient.pfcount('visit:2020-01-02', (err, result) => { |
그 외 커맨드
pub/sub
publish(발송) / subscribe(구독)의 약자
메세지를 주고 받는 기능, 주의해야 할 점은 구독중인 경우 다른 redis의 명령어를 사용 불가
1 | // publish.js |
1 | // subscribe.js |
subscribe.js를 실행을 하면 채널(MyChannel)을 구독 중이고
publish.js를 실행을 하게 되면 내가 작성한 메세지가 채널(MyChannel)에 보내게 되고
subscribe.js는 구독중이므로 publish.js에서 보낸 메세지를 계속 출력을 한다.
트랜잭션
redis에서 순차적 실행할 수 있는 커맨드를 지원을 한다.
그러나 중간에 에러가 나도 롤백이 안 되고 다음으로 넘어감…
1 | const multi = redisClient.multi(); |
그러나 실제로 들어 가서 보면 tr이 3이 되어 있음
watch
watch를 선언한 key를 트랜잭션을 쓸려고 했을 때 외부에서 값을 변경하게 되는 경우
트랜잭션을 실행하지 않는다.
1 | redisClient.watch('tt'); |
실제 tt의 value를 보면 10으로 되어 있다.