Introduction
요즘 부쩍 레디스를 자주 사용하다보니 매번 헤깔려하는 데이터 타입들을 정리해보고자 한다. 이전에도 그랬는지 모르겠는데 레디스 공식 문서가 정말 정리가 잘 되어있는 것 같다.
Redis 데이터 타입 알아보기
Redis 데이터 타입 (Collection)
Redis는 자료구조 서버이다. 이 점으로 인해, Redis는 Key-Value 스토리지에서 Value에 단순한 Object가 아니라 다양한 자료구조를 지원한다.
인기 있는 데이터 타입으로는 Strings, Lists, Sets, Hashes, Sorted Sets, Streams, HyperLogLogs가 있다.
Redis 데이터 타입 사용 시 하나의 컬렉션에 너무 많은 아이템을 담으면 좋지 않다.
가능하면 만 개 이하의, 몇 천 개 수준의 데이터 셋을 유지하는 게 Redis 성능에 영향 주지 않는다.
1. Strings
•
가장 기본적인 Redis 데이터 유형입니다.
•
값은 최대 512MB이다.
•
Text, serialized objects, binary arrays를 포함한 바이트 시퀀스를 저장할 수 있다.
•
HTML fragment나 페이지를 캐싱할 때 유용하다.
•
카운터를 구현하거나 비트 단위의 연산을 수행할 때도 사용할 수 있다.
•
단순 증감 연산에 좋다.
•
string-string 매핑을 이용하여 연결되는 자료 매핑을 할 수도 있다. HTML 매핑도 가능하다.
•
대부분의 스트링 작업은 로, 매우 효율적이다.
그러나 Substrate, GETRANGE 및 SETRANGE 명령을 사용하면 이 될 수 있다. 이러한 랜덤 액세스 문자열 명령은 큰 문자열을 처리할 때 성능 문제를 일으킬 수 있다.
•
구조화된 데이터를 직렬화된 문자열로 저장하는 경우 Redis hash 또는 JSON을 고려할 수도 있다.
명령어
Commands | Syntax | Description |
key | 1씩 감가, key가 존재하지 않으면 연산 수행 전 0으로 설정 | |
key decrement | decrement만큼 감소. 신규이면 -decrement로 setting | |
key | 데이터 조회 | |
key value | 기존 데이터를 조회하고 새 데이터를 저장 | |
key | 1씩 증가, key가 없으면 연산 수행 전 0으로 설정 | |
key increment | increment만큼 증가. 신규이면 increment로 setting | |
key [key…] | 여러 개의 데이터를 한번에 조회 | |
key value [EX seconds] [PX milliseconds] [NX | XX] | 데이터를 저장, key가 이미 있으면 덮어쓴다 | |
key value [key value…] | 여러 개의 데이터를 한 번에 저장 또는 수정 | |
key value [key value…] | 지정한 key가 없을 경우에만, 여러 개의 데이터를 한 번에 저장 | |
key offset value | 지정한 위치(offset)부터 데이터를 겹쳐쓴다 | |
key | 데이터의 바이트 수를 리턴 | |
key start end | 데이터의 일부 문자열을 조회 | |
key increment | 실수 연산, increment만큼 증가 | |
key1 key2 [LEN] [IDX] [MINMATCHLEN min-match-len] [WITHMATCHLEN] | 가장 유사한 문자열을 찾는다 | |
key [EX seconds | PX milliseconds | EXAT unix-time-seconds |
PXAT unix-time-milliseconds | PERSIST] | 데이터 조회와 만료 시간 설정
- EX secondes : 만료 시간(초)을 설정
- PX milliseconds : 만료 시간(밀리초)을 설정
- EXAT timestamp-seconds : 만료 시간(유닉스 시간-초)을 설정
- PXAT timestamp-milliseconds : 만료 시간(유닉스 시간-밀리초)을 설정
- PERSIST : 키와 연결하여 사용할 시간을 제거 | |
key | 데이터 조회와 삭제 | |
key value | string 끝에 value를 추가. key가 없으면 빈 문자열로 생성되어 설정된다. |
2. Lists
•
List는 스트링 값의 링크된 리스트이다.
•
리스트는 다음을 위해 자주 사용된다.
◦
Queue와 Stack을 구현
◦
Background worker 시스템에 대한 큐 관리 구축
•
Redis List는 Linked List를 통해 구현된다.
◦
리스트 안에 수백만 개의 요소가 있더라도 리스트의 머리나 꼬리에 새 요소를 추가하는 작업은 일정한 시간 안에 수행된다는 것을 의미
→ 원소가 10개인 리스트의 머리에 LPUSH 명령으로 새 요소를 추가하는 속도는 원소가 1천만 개인 목록의 머리에 요소를 추가하는 속도와 같다.
⇒ 데이터베이스 시스템에서 매우 빠른 방법으로 매우 긴 리스트에 요소를 추가할 수 있다.
•
Linked List로 구현된 리스트이기 때문에 인덱스를 사용하여 요소에 액세스하는 것은 그다지 빠르지 않다.
•
일정 시간 동안 일정한 길이로 수집될 수 있다.
•
항목을 고정된 순서대로 인덱싱하는 데 사용할 수 있다.
•
Active-Active 데이터베이스에서 자주 사용된다.
•
최대 길이는 2^32 - 1 (4,294,967,295)개의 요소이다.
•
머리나 꼬리에 접근하는 List 연산은 로, 매우 효율적이다.
그러나 LINDEX, LINSERT, LSET 등과 같이 리스트 내의 요소를 조작하는 명령어는 보통 이다. 이러한 명령어를 실행할 때 주의해야 하며, 주로 큰 리스트에서 연산할 때 주의해야 한다.
•
불확실한 일련의 이벤트를 저장하고 처리해야 할 경우 List의 대안으로 Redis Streams을 고려할 수 있다.
일반적인 사용 사례
List에 대한 대표적인 사용 사례는 다음과 같다:
•
사용자가 소셜 네트워크에 게시한 최신 업데이트 기록
•
생산자가 품목을 목록으로 밀어 넣고 소비자(일반적으로 근로자)가 해당 품목을 소비하여 작업을 수행하는 소비자-생산자 패턴을 사용하는 프로세스 간의 커뮤니케이션.
만약 사진 공유 소셜 네트워크에 게시된 최신 사진을 홈 페이지에 표시하고 액세스 속도를 높이고자 한다면:
•
사용자가 새로운 사진을 올릴 때마다 LPUSH로 ID를 리스트에 추가한다.
•
사용자가 홈페이지를 방문하면 최신 게시물 10개를 받기 위해 LANGE 0 9를 사용한다.
(일정 크기를 고정적으로 빠르게 반환할 수 있다.)
Capped List
많은 사용 사례를 보면, 소셜 네트워크 업데이트, 로그 또는 기타 모든 것과 같은 최신 항목을 저장하기 위해 리스트를 사용하는 것을 알 수 있다.
이때 Capped List을 사용하면 리스트를 최신 N개 항목만 기억하고 LTRIM 명령을 사용하여 가장 오래된 항목은 모두 폐기할 수 있다. LTRIM 명령은 지정된 범위의 요소를 대체하는 대신 이 범위를 새 목록 값으로 설정한다. 지정된 범위 밖의 요소는 모두 제거된다.
LANGE는 O(N) 명령이기 때문에, 리스트의 머리 또는 꼬리 쪽으로 작은 범위에 액세스하는 것은 일정한 시간이 걸린다.
리스트에서 작업 차단
리스트에는 큐를 구현하는 적합한 특정 기능이 있으며, 일반적으로 프로세스 간 통신 시스템의 빌딩 블록(blocking operations)으로 사용된다.
일반적인 생산자/소비자 설정은 한 프로세스로 아이템을 리스트에 넣고 다른 프로세스를 사용하여 실제로 해당 아이템에 대한 작업을 수행한다. 이는 다음과 같은 간단한 방법으로 구현할 수 있다:
•
리스트에 아이템을 넣기 위해 생산자가 LPUSH를 호출한다.
•
리스트에서 아이템을 추출/처리하기 위해 소비자가 RPOP을 호출한다.
그러나 목록이 비어 처리할 것이 없으면 RPOP은 NULL을 반환한다. 이 경우 소비자는 시간을 기다렸다가 다시 시도해야 한다. (이를 폴링(polling)이라고 한다)
이 때문에 Redis는 리스트가 비어 있는 경우 차단할 수 BRPOP 및 BLPOP이라는 명령을 제공한다. 리스트에 새 요소가 추가되거나 사용자 지정 시간 초과에 도달한 경우에만 발신자에게 리턴된다.
다음은 작업자에게 사용할 수 있는 BRPOP 호출의 예이다:
public class ListExample {
public void run() {
UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379");
//....
long res31 = jedis.rpush("bikes:repairs", "bike:1", "bike:2");
System.out.println(res31); // >>> 2
List<String> res32 = jedis.brpop(1, "bikes:repairs");
System.out.println(res32); // >>> (bikes:repairs, bike:2)
List<String> res33 = jedis.brpop(1,"bikes:repairs");
System.out.println(res33); // >>> (bikes:repairs, bike:1)
List<String> res34 = jedis.brpop(1,"bikes:repairs");
System.out.println(res34); // >>> null
//....
}
}
Java
복사
"리스트 bikes의 요소를 기다리십시오: 수리하십시오. 그러나 1초 후에 요소가 없으면 반환하십시오."라는 의미이다.
0을 시간 초과로 사용하여 요소를 영원히 기다릴 수 있으며, 하나의 목록이 아닌 여러 목록을 지정하여 여러 목록에서 동시에 대기하고 첫 번째 목록에서 요소가 수신되면 알림을 받을 수도 있다.
키 자동 생성 및 제거
지금까지 예제에서는 요소를 푸시하기 전에 빈 리스트를 만들거나, 더 이상 요소가 없을 때 빈 리스트를 제거할 필요가 없었다. 리스트가 비어 있을 때 키를 삭제하거나, 키가 존재하지 않고 요소를 추가하려고 할 때 빈 리스트을 만드는 것은 Redis의 책임이다.
이는 리스트에만 해당되는 것이 아니라 Streams, Sets, Sorted Sets 및 Hashes와 같은 여러 요소로 구성된 모든 Redis 데이터 유형에 적용된다.
기본적으로 세 가지 규칙으로 요약할 수 있다:
1.
요소를 집계 데이터 유형에 추가할 때 대상 키가 없으면 요소를 추가하기 전에 빈 집계 데이터 유형이 생성된다.
2.
집계 데이터 유형에서 요소를 제거할 때 값이 비어 있으면 키가 자동으로 파괴된다. Stream 데이터 유형만 이 규칙에서 예외된다.
3.
빈 키(존재하지 않거나 데이터를 보유하지 않는 키)에 명령을 사용하면 호출하면, 명령은 키가 존재하는 것처럼 동작하고 기대하는 빈 list, set, hash, 또는 기타 집계 유형을 보유한다.
예를 들어, 빈 키에서 LLEN을 호출하면 빈 리스트의 길이가 0이므로 0이 반환된다. 마찬가지로 빈 키에서 DEL이나 SPOP과 같이 요소를 제거하는 명령을 사용하면 0을 반환하여 요소가 제거되지 않았음을 나타낸다.
명령어
Commands | Syntax | Description |
source destination <LEFT | RIGHT> <LEFT | RIGHT> timeout | 리스트에서 요소를 꺼내고(POP), 다른 리스트에 넣는다(Push).
마지막 요소가 이동하면 리스트를 삭제한다. | |
timeout numkeys key [key ...] <LEFT | RIGHT> [COUNT count] | 여러 리스트 중 하나의 첫 번째 요소를 꺼낸다. 마지막 요소가 POP 되었을 때 리스트를 삭제한다. | |
key [key ...] timeout | 리스트의 첫 번째 요소를 제거하고 리턴한다. 마지막 요소가 POP 되었을 때 리스트를 삭제한다. | |
key [key ...] timeout | 리스트의 마지막 요소를 제거하고 리턴한다. 마지막 요소가 POP 되었을 때 리스트를 삭제한다. | |
key index | 인덱스에 해당하는 요소 리턴 | |
key <BEFORE | AFTER> pivot element | 다른 요소 전 또는 후에 삽입 | |
key | 리스트 길이 리턴 | |
source destination <LEFT | RIGHT> <LEFT | RIGHT> | 리스트에서 꺼내고, 다른 곳에 넣은 요소를 리턴(리스트 간 데이터 이동). 마지막 요소가 이동됐을 때 리스트 삭제 | |
numkeys key [key ...] <LEFT | RIGHT> [COUNT count] | 여러 요소들을 삭제한 후, 그 요소들을 리턴. 마지막 요소가 이동됐을 때 리스트 삭제 | |
key [count] | 리스트의 첫번째 요소를 제거 후, 해당 요소를 리턴. 마지막 요소가 이동됐을 때 리스트 삭제 | |
key element [RANK rank] [COUNT num-matches] [MAXLEN len] | 리스트에서 매칭되는 요소의 인덱스 리턴 | |
key element [element ...] | 하나 이상의 요소를 리스트 앞에 추가. key가 존재하지 않으면 key를 생성 | |
key element [element ...] | 리스트가 존재할 때만 하나 이상의 요소를 리스트 앞에 추가 | |
key start stop | 인덱스로 범위를 지정해 리스트 조회 | |
key count element | 리스트에서 요소 삭제. 마지막 요소가 이동 됐을 때 리스트 삭제 | |
key index element | 인덱스로 특정 위치의 값을 바꿈 | |
key start stop | 인덱스로 지정한 범위 밖의 값들을 삭제. 모든 요소가 트리밍 되었을 때 리스트 삭제 | |
key [count] | 리스트의 마지막 값을 리턴하고 삭제. 마지막 리스트가 pop 됐을 때 리스트 삭제 | |
key element [element ...] | 리스트에 하나 이상의 값을 추가. key가 존재하지 않으면 새로 생성 | |
key element [element ...] | 리스트가 존재할 때만 값을 추가 |
3. Sets
•
Redis Set은 고유한 문자열(멤버)의 순서가 지정되지 않은 집합이다. Set을 사용하여 효율적으로 다음을 수행할 수 있다:
◦
고유한 아이템 추적(예: 특정 블로그 게시물에 액세스하는 모든 고유 IP 주소 추적)
◦
관계(예: 주어진 역할을 가진 모든 사용자의 집합)를 나타낸다.
◦
교집합, 합집합, 차집합 등의 공통 집합 연산을 수행
•
최대 사이즈는 2^32 - 1 (4,294,967,295) members이다.
•
아이템 추가, 제거 및 집합 멤버인지 확인하는 것을 포함한 대부분의 작업은 이다.
•
그러나 수십만 개 이상의 멤버로 구성된 큰 Set의 경우 SMEMBERS 명령을 실행하면 단일 응답으로 전체 Set를 반환하게 된다. 이때 시간 복잡도가 이 된다. 대안으로 집합의 모든 멤버를 반복적으로 검색할 수 있는 SSAN을 사용하면 된다.
•
대규모 데이터셋(또는 스트리밍 데이터)에서 회원 확인을 설정하면 많은 메모리가 사용될 수 있다. 메모리 사용량이 걱정되고 완벽한 정밀도가 필요하지 않다면 Bloom filter 또는 Cuckoo filter를 대안으로 고려할 수 있다.
•
기본 명령어
4. Hashes
•
Redis Hash는 필드-값 쌍의 집합으로 구조화된 레코드 유형이다.
•
해시를 사용하여 기본 객체를 나타내고 카운터 그룹을 저장할 수 있습니다.
•
해시는 객체를 표현하는 데 유용하다. 실제로 해시 내부에 넣을 수 있는 필드 수에는 실질적인 제한이 없으므로 응용 프로그램 내부에서 여러 가지 방법으로 해시를 사용할 수 있다.
•
대부분의 해시 명령어는 이다.
•
HKEY, HVALS 및 HGETALL과 같은 몇 가지 명령은 이며, 여기서 n은 필드 값 쌍의 수이다.
•
모든 해시는 최대 4,294,967,295(2^32 - 1)개의 필드-값 쌍을 저장할 수 있다. 실제로, 해시는 Redis 배포를 호스팅하는 VM의 전체 메모리에 의해서만 제한된다.
기본 명령어
5. Sorted Sets
•
Sorted set은 연관된 점수로 정렬된 고유한 문자열(멤버)의 모음다. 두 개 이상의 문자열이 동일한 점수를 가지면 문자열이 사전식으로 정렬된다.
•
Sorted Set의 일부 사용 사례는 다음과 같다:
◦
리더보드 : 예를 들어, 대규모 온라인 게임에서 가장 높은 점수로 정렬된 목록을 쉽게 유지할 수 있다.
◦
처리율 제한 장치(Rate Limiter) : sliding-window 처리율 제한 장치를 구축하여 과도한 API 요청을 방지할 수 있다.
•
대부분의 연산은 이며, 여기서 n은 멤버 수이다.
•
반환 값이 큰 ZRANGE 명령을 실행할 때는 몇 가지 주의해야 한다(예: 수만 개 이상). 이 명령의 시간 복잡도는 이며, 여기서 m은 반환된 결과의 수이다.
•
Sorted Set은 다른 Redis 데이터 구조를 인덱싱하는 데 사용되기도 한다. 데이터를 인덱싱하고 쿼리해야 하는 경우, JSON 데이터 유형과 검색 및 쿼리 기능을 고려할 수 있다.
Set과 Sorted Set
Set과 마찬가지로 Sorted Set도 중복되지 않는 문자열 요소로 구성되므로 이러한 의미에서는 Sorted set도 Set이다.
그러나 Set 내부의 요소는 순서가 지정되지 않지만 Sorted Set의 모든 요소는 점수라고 하는 부동 소수점 값과 연결된다(모든 요소가 값에 매핑되기 때문에 해시와 유형이 유사하다).
또한 Sorted Set의 요소는 요청 순에 따라 순서가 지정되지 않고, 다음 규칙에 따라 순서가 지정된다:
•
B와 A가 점수가 다른 두 요소인 경우, A.score가 > B.score이면 A > B이다.
•
B와 A의 점수가 정확히 같으면, 사전 순을 따른다. 예를 들어, A 문자열이 B 문자열보다 사전적으로 크다면 A > B이다. Sorted Set에는 고유한 요소만 있으므로 B와 A 문자열은 동일할 수 없다.
스코어 업데이트 : 리더보드
Sorted set의 스코어는 언제든지 업데이트 될 수 있다. 정렬된 집합에 이미 포함된 요소에 대해 ZADD를 호출하는 것만으로도 스코어(및 위치)가 시간 복잡도로 업데이트 된다. 따라서 Sorted Set은 업데이트가 많을 때 적합하다. 이러한 특성 때문에 리더보드에 사용하기 좋다.
기본 명령어
6. Streams
•
Stream은 일반적인 append-only 로그의 일부 한계를 극복하기 위해 여러 연산을 구현하는 데이터 구조이다.
•
Redis Streams는 여러 소비자와 동시에 공유할 수 있는 이벤트의 중심 소스 역할을 하여 이러한 이벤트를 실시간으로 처리하거나 반응할 수 있다.
•
레디스 스트림의 사용 사례는 다음과 같다:
◦
이벤트 소싱(예: 사용자 조치 추적, 클릭 등)
◦
센서 모니터링(예: 현장의 장치에서 판독)
◦
알림(예: 각 사용자의 알림에 대한 레코드를 별도의 스트림에 저장)
•
Redis는 각 스트림 항목에 대해 고유한 ID를 생성한다.
◦
ID를 사용하여 나중에 관련 항목을 검색하거나 스트림의 모든 후속 항목을 읽고 처리할 수 있다.
◦
ID는 데이터가 저장된 시간이다.
•
Redis 스트림은 여러 가지 트리밍(Trimming) 전략(스트림이 무한대로 성장하는 것을 방지하기 위한)과 하나 이상의 소비 전략(XREAD, XREADGROUP 및 XRANGE 참조)을 지원한다.
•
스트림에 entry을 추가하는 것은 이다.
•
임의의 단일 항목에 액세스하는 것은 이며, 여기서 n은 ID의 길이다. 스트림 ID는 일반적으로 짧고 고정된 길이이므로, 이는 효과적으로 일정한 시간 조회로 줄어든다.
기본 명령어
Consumer groups
Redis Consumer groups은 스트림에서 데이터를 얻고 여러 소비자에게 서비스를 제공하는 유사 소비자 역할을 한다.
•
Consumer groups은 각 메시지가 다른 소비자에게 제공되도록 하여 여러 명의 소비자가 동일한 메시지를 받는 것을 방지한다.
•
Consumer groups 내의 소비자는 클라이언트 구현에서 선택한 문자열(대소문자 구분)인 이름으로 식별 된다.
•
Consumer groups은 처음 언급될 때 자동으로 생성되므로 명시적으로 생성할 필요가 없다.
•
Consumer groups은 연결이 끊어진 후에도 상태를 유지하므로, 고객이 이전과 동일한 소비자인지 알 수 있습니다.
•
Consumer groups의 메시지는 명시적인 확인 명령을 사용하여 처리된 것으로 표시할 수 있다.
•
Consumer groups은 보류 중인 메시지(전달되었지만 아직 처리된 것으로 확인되지 않은 메시지)를 추적한다.
•
Redis는 Consumer groups을 사용하여 스트림으로부터 읽을 수 있는 XREADGROUP과 같은 명령을 제공한다.
이러한 특징으로 인해, Consumer groups은 여러 소비자가 동일한 스트림에서 서로 다른 메시지 하위 집합을 처리해야 하는 시나리오에 유용하다.
→ 확장성과 워크로드의 효율적인 배포를 가능하게 한다.
Kafka(TM) 파티션과의 차이점
Redis 스트림의 소비자 그룹은 어떤 면에서는 Kafka(TM) 파티셔닝 기반 소비자 그룹과 비슷할 수 있지만, 실제로는 매우 다르다.
파티션은 논리적일 뿐, 메시지는 하나의 Redis 키에 입력되기 때문에 서로 다른 클라이언트를 서비스하는 방식은 새로운 메시지를 처리할 준비가 된 사람이 누구인지에 따라 결정된다. 예를 들어, 어떤 시점에서 소비자 C3에 영구적으로 장애가 발생하면 Redis는 도착하는 모든 새 메시지를 C1과 C2에 계속 서비스한다. 마치 지금은 두 개의 논리적 파티션만 있는 것처럼 말이다.
마찬가지로 주어진 소비자가 다른 소비자보다 메시지 처리 속도가 훨씬 빠르다면, 이 소비자는 같은 시간 안에 비례적으로 더 많은 메시지를 받게 된다. 레디스는 승인되지 않은 모든 메시지를 명시적으로 추적하고, 누가 어떤 메시지를 받았는지, 어떤 소비자에게도 전달되지 않은 첫 번째 메시지의 ID를 기억하기 때문에 가능하다.
그러나 하나의 Redis 스트림이 여러 인스턴스로 자동으로 분할되지는 않는다. 그러므로 여러 키와 Redis Cluster와 같은 일부 샤딩(sharding) 시스템 또는 다른 응용 프로그램별 샤딩(sharding) 시스템을 사용해야 한다.
즉, 다음과 같은 것이 사실이라고 말할 수 있다:
•
1stream -> 1consumer를 사용하는 경우 메시지를 순서대로 처리하고 있다.
•
N개의 소비자와 함께 N개의 스트림을 사용하여 특정 소비자만이 N개의 스트림의 부분 집합에 도달하도록 하는 경우, 위의 1개의 스트림 -> 1개의 소비자 모형을 확장할 수 있다.
•
1stream -> N개의 소비자를 사용하는 경우, N개의 소비자에게 로드 밸런싱을 수행하고 있지만, 이 경우 동일한 논리 항목에 대한 메시지가 순서에 맞지 않게 소비될 수 있다. 이는 특정 소비자가 메시지 4를 처리하는 경우보다 메시지 3을 더 빠르게 처리할 수 있기 때문이다.
⇒ 따라서 기본적으로 Kafka 파티션은 N개의 다른 Redis 키를 사용하는 것과 더 유사한 반면, Redis 소비자 그룹은 주어진 스트림에서 N개의 다른 소비자에게 보내는 메시지의 server-side 로드 밸런싱 시스템이다.
Capped Stream
Redis의 Caped Streams는 제한된 수의 스트림 항목을 유지하는 고정된 크기의 스트림을 한다. 이는 스트림에서 특정 개수의 최신 항목만 유지하고 이전 항목은 자동으로 폐기하려는 시나리오에서 유용할 수 있다.
jedis.xadd("race:italy", new HashMap<String,String>(){{put("rider","Jones");}},XAddParams.xAddParams().maxLen(10));
jedis.xadd("race:italy", new HashMap<String,String>(){{put("rider","Wood");}},XAddParams.xAddParams().maxLen(10));
jedis.xadd("race:italy", new HashMap<String,String>(){{put("rider","Henshaw");}},XAddParams.xAddParams().maxLen(10));
long res35 = jedis.xlen("race:italy");
System.out.println(res35); // >>> 8
List<StreamEntry> res36 = jedis.xrange("race:italy","-","+");
System.out.println(res36);
// >>> [1701771219852-0 {rider=Castilaa}, 1701771219852-1 {rider=Royce}, 1701771219853-0 {rider=Sam-Bodden}, 1701771219853-1 {rider=Prickett}, 1701771219853-2 {rider=Norem}, 1701771219858-0 {rider=Jones}, 1701771219858-1 {rider=Wood}, 1701771219859-0 {rider=Henshaw}]
StreamEntryID id6 = jedis.xadd("race:italy", new HashMap<String,String>(){{put("rider","Smith");}},XAddParams.xAddParams().maxLen(2));
List<StreamEntry> res37 = jedis.xrange("race:italy","-","+");
System.out.println(res37);
// >>> [1701771067332-1 {rider=Henshaw}, 1701771067332-2 {rider=Smith}]
Java
복사
MAXLEN을 사용하면 지정된 길이에 도달하면 이전 항목이 자동으로 제거되므로 스트림은 일정한 크기로 유지된다. 현재 주어진 기간보다 오래되지 않은 항목만 유지하도록 스트림에 지시할 수 있는 옵션은 없다. 이러한 명령을 구현할 경우, 오래된 항목을 제거하기 위해 상당한 시간 동안 명령 실행을 차단해야 하므로 Redis의 성능과 응답성에 부정적인 영향을 미칠 수 있기 때문이다.
그러나 MAXLEN을 사용한 트리밍은 비용이 많이 들 수 있다. 따라서 다음과 같은 형태의 명령어를 사용할 수 있다:
XADD race:italy MAXLEN ~ 1000 * ... entry fields here ...
Java
복사
~ 인수는 이것이 정확히 1000개의 항목일 필요는 없다는 것을 의미한다. 1000개 또는 1010 또는 1030개가 될 수 있으므로 적어도 1000개의 항목을 저장해야 한다. 이 인수를 사용하면 트리밍은 전체 노드를 제거할 수 있을 때만 수행되기 때문에 훨씬 효율적이다.
XTRIM 명령도 있는데, 이 명령은 자체적으로 실행할 수 있다는 점을 제외하고는 위의 MAXLEN 옵션과 매우 유사하게 실행된다:
long res38 = jedis.xtrim("race:italy",XTrimParams.xTrimParams().maxLen(10).exactTrimming());
System.out.println(res38); /// >>> 0
Java
복사
또는 XADD 옵션의 경우:
long res39 = jedis.xtrim("race:italy",XTrimParams.xTrimParams().maxLen(10));
System.out.println(res39); /// >>> 0
Java
복사
그러나 XTRIM은 다양한 트리밍 전략을 받아들이도록 설계되었다. 또 다른 트리밍 전략은 MINID로, 지정된 것보다 낮은 ID를 가진 항목을 제거한다.
Redis Stream은 특정 기간을 기준으로 항목을 자동으로 만료하거나 제거하지 않지만,
각 항목의 타임스탬프를 주기적으로 확인하고 원하는 보존기간보다 오래된 항목을 제거하여 스트림의 항목 보존을 수동으로 관리할 수 있다.
→ COUNT 옵션과 함께 XRANGE 또는 XREAD와 같은 명령을 사용하여 스트림에서 항목 배치를 검색한 다음, 타임스탬프에 따라 오래된 항목을 필터링할 수 있다.
지속성, 복제 및 메시지 안전성
스트림은 다른 Redis 데이터 구조와 마찬가지로 복제하고 AOF 및 RDB 파일로 유지되기 위해 비동기식으로 복제된다. 그러나 Consumer group 전체 상태도 AOF, RDB 및 복제본으로 전파되므로 메시지가 마스터에 보류 중인 경우에도 복제본에 동일한 정보가 저장된다. 마찬가지로, 다시 시작한 후 AOF는 소비자 그룹의 상태를 복원한다.
그러나 Redis 스트림과 소비자 그룹은 Redis 기본 복제를 사용하여 지속되고 복제된다:
•
응용 프로그램에서 메시지의 지속성이 중요한 경우, 강력한 fsync 정책과 함께 AOF를 사용해야 한다.
•
기본적으로 비동기 복제는 XADD 명령 또는 Consumer groups 상태 변경이 복제되도록 보장하지 않는다. 페일오버 후, 마스터로부터 데이터를 수신하는 복제본의 기능에 따라 무엇인가가 누락될 수 있다.
•
WAIT 명령은 복제본 집합에 대한 변경사항의 전파를 강제하기 위해 사용될 수 있다. 그러나 이렇게 하면 데이터가 손실될 가능성이 매우 낮지만, Sentinel 또는 Redis Cluster에서 운영하는 Redis 페일오버 프로세스는 특정 장애 조건에서는 일부 데이터가 부족한 복제본을 촉진할 수 있다.
7. HyperLogLog
•
HyperLogLog(HLL)는 집합의 카디널리티를 추정하는 확률적 데이터 구조로, 효율적인 공간 활용을 위해 완벽한 정확도를 추정한다.
◦
Cardinality(카디널리티) : 특정 데이터 집합의 유니크(Unique)한 값의 개수
•
HyperLogLog 구현은 최대 12KB를 사용하며 0.81%의 표준 오차를 제공한다.
◦
고유한 항목을 세는 데는 보통 항목 수에 비례하는 메모리 양이 필요하다. 그러나 메모리를 정밀도와 교환하는 일련의 알고리즘이 존재한다. 이 알고리즘은 셀 수 있는 항목 수에 비례하는 메모리 양을 사용할 필요가 없으며, 대신 일정한 양의 메모리를 사용할 수 있다. 최악의 경우 12k 바이트를 사용하거나, HLL에 요소가 거의 없다면 훨씬 적은 양을 사용할 수 있다.
•
중복되지 않는 대용량 데이터를 count할 때 주로 많이 사용한다.
•
HLL은 기술적으로 다른 데이터 구조이지만 Redis 문자열로 인코딩되므로 GET를 호출하여 HLL을 직렬화하고 SET를 호출하여 서버로 다시 병렬화할 수 있다.
•
개념적으로 HLL API는 동일한 작업을 수행하기 위해 Sets를 사용하는 것과 같다.
◦
관측된 모든 요소를 집합으로 SADD하고, SADD가 기존 요소를 다시 추가하지 않기 때문에 SCARD를 사용하여 집합 내부의 요소 수를 확인한다.
•
HyperLogLog는 일정한 시간과 공간에서 작성(PFADD) 및 읽기(PFCOUNT)가 수행된다. HLL 병합은 이며, 여기서 n은 스케치의 수이다.
•
HyperLogLog는 최대 18,446,744,073,709,551,616(2^64)의 멤버로 구성된 집합의 카디널리티를 추정할 수 있다.
사용 사례
웹 페이지의 익명 유니크 방문(SaaS, 분석 도구)
이 응용 프로그램은 다음 질문에 답한다:
•
오늘 이 페이지에 몇 번이나 유니크한 방문을 했나요?
•
이 노래를 재생한 유니크한 사용자는 몇 명입니까?
•
이 영상을 본 유니크한 사용자는 몇 명입니까?
IP 주소 또는 다른 종류의 개인 식별자를 저장하는 것은 일부 국가에서는 법에 위배되며, 이로 인해 웹 사이트에서 고유한 방문자 통계를 얻는 것이 불가능하다.
HyperLogLog는 페이지(비디오/송)당 1개씩 생성되며, 방문 시마다 모든 IP/아이덴티티가 추가된다.
기본 명령어
More posts like this
Search