NGINX Cache 배치 전략
이번 포스트에서는 NGINX Cache 의 또 다른 측면인 캐시 배치에 대해 살펴보고 Slow 캐시 스토리지에 대처하기 위한 전략을 살펴보겠습니다.
목차
1. NGINX Cache 위치 선택하기
2. 캐시된 데이터는 어디에 저장될까요?
3. NGINX Cache에 tmpfs를 사용할 수 있을까요?
4. 읽기 작업 최적화
5. NGINX Cache 쓰기 작업에 대한 디스크 구성의 효율성 테스트
5-1. 테스트 프레임워크
5-2. 기준 처리량 설정
5-3. 캐시 쓰기 성능 측정
5-4. 예측할 수 없는 요인 완화하기
6. NGINX Cache 분석
1. NGINX Cache 위치 선택하기
NGINX는 각각 다른 파일 시스템 위치에 매핑된 여러 캐시 위치를 관리할 수 있으며, 요청 별 어떤 캐시를 사용할지 선택하도록 NGINX를 구성할 수 있습니다.
다음 샘플 구성에서 proxy_cache_path
지시문은 로컬 디렉터리 /mnt/ssd/cache
및 /mnt/disk/cache
에 각각 마운트된 두 개의 캐시, ssd_cache
및 disk_cache
를 생성합니다. map
지시문은 요청 URL을 검사하여 동영상 다운로드인 것으로 보이는 모든 요청(URL에 .mp4
또는 .avi
가 포함됩니다)에 대해 disk_cache
를 선택합니다. 다른 모든 요청에는 기본 ssd_cache
위치가 선택됩니다.
# Define caches and their locations
proxy_cache_path /mnt/ssd/cache keys_zone=ssd_cache:10m levels=1:2 inactive=600s
max_size=700m;
proxy_cache_path /mnt/disk/cache keys_zone=disk_cache:100m levels=1:2 inactive=24h
max_size=80G;
# Requests for .mp4 and .avi files go to disk_cache
# All other requests go to ssd_cache
map $request_uri $cache {
~.mp4(?.*)?$ disk_cache;
~.avi(?.*)?$ disk_cache;
default ssd_cache;
}
server {
# select the cache based on the URI
proxy_cache $cache;
# ...
}
Note: 간단한 배포의 경우, 서로 다른 URL에 대해 별도의 location 블록을 생성하고 각 블록 내에 proxy_cache 지시문을 사용하여 서로 다른 캐시 위치를 지정할 수 있습니다.
2. 캐시된 데이터는 어디에 저장될까요?
NGINX와 NGINX Plus는 하이브리드 디스크 및 메모리 캐시를 활용합니다. 캐시가 디스크, SSD 또는 다른 곳에 있든 관계없이 운영 체제 페이지 캐시는 캐시된 항목이 요청될 때 주 메모리로 가져옵니다. 가장 최근에 요청된 캐시된 콘텐츠는 다른 용도로 메모리가 필요할 때 교체됩니다. 운영 체제는 호스트에서 사용되지 않는 주 메모리를 활용하기 위해 페이지 캐시를 능동적으로 관리합니다.
캐시된 콘텐츠에 대한 메타데이터는 항상 호스트의 모든 NGINX Worker Process에서 액세스할 수 있는 공유 메모리 Zone의 메인 메모리에 저장됩니다. 이 Zone은 proxy_cache_path 지시문에 keys_zone 파라미터를 사용하여 할당됩니다. NGINX cache_loader
프로세스는 시작할 때 메타데이터를 초기화합니다.
3. NGINX Cache 에 tmpfs를 사용할 수 있을까요?
tmpfs
에 캐시할 수 있습니다. – 일시적인 In-Memory 파일 시스템이기는 하지만 재부팅 시 지속성이 부족하다는 점 외에도 몇 가지 문제가 있습니다.
tmpfs
파일시스템은 사용 가능한 RAM에 의해 제한되기 때문에 필연적으로 작을 수밖에 없습니다. Worker Process가 캐시에 새 리소스를 추가하면 캐시 Manager Process가 구성된 max_size
를 유지하기 위해 백그라운드에서 캐시를 정리하기 때문에 NGINX는 캐시를 과도하게 채울 수 있습니다. 따라서 캐시 크기를 조정할 때 여유 용량을 허용해야 하며, 이는 작은 캐시 파일 시스템에서 낭비가 될 수 있습니다.
또한 메모리가 제한되면 tmpfs
파일시스템은 디스크로 변경됩니다. tmpfs
캐시가 사용하는 메모리는 페이지 캐시가 더 큰 온디스크 캐시를 위해 효과적으로 사용할 수 있습니다.
4. 읽기 작업 최적화
콘텐츠가 메인 메모리에서 사용 가능한 경우(최근에 사용되었거나 NGINX Cache 에 기록되었기 때문에) Cache 읽기 작업이 즉시 이루어지며, 콘텐츠가 메인 메모리에 없는 경우 일반적으로 스토리지에서 콘텐츠를 검색하는 동안 NGINX가 차단됩니다. thread pool
기능은 차단 읽기 작업을 aio Thread로 넘겨서 메인 NGINX Worker Thread가 차단되지 않도록 함으로써 이 문제를 완화합니다.
5. NGINX Cache 쓰기 작업에 대한 디스크 구성의 효율성 테스트
이 글의 나머지 부분에서는 NGINX Cache 쓰기 작업이 주를 이루는 다른 종류의 Workload를 살펴보고 디스크 선택이 NGINX Cache 처리량에 어떤 영향을 미치는지 살펴보겠습니다. 디스크 속도는 NGINX Cache 에 새 콘텐츠를 추가할 수 있는 속도만 제한하는 것이 아니라 페이지 NGINX Cache 가 새 콘텐츠를 디스크에 충분히 빠르게 Commit할 수 없을 때 NGINX Worker Thread가 차단되어 전체 처리량을 제한할 수 있습니다.
5-1. 테스트 프레임워크
첫 번째 테스트 시스템은 로컬 SSD 스토리지와 두 개의 대형 마그네틱 EBS 블록 디바이스가 있는 단일 Amazon EC2 t2.small
인스턴스였습니다.

두 번째 테스트 시스템은 로컬 하드 드라이브가 장착된 더 빠른 Bare‑Metal 서버였습니다.
부하를 생성하기 위해 wrk를 사용했습니다. wrk 클라이언트는 request.lua
라는 다음 스크립트를 실행하여 임의의 이름의 파일에 대한 요청을 생성하고 이를 모든 NGINX Worker에 배포하도록 wrk를 구성합니다.
-- Generate requests like GET /753327908949534239.txt
-- See http://stackoverflow.com/a/24097793
math.randomseed( tonumber( tostring( {} ):sub(8) ) + os.time() )
request = function()
r1 = math.random( 100000000, 999999999 )
r2 = math.random( 100000000, 999999999 )
path = "/" .. r1 .. r2 .. ".txt"
return wrk.format(nil, path)
end
콘텐츠 생성기에서는 모든 요청에 대해 동일한 1MB 파일을 반환하도록 NGINX를 구성했습니다:
location / {
try_files /1mb.txt =404;
expires max;
}
5-2. 기준 처리량 설정
먼저 캐싱 없이 wrk 테스트를 실행하여 시스템의 최대 용량을 확인했습니다.
EC2 인스턴스의 총 처리량(클라이언트, Proxy, 콘텐츠 생성기 합산)은 AWS 서버에서 초당 약 486MB(3.9Gbps)였습니다.
ubuntu$ ./wrk -c 10 -t 10 -d 60 -s request.lua http://localhost/
Running 1m test @ http://localhost/
10 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 20.54ms 1.81ms 58.47ms 93.50%
Req/Sec 48.68 3.96 80.00 83.30%
29218 requests in 1.00m, 28.54GB read
Requests/sec: 486.35
Transfer/sec: 486.47MB
Bare‑Metal는 유사한 테스트에서 초당 약 6.3GB(50Gbps)의 속도를 달성했습니다.
5-3. 캐시 쓰기 성능 측정
그런 다음 네 가지 다른 구성의 디스크 볼륨 테스트를 진행했습니다.
- Single Disk – 캐시 쓰기는 단일 디스크로 전달됩니다.
- Mirror – 캐시 쓰기는 중복성을 위해 두 개의 디스크에 미러링됩니다.
- Stripe – 캐시 쓰기는 성능을 위해 두 개의 물리적 디스크에 걸쳐 Stripe됩니다.
- Hash – 캐시 쓰기는 디스크당 하나씩 두 개의 독립적인 캐시로 분할됩니다.
Mirror 및 Stripe 전략을 구현하기 위해 Linux LVS를 사용하여 두 디스크의 파티션을 Mirror 또는 Stripe 레이아웃으로 배열했습니다.
Hash 전략을 구현하기 위해 NGINX 구성에 split_clients
지시문을 포함했습니다. 이 구성은 캐시된 콘텐츠를 각 물리적 디스크에 하나씩 proxy_cache_path 지시문에 의해 생성된 두 개의 캐시 간에 균등하게 분할합니다.
proxy_cache_path /mnt/disk1/cache keys_zone=disk1:100m levels=1:2 inactive=600s
max_size=5G use_temp_path=off;
proxy_cache_path /mnt/disk2/cache keys_zone=disk2:100m levels=1:2 inactive=600s
max_size=5G use_temp_path=off;
split_clients $request_uri $cache {
50% disk1;
* disk2;
}
upstream upstreams {
upstream-server-1;
upstream-server-2;
# additional upstream servers
}
server {
listen localhost:80;
proxy_cache $cache;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://upstreams;
}
status_zone loadbalancer;
}
느리고 예측할 수 없는 디스크 속도가 예상되는 Amazon EC2 서버와 더 빠른 Bare‑Metal 서버 모두에서 네 가지 전략을 테스트했습니다.
모든 테스트에서 디스크 I/O가 제한 요소였으며, iostat는 개별 물리적 및 가상 디스크의 부하를 샘플링 편향 내에서 보여주었습니다. 예를 들어, 이 출력은 Stripe 전략을 테스트할 때의 활동을 보여주며, 가상 dm-0 디바이스가 물리적 xvdb 및 xvdc 디바이스로 지원되는 것으로 표시됩니다.
ubuntu$ iostat 3
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 0.55 98.89 0.55 0.00
Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
xvda 0.00 0.00 0.00 0 0
xvdb 267.87 141.83 32803.32 512 118420
xvdc 252.35 0.00 32131.72 0 115995
dm-0 1178.67 0.00 59157.89 0 213560
dm-1 0.28 0.00 0.97 0 3
dm-2 2.22 141.83 0.00 512 0
dm-3 2.22 0.00 141.83 0 512
dm-4 0.00 0.00 0.00 0 0
각 전략에 대해 많은 수의 테스트 실행(20회 이상)을 평균하여 다음과 같은 결과를 얻었습니다. EC2 테스트 결과는 큰 변동성을 보인 반면, Bare‑Metal 테스트는 시스템이 정상 상태에 도달한 후 훨씬 더 일관된 결과를 보였습니다.
Cache Strategy | Throughput on EC2 (MB/s) | Throughput on Bare Metal (MB/s) |
---|---|---|
Single Disk | 39.8 | 95.0 |
Mirror | 31.5 | 81.0 |
Stripe | 51.1 | 192.5 |
Hash | 56.0 | 215.3 |
5-4. 예측할 수 없는 요인 완화하기
디스크 I/O는 시스템 성능에 많은 예측 불가능성을 더하기 때문에 각 테스트 실행 전에 NGINX Cache 를 초기화하여 비우는 데 주의를 기울였습니다.
NGINX cache_manager 프로세스는 정기적으로 실행되며 백그라운드에서 NGINX Cache 를 잘라내는 작업을 담당합니다. NGINX Cache 가 너무 빨리 채워지고 있었기 때문에 cache_manager는 NGINX Cache 를 제한 범위 내에 유지하고 성능에 측정 가능한 영향을 미치기 위해 공격적으로 NGINX Cache 를 잘라야 했습니다.
테스트 중에 NGINX Cache 가 완전히 채워지지 않았는지 확인하기 위해 NGINX Plus의 Live Activity 모니터링 대시보드(NGINX Plus API 모듈로 지원)를 사용하여 NGINX Cache 사용량을 실시간으로 모니터링했습니다.

대시보드에 보고된 적중률은 0%를 넘지 않아 로드 제너레이터가 반복되지 않는 임의의 URL을 생성하고 있음을 확인할 수 있었습니다.
6. NGINX Cache 분석
이 인위적인 벤치마크의 목표는 다양한 NGINX Cache 구성의 성능을 테스트하는 것입니다. 이 테스트는 첫 번째 요청 이후 다시 요청되지 않는 1MB 파일로 가능한 한 빨리 NGINX Cache 를 채우는 것입니다(재사용이 없으므로 매우 인위적입니다).
간단한 단일 디스크 전략은 더 복잡한 배열을 비교할 수 있는 기준이 됩니다.
당연히 Mirror 전략은 단일 디스크보다 약간 느렸습니다. 두 개의 물리적 디스크 간에 쓰기를 함께 수행할 수 있지만 Mirror는 약간의 오버헤드가 발생합니다. 그럼에도 불구하고 NGINX Cache 가 단일 디스크의 장애에 취약하지 않도록 Mirror 배열을 사용하는 것이 좋습니다.
Stripe과 Hash는 모두 단일 디스크보다 빠릅니다. Hash의 결과는 예상보다 높았지만(싱글 디스크의 두 배 이상) 테스트가 일관적이고 쉽게 복제되었습니다. Hash가 Stripe보다 약간 더 빠른 것으로 나타났습니다.
자체 환경에서 NGINX Plus 로 캐싱 전략을 시험해보고 싶으시면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.
댓글을 달려면 로그인해야 합니다.