
안녕하세요! NewCodes 개발자입니다.
NewCodes는 기술 블로그 큐레이팅 서비스입니다.
각 기업의 최신 기술 블로그를 모아서 한 번에 보여주고 있어요!
올해 5월부터 저 혼자 군대에서 만들기 시작했어요!
현재에도 서버를 안정적으로 운영하려 노력하고 있습니다 😊
저번 글에서는 CDN을 통해 조회 성능을 최적화한 걸 보여드렸는데요.
이번에는 NginX 도입을 통해 얻은 효과를 보여드리려 해요.
또한, NginX가 어떤 원리로 동작하고 어떻게 점유율 1등을 차지했는지 알아보려 해요.
바로 알아보시죠!
2025년 9월 기준 netcraft에 따른 Web Server 점유율 통계를 살펴봅시다.

NginX의 웹 서버 시장 점유율은 약 25%입니다. 가장 높은 점유율을 차지하고 있습니다. 정말 많은 웹 사이트에서 NginX를 사용하고 있는 이유는 무엇일까요?
NginX의 이름에서 보이듯이 폭발적이고 빠른 Engine의 성능을 보여줄 것 같습니다. 성능이 얼마나 빠른지부터 살펴보시죠.
이는 NewCodes 프로젝트에서 직접 측정한 결과입니다.
NginX를 도입하기 이전의 성능을 살펴봅시다. NewCodes의 메인 게시글을 기준으로 조회했을 때의 결과입니다.

Waiting for server response가 322.74ms 나왔습니다.
참고로 Waiting for server response는 TTFB(Time to First Byte)와도 같습니다.
클라이언트가 서버에 요청을 보내고 나서부터 서버로부터 첫 바이트를 응답 받기까지의 시간입니다.
WAS에서 Local Cache를 도입했을 때는 이 정도 성능입니다.

TTFB가 72.86ms 나왔습니다.
아래는 NewCodes의 아키텍처입니다.

위와 같이 NginX를 도입하고 나서의 성능을 살펴봅시다.

TTFB가 14.89ms 나왔습니다.
322.74ms -> 14.89ms로 줄어들면서 TTFB가 약 95% 감소했습니다!!
로컬 캐시를 통해 조회한 것과도 비교해봅시다!
72.86ms -> 14.89ms로 줄어들면서 TTFB가 약 80% 감소했습니다!!
이는 비약적인 성능 향상이며, 더욱 매끄러운 사용자 경험을 가져올 겁니다.
참고로 위 측정 결과는 여러 번 측정하면서 중앙값과 유사한 값을 캡처본으로 가져온 것입니다.
이런 성능 향상을 가져다줄 수 있는 건, NginX에서 콘텐츠를 캐싱하기 때문입니다.
Web Server는 WAS에서 제공하는 여러 콘텐츠를 캐시할 수 있습니다. 이를 바탕으로 WAS의 트래픽 부담을 줄여주고, 사용자에게 더욱 빠르게 콘텐츠를 제공합니다.
NginX에서는 각 endpoint에 따라 미리 캐시 설정을 할 수 있습니다.
NewCodes의 캐시 설정 일부를 보여드리겠습니다.
# api로 시작하는 엔드포인트는 캐시하지 않음
location ^~ /api/ {
proxy_pass http://backend;
# 캐시 설정
proxy_no_cache 1;
proxy_cache_bypass 1;
...
}
# 위에서 명시한 엔드포인트 제외한 모든 경로
location / {
proxy_pass http://backend;
# 캐시 설정
proxy_cache newcodes_cache; # newcodes_cache 라는 캐시 저장소 사용
proxy_cache_valid 200 10m; # 캐시 시간은 10분으로 설정
proxy_cache_valid 404 1m; # 404 응답일 시 1분 캐시
proxy_cache_valid 500 502 503 504 1m; # 5xx 응답일 시 1분 캐시
# Cache-Control: no-cache일 시 캐시된 걸로 응답하지 않고 Origin에게 요청
proxy_cache_bypass $http_cache_control;
...
}
캐시할 endpoint와 하지 않을 endpoint를 구분할 수 있습니다.
그리고 상태 코드에 따라서 캐시 TTL을 다르게 걸 수 있습니다.
그런데 위 설정대로 하면 사용자는 Cache Miss를 경험할 수 있습니다.
/ 요청이 한 번 들어와서 상태 코드 200으로 10분간 캐시가 걸어졌다고 생각해봅시다.
10분 후 캐시가 만료되면, 이후 요청하는 사용자는 Cache Miss를 경험하게 됩니다.
그러면 WAS에 다시 요청을 하여 200 코드를 얻고 NginX에서 10분간 캐시를 다시 걸 겁니다.
즉, 캐시가 만료되고 나서 첫 번째로 요청하는 사용자는 Cache Miss를 필연적으로 경험하게 됩니다.
단 한 명의 사용자라도 Cache Miss로 인해 웹 페이지가 늦게 로딩되는 경험을 방지하고자 합니다. 이를 위해 아래 설정을 추가로 해주었습니다.
# 캐시된 항목이 만료되었을 때 백그라운드에서 갱신할 수 있게 하도록 하는 설정
proxy_cache_background_update on;
# 캐시 갱신 중일 때 사용자에게 stale(만료된) 캐시 제공
proxy_cache_use_stale updating;
캐시가 만료되면 첫 요청자에게는 stale(오래된) 캐시를 즉시 반환합니다. 백그라운드에서는 WAS에서 새 콘텐츠를 비동기로 가져와 캐시를 갱신합니다.
이렇게 해서 Cache Miss를 방지할 수 있습니다!
그런데 여기서 의문이 들 수 있습니다.
이러한 캐싱은 어떤 Web Server든지 간에 할 수 있는 거 아냐?
굳이 NginX를 써야해?
NginX의 진짜 장점은 트래픽이 많은 상황에서 발휘됩니다.
NginX는 많은 트래픽을 적은 자원으로 빠르게 처리할 수 있습니다. 그 배경에는 Event-Driven Artchitecture가 존재합니다.

NginX에는 master process와 worker process가 있습니다. 우선 각각의 프로세스가 어떤 역할을 하는지 알아야 합니다.
master process는 환경 설정, 포트 바인딩 등과 같은 권한이 필요한 일을 수행합니다. 우두머리와 같은 역할을 하며, 유저와의 커넥션을 직접 처리하진 않습니다.
worker process는 여러 커넥션을 직접 처리하여 사용자에게 응답을 제공합니다. 이는 보통 CPU core 개수만큼 만들어집니다. NginX의 높은 성능은 바로 이 worker process 덕분입니다. 이에 대해 더 자세히 알아봅시다.
worker process는 여러 커넥션을 논블로킹 방식으로 연결하여 Context Switching 횟수를 줄입니다.
Web Server에서 수많은 I/O 요청을 높은 성능으로 처리하기 위해서는 I/O로 인한 Context Swiching 비용을 줄이는 게 관건입니다. I/O에서 Context Switching은 비용이 높은 작업이기 때문입니다.
Context Switching 관련하여 I/O 최적화가 생소하시다면 아래 글을 참고해주세요!
Java로 알아보는 TCP Socket Programming
worker process는 어떻게 Context Switching 횟수를 줄일까요?

하나의 worker process는 여러 개의 커넥션을 담당합니다. 하나의 worker process가 여러 커넥션을 담당할 수 있는 이유는 Event-Driven 방식으로 동작하기 때문입니다.
worker process는 여러 listen socket, connection socket을 통해 새로운 event를 기다립니다. 그리고 이벤트가 발생하면 각 소켓에 맞는 동작을 합니다.
worker process는 하나의 socket만 담당하는 게 아니라 여러 개의 socket을 담당하며 논블로킹 방식으로 동작합니다. 이를 통해 하나의 worker process는 수천 개의 커넥션까지 동시에 처리할 수 있습니다.
논블로킹/블로킹이 익숙하지 않다면 아래 글을 참고해주세요!
동기 비동기 블로킹 논블로킹, 이 글 하나로 끝내자!
NewCodes에서는 Docker를 통해 NginX를 띄우고 있습니다.
docker stats 명령어를 통해 NginX가 사용중인 메모리를 살펴본 결과!
'21MB'가 나왔습니다.
NginX에서 connection 하나 당 25KB 정도의 메모리가 필요하다고 합니다. 50,000개의 동시 연결이 있다고 해도 약 1.2GB의 메모리가 필요합니다.
NginX의 장점은 적은 리소스로 수많은 요청을 동시에 처리할 수 있다는 점입니다.
NginX는 단순히 정적 콘텐츠를 캐시해서 빠르게 보여주는 역할만 하는 게 아닙니다.
Web Server는 정적 콘텐츠를 제공하는 것뿐만 아니라, reverse proxy로써의 역할도 합니다.

reverse proxy란 Origin Server 대신 요청을 받아 처리하는 역할을 합니다. Origin Server를 거쳐야 하는 요청이라면 해당 서버에게 전달하고 응답을 생성합니다.
Origin Server에서 바로 요청을 처리하면 되지, 굳이 앞단에서 대신 요청을 받아주는 이유는 무엇일까요?
Reverse Proxy는 어떤 이점이 있는 걸까요?
NewCodes에서 NginX를 활용하는 방식을 통해 이 이유를 설명하려 합니다.
새롭게 배포할 코드가 있어 서버를 다시 껐다 켜야 한다면, 한 동안 서버는 멈춰있을 겁니다. 몇 초 혹은 몇 분 동안 사용자는 서비스를 정상적으로 이용할 수 없게 됩니다.
이러한 문제를 해결해주는 게 무중단 배포입니다. Reverse Proxy가 있으면 무중단 배포를 구현하기 용이해집니다.
무중단 배포에는 여러 방식이 있는데 그 중에서도 NewCodes에서 택한 방법은 블루-그린 무중단 배포 방법입니다.

현재 활성화되어 있는 서버를 블루라고 해봅시다. 이 때, 새로운 변경사항을 반영항 서버를 따로 시작합니다. 이를 그린이라고 합니다.
그린 서버가 준비 완료됐다면 Reverse Proxy에서 블루로 향하던 트래픽을 그린으로 변경합니다. 그러면 서버의 중단 시간을 최소화하여 새로운 변경사항을 배포할 수 있습니다.
NginX에서 아래처럼 설정했습니다.
# 업스트림 설정 (Blue-Green 배포용)
upstream backend {
# Active 서버 (기본은 blue)
server newcodes-backend-blue:8080 max_fails=3 fail_timeout=30s;
# green은 처음에는 주석처리, 향후 재배포 시 활용
# server newcodes-backend-green:8080 max_fails=3 fail_timeout=30s;
}
upstream이란 클라이언트의 요청을 실제로 처리할 서버들의 그룹입니다. 여기에 여러 서버를 명시하면 알아서 load balancing까지 수행합니다.
green으로 트래픽을 전환하고자 할 때는 blue를 주석처리하고, green을 활성화하면 됩니다. 직접 만든 deploy 스크립트를 통해 자동으로 해당 주석을 풀고 걸도록 했습니다.
악의적인 사용자가 서버에 굉장히 많은 요청을 짧은 시간 내에 보낼 때, Reverse Proxy에서는 방어할 수 있는 방법이 있습니다. 바로 rate limiting입니다.
rate limiting이란 클라이언트가 일정 시간 동안 보낼 수 있는 요청 횟수를 제한하는 기능입니다.
NewCodes를 운영하며 생긴 일이 있었습니다. 중국 IP에서 한 사용자로부터 SQL Injection 공격을 받았었습니다. 어느 날 로그를 살펴보던 중 이상한 접속 로그가 있더군요.

다행히도 NewCodes는 뚫리진 않았습니다.
그런데 뚫릴 여지는 있었습니다. NewCodes에서는 관리자 로그인 및 관리자 기능을 제공하고 있습니다.
관리자 로그인 url은 따로 버튼이나 manifest.json 등에 남기진 않았습니다. 그래서인지 로그인에 대한 공격은 없더군요. 만약, 로그인 url을 알아내서 브루트포스 공격을 했다면 당했을지도 모릅니다.
이를 방지하고자 NginX에 rate limiting을 걸어뒀습니다.
# Rate limiting 설정
limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;
location ^~ /api/ {
# Rate limiting
limit_req zone=api burst=10 nodelay;
limit_req_status 444;
...
}
위와 같이 /api 요청에 대해서는 1분 당 60개의 요청만 허용 가능하도록 설정했습니다. 1초당 api를 한 번씩 보내는 꼴이라면 비정상적인 요청이라 판단하고 444(NginX No Response)로 응답을 끊기로 했습니다.
/api가 아닌 다른 endpoint에 대해서도 알맞게 rate limiting을 걸어줬습니다. 로그인 api에 대해서는 더욱 낮은 rate를 걸어뒀습니다.
NginX가 1등인 이유는 가볍고 빠르며, Reverse Proxy로써 여러 기능을 제공하기 때문입니다.
여기까지 해서 NewCodes에서 도입했던 NginX에 대한 이야기를 마무리 하려 합니다!
기술 블로그 큐레이팅 서비스 NewCodes 많이 방문해주세요!!

북마크 하시고 시간 날 때 한 번씩 들어와서 글 읽어보시는 거 추천드려요 ㅎㅎ
피드백도 언제든지 환영입니다!! 제일 하단에 피드백 버튼이 있어요!
읽어주셔서 감사합니다!
다음 글은 로컬 캐시 도입에 대한 내용으로 찾아올게요!
오 stale 캐시 설정은 새롭게 알아가네요. 포스팅 잘 읽었습니다 ㅎㅎ 감사합니다