BE-commerce 프로젝트
약 4분 분량 성능·용량

캐시 압축은 동시 요청이 몰릴수록 이득이 커졌다

목차

한 스레드로 정한 캐시 압축의 두 결정(1KB부터 압축, 코덱은 Snappy)을 스레드 32개까지 다시 쟀고 두 결정은 그대로 섰습니다. 예상과 달랐던 것은 방향입니다. 압축은 CPU를 쓰니 동시 요청에서 불리할 것으로 봤는데 동시성이 오를수록 압축의 이득이 커졌습니다. 50KB 값을 32스레드로 넣고 읽을 때 p95가 압축 없이 5.89ms, Snappy로 2.24ms였습니다.

결론부터

  • 동시성이 올라가도 1KB 임계값과 Snappy 선택은 바뀌지 않았습니다. 32스레드에서 1KB Snappy p95는 1.57ms로, 압축하지 않을 때의 1.48ms와 1.06배 차이였습니다.
  • 50KB 값은 32스레드에서 Snappy p95 2.24ms, 압축 없음 5.89ms였습니다. 압축 CPU 비용보다 공유 Redis 연결에서 줄을 서는 바이트 수를 줄인 효과가 더 컸습니다.
  • 이 결과는 같은 Redis 연결을 공유하는 현재 앱의 측정입니다. 다른 연결 수나 더 큰 페이로드에서는 다시 재야 합니다.

동시 요청에서 압축 비용이 어떻게 달라지는가

ch39에서 Redis 캐시 값을 몇 바이트부터 압축할지 쟀습니다. 압축과 압축 없음의 지연이 만나는 교차점은 약 240바이트였지만 그 근처 이득이 몇 %라 임계값은 보수적으로 1KB에 두었습니다. 코덱은 통념과 달리 LZ4보다 Snappy가 CPU를 덜 썼습니다.

그 측정은 한 스레드였습니다. 압축은 CPU를 쓰는 일이라 요청이 몰리면 압축끼리 CPU를 다투고 줄이 생길 수 있다고 그 글의 대가에 적었습니다. 이번에 그것을 쟀습니다.

스레드 1·8·32에서 압축 없음, LZ4, Snappy를 비교했다. 공유 연결에서는 바이트 수가 줄을 세워 동시성이 오를수록 압축 쪽이 빨라졌다

방법

  • 실제 Redis에 앱과 같은 방식(StringRedisTemplate, 연결 하나 공유)으로 넣고 읽습니다. 값은 ch39와 같은 추천 컨텍스트 JSON 모양입니다
  • 스레드마다 200개를 넣고 같은 순서로 읽습니다
  • 크기 1KB·50KB·500KB × 압축 없음·LZ4·Snappy × 스레드 1·8·32. 코덱은 기동 설정이라 코덱마다 앱을 다시 띄웠습니다
  • “넣고 읽기”는 압축 + 저장 + 조회 + 해제 한 번의 시간입니다. 연산당 CPU는 스레드별 CPU 시간의 합을 연산 수로 나눈 값입니다

측정 전에 기준을 적었습니다. 32스레드에서 1KB의 Snappy 넣고 읽기 p95가 압축 없음의 1.2배 이하면 임계값을 유지하고 8·32스레드에서 연산당 CPU가 Snappy < LZ4면 코덱을 유지합니다.

결과

크기스레드압축 없음 p95LZ4Snappy압축 없음 CPU/연산LZ4Snappy
1KB10.26ms0.30ms0.38ms37µs48µs46µs
1KB80.68ms0.49ms0.60ms58µs62µs57µs
1KB321.48ms1.43ms1.57ms62µs64.65µs64.65µs
50KB81.22ms0.98ms0.64ms80µs136µs99µs
50KB325.89ms4.87ms2.24ms94µs181µs113µs
500KB88.82ms11.28ms4.02ms247µs917µs804µs
500KB3235.83ms24.59ms11.90ms213µs815µs765µs
  • 임계값 1KB 유지. 32스레드에서 1KB Snappy p95 1.57ms, 압축 없음 1.48ms로 1.06배입니다. p99는 3.32ms 대 1.84ms로 더 벌어졌습니다
  • Snappy 유지. 8·32스레드 여섯 칸 중 다섯에서 Snappy의 연산당 CPU가 낮았습니다. 1KB·32스레드는 64.647µs 대 64.649µs로 같았습니다. 기준 문장이 동점을 다루지 않았고 뒤집힌 칸이 없어 유지로 판정했습니다
  • 500KB 꼬리. 32스레드 조회 p99가 압축 없이 40.21ms, Snappy로 8.42ms였습니다

왜 동시성에서 압축이 이기나

압축은 CPU를 더 씁니다. 표의 연산당 CPU를 보면 어느 칸에서나 압축 없음이 가장 적습니다. 그런데 지연은 반대입니다.

앱은 Redis 연결 하나를 모든 요청이 나눠 씁니다. 한 연결 위의 명령은 차례로 나가고 차례로 돌아오니, 앞 요청의 바이트가 다 지나가야 뒤 요청이 움직입니다. 동시 요청이 많을수록 줄이 길어지고 줄의 길이는 바이트 수에 비례합니다. 압축은 CPU를 조금 더 쓰는 대신 그 줄을 줄였습니다.

ch39에서 “Redis가 로컬이라 전송이 루프백이고 원격이면 줄어든 바이트가 지연으로 돌아와 임계값이 더 내려간다”고 적었습니다. 같은 방향의 효과가 로컬에서도 동시성으로 나타났습니다.

한계

  • 각 조건 한 번씩 쟀습니다. LZ4는 500KB 한 스레드에서 p99가 8.54ms로 튀었습니다
  • 스레드 32는 이 머신(Apple M2 Pro)의 코어 수보다 많습니다
  • 연결 하나를 공유하는 구성입니다. 연결 풀을 쓰면 바이트 줄 서기 효과가 다를 수 있습니다
  • 압축은 여전히 켜져 있지 않습니다. 가장 큰 캐시(추천 컨텍스트)는 Redis 안의 Lua 스크립트가 값을 읽어 병합하므로 압축하면 그 병합이 깨집니다

정리

  • 임계값 1KB와 Snappy는 32스레드에서도 기준을 넘었습니다
  • 공유 연결에서는 동시성이 오를수록 압축의 이득이 커졌습니다. 50KB 이상 값에서 두 배 넘게 빨랐습니다
  • 압축을 실제로 켤 첫 자리는 홈 화면 결과 캐시입니다. 켤 때 p95와 메모리를 다시 잽니다
프로필 사진
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글

댓글 수정/삭제는 GitHub Discussions에서 가능합니다.