Redis 성능 병목 현상 상위 5가지 및 해결 방법

Redis 배포에서 최고의 성능을 끌어내기 위한 필수 가이드로, 일반적인 병목 현상을 식별하고 해결하는 방법을 알아보세요. 느린 O(N) 명령어, 과도한 네트워크 왕복, 메모리 압박 및 비효율적인 제거 정책, 지속성 오버헤드, CPU 바운드 작업과 같은 문제를 식별하고 해결하는 방법을 배웁니다. 이 문서는 파이프라이닝과 `SCAN` 활용부터 데이터 구조 및 지속성 최적화에 이르기까지 실행 가능한 단계, 실제 예제 및 모범 사례를 제공하여 캐싱, 메시징 및 데이터 저장 요구 사항에 대해 Redis 인스턴스가 빠르고 안정적으로 유지되도록 보장합니다.

Redis 성능 병목 현상 상위 5가지 및 해결 방법

Redis 성능 문제는 일반적으로 한 가지를 기억할 때까지 신비롭게 보입니다. Redis는 빠르지만 작업에서 면제되지는 않습니다. 백만 개의 키를 탐색하는 명령은 여전히 백만 개의 키를 탐색합니다. 네트워크 왕복당 하나의 명령을 보내는 클라이언트는 여전히 모든 왕복에 대해 비용을 지불합니다. 메모리가 부족한 서버는 구성에 따라 여전히 제거, 스왑, 쓰기 거부 또는 중단되어야 합니다.

Redis 속도가 느려지면 임의의 설정을 변경하는 것부터 시작하지 마십시오. 증거부터 시작하십시오:

redis-cli INFO
redis-cli SLOWLOG GET 20
redis-cli LATENCY DOCTOR
redis-cli INFO commandstats
redis-cli INFO memory

이러한 명령은 일반적으로 느린 명령, 네트워크 왕복, 메모리 압박, 지속성 오버헤드 또는 CPU 포화의 다섯 가지 병목 현상 중 하나를 가리킵니다.

1. 대규모 데이터에 대한 느린 명령

Redis에는 많은 작은 상수 시간 연산이 있지만 모든 명령이 작은 것은 아닙니다. KEYS, 대규모 LRANGE, SMEMBERS, HGETALL, 대규모 범위에 대한 ZRANGE, SORT 및 긴 Lua 스크립트와 같은 명령은 실행되는 동안 다른 클라이언트를 차단할 수 있습니다.

전형적인 사고는 정리 또는 디버깅 명령으로 시작됩니다:

KEYS *

소규모 개발 인스턴스에서는 즉시 반환됩니다. 수백만 개의 키가 있는 프로덕션 키스페이스에서는 애플리케이션 요청이 쌓일 만큼 서버를 오래 중단시킬 수 있습니다. "사용자당 몇 개의 필드"로 시작하여 조용히 거대한 객체가 된 해시에서도 동일한 패턴이 발생합니다.

증거를 찾으십시오:

redis-cli SLOWLOG GET 20
redis-cli INFO commandstats
redis-cli LATENCY LATEST

SLOWLOG는 구성된 임계값을 초과한 명령을 기록합니다. INFO commandstats는 명령별 호출 횟수와 누적 시간을 보여줍니다. 하나의 명령이 시간을 지배한다면 거기서부터 시작하십시오.

액세스 패턴을 수정하십시오:

redis-cli --scan --pattern 'user:*'

키스페이스 반복에는 KEYS 대신 SCAN을 사용하십시오. 대규모 해시, 세트 및 정렬된 세트에는 HSCAN, SSCANZSCAN을 사용하십시오. 전체 구조 대신 페이지 또는 범위를 가져오십시오:

LRANGE feed:user:42 0 49
ZRANGE leaderboard 0 99 WITHSCORES

객체가 너무 커진 경우 애플리케이션이 읽는 방식에 따라 분할하십시오. 수천 개의 관련 없는 필드가 있는 단일 user:42 해시는 쓰기에 편리할 수 있지만 프로필 설정만 필요한 읽기에는 고통스러울 수 있습니다. user:42:profile, user:42:prefsuser:42:counters와 같은 별도의 키는 요청당 처리되는 데이터 양을 줄일 수 있습니다.

삭제의 경우 값이 클 수 있는 경우 UNLINK를 선호하십시오:

UNLINK old:large:set

UNLINK는 키스페이스에서 키를 제거하고 비동기적으로 메모리를 해제합니다. 대규모 값의 경우 DEL보다 안전하지만 대량 정리에는 여전히 제한이 필요합니다.

2. 너무 많은 네트워크 왕복

Redis는 마이크로초 단위로 명령을 처리할 수 있지만 애플리케이션은 네트워크에서 밀리초를 소비합니다. 요청 경로가 50개의 순차적 Redis 명령을 보내는 경우 Redis 자체가 정상이더라도 네트워크가 전체 시간을 지배할 수 있습니다.

이것은 다음과 같은 코드에서 일반적입니다:

for user_id in user_ids:
    profile = redis.get(f"user:{user_id}:profile")

GET은 다음 항목이 시작되기 전에 자체 응답을 기다립니다. 네트워크를 통해 이는 비용이 많이 듭니다.

파이프라이닝을 사용하십시오:

pipe = redis.pipeline(transaction=False)
for user_id in user_ids:
    pipe.get(f"user:{user_id}:profile")
profiles = pipe.execute()

파이프라이닝은 각 응답을 개별적으로 기다리지 않고 여러 명령을 보냅니다. Redis는 여전히 명령을 순서대로 실행하지만 클라이언트는 모든 명령에 대해 왕복 비용을 지불하지 않습니다.

적합한 경우 다중 키 명령을 사용하십시오:

MGET user:1:profile user:2:profile user:3:profile

모든 요청을 하나의 거대한 파이프라인으로 만들지 마십시오. 대규모 파이프라인은 메모리 사용량을 증가시키고 응답 버스트를 생성할 수 있습니다. 명백한 왕복 낭비를 제거할 만큼만 배치한 다음 측정하십시오.

읽기 중심 경로의 경우 애플리케이션이 Redis에 반복적으로 값을 요청하는지 확인하십시오. 이 값은 요청 기간 동안 한 번 가져와서 재사용할 수 있습니다. 작은 로컬 요청 캐시는 Redis를 전혀 변경하지 않고 우발적인 중복 읽기를 제거할 수 있습니다.

3. 메모리 압박 및 제거 변동

Redis는 메모리 중심입니다. 메모리가 부족해지면 성능은 여러 방식으로 악화됩니다. 제거는 CPU 비용을 발생시키고, noeviction에서는 쓰기가 실패할 수 있으며, 지속성 포크는 더 어려워질 수 있고, 복제본은 지연될 수 있으며, 호스트가 잘못 구성되었거나 과부하된 경우 운영 체제가 스왑할 수 있습니다.

메모리를 확인하십시오:

redis-cli INFO memory
redis-cli INFO stats | grep evicted_keys
redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy

중요한 징후:

  • used_memorymaxmemory에 가깝습니다.
  • evicted_keys가 빠르게 증가하고 있습니다.
  • 호스트가 스왑하고 있습니다.
  • 큰 키가 예상보다 더 많은 메모리를 소비합니다.
  • 만료되는 캐시 키에 실제로 TTL이 없습니다.

큰 키를 조심스럽게 찾으십시오. 피크 트래픽 중에는 광범위한 비용이 많이 드는 명령을 실행하지 마십시오. --bigkeys로 샘플링하는 것이 도움이 될 수 있습니다:

redis-cli --bigkeys

캐시의 경우 데이터와 일치하는 메모리 제한 및 제거 정책을 설정하십시오:

maxmemory 4gb
maxmemory-policy allkeys-lru

모든 키가 캐시 항목인 경우 allkeys-lru 또는 allkeys-lfu가 적합할 수 있습니다. volatile-lru는 TTL이 있는 키만 제거하므로 영구 키가 캐시 키와 인스턴스를 공유할 때 유용합니다. noeviction은 Redis를 기본 데이터 저장소로 사용할 때 종종 올바른 선택입니다. 내구성 있는 데이터를 조용히 제거하는 것은 오류를 반환하는 것보다 더 나쁘기 때문입니다.

쓰기 시 TTL을 설정하십시오:

SET cache:product:123 "$json" EX 300

세션 저장소의 경우 신중하십시오. 만료되지 않은 세션 키는 일반적으로 버그입니다. 속도 제한기의 경우 카운터는 창과 함께 만료되어야 합니다. 스트림의 경우 오래된 항목을 정리하십시오. Redis의 메모리 누수는 종종 수명 주기를 받지 못한 애플리케이션 데이터입니다.

또한 중요한 경우 키 및 값 오버헤드를 줄이십시오. 수천 개의 작은 키는 예상보다 더 많은 메타데이터 비용이 들 수 있습니다. 때로는 압축된 해시가 많은 개별 키보다 나을 수 있습니다. 때로는 읽기에 하나의 필드만 필요하기 때문에 그 반대가 사실입니다. 실제 액세스 패턴으로 측정하고 하나의 모양이 항상 최고라고 가정하지 마십시오.

4. 지속성 및 디스크 I/O 중단

지속성은 데이터를 보호하지만 이해해야 할 디스크 및 포크 동작을 도입합니다. RDB 스냅샷 및 AOF 재작성은 일반적으로 백그라운드 작업이지만 포크 시간, copy-on-write 메모리 압박 및 디스크 I/O를 통해 대기 시간을 유발할 수 있습니다.

지속성 상태를 확인하십시오:

redis-cli INFO persistence
redis-cli LATENCY LATEST
iostat -xz 1

실패한 백그라운드 저장, 긴 포크 시간, AOF 재작성 활동 및 디스크 포화를 찾으십시오. 대기 시간 급증이 BGSAVE 또는 BGREWRITEAOF와 일치하면 지속성 튜닝이 우선 순위 목록에 있어야 합니다.

AOF의 경우 주요 내구성/성능 설정은 다음과 같습니다:

appendfsync everysec

everysec은 일반적인 균형 잡힌 선택입니다. always는 모든 쓰기를 동기화하며 매우 느릴 수 있습니다. no는 동기화를 운영 체제에 맡기고 충돌 시 더 많은 데이터 손실 위험을 감수합니다.

RDB의 경우 의도하지 않은 한 바쁜 쓰기 워크로드에서 지속적으로 실행되는 스냅샷 규칙을 피하십시오:

save 900 1
save 300 10
save 60 10000

이러한 예제 기본값은 모든 워크로드에 자동으로 적합하지 않습니다. 폐기 가능한 캐시로 사용되는 높은 쓰기 Redis는 지속성이 전혀 필요하지 않을 수 있습니다. 작업 큐 또는 세션 저장소로 사용되는 Redis 인스턴스는 아마도 필요하지만 허용 가능한 손실 창이 명확해야 합니다.

지속성이 애플리케이션 트래픽과 경쟁하는 경우 다음을 고려하십시오:

  • 더 빠른 로컬 SSD 스토리지.
  • Redis 지속성을 다른 디스크 집약적 서비스와 분리.
  • 기본이 해당 설계를 견딜 수 있는 경우 복제본에서 지속성 실행.
  • 데이터 세트 크기를 호스트가 편안하게 포크할 수 있는 크기 미만으로 유지.
  • Redis가 백그라운드 저장을 위해 권장하는 Linux vm.overcommit_memory=1 설정.

데이터가 진정으로 폐기 가능하지 않은 경우 "성능 수정"을 위해 맹목적으로 지속성을 비활성화하지 마십시오. 그래프를 더 좋게 보이게 할 수 있지만 재시작을 데이터 손실로 바꿀 수 있습니다.

5. CPU 포화 및 단일 스레드 명령 실행

Redis 명령 실행은 대부분 단일 스레드이지만 최신 Redis는 일부 I/O 및 백그라운드 작업에 추가 스레드를 사용합니다. 하나의 코어가 Redis에 고정되어 있으면 동일한 인스턴스에 더 많은 유휴 코어를 추가해도 핫 명령 경로에 도움이 되지 않을 수 있습니다.

호스트 및 Redis 명령 혼합을 확인하십시오:

top -H -p $(pgrep redis-server)
redis-cli INFO commandstats
redis-cli SLOWLOG GET 20
redis-cli INFO clients

일반적인 CPU 원인:

  • 대규모 세트, 정렬된 세트, 목록 또는 해시 연산.
  • 무거운 Lua 스크립트.
  • 예상보다 큰 값을 유발하는 애플리케이션의 압축 또는 직렬화 오버헤드.
  • 매우 높은 Pub/Sub 팬아웃.
  • 메모리 압박으로 인한 비용이 많이 드는 제거.
  • 지속적으로 재연결하거나 작은 명령을 발행하는 너무 많은 연결.

작업을 줄이거나, 분할하거나, 분산하여 CPU를 수정하십시오.

명령 및 데이터 모양을 변경하여 작업을 줄이십시오. 50개의 항목만 필요한 경우 5,000개를 가져오지 마십시오. 모든 요청이 하나의 플래그를 읽기 위해 500KB JSON blob을 구문 분석하는 경우 해당 플래그를 더 작은 키 또는 필드로 분할하십시오.

증분 스캔을 사용하여 긴 루프를 클라이언트로 이동하여 작업을 분할하십시오:

HSCAN big:hash 0 COUNT 100

읽기를 위한 복제본 또는 샤딩을 위한 Redis Cluster로 작업을 분산하십시오. 복제본은 읽기 중심 트래픽에 도움이 되지만 기본에서 쓰기를 더 저렴하게 만들지는 않습니다. Redis Cluster는 키를 기본 간에 분산하여 총 CPU 및 메모리 용량을 늘릴 수 있지만 운영 복잡성과 키 슬롯 제약 조건도 추가합니다.

Pub/Sub의 경우 출력 버퍼 및 팬아웃을 확인하십시오:

redis-cli PUBSUB NUMSUB events:updates
redis-cli CLIENT LIST

느린 구독자는 메모리 압박으로 이어질 수 있습니다. 수천 명의 구독자는 하나의 게시를 많은 양의 네트워크 출력으로 바꿀 수 있습니다. Pub/Sub가 무거운 경우 별도의 Redis 인스턴스에서 격리하는 것을 고려하십시오.

빠른 분류 워크플로우

Redis 대기 시간이 증가하면 다음 순서로 이러한 검사를 실행하십시오:

redis-cli --latency
redis-cli SLOWLOG GET 10
redis-cli LATENCY DOCTOR
redis-cli INFO memory
redis-cli INFO clients
redis-cli INFO persistence
redis-cli INFO commandstats

그런 다음 질문하십시오:

  • 느린 명령이 나타났습니까?
  • 메모리가 maxmemory에 도달했거나 제거를 시작했습니까?
  • 지속성이 저장 또는 재작성을 시작했습니까?
  • 연결된 클라이언트 또는 차단된 클라이언트가 급증했습니까?
  • 하나의 명령의 호출 횟수 또는 시간이 폭발했습니까?
  • 애플리케이션 배포가 Redis 액세스 패턴을 변경했습니까?

대부분의 Redis 병목 현상은 하나의 마법 설정으로 해결되지 않습니다. 작업 부하를 더 작게, 더 증분적으로, 더 배치 처리하거나 더 잘 격리하여 해결됩니다. 최고의 Redis 배포는 지루합니다. 키는 적시에 만료되고, 큰 작업은 페이지 처리되며, 클라이언트는 현명하게 파이프라인을 사용하고, 지속성 설정은 데이터의 가치와 일치하며, 모니터링은 사용자가 느끼기 전에 추세를 포착합니다.

수정 전후에 측정할 사항

성능 수정은 관련 작업 부하에 대한 그래프가 변경된 경우에만 실제입니다. 코드 또는 구성을 변경하기 전에 작은 기준을 캡처하십시오:

redis-cli INFO stats
redis-cli INFO commandstats
redis-cli INFO memory
redis-cli INFO persistence
redis-cli SLOWLOG GET 20

시스템 수준에서 CPU, 디스크, 메모리, 스왑 및 네트워크 처리량을 캡처하십시오. Redis가 컨테이너에서 실행되는 경우 컨테이너 제한과 호스트 압력을 모두 확인하십시오. Redis 프로세스는 자체 메모리 보기 내에서 정상으로 보일 수 있지만 호스트는 다른 서비스의 디스크 또는 CPU 압력을 받고 있습니다.

변경 후 다음을 비교하십시오:

  • Redis 지원 요청에 대한 p50, p95 및 p99 애플리케이션 대기 시간.
  • 요청 대기 시간뿐만 아니라 Redis 명령 대기 시간.
  • 명령별 Slowlog 항목.
  • 제거율 및 메모리 여유 공간.
  • 연결된 클라이언트 및 거부된 연결.
  • 지속성 포크 시간 및 AOF/RDB 상태.
  • 복제본이 읽기를 제공하거나 내구성을 보호하는 경우 복제본 지연.

통증만 이동시키는 수정에 주의하십시오. 예를 들어, 대규모 파이프라인은 요청 대기 시간을 줄일 수 있지만 메모리 급증을 증가시킬 수 있습니다. AOF를 비활성화하면 디스크 대기 시간이 제거될 수 있지만 복구가 약화됩니다. maxmemory를 늘리면 제거가 지연될 수 있지만 시스템이 이미 공유된 경우 호스트가 부족해질 수 있습니다.

한 가지 유용한 방법은 변경한 정확한 Redis 패턴을 중심으로 작은 부하 테스트를 작성하는 것입니다. 이전 코드가 40개의 순차적 GET을 수행한 경우 실제 페이로드 크기로 순차적 GETMGET 또는 파이프라이닝을 테스트하십시오. 이전 코드가 HGETALL을 사용한 경우 요청에 실제로 필요한 필드에 대해 HGET을 테스트하십시오. Redis 튜닝은 일반적인 "Redis ops per second" 숫자가 아닌 실제로 실행하는 모양을 벤치마킹할 때 훨씬 쉽습니다.

마지막으로 롤백을 간단하게 유지하십시오. Redis 성능 변경은 종종 애플리케이션 코드, 클라이언트 설정 및 서버 구성에 동시에 위치합니다. 가능하면 한 번에 한 가지만 변경하십시오. 여러 가지를 변경해야 하는 경우 각 변경이 개선해야 하는 증상을 기록하십시오. 이렇게 하면 다음 엔지니어가 아무도 제거하고 싶어하지 않는 신비한 설정 더미를 물려받는 것을 방지할 수 있습니다.