본문 바로가기

SearchDeveloper/개발자를 위한 레디스

13 레디스 운영하기 (끝)

레디스를 잘 운영하기 위한 대시보드 모니터링, 레디스 마이그, 장애 유발 가능한 환결 설정/커맨드를 알아보자

 

레디스 모니터링 구축하기

  • 익스포터: 상태를 실시간으로 스크랩해 메트릭 수집하는 프로그램
  • 프로메테우스: 메트릭 기반 오픈소스 모니터링 시스템
    • 지정한 타깃(익스포터)으로 HTTP로 직접 접근해 시계열 데이터를 pull방식으로 수집한다.
    • promQL 쿼리 지원
  • 그라파나: 오픈소스 메트릭 데이터 시각화 도구
    • 프로메테우스 및 여러 데이터소스를 추가해 메트릭을 시각화해서 볼 수 있다.
  • 얼럿 매니저: 프로메테우스에서 설정한 알람 규칙 임계치 도달하면 얼럿 매니저를 통해 sms/이메일/슬랙 등으로 전송할 수 있다.

 

설치

익스포터 설치

  • 노드 익스포터
  • 레디스 익스포터

얼럿 매니저 설치

프로메테우스 설치

그라파나 설치

 

레디스 플러그인으로 대시보드 만들기

  • 프로메테우스 필요 없음
  • 그라파나에서 플러그인 설치하면 됨



레디스 버전 업그레이드

  • 레디스는 릴리즈 주기가 빠른 편이라 EOL(End Of Life)도 짧은 편이다. 그러니 주기적으로 버전업하는게 좋다.
  • 운영중 레디스 업그레이드 하는 2가지 방법 (센티널, 클러스터 없이)
    • 새 버전 레디스를 새 서버에 설치하고, 기존 버전 레디스의 데이터를 복제한다. (접속 정보 변경 필요)
    • 실행 중 레디스 중지하고 신규 레디스로 재실행하기. (다운타임 발생)

→ 센티널, 클러스터 구조면 복제본부터 업그레이드하면 돼서 다운타임 없음, 접속 정보 변경 필요 없음

 

센티널일 때 레디스 버전업하기

  1. 신규 버전 레디스 바이너리 파일 다운

-------------------------------------------------------

  1. 센티널 3대 종료
  2. 신규 버전 폴더에 sentienl.conf 복사
  3. 센티널 시작

-------------------------------------------------------

  1. 복제본 중단
  2. 신규 버전 폴더에 redis.conf 복사
  3. 복제본 시작
  4. 센티널에서 수동 페일오버 수행 (마스터↔복제본 스위치)

-------------------------------------------------------

  1. 구 마스터 중단
  2. 신규 버전 폴더에 redis.conf 복사
  3. 마스터 시작
  4. 센티널에서 수동 페일오버(페일백) 수행

 

클러스터일 때 레디스 버전업하기

  • 센티널 인스턴스 신경 안 써도 돼서 더 간단
  • 복제본 먼저 버전업 하고 → 페일오버 해서 → 구 마스터 마저 버전업하기

 

레디스 운영 가이드

운영 중 장애 종료, 성능 저하 요소, 모니터링할 지표, 장애 시나리오 알려주겠다.

 

장애 또는 성능 저하 유발할 수 있는 레디스 설정

maxmemory-policy

  • 메모리 한계 도달 시 어떤 키 제거할지 정하는 정책
  • 기본은 noeviction 으로 데이터 삭제하지 않고 에러 반환함. 그럼 장애로 이어질 수 있어서 데이터 삭제될 수 있게 allkeys-lru 권장

 

stop-writes-on-bgsave-error

  • RDB 스냅샷 정삭적으로 저장되지 않으면 레디스로의 모든 쓰기 작업 중지함
  • 더 큰 장애 유발될 수 있어서 설정 비활성화 권장 (no로)

 

자동 백업 옵션

  • RDB, AOF로 백업 파일 생성하는건 레디스에 큰 부하임
    • 포그라운드면 다른 작업들 대기해야해서 성능 영향 미침
    • 백그라운드면 COW(Copy-On-Write)라 메모리 사용량이 최대 maxmemory 의 두 배까지 증가 가능 (OOM 발생 가능)
  • 그러니 백업은 의도한 시간에 수행되도록 수동으로 시간 설정하는게 좋다.

 

1) save 옵션 해제

127.0.0.1:6379> CONFIG GET save
1) "save"
2) "3600 1 300 100 60 10000" # 3600초(1시간) 동안 최소 1번 or 300초(5분) 동안 최소 100번 or 60초(1분) 동안 최소 10000번 변경 작업이 수행되면 자동으로 RDB 파일 생성 

# 해제
127.0.0.1:6379> CONFIG SET save ""
OK

원하는 시간에 백업 수행하게 하려고

 

2) auto-aof-rewrite-percentage 옵션 해제

appendonly yes 로 AOF 형식 백업인 경우, 기본 AOF 파일이 기존 AOF 파일보다 100%증가하면 재작성됨

127.0.0.1:6379> CONFIG GET auto-aof*
1) "auto-aof-rewrite-min-size"
2) "67108864"
3) "auto-aof-rewrite-percentage"
4) "100"

127.0.0.1:6379> CONFIG SET auto-aof-rewrite-percentage 0
OK

0으로 바꿔서 자동 재작성 방지

min size, percentage 두 조건이 AND로 충족해야 자동 재작성되기 때문에 하나만 0으로 만들면 됨

 

레디스 운영 및 성능 최적화

오래 걸리는 커맨드 사용

  • O(N)이상 시간복잡도 걸리는 커맨드는 지양해야한다.
  • KEYS, FLUSHALL 에 더불어 set, list, hash 처럼 한 자료 구조 안에 여러 아이템 갖고 있는 경우도 아이템 개수에 비례해 실행 시간이 증가한다.

 

키스페이스 커맨드

※키스페이스: 키공간 확인 (ex. 현재 DB에 키가 몇 개나 있고, 메모리를 얼마나 쓰고 있는지, 키 중심으로 발생하는 이벤트)

  • 시간복잡도에서의 N: 전체 키의 개수

 

KEYS

  • 모든 키 접근하기 때문에 O(N)
  • SCAN으로 대체하는게 좋다.

 

FLUSHALL

  • ASYNC 옵션 없으면 동기로 동작해 O(N)
  • 모든 키 삭제하는데, FLUSHALL 실행 중 새로 생성된 키는 삭제하지 않는다.

 

FLUSHDB

  • ASYNC 옵션 없으면 동기로 동작해 O(N)
  • DB에 속한 모든 키 삭제함
  •      
    구분 Standalone (FLUSHDB) Cluster (FLUSHDB)
    삭제 단위 선택된 논리 DB (예: DB 0) 현재 접속한 노드 전체
    다른 데이터 영향 다른 번호(DB 1~15) 데이터는 안전함 다른 노드(Master B, C) 데이터는 안전함
    전체 삭제하려면? FLUSHALL 실행 모든 마스터 노드에서 각각 실행해야 함

NOTE: LAZY FREEING

  • 키 삭제인 DEL은 동기로 삭제, 비동기로 하려면 UNLINK로 삭제
  • 레디스가 시스템적으로 삭제하는 경우, 기본은 동기 삭제인데 설정값 변경해서 비동기로 삭제하게 할 수 있다.
    • lazyfree-lazy-evction: maxmemory로 인한 키 이빅션
    • lazyfree-lazy-expire: 만료 시간된 키 삭제
    • lazyfree-lazy-server-del: 서버 동기화나 다른 명령 사이드이펙트(RENAME)로 인한 키 삭제
    • lazyfree-lazy-flush: 복제 동기화 시 DB 내용 삭제(복제본 FLUSH)
    • lazyfree-lazy-user-del: 사용자 코드에서 DEL 호출을 UNLINK와 동일하게 처리
    • 기본값은 no. yes로 변경해야 비동기 삭제임
  • 바로 삭제 안 되고 메모리에 일시적으로 남아있기 때문에 메모리 사용량 모니터링 필요
  • yes여도 내부 알고리즘에 따라 동기가 더 효율적으로 판단되면 동기로 삭제한다.

 

자료구조 공통 커맨드

  • N: 자료구조 내 아이템 개수에 비례해 수행시간 증가한다.

 

DEL

  • string 키 삭제는 O(1)인데 list, set, sorted set, hash는 O(N)
  • 오래 걸린다 싶으면 백그라운드 UNLINK 추천

 

SORT / SORT_RO

  • list, set, sorted set에서만 사용 가능
  • 키 내부 아이템 정렬해서 반환한다. ASC/DESC/ALPHA 정렬 가능, LIMIT으로 제한 가능
  • O(N + Mlog(M))
    • N:세트 내 아이템 수, M:반환되는 아이템 수(M)
  • STORE(반환값 저장) 옵션 때문에 쓰기 커맨드로 분류돼 복제본에서 못 썼는데 7버전부터 SORT_RO 가 추가돼 이걸로 복제본에서 사용 가능

 

SET

  • 집합 연산 커맨드는 집합의 카디널리티에 비례해 수행 시간 증가한다.
  • SDIFF, SUNION, SINTER 관련 커맨드 모두 O(N)
    • N: 집합 내 아이템 총 개수
  • SINTERCARD: O(N*K) (카디널리티 숫자만 반환, 레디스 7버전 이후)

 

LIST

    • 양 끝 인덱스 제외하고는 O(N) 걸림
    • LINDEX(인데스 위치의 아이템), LINSERT, LSET(특정 인덱스 아이템 문자열 변경), LPOS(문자열 존재 인덱스 확인)
    •  

❓왜 LINDEX는 왜 O(1)이 아니라 O(N)이야? 자바의 List .get()은 O(1)인데
자바는 메모리의 연속적인 공간을 쓰는데 레디스는 링크드 리스트로 포인터로 연결하고 있어서 그럼

  •  

 

HASH

  • HGETALL(모든 아이템의 키,값 반환), HKEYS(모든 아이템의 키), HVALS(모든 아이템의 값) 모두 O(N)

 

SORTED SET

  • 데이터가 입력되는 시점에 정렬되므로 O(log(N))
  • 아이템 수 증가해도 선형으로 증가하지 않아서 효율적이다.
  • 집합 연산할 때는 set처럼 아이템 개수 비례해 증가함
    • ZDIFF: O(L + (N-K) log(N))
      • L: 모든 집합의 총 아이템 수, N: 첫 번째 세트의 크기, K: 결과 세트의 크기
    • ZUNION: O(N) + O(M * log(M))
      • N: 모든 집합의 총 아이템 수, M: 결과 집합의 아이템 수
    • ZINTER: O(N*K) + O(M * log(M))
      • N: 가장 작은 집합의 카디널리티, K: 연산을 수행하는 집합의 수, M: 결과 집합의 카디널리티
    • ZINTERCARD: O(N*K) (카디널리티 숫자만 반환, 레디스 7버전 이후)

 

레디스 트랜잭션 사용과 주의사항

  • 원자적으로 커맨드 보장 가능. 2가지 방식 지원 - 1)MULTI/EXEC 2)루아 스크립트

 

1) MULTI/EXEC

  • MULTI: 트랜잭션 커맨드 시작, EXEC: 트랜잭션 실행
  • 트랜잭션 실행 도중 오류 발생 시 모든 커맨드 롤백 후 트랜잭션 종료
> MULTI
OK

(TX)> INCRBY account_balance 100
QUEUED

(TX)> RPUSH tranaction_log "입금 100"
QUEUED

(TX)> EXEC
1) (integer) 100
2) (integer) 1

 

2) 루아 스크립트

  • 가볍고 빠른 임베디드 가능한 스크립트 언어
  • 스크립트는 원자적으로 실행되니까 중간에 다른 클라 요청 수행되지 않아서 데이터 일관성 유지 가능
  • 일부 명령어 실패해도 다음 명령어 실행되고 롤백은 발생 안 함
  • SRIPT LOAD 로 스크립트 입력하면 hash값 반환됨. EVALSHA 로 hash값 실행하면 스크립트 재사용 가능

 

주의할 점

  • 트랜잭션, 루아 실행하는 동안 다른 클라는 대기해야하므로 트랜잭션 길어지지 않게 해야한다.
    • 그래서 블로킹 커맨드(BLPOP, BRPOP)은 사용 못 함

 

has-get/has-del 패턴

  • 데이터 조회, 삭제할 때 EXISTS로 먼저 확인하는건 성능이 안 좋다. (테스트 결과 2배 안 좋음)
  • GET은 없으면 nil 반환하고 DEL은 키 없어도 에러 반환하지 않으니까 EXISTS 쓰지 말고 바로 커맨드 날려라

 

클라이언트 출력 버퍼 사이즈

  • 클라 출력 버퍼는 클라한테 응답 반환 전에 일시적으로 저장하는 역할
  • 대용량 데이터나 많은 클라 요청 처리해야하면 출력 버퍼 크기 작아 버퍼 오버플로우 발생할 수 있으니 늘리는게 좋다.

(hard limit, soft limit, soft seconds)

  • client-output-buffer-limit normal 0 0 0 : 일반 클라는 출력 버퍼
    • 제한 비활성화돼있어 제한없이 데이터 수신 가능
  • client-output-buffer-limit replica 256mb 64mb 60: 복제본에 대한 출력 버퍼
    • 하드 제한 256MB, 소프트 제한 64MB, 소프트 제한을 초과한 경우 60초 동안 소프트 제한을 계속 초과하면 클라이언트 연결 종료
    • maxmemorymaxclients 설정 확장할 때 복제본 출력 버퍼도 동시에 조정해야한다. (복제양도 늘어나므로)
  • client-output-buffer-limit pubsub 32mb 8mb 60 : pubsub에 대한 출력 버퍼
    • 해당 조건 만족하면 클라 연결 종료
    • pubsub 출력 버퍼 조절하지 않으면 메시지 유실돼 복구 불가됨 (키스페이스 알림 기능, 아래 케이스)

 

레디스 키스페이스 알림 기능

  • 키 변경 사항 모니터링 해 pubsub 채널로 변경 사항 메시지 구독하는 기능임
  • notify-keyspace-events 설정 파일에서 어떤 이벤트 받을 건지 설정하면 키 만료는 __keyevent@0__:expired 채널로 이벤트 온다.
  • 근데 많은 키가 만료돼 메시지가 대량 발송돼 pubsub 버퍼 크기를 초과하면 유실되버림
  • fire-and-forget 패턴이라 클라 출력 버퍼 초과돼서 한번 끊겼다 재연결되면 그 사이에 발행된 메시지는 확인 불가됨
  • 해결: pubsub 버퍼 크기 늘리기, 여러 레디스 인스턴스로 pubsub 이벤트 분산시키기

 

특정 프리픽스 가진 키 삭제하기

  • 특정 프리픽스만 삭제하는 레디스 기능은 없음
  • 프리픽스 삭제하는 법
    • ❌ prefix pattern 만족하는 키 SCAN으로 조회해 끝날 때까지 모으고 for문으로 키 DEL 하기
      • SCAN할 때 네트워크 통신 반복적으로 걸리고 DEL 할 때도 반복적으로 왕복시간 소요돼서 느림
      • pattern = 'prefix:*'
        count = 100
        cursor, keys = 0, []
        

SCAN 명령을 사용해 특정 프리픽스를 가진 키 검색

while True:
cursor, partial_keys = r.scan(cursor, match=pattern)
keys.extend(partial_keys)
if cursor == 0:
break

검색된 키 삭제

for key in keys:
r.delete(key)


<br>

    - ✅ (최소한의 영향) 루아 스크립트
        - SCAN단위로 네트워크 통신하기 때문에 네트워크 I/O 최소화
        - 대신 한번작업 너무 오래 안 걸리게(다른 클라 대기타므로) 배치 수 조절
        - ```java
# 삭제할 키의 패턴 및 스캔 파라미터 설정
pattern = 'prefix:*'
count = 100
cursor = "0"

# 루아 스크립트 정의
lua_script = """
local cursor = ARGV[1]
local pattern - ARGV[2]
local count = ARGV[3]

local keys = redis.call("SCAN", cursor, "MATCH", pattern, "COUNT", count)
local cursor = keys[1]
local keyList = keys[2]

for _, key in ipairs(keyList) do
  redis.call("DEL", key)
end

return {cursor, #keyList}
"""

total_deleted_count = 0

# 반복적으로 루아 스크립트를 호출해 키를 조회하고 삭제
while True:
  result = r.eval(lua_script, 0, cursor, pattern, count)
  cursor, deleted_count = result[0], result[1]
  total_deleted_count += deleted_count

  if cursor == "0":
    break



레디스 모니터링

슬로우 로그

> SLOWLOG GET
1) 1) (integer) 1923
   2) (integer) 1696344048
   3) (integer) 35233
   4) 1) "SCAN"
      2) "10179327"
      3) "COUNT"
      4) "50000"
  • 슬로우로그 고유 ID
  • 실행 시간(timestamp): 명령이 실행된 시간
  • 실행 시간(마이크로초): 명령이 실행되는 데 소요된 시간 (독스, The amount of time needed for its execution, in microseconds)
  • 명령: 느리게 수행됐던 커맨드

 

slowlog-log-slower-than

127.0.0.1:6379> CONFIG GET slowlog-log-slower-than
1) "slowlog-log-slower-than"
2) "10000"

디폴트: 10000마이크로초(10ms) 넘은 커맨드를 기록

 

slowlog-max-len

127.0.0.1:6379> CONFIG GET slowlog-max-len
1) "slowlog-max-len"
2) "128"

기본: 최대 128개 슬로우로그만 유지

 

그래프 지표

CPU

  • 백업 파일 저장이나 UNLINK같은 백그라운드 작업에 다른 CPU 활용할 수 있으니 부하 모니터링 필요
  • 시간 복잡도 높은 커맨드, 카디널리티가 높은 집합 자료 구조, 많은 아이템이 저장된 단일 hash면 CPU 영향 준다.

 

메모리

    • used_memory: 레디스가 현재 할당한 메모리
    • ByteUsedForCache: 현재 메모리 사용량
    • DatabaseMemoryUsagePercentage: maxmemory 중 백분율 사용량
      • 100이면 정책에 따라 이빅션 실행 가능
    • used_memory 는 레디스가 논리적으로 사용하는 메모리, used_memory_rss (Resident Set Size)는 OS가 레디스 프로세스에 할당한 실제 물리적인 메모리
      •      
        지표 의미 비유
        used_memory 레디스가 데이터를 저장하기 위해 실제로 점유한 데이터의 양 (논리적) 책꽂이에 꽂힌 '책들의 실제 부피'
        used_memory_rss OS가 레디스 프로세스에 실제로 할당해 준 총 메모리 양 (물리적) 책들을 꽂기 위해 차지하고 있는 '책장 전체의 공간'
      • 논리적 메모리에 비해 물리적으로 사용하는 메모리가 너무 커질 때 메모리 단편화 문제 발생 가능
      •  

메모리 단편화(Fragmentation)란?

      •  

메모리 단편화는 **"레디스는 데이터를 지웠다고 하는데, OS는 그 공간을 아직 돌려받지 못한 상태"**에서 주로 발생합니다.

      • 발생 원인: 레디스가 데이터를 삭제하면 해당 메모리 공간은 '빈 상태'가 됩니다. 하지만 OS의 메모리 할당자(Allocator, 주로 jemalloc 사용)는 이 빈 공간이 너무 작거나 흩어져 있으면 다른 프로세스에게 돌려주지 않고 레디스가 나중에 다시 쓰도록 그대로 둡니다.
      • 문제 상황: 실제 데이터(used_memory)는 1GB뿐인데, OS가 할당한 메모리(used_memory_rss)는 2GB라면? 중간에 **1GB만큼의 쓸모없는 빈틈(단편화)**이 생긴 것입니다.
    • 메모리 효율성이 떨어진다. 단편화가 문제가 된다면 CONFIG SET activedefrag yes 활성화할 수 있음(버전 4이상), 문제 없으면 안 해도됨

 

네트워크

  • 레디스는 더 많은 명령 처리할 수 있는데 네트워크 RTT 때문에 처리량 낮을 수 있음
  • 네트워크 지표가 특정 지점에서 상승하지 않으면 네트워크 임계치에 걸려 병목 현상인지 확인해라
  • 네트워크 사용량 증가 원인이
    • 읽기 작업이면: 복제본 활용하게 해야함, 이미 그러면 추가 복제본 구성해 분산시키기
    • 쓰기 작업이면: 서버 사양 업그레이드. 아님 클러스터 모드로 쓰기 작업을 여러 노드로 분산시키기

 

커넥션

    • 클라 연결 추이 모니터링
    • 일반적인 수준과 갑자기 다르게 활성 연결 수 증가하면 클라 연결 비정상 종료돼 유지되고 있는건 아닌지 확인 필요
    • tcp-keepalive 설정으로 유휴 연결 끊게 할 수 있음 (기본: 300초마다 연결 유효성 확인, 문제 있으면 끊음)
    • 클라를 새롭게 연결하는건 상당한 비용이 드니 앺은 기존 연결 재사용하기 위해 커넥션 풀링 사용하는게 좋다.
      •  

1. 왜 새롭게 연결하는 것이 '비싼 비용'인가요?

      •  

레디스와 앱이 처음 연결될 때는 다음과 같은 복잡한 단계(Handshake)를 거칩니다.

      •  

TCP 3-Way Handshake: 네트워크상에서 서로 통신할 준비가 됐는지 3번 확인합니다.

      •  

인증(AUTH): 패스워드가 있다면 확인하는 절차가 추가됩니다.

      •  

프로토콜 설정: 레디스와 데이터를 주고받을 형식을 맞춥니다.

      •  

이 과정은 실제 데이터를 주고받는 시간보다 준비 운동(Overhead) 시간이 더 걸릴 정도로 리소스를 많이 잡아먹습니다. 초당 수만 건의 요청이 들어오는 환경에서 매번 이 짓을 한다면 서버는 금방 지쳐버리겠죠.

      •  

2. 커넥션 풀링(Connection Pooling)의 원리

      •  

커넥션 풀은 미리 **'연결된 통로'**들을 만들어 놓고 커다란 바구니(Pool)에 담아두는 개념입니다.

      •  

요청 발생: 앱이 레디스에 데이터를 넣으려 할 때, 새로 연결하지 않고 바구니에서 노는(Idle) 연결을 하나 꺼내 씁니다.

      •  

작업 완료: 데이터를 다 보냈으면 연결을 끊는(Close) 게 아니라, 다시 바구니에 반납합니다.

      •  

재사용: 다음번 요청이 들어오면 방금 반납된 그 연결을 그대로 다시 꺼내 씁니다


    • Lettuce는 "많은 요청을 하나의 연결에 태우기" 위해 멀티플렉싱(정확히는 비동기 I/O 공유)을 쓰는것이라 커넥션 풀링 굳이 필요 없음
  • TLS 통신일 경우 TLS 핸드셰이크가 많은 시간과 CPU 소모되니 새로운 연결 개수 제어하는게 좋다.

 

복제

  • 복제본이 마스터 전송 속도 못 따라잡는 복지 지연이 있는지 모니터링
    • 복제 지연이 길어지면 복제본이 전체 동기화 요청해야할 수도 있어서 그럼 마스터 노드에서 스냅샷 생성해야해서 성능 저하 초래함
  • 복제 지연 원인: 많은 쓰기 작엄, 네트워크 대역폭 고갈, 복제 출력 버퍼 크기

 

'SearchDeveloper > 개발자를 위한 레디스' 카테고리의 다른 글

12장 클라이언트 관리  (0) 2026.06.07
11장 보안  (0) 2026.06.07
10장 클러스터  (0) 2026.06.07
9장 센티널  (0) 2026.06.07
8장 복제  (1) 2026.06.07