느려진 서비스를 튜닝하는 방법: 서버 성능 튜닝의 기초

서비스가 느려졌다면 어디서부터 점검해야 할까?
사실 처음에는 당황하기 마련이다. 어디가 느려진거지?!
시스템을 구성하는 여러 요소 중 어디가 병목인지 파악하고, 적절한 개선책을 적용하는 것이 핵심이다.
병목지점부터 찾아라
어떤 개선도 "어디가 느린지"를 모르면 의미가 없다!
- APM (Application Performance Monitoring): New Relic, Datadog, Pinpoint 등
- 시스템 모니터링: top, htop, iostat, vmstat
- 로그 기반 분석: ELK, Grafana Loki
수직 확장 vs 수평 확장
성능 개선의 큰 방향은 두 가지가 존재한다.

수직 확장 (Scale-Up)
CPU, 메모리, 디스크 등 자원 증설
장점: 빠르게 적용 가능
단점: 한계가 존재, 비용 증가

수평 확장 (Scale-Out)
서버를 여러 대로 나눔
장점: 확장성 높음, 장애 격리 용이
단점: 인프라 복잡도 증가 (Load Balancer, 세션 처리 등)
로드 밸런서: 트래픽 분산의 핵심

Round Robin, IP Hash 등 다양한 분산 전략이 존재
Health Check: 죽은 서버로의 트래픽 전달 방지
L7(Layer 7) 방식일 경우 URL, 헤더 기반 라우팅 가능
그런데 서버 증설만 할 수 있을까?
현실적으로 서버 증설은 많이 힘든 부분이다. 수직확장은 한계가 보이고, 수평확장은 클라우드 서비스가 아닌 이상
당장 대처하기가 힘들 뿐더러 백엔드 개발자라면 비용 예산도 생각해야된다. 소스적으로 수정할 방향이 있는지 확인해보자.
DB 커넥션 풀: 연결의 재사용
DB는 커넥션 생성 비용이 크므로 커넥션 풀로 재사용
✅주요 설정 항목
|
⚠️ 풀 크기를 너무 크게 잡으면 오히려 DB 부하가 폭증할 수 있으며, 서버의 상태를 잘 보면서 진행해야된다.
(서버 CPU 사용량이 80% 인데 크게 잡아버리면...)
캐시 전략: 속도의 열쇠
서버 캐시 (메모리 기반)
캐시의 종류는 주로 아래와 같다

로컬 캐시
로컬 캐시는 서버 프로세스와 동일한 메모리를 캐시 저장소로 사용한다.
장점은 속도, 캐시 데이터에 빠르게 접근할 수 있으며 구조를 단순하게 유지할 수 있다.
하지만 데이터 크기에 제한이 존재하며 서버 프로세스를 재시작 시 메모리에 존재하던 캐시 데이터가 모두 삭제되어 적중률이 순간적으로 떨어지는 단점이 존재한다.
리모트(글로벌) 캐시
리모트 캐시는 별도 프로세스를 캐시 저장소로 사용한다.
Redis, Memcached와 같은 걸 의미하며 로컬 캐시와는 다르게 캐시 크기를 유연하게 확장할 수 있다.
서버 프로세스가 재시작되더라도 저장된 캐시 데이터는 그대로 유지되며 적중률을 높일 수 있다.
하지만 속도가 네트워크 통신을 하기 때문에 로컬 캐시보다 느릴 수밖에 없다.
| 캐시에 보관할 데이터 규모가 작고 변경 빈도가 매우 낮다면 로컬 캐시로 충분하지만 데이터 규모가 크다면 리모트 캐시를 사용해야된다. |
캐시가 얼마나 효율적으로 사용되는 지는 적중률(Hit Rate) 높이면 서비스 속도 체감이 빨라진다.
| 적중률(Hit Rate) = 캐시에 존재한 건수/캐시에서 조회를 시도한 건수 |
적중률을 높이는 가장 간단한 방법은 캐시에 최대한 많은 데이터를 저장하는 거지만
캐시는 메모리 자원을 사용하기 때문에 모든 데이터를 무작정 저장할 수는 없다.
캐시에 보관하는 데이터는 제한이 있으므로 제거하는 삭제 정책 을 잘 사용하면 메모리를 효율적으로 관리할 수 있다.
| 삭제 정책 LRU(Least Recently Used) : 가장 오래전에 사용된 데이터를 제거한다. LFU (Least Recently Used) : 가장 적게 사용된 데이터를 유지한다. FIFO(First in First Out) : 먼저 추가된 데이터를 먼저 삭제한다. 캐시에는 유효시간(TTL, 만료 시간)을 설정하는 방식도 함께 사용 |
캐시 사전 적재 (Pre-warming)
트래픽이 순간적으로 급증하는 패턴을 보인다면 캐시에 데이터를 미리 저장하는 것도 고려할 수 있다.
// 스프링 부트에서는 놀랍게도 제공해주는 어노테이션이 존재한다.
@Cacheable
캐시 무효화 (Invalidation)
캐시를 사용할 때 반드시 신경 써야 할 점은 유효하지 않은 데이터를 적절한 시점에 캐시에서 삭제하는 것이다.
DB 데이터 변경 시 캐시도 즉시 캐시를 무효화해야되며 변경에 민감한 데이터는 로컬 캐시가 아닌 리모트 캐시에 보관해야된다.
변경에 민감하지 않고 데이터 크기가 작다면 캐시의 유효 시간을 설정하여 주기적으로 갱신하는 방식을 사용해도 된다.
메모리 관리와 GC 튜닝
JAVA 의 경우 GC 를 사용하는데 Full GC 발생 시 전체 애플리케이션 실행이 일시 중단하게 된다. (Stop-The-World)
| GC(가비지 컬렉터, Garbage Collector) 사용이 끝난 객체를 힙 메모리에서 바로 삭제하지 않고 정해진 규칙에 따라 사용하지 않는 메모리를 찾아서 반환 |
✅ 실제 메모리 사용 패턴에 맞게 최대 힙 크기를 조정
사용하는 메모리양과 객체 수가 많을수록 GC 실행 시간이 길어지며, 반대로 메모리 사용을 줄이면 GC 시간도 줄어들 가능성이 높아진다.
✅대량의 객체를 생성 시 주의
메모리가 한정되어있는 상태에서 많은 양의 메모리를 사용하게 된다면 메모리 부족상태가 발생할 수 있다.
대량의 객체가 생성되는 것을 방지하려면 조회 범위를 제한하는 것이 좋다.
응답 데이터 압축
GZIP, Brotli로 HTTP 응답 압축 (gzip 을 많이 사용하는 것 같다.)
| gzip 으로 압축하면 70% 이상 크기를 줄일 수 있으며 데이터 전송 크기가 줄어든 만큼 전송 시간도 빨라진다. 응답 시간이 짧아진다. 아파치나 Nginx와 같은 웹 서버는 압축 기능을 제공하고 있으므로 약간의 설정만 추가하면 즉시 효과를 볼 수 있다. |
- 텍스트 기반 (JSON, HTML, CSS 등) 리소스에 효과적
- 단, CPU 사용량 증가 주의
- 방화벽 설정 확인
정적 자원 최적화
이미지가 많은 온라인 쇼핑몰 사이트의 첫 페이지는 정적 자원이 전체 데이터의 80%를 차지하기도 한다.
매번 이미지나 JS 파일을 다운로드 하면 서버 입장에서 좋을게 없기 때문에 브라우저 캐시를 활용하면 좋다.
브라우저 캐시
- Cache-Control, ETag, Expires 헤더 설정
- JS, CSS, 이미지 등 캐싱으로 로딩 속도 향상
CDN 활용

- CDN(콘텐츠 전송 네트워크) , Amazon CloudFront, Akamai, CloudFlare 등
- 전 세계 엣지 서버에서 정적 자원 제공
- 트래픽 절감 + 응답 시간 단축
대기 처리
콘서트 예매처럼 사용자가 순간적으로 폭증할 때에는 어떻게 처리해야 될까?
서버만 증설한다고 끝나지 않고 DB 성능도 문제가 된다.
DB 증설을 하면 된다고 하지만 전체 서비스 시간의 1%도 되지 않는 시간을 위해 DB 증설을 하게 되는 건 비용이 커지게 된다.
보통 우리가 인터파크 티켓이나 자격증 시험 접수 시 아래와 같은 화면을 보았을 것이다.

트래픽이 순간적으로 증가할 때 동시에 수용할 사용자 수를 제한하고 나머지 사용자를 대기 처리하면 이점을 얻을 수 있다.
'Server' 카테고리의 다른 글
| [개발] 외부 연동이 문제일 때 살펴봐야 할 것들 (2) | 2025.06.04 |
|---|---|
| [개발] 성능을 좌우하는 DB 설계와 쿼리 (1) | 2025.05.26 |
| [개발] 느려진 서비스, 어디서부터 봐야될까? #1. 처리량과 응답시간 (2) | 2025.05.17 |
| [Linux] systemd 은 뭐하는 아이 일까? (1) | 2024.10.20 |
| [Linux] Docker 을 이용하여 CentOS 실습 환경 만들기 (3) | 2024.10.19 |