Redis는 성능을 최우선으로 삼아 개발되고 있습니다. 매 릴리스마다 안정적이면서도 빠른 제품을 경험하실 수 있도록 최선을 다하고 있습니다.
그럼에도 불구하고 Redis의 효율성을 더욱 끌어올릴 여지를 찾거나 성능 저하(regression)를 조사해야 하는 경우가 있습니다. 이를 위해서는 체계적이고 간결한 방식으로 Redis 성능을 모니터링하고 분석할 방법이 필요합니다. 이 글은 바로 그러한 최적화 작업의 한 사례를 담은 이야기입니다.
최종적으로 우리는 스트림(Stream) 데이터 수집(ingest) 성능을 약 20% 향상시켰으며, 이 개선 사항은 이미 Redis v7.0에서 확인하실 수 있습니다.
표준 SPEC 기반 벤치마크
본격적인 최적화 작업에 들어가기 전에, 우리가 어떤 과정을 거쳐 개선점에 도달했는지 큰 그림부터 소개하겠습니다.
앞서 언급했듯이 우리의 목표는 Redis의 성능 저하와 CPU 상(on-CPU) 성능 개선 여지를 식별하는 것입니다. 이를 위해 성능 및 관측 가능성(observability)과 관련된 요구사항과 기대치에 대해 회사 간, 커뮤니티 간 공통 표준을 마련할 필요성을 느꼈습니다.
요약하자면, 우리는 SPEC 벤치마크를 브랜치/태그별로 분류하여 지속적으로 실행하고, 프로파일링 도구·프로버(prober) 출력과 클라이언트 출력을 포함한 성능 데이터를 완전 자동화된 "무인(zero-touch)" 모드로 해석합니다.
사용된 도구는 모두 오픈소스이며, memtier_benchmark, redis-benchmark, Linux perf_events, bcc/BPF 추적 도구, Brendan Gregg의 FlameGraph 저장소 등 널리 검증된 프레임워크에 기반합니다.
Redis에서 프로파일러를 활용하는 방법에 대한 자세한 내용이 궁금하시다면, 매우 상세한 문서인 "Performance engineering guide for on-CPU profiling and tracing"(CPU 프로파일링 및 트레이싱 성능 엔지니어링 가이드)을 참고하시기 바랍니다.
중복 연산 제거로 성능 개선하기
첫 번째 단계가 마련되자마자, 우리는 프로파일링 도구와 프로버의 출력 결과 분석에 착수했습니다. 흥미로운 패턴을 보여준 벤치마크 중 하나는 스트림(Streams) 수집 벤치마크였습니다. 이 벤치마크는 아래와 같은 명령어를 사용해 단순히 스트림에 데이터를 추가하는 작업입니다:
XADD key * field value
분석 결과, ID 없이 스트림에 데이터를 추가할 때 SDS 생성/해제/sdslen 과정에서 중복 작업이 발생하여 약 10%의 CPU 사이클이 낭비된다는 사실을 확인했습니다. 자세한 내용은 아래 두 perf 리포트 출력에서 확인할 수 있습니다.
동일한 입력에 대해 sdscatfmt와 _sdsnewlen이 두 번씩 호출되고 있었던 것입니다:
이 발견을 통해 스트림 수집 성능을 약 9~10% 최적화할 수 있었으며, 아래 벤치마크 결과로 그 효과를 확인했습니다:
unstable 브랜치 기준선(6b403f5):
PR 첫 번째 커밋(중복 작업 제거):
중복 메모리 할당 제거로 성능 개선하기
이 초기 성과는 코어 팀 멤버인 Oran에 의한 추가 분석으로 이어졌고, 그는 같은 코드 블록 내에서 또 다른 CPU 사이클 낭비를 발견했습니다. 이번 원인은 비효율적인 메모리 관리였습니다. 빈 SDS를 먼저 할당한 뒤 곧바로 재할당(realloc)하고 있었던 것입니다. 호출 횟수를 줄이면 아래와 같이 추가적인 속도 향상을 얻을 수 있습니다.
두 번째 커밋(재할당 제거):
측정된 성능 향상
예상대로, 내부적으로 호출되는 함수들에서 중간 계산 결과를 재활용함으로써 불필요한 연산과 할당을 줄였더니, Redis Streams의 전체 CPU 시간이 약 20% 감소하는 효과를 측정할 수 있었습니다.
우리는 이 사례가 체계적이고 단순한 개선만으로도 Redis처럼 이미 깊이 최적화된 코드에서조차 눈에 띄는 성능 향상을 만들어낼 수 있음을 보여준다고 믿습니다.
우리의 목표는 Redis에 대한 성능 가시성을 확장하는 것입니다. 산업계와 학계를 아우르는 조직과 개인 모두의 기여를 환영합니다. 측정하지 않으면 개선할 수 없습니다.