Contents
see List개요
Redis 8.0은 2024년 말에 출시 예정인 메이저 버전으로, 새로운 데이터 구조, 성능 개선, 그리고 클러스터 관리 기능 강화가 포함됩니다. 특히 Time Series, Probabilistic, Graph 데이터 구조가 코어에 통합되어 더욱 다양한 유스케이스를 네이티브로 지원합니다.
이 글에서는 Redis 8.0의 주요 신기능과 실무 활용 사례를 살펴보겠습니다.
핵심 개념
1. Native Time Series
RedisTimeSeries 모듈이 코어에 통합되어, IoT 센서 데이터, 메트릭, 로그 등 시계열 데이터를 효율적으로 저장하고 집계할 수 있습니다. 다운샘플링, 압축, 윈도우 집계를 네이티브로 지원합니다.
2. Probabilistic Data Structures
Bloom Filter, Cuckoo Filter, Count-Min Sketch, Top-K 등이 기본 제공되어, 대규모 데이터에서 확률적 쿼리를 메모리 효율적으로 수행할 수 있습니다.
3. Active-Active Geo-Replication
다중 리전 간 양방향 복제를 지원하여, 글로벌 애플리케이션의 지연시간을 최소화하고 고가용성을 제공합니다. Conflict-free Replicated Data Types(CRDT)로 충돌을 자동 해결합니다.
4. JSON 성능 향상
RedisJSON의 쿼리 성능이 최대 5배 향상되었으며, JSONPath 표준을 완전히 지원합니다.
실전 예제
Time Series 활용 (실시간 메트릭)
# Time Series 생성 (센서 온도 데이터)
TS.CREATE sensor:temp:1 RETENTION 86400000 LABELS sensor_id 1 location seoul
# 데이터 포인트 추가 (자동 타임스탬프)
TS.ADD sensor:temp:1 * 23.5
TS.ADD sensor:temp:1 * 24.1
TS.ADD sensor:temp:1 * 23.8
# 특정 시간 범위 조회
TS.RANGE sensor:temp:1 1704067200000 1704153600000
# 집계 쿼리 (1시간 평균)
TS.RANGE sensor:temp:1 - + AGGREGATION avg 3600000
# 다운샘플링 룰 생성 (1시간 평균을 자동 저장)
TS.CREATERULE sensor:temp:1 sensor:temp:1:avg AGGREGATION avg 3600000
# 여러 센서 동시 조회 (레이블 기반)
TS.MRANGE - + FILTER location=seoul GROUPBY sensor_id REDUCE avg
// Node.js에서 Time Series 활용
import { createClient } from 'redis';
const client = createClient();
await client.connect();
// 메트릭 기록
async function recordMetric(key: string, value: number) {
await client.ts.add(key, '*', value);
}
// 최근 1시간 평균 조회
async function getHourlyAverage(key: string) {
const now = Date.now();
const hourAgo = now - 3600000;
const data = await client.ts.range(key, hourAgo, now, {
AGGREGATION: { type: 'AVG', timeBucket: 60000 } // 1분 단위 평균
});
return data;
}
// 실시간 대시보드 데이터
const metrics = await getHourlyAverage('api:latency');
console.log(metrics);
// [{ timestamp: 1704070800000, value: 45.2 }, ...]
Bloom Filter 활용 (중복 감지)
# Bloom Filter 생성 (1억 개 아이템, 1% 오탐률)
BF.RESERVE unique:users 100000000 0.01
# 아이템 추가
BF.ADD unique:users user@example.com
# 반환: 1 (새로 추가됨)
BF.ADD unique:users user@example.com
# 반환: 0 (이미 존재함)
# 존재 여부 확인
BF.EXISTS unique:users user@example.com
# 반환: 1 (존재함)
# 다중 아이템 추가
BF.MADD unique:users alice@example.com bob@example.com charlie@example.com
# 반환: [1, 1, 1]
# Bloom Filter 정보 조회
BF.INFO unique:users
# Capacity, Size, Number of items, Expansion rate 등
// 중복 이메일 감지 시스템
async function isEmailSeen(email: string): Promise {
const exists = await client.bf.exists('email:seen', email);
return exists === 1;
}
async function markEmailAsSeen(email: string): Promise {
const added = await client.bf.add('email:seen', email);
return added === 1; // true면 새로운 이메일
}
// 사용 예
const email = 'user@example.com';
if (await isEmailSeen(email)) {
console.log('이미 처리된 이메일');
} else {
await markEmailAsSeen(email);
await processEmail(email);
}
Count-Min Sketch 활용 (빈도 추정)
# Count-Min Sketch 생성 (0.1% 오차, 99.9% 정확도)
CMS.INITBYDIM pageviews 2000 10
# 카운트 증가
CMS.INCRBY pageviews /home 1
CMS.INCRBY pageviews /about 1
CMS.INCRBY pageviews /home 1
CMS.INCRBY pageviews /products 5
# 빈도 조회
CMS.QUERY pageviews /home
# 반환: 2
CMS.QUERY pageviews /products
# 반환: 5
# Top-K 활용 (상위 K개 추적)
TOPK.RESERVE popular:pages 10 # 상위 10개 페이지 추적
TOPK.INCRBY popular:pages /home 1 /about 1 /home 1 /products 5
TOPK.LIST popular:pages
# 반환: ["/products", "/home", "/about"]
// 실시간 페이지뷰 순위 시스템
async function trackPageView(path: string) {
await client.cms.incrBy('pageviews', { [path]: 1 });
await client.topK.incrBy('top:pages', { [path]: 1 });
}
async function getTopPages(count: number = 10) {
const pages = await client.topK.list('top:pages');
// 정확한 카운트 조회
const counts = await Promise.all(
pages.map(page => client.cms.query('pageviews', page))
);
return pages.map((page, i) => ({
path: page,
views: counts[i]
}));
}
// 실시간 대시보드
const topPages = await getTopPages();
console.log(topPages);
// [{ path: '/products', views: 1543 }, ...]
JSON 고급 활용
# JSON 문서 생성
JSON.SET user:1 $ '{"name":"홍길동","age":30,"skills":["JavaScript","Python"],"profile":{"bio":"개발자","location":"서울"}}'
# JSONPath 쿼리
JSON.GET user:1 $.profile.location
# 반환: ["서울"]
# 배열 요소 추가
JSON.ARRAPPEND user:1 $.skills "TypeScript"
# 숫자 증가
JSON.NUMINCRBY user:1 $.age 1
# 부분 업데이트
JSON.SET user:1 $.profile.bio "시니어 개발자"
# 조건부 검색 (RedisSearch와 통합)
FT.SEARCH idx:users '@age:[25 35] @skills:{JavaScript}'
// 복잡한 JSON 쿼리
interface User {
name: string;
age: number;
skills: string[];
profile: {
bio: string;
location: string;
};
}
async function getUser(id: number): Promise {
const data = await client.json.get(`user:${id}`, { path: '$' });
return data ? data[0] : null;
}
async function updateUserSkills(id: number, skill: string) {
await client.json.arrAppend(`user:${id}`, '$.skills', skill);
}
async function incrementAge(id: number) {
await client.json.numIncrBy(`user:${id}`, '$.age', 1);
}
활용 팁
- Time Series 보관 정책: RETENTION을 설정하여 오래된 데이터를 자동 삭제하고 메모리를 절약하세요. 실시간 데이터는 1일, 집계 데이터는 1년 보관이 일반적입니다.
- Bloom Filter 크기 계산: 예상 아이템 수와 허용 가능한 오탐률(false positive rate)을 고려하여 적절한 크기를 설정하세요. BF.RESERVE의 capacity와 error를 조정합니다.
- Count-Min Sketch vs Exact Count: 정확한 카운트가 필요하면 Hash를 사용하고, 메모리 효율이 중요하면 CMS를 사용하세요. CMS는 메모리가 일정하지만 약간의 오차가 있습니다.
- JSON 인덱싱: RedisSearch를 함께 사용하면 JSON 필드에 대한 복잡한 쿼리와 전문 검색이 가능합니다.
- 파이프라이닝: 여러 명령을 파이프라인으로 묶으면 네트워크 왕복 시간을 줄여 성능을 크게 향상시킬 수 있습니다.
- Lua 스크립트: 복잡한 원자적 연산은 Lua 스크립트로 구현하여 데이터 일관성을 보장하세요.
- 메모리 모니터링: INFO MEMORY와 MEMORY STATS로 메모리 사용량을 주기적으로 확인하고, maxmemory-policy를 적절히 설정하세요.
마무리
Redis 8.0은 단순한 캐시를 넘어 실시간 분석, 확률적 데이터 구조, 시계열 데이터 등 다양한 유스케이스를 네이티브로 지원하는 강력한 데이터 플랫폼으로 진화했습니다.
Time Series로 실시간 메트릭을 추적하고, Bloom Filter로 중복을 효율적으로 감지하며, Count-Min Sketch로 대용량 스트림을 분석하세요. Redis의 높은 성능과 새로운 데이터 구조를 결합하면 복잡한 요구사항도 간단하게 해결할 수 있습니다.