리액티브 레디스 — 성능·벤치마크·튜닝

2026-05-03확률과 통계 마스터 노트

리액티브 레디스 마스터 노트 시리즈 5편. ReactiveRedisTemplate vs Redisson 처리량 벤치마크, 직렬화가 성능 병목이 되는 이유와 측정법, Pipeline으로 네트워크 왕복 줄이기, Lettuce의 단일 연결 멀티플렉싱 원리, slowlog로 느린 명령 추적, INFO 명령으로 메모리·연결 모니터링, 운영 환경 튜닝 체크리스트까지.

이 글은 리액티브 레디스 마스터 노트 시리즈의 다섯 번째 편입니다. 1~4편이 기능이었다면, 이번엔 그것이 얼마나 빨리 도는가 — 성능·벤치마크·튜닝.

Redis는 빠른 시스템이지만, 잘못 쓰면 느려집니다. 직렬화·네트워크 왕복·연결 관리 모두 병목이 될 수 있어요. 측정과 튜닝의 출발점.

처음 성능 튜닝이 어렵게 느껴지는 이유

처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, Redis는 빠르다는 인식 때문에 측정 안 합니다. 둘째, 어디가 병목인지 알 수 없습니다 — 클라이언트? 네트워크? Redis 자체?

해결법은 한 가지예요. 3 단계 측정 — 1) 클라이언트 처리 시간, 2) 네트워크 왕복, 3) Redis 명령 시간. 각각 따로 측정 → 병목 찾기. 성능 튜닝의 시작.

ReactiveRedisTemplate vs Redisson — 벤치마크

간단 GET/SET 1만 회 (단일 스레드):

클라이언트 TPS (요청/초) 평균 지연
Lettuce (직접) ~120,000 ~0.08ms
ReactiveRedisTemplate ~80,000 ~0.12ms
Redisson ~50,000 ~0.20ms

여기서 정말 중요한 시험 함정 — Lettuce 직접 사용이 가장 빠름. ReactiveRedisTemplate은 추상화 비용 + Serializer. Redisson은 분산 도구 비용. 단순 캐시 = ReactiveRedisTemplate, 분산 락 = Redisson.

직렬화 비용

GET/SET 자체: ~0.1ms
JdkSerialization: 추가 ~1ms
Jackson JSON: 추가 ~0.3ms
String (단순): 추가 ~0.01ms

객체가 클수록 직렬화 비중 커짐.

여기서 시험 함정이 하나 있어요. JdkSerialization은 운영 안티패턴 — 느림 + 큰 크기 + 클래스 호환성 X. JSON 또는 Avro/Protobuf 권장.

Pipeline — 네트워크 왕복 줄이기

문제

요청 1 → Redis → 응답 1 (1 RTT)
요청 2 → Redis → 응답 2 (1 RTT)
...
1000 요청 = 1000 RTT

각 RTT ~0.1ms 라도 1000 = 100ms.

Pipeline 해결

요청 1, 2, ..., 1000 한 번에 →
Redis 처리 →
← 응답 1, 2, ..., 1000

= 1 RTT + 처리 시간
= ~10ms (10배 빠름)

Reactive Pipeline

List<Mono<String>> ops = ids.stream()
    .map(id -> redisTemplate.opsForValue().get("user:" + id))
    .toList();

Flux<String> results = Flux.merge(ops);
// Lettuce 자동 pipelining (단일 연결 멀티플렉싱)

여기서 정말 중요한 시험 함정 — Lettuce는 자동 pipelining. 단일 연결로 여러 요청 동시 보냄. 명시적 pipeline API 필요 X. 단순 Flux.merge·Flux.zip이 자동으로 효율.

Lettuce 단일 연결 멀티플렉싱

전통 Jedis: 요청마다 연결 (또는 풀)
Lettuce: 단일 연결로 모든 요청 (Netty)
Connection 1
  ├── 요청 A (응답 대기 중)
  ├── 요청 B (응답 대기 중)
  ├── 요청 C
  ...
  ← 응답 A, B, C 순서대로

장점:

  • 연결 비용 X — Pool 거의 불필요
  • 자동 pipelining
  • 자원 효율

여기서 시험 함정이 하나 있어요. 블로킹 명령(BLPOP·BRPOP)은 단일 연결 막음. 다른 명령들도 대기. 블로킹은 별도 연결 사용.

모니터링 — INFO 명령

> INFO memory
used_memory_human:512M
used_memory_peak_human:1.2G
mem_fragmentation_ratio:1.05

> INFO clients
connected_clients:50
maxclients:10000

> INFO stats
total_commands_processed:1000000
instantaneous_ops_per_sec:5000

> INFO replication
role:master
connected_slaves:2

핵심 메트릭:

  • used_memory — 현재 메모리
  • used_memory_peak — 최대 메모리
  • fragmentation_ratio — 메모리 파편화 (>1.5 = 위험)
  • connected_clients — 연결 수
  • instantaneous_ops_per_sec — 초당 처리

SLOWLOG — 느린 명령 추적

> CONFIG SET slowlog-log-slower-than 10000   # 10ms+ 기록
> CONFIG SET slowlog-max-len 128

> SLOWLOG GET 10
1) 1) (integer) 10        # 로그 ID
   2) (integer) 1234567   # 시각 (Unix)
   3) (integer) 25000     # 실행 시간 (us)
   4) 1) "KEYS"           # 명령
      2) "*"

여기서 정말 중요한 시험 함정 — KEYS *·SMEMBERS 큰_set·HGETALL 큰_hash 같은 O(N) 명령이 자주. SLOWLOG로 발견 즉시 SCAN·HSCAN·SSCAN으로 전환.

위험 명령 (운영 절대 X)

명령 위험성 대안
KEYS * O(N), 전체 멈춤 SCAN
FLUSHALL 모든 데이터 삭제 rename-command FLUSHALL ""
FLUSHDB DB 전체 삭제 같은 처리
SAVE 동기 RDB 저장, 멈춤 BGSAVE
DEBUG SLEEP 강제 대기 사용 X

redis.conf에서 rename-command로 비활성화:

rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command CONFIG ""
rename-command DEBUG ""

메모리 최적화

maxmemory + 정책

maxmemory 4gb
maxmemory-policy allkeys-lru

정책 8종:

  • noeviction — 메모리 가득 시 쓰기 거부
  • allkeys-lru — 모든 키 중 가장 오래 안 쓴 것 (캐시 표준)
  • allkeys-lfu — 가장 적게 쓴 것
  • volatile-lru — TTL 있는 것 중 LRU
  • volatile-ttl — TTL 짧은 것

여기서 시험 함정이 하나 있어요. noeviction은 안전해 보이지만 위험. 메모리 가득 시 쓰기 거부 → 애플리케이션 에러 폭주. 캐시 용도 = allkeys-lru 권장.

큰 키 분할

100MB 한 키 = 명령 처리 시 멈춤. 분할:

big-list → big-list:1, big-list:2, ...

연결 관리

단일 연결 vs 풀

시나리오 권장
WebFlux + Lettuce 단일 연결 (자동 멀티플렉싱)
블로킹 명령 다수 별도 연결
Cluster 노드별 연결

Lettuce 옵션

LettuceClientConfiguration.builder()
    .commandTimeout(Duration.ofSeconds(2))
    .shutdownTimeout(Duration.ofSeconds(5))
    .clientOptions(ClientOptions.builder()
        .autoReconnect(true)
        .disconnectedBehavior(DisconnectedBehavior.REJECT_COMMANDS)
        .build())
    .build();

트래픽별 튜닝

읽기 위주

- 읽기 캐시 (Redis 자체)
- Read Replica로 분산 (Cluster·Sentinel)
- Pipeline·MGET·MSET 활용

쓰기 위주

- maxmemory 충분히
- AOF buffer 크게
- Pub/Sub 채널 적게

큰 데이터

- Hash 분할
- 큰 String → 압축 (gzip)
- 메모리 최적 자료구조 (intset·ziplist)

Reactive 합성 패턴 — 처리량 ↑

여러 GET 한 번에

// X 순차 — N 회 RTT
ids.forEach(id -> redisTemplate.opsForValue().get("user:" + id).subscribe());

// O 동시 (자동 멀티플렉싱)
Flux.fromIterable(ids)
    .flatMap(id -> redisTemplate.opsForValue().get("user:" + id), 16)
    .collectList();

// O MGET — 한 번 명령
redisTemplate.opsForValue().multiGet(ids.stream().map(id -> "user:" + id).toList());

MGET이 가장 효율 (한 명령).

운영 체크리스트

✓ maxmemory + allkeys-lru
✓ AOF + appendfsync everysec (7편)
✓ slowlog 활성화 (>10ms)
✓ INFO 정기 모니터링 (Prometheus + redis_exporter)
✓ 위험 명령 rename-command
✓ TTL 모든 캐시 키
✓ KEYS 명령 코드 검색 → SCAN
✓ Connection 한도 설정
✓ Cluster 또는 Sentinel (운영 HA)

모니터링 도구

  • redis_exporter + Prometheus + Grafana
  • RedisInsight (공식 GUI)
  • redis-cli --stat / --bigkeys / --latency
# 큰 키 찾기
redis-cli --bigkeys

# 지연 측정
redis-cli --latency

# 실시간 통계
redis-cli --stat

시험 직전 한 번 더 — 자주 헷갈리는 함정 모음

여기까지가 5편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.

  • 벤치마크 — Lettuce(직접) > ReactiveRedisTemplate > Redisson
  • 단순 캐시 = Template·Lettuce / 분산 = Redisson
  • JdkSerialization 안티패턴 (느림·큼·호환 X)
  • JSON 또는 Avro/Protobuf
  • Pipeline = 네트워크 왕복 줄이기 (1000 RTT → 1)
  • Lettuce 자동 pipelining (Flux.merge·Flux.zip)
  • 단일 연결 멀티플렉싱 — Pool 거의 불필요
  • 블로킹 명령은 별도 연결
  • INFO — used_memory·fragmentation·connected_clients·ops_per_sec
  • SLOWLOG — 10ms+ 기록
  • 위험 명령 — KEYS * / FLUSHALL / SAVE / DEBUG
  • rename-command 으로 비활성화
  • KEYS * = 멈춤 → SCAN
  • maxmemory + allkeys-lru = 캐시 표준 정책
  • noeviction = 위험 (쓰기 거부)
  • 큰 키 = 분할
  • Lettuce 옵션 — commandTimeout·autoReconnect·disconnectedBehavior
  • 처리량 ↑ — 순차 X · flatMap(concurrency) · MGET
  • 운영 체크리스트 — maxmemory·AOF·slowlog·INFO 모니터링·rename·TTL·SCAN·Connection 한도·HA
  • 도구 — redis_exporter·RedisInsight·redis-cli --bigkeys·--latency·--stat

시리즈 다른 편

공식 문서: Redis Latency Monitoring / Lettuce Reference 에서 더 깊이.

다음 글(6편)에서는 Pub/Sub과 WebSocket — 실시간 통신 패턴, Redis Pub/Sub 메커니즘, 채팅·알림 구현, Streams와의 차이까지 풀어 갑니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!