캐시 압축은 동시 요청이 몰릴수록 이득이 커졌다
목차
한 스레드로 정한 캐시 압축의 두 결정(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를 다투고 줄이 생길 수 있다고 그 글의 대가에 적었습니다. 이번에 그것을 쟀습니다.
방법
- 실제 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면 코덱을 유지합니다.
결과
| 크기 | 스레드 | 압축 없음 p95 | LZ4 | Snappy | 압축 없음 CPU/연산 | LZ4 | Snappy |
|---|---|---|---|---|---|---|---|
| 1KB | 1 | 0.26ms | 0.30ms | 0.38ms | 37µs | 48µs | 46µs |
| 1KB | 8 | 0.68ms | 0.49ms | 0.60ms | 58µs | 62µs | 57µs |
| 1KB | 32 | 1.48ms | 1.43ms | 1.57ms | 62µs | 64.65µs | 64.65µs |
| 50KB | 8 | 1.22ms | 0.98ms | 0.64ms | 80µs | 136µs | 99µs |
| 50KB | 32 | 5.89ms | 4.87ms | 2.24ms | 94µs | 181µs | 113µs |
| 500KB | 8 | 8.82ms | 11.28ms | 4.02ms | 247µs | 917µs | 804µs |
| 500KB | 32 | 35.83ms | 24.59ms | 11.90ms | 213µs | 815µs | 765µ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에서 가능합니다.