Redis Strings
연속된 bytes, text, serialized objects, binary arrays 등을 저장
- 가장 기본적인 데이터 타입
- 주로 caching을 할 때 사용되지만 counter, bitwise operations에서도 사용됨
- value값은 최대 512MB를 초과할 수 없다.
- structured data를 저장해야 하는 경우, string보다는 hashes나 JSON 타입을 사용하는 것을 권장
명령어
- SET 명령어는 기본적으로 동일한 key에 대해 value를 overwrite
- setnx를 사용하면 해당 key가 없는 경우에만 write할 수 있다.
- setxx의 경우 key가 존재하는 경우에만 write
- GETSET 명령어는 새로운 값을 SET하고 이전 값을 가져오도록 할 수 있다.
- 값을 카운팅을 하다가 주기적으로 flush하고 값을 가져오고 싶은 경우에 사용할 수 있다.
- GET/SET을 따로 수행한다면 값을 GET하고 SET을 수행 하는 사이에 새로운 값이 들어오는 경우 제대로 카운팅이 안 될 수 있다.
- 값을 카운팅을 하다가 주기적으로 flush하고 값을 가져오고 싶은 경우에 사용할 수 있다.
- 여러 값을 한번에 GET하고 SET을 하려면 MSET, MGET을 사용
- INCR, INCRBY 등의 명령어를 통해 value값을 증가시킬 수 있다.
- value값이 string으로 설정되어 있더라도 integer값으로 인식
성능
- 대부분의 string operation은 O(1) 성능
- SUBSTR, GETRANGE, SETRANGE 명령어의 경우 O(N)이 될 수 있다.
Redis Lists
string values의 linked lists
- 주로 redis 내에서 stack이나 queue를 구현할 때 사용
- ex) background worker systems을 위한 queue management를 구축할 때
redis는 lists를 array base가 아닌 linked list base로 구현하였다.
- LPUSH, RPUSH, LPOP, RPOP 등의 명령어는 모두 O(1)만에 수행 가능
- 반면 index 기반으로 값을 접근하는 것은 O(N) 시간이 걸린다는 단점이 존재
- 하지만 database system에서 새로운 값을 매우 긴 리스트에 추가하는 것을 빠른 시간에 수행하는 것이 중요
- 만약 대규모 collection에서 중간에 존재하는 값을 빠르게 접근하는 것이 필요하다면 Sorted sets을 사용
명령어
- LPUSH, RPUSH, LPOP, RPOP, LLEN, LMOVE, LTRIM
- LLEN : 리스트 길이 반환
- LMOVE : 원자적으로 한 리스트의 값을 다른 리스트로 이동
- LTRIM : list를 특정 길이로 축소
리스트 명령어에는 blocking commands가 존재
- BLPOP : list의 헤더의 데이터를 하나 꺼내서 반환. 이때 리스트가 비어있다면 새로운 값이 추가되거나 특정 시간이 될 때까지 대기한다.
- pub/sub 등을 구현할 때 활용할 수 있을 것 같다
- BLMOVE : 원자적으로 한 리스트 값을 다른 리스트로 이동. 이때 소스 리스트가 비어있다면 새로운 값이 추가될 때까지 대기한다.
- pub/sub 및 streams 구현할 때 활용할 수 있을 것 같다
사용예
- 특정 데이터의 history를 관리하고 최근 n개를 조회할 때
- 사용자들의 사진을 공유하는 사이트가 있다고 할 때, 사용자들이 사진을 업로드할 때마다 redis list에 사진ID를 append, 이후 다른 유저가 사이트에 방문했을 때에는 LRANGE 0 9와 같은 명령어를 통해 최근 10개의 사진을 보여줄 수 있다.
- 리스트에 모든 history를 유지하는게 아닌, 최근 N개만 저장하고 싶은 경우, LTRIM 명령어를 같이 활용할 수 있다.
res27 = r.lpush(
"bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5"
)
print(res27) # >>> 5
res28 = r.ltrim("bikes:repairs", 0, 2)
print(res28) # >>> True
res29 = r.lrange("bikes:repairs", 0, -1)
print(res29) # >>> ['bike:5', 'bike:4', 'bike:3']
- LRANGE과 같은 명령어는 이론상으로 O(N)의 시간이 소요되지만, head/tail에서부터 시작해 적은 수를 가져온다면 상수 시간으로 처리할 수 있다.
Blocking operations
BRPOP , BLPOP 등의 명령어를 통해 리스트의 값을 polling 방식이 아닌 interrupt 방식으로 구현
- redis에서 해당 리스트의 값이 추가되면 호출한 곳에 알려준다.
- 하나의 client에서 여러 list에 대해서 대기할 수도 있다.
- 여러 client가 하나의 리스트에 대해 대기를 하고 있는 경우 대기한 순서대로 값을 가져가게 된다.
- 기존 RPOP, LPOP과 다르게 반환값 형태가 (키, 값) 형태로 되어 있다.
- 여러 리스트에 대해 대기 할 수 있으므로 key값을 통해 이를 구분
res31 = r.rpush("bikes:repairs", "bike:1", "bike:2")
print(res31) # >>> 2
res32 = r.brpop("bikes:repairs", timeout=1)
print(res32) # >>> ('bikes:repairs', 'bike:2')
res33 = r.brpop("bikes:repairs", timeout=1)
print(res33) # >>> ('bikes:repairs', 'bike:1')
res34 = r.brpop("bikes:repairs", timeout=1)
print(res34) # >>> None
한계
redis list의 최대 사이즈는 2^32 - 1이다.
성능
- list의 head/tail에 접근하는 것은 O(1)
- LINDEX, LINSERT, LSET과 같이 리스트 내부의 값들을 복제하는 명령어의 경우 O(N)
Redis Sets
redis sets : 순서가 없는 유니크한 strings의 집합
- 각 key의 unique item을 추적하기 위해 주로 사용
명령어
- SADD : set에 새로운 member를 추가
- SREM : set에서 특정 member를 제거
- SPOP : set의 특정 member를 반환 후 제거
- SISMEMBER : 특정값이 set의 member인지 확인
- smismember : 여러 값을 동시 조회
- SINTER : set의 intersection 반환
- SDIFF : set의 difference 반환
- SUNION : set의 union 반환
- SCARD : set의 사이즈를 반환
- smembers : set의 모든 멤버를 반환
- srandmember : set내의 멤버 중 하나를 랜덤으로 반환(제거 X)
한계
redis set의 최대 멤버 수는 2^32 - 1이다.
성능
- redis sets의 대부분의 명령어는 O(1)
- SMEMBERS의 경우 O(n)이므로 반드시 주의해서 사용해야 한다.
- SMEMBERS를 반드시 사용해야 하는 경우 SSCAN으로 대체하는 것을 고려
Redis Hashes
redis hashes : field-value pair로 이루어진 record types
- counters나 기타 값들에 대한 그룹핑을 해서 저장해 사용할 수 있다.
Map<String, String> bike1 = new HashMap<>();
bike1.put("model", "Deimos");
bike1.put("brand", "Ergonom");
bike1.put("type", "Enduro bikes");
bike1.put("price", "4972");
Long res1 = jedis.hset("bike:1", bike1);
System.out.println(res1); // 4
String res2 = jedis.hget("bike:1", "model");
System.out.println(res2); // Deimos
String res3 = jedis.hget("bike:1", "price");
System.out.println(res3); // 4972
Map<String, String> res4 = jedis.hgetAll("bike:1");
System.out.println(res4); // {type=Enduro bikes, brand=Ergonom, price=4972, model=Deimos}
- HSET을 통해서 여러 fields를 한번에 setting할 수 있다.
- HGET은 하나의 field 값만을 반환
- 여러 fields를 한번에 반환하고자 한다면 HMGET을 사용
List<String> res5 = jedis.hmget("bike:1", "model", "price");
System.out.println(res5); // [Deimos, 4972]
- 특정 field의 값만 증감시킬 수 있다.
Long res6 = jedis.hincrBy("bike:1", "price", 100);
System.out.println(res6); // 5072
Long res7 = jedis.hincrBy("bike:1", "price", -100);
System.out.println(res7); // 4972
명령어
- HSET : hash 세팅
- HGET : hash 내 field 값 반환
- HMGET : hash 내 fields 값 반환
- HINCRBY : hash 내 field의 값 증감
- HKEYS : 해쉬 내의 모든 key값 반환
- HVALS : 해쉬 내의 모든 value값 반환
- HGETALL : 해쉬 내 모든 key/value값 반환
한계
- hash는 2^32 - 1의 field-value pair를 저장할 수 있다.
- 사실상 memory 상의 문제가 아니면 부족하지 않음
성능
- 대부분의 hash 명령어는 O(1)
- 일부 명령어(HKEYS, HVALS, HGETALL)은 O(N)
- N : field-value pair의 개수
Redis Sorted Sets
Redis Sorted Sets : 기존 redis sets에 특정 score를 기준으로 정렬된 set
- 동일한 score가 존재한다면 key string의 사전순으로 정렬되어 있다.
- 내부적으로 skip list와 hash table이 혼합된 data structures로 구성되어 있다.
- 값을 추가할 때에는 O(log(N))이 소요되지만, 가져올 때에는 O(1)로 가져올 수 있다.
사용예
- LeaderBoards 구축에 사용될 수 있다.
- Rate limiters에서 사용될 수 있다.
- API requests의 초과를 방지하기 위함
명령어
res1 = r.zadd("racer_scores", {"Norem": 10})
print(res1) # >>> 1
res2 = r.zadd("racer_scores", {"Castilla": 12})
print(res2) # >>> 1
res3 = r.zadd(
"racer_scores",
{"Sam-Bodden": 8, "Royce": 10, "Ford": 6, "Prickett": 14, "Castilla": 12},
)
print(res3) # >>> 4
- ZADD : SADD와 비슷하지만, score argument가 추가된다.
- ZRANGE : 오름차순 기준으로 특정 범위의 값을 가져올 수 있다.
- withscores 옵션을 통해 스코어값도 같이 가져올 수 있다.
res6 = r.zrange("racer_scores", 0, -1, withscores=True)
- ZREVRANGE : 내림차순 기준으로 특정 범위의 값을 가져올 수 있다.
- withscores 옵션을 통해 스코어값도 같이 가져올 수 있다.
- ZRANGEBYSCORE : 스코어를 기준으로 해당 스코어 범위에 있는 값을 가져올 수 있다.
res7 = r.zrangebyscore("racer_scores", "-inf", 10)
- ZREMRANGEBYSCORE : 스코어를 기준으로 범위 삭제를 할 수 있다.
res9 = r.zremrangebyscore("racer_scores", "-inf", 9)
- ZRANK : 해당 값의 랭킹값을 반환(0부터 시작)
- ZREVRANK : 해당 값의 랭킹값을 역순으로 반환
res11 = r.zrank("racer_scores", "Norem")
print(res11) # >>> 0
res12 = r.zrevrank("racer_scores", "Norem")
print(res12) # >>> 2
- ZINCRBY : 특정 값의 score를 증감
res19 = r.zincrby("racer_scores", 50, "Wood")
성능
- 대부분의 명령어는 O(log(N))
- N : 멤버 수
- ZRANGE의 경우 O(log(n) + M)이기 때문에 큰 sorted set에서 넓은 범위를 가져올 때 주의해서 사용해야 한다.
- n : 멤버 수
- m : 반환될 멤버 수
Redis Streams
Redis Streams : append-only log만 가능한 data structure
- O(1)의 random access 제공
- 복잡한 consuming strategies 제공
- consumer groups..
사용예
- Event Sourcing(유저 행동 및 클릭 추적)
- Sensor monitoring(실제 디바이스로부터의 정보)
- Notifications(각 user의 알람을 각각 별도의 stream에 저장)
기본명령어
- XADD : stream에 새로운 데이터 저장
- XREAD : 한 개 또는 여러개의 데이터를 읽어온다. 이 때 특정 위치부터 시작
- XRANGE : 두 entries ID 내의 모든 entries를 반환
- XLEN : stream의 길이 반환
res1 = r.xadd(
"race:france",
{"rider": "Castilla", "speed": 30.2, "position": 1, "location_id": 1},
)
print(res1) # >>> 1692629576966-0
res2 = r.xadd(
"race:france",
{"rider": "Norem", "speed": 28.8, "position": 3, "location_id": 1},
)
print(res2) # >>> 1692629594113-0
res3 = r.xadd(
"race:france",
{"rider": "Prickett", "speed": 29.7, "position": 2, "location_id": 1},
)
print(res3) # >>> 1692629613374-0
- 반환값은 entry의 ID가 자동으로 생성되어 반환된다.
- 반환되는 entry ID는 이전보다 증가된 값이다.
res5 = r.xread(streams={"race:france": 0}, count=100, block=300)
print(
res5
) # >>> [['race:france', [('1692629576966-0', {'rider': 'Castilla', 'speed': '30.2', ...})]
- stream의 끝에서부터 최대 100개까지 데이터를 읽어온다.
- 데이터가 없는 경우 최대 300ms 동안 기다린다.
Entry ID
<milliSecondsTime>-<sequenceNumber>로 구성되어 있다.
- milliSecondsTime은 Redis Node의 local time
- 이때 local time이 달라져 현재 milliSecondsTime이 이미 추가된 entryID의 milliSecondsTime보다 작은 경우, 이미 생성된 milliSecondsTime으로 대체된다.
- 즉, entryID의 반드시 증가하는 특성은 그대로 유지
사용자가 직접 별도의 ID를 지정해줄 수도 있다.
res8 = r.xadd("race:usa", {"racer": "Castilla"}, id="0-1")
print(res8) # >>> 0-1
res9 = r.xadd("race:usa", {"racer": "Norem"}, id="0-2")
print(res9) # >>> 0-2
추가내용
Redis Geospatial
Redis Geospatial : 좌푯값을 저장하고, 좌푯값을 기준으로 주변을 검색할 수 있도록 하는 data structure
- GEOADD : geospatial index를 저장
- GEOSEARCH : 주어진 locations과 radius, bounding box를 기준으로 값을 반환
res1 = r.geoadd("bikes:rentable", [-122.27652, 37.805186, "station:1"])
print(res1) # >>> 1
res2 = r.geoadd("bikes:rentable", [-122.2674626, 37.8062344, "station:2"])
print(res2) # >>> 1
res3 = r.geoadd("bikes:rentable", [-122.2469854, 37.8104049, "station:3"])
print(res3) # >>> 1
res4 = r.geosearch(
"bikes:rentable",
longitude=-122.27652,
latitude=37.805186,
radius=5,
unit="km",
)
print(res4) # >>> ['station:1', 'station:2', 'station:3']
Redis bitmaps
Redis bitmaps : bitmap은 실제 데이터 타입이 아닌, bit-oriented operations으로 구성되어 있는 string type의 데이터
- SETBIT : bit를 0 또는 1로 변경
- GETBIT : bit를 반환
- BITOP : 한 개 또는 여러 개의 strings에 대해 bitwise operation을 수행
> SETBIT pings:2024-01-01-00:00 123 1
(integer) 0
> GETBIT pings:2024-01-01-00:00 123
1
참고
'Redis(레디스)' 카테고리의 다른 글
| Redis Sentinel (1) | 2023.12.10 |
|---|---|
| Redis client-side caching (1) | 2023.12.10 |
| Redis pipelining (0) | 2023.12.10 |
| Redis pub/sub & redis stream과의 차이점 (1) | 2023.12.10 |
| Redis Transaction (1) | 2023.12.10 |