API 응답 속도 (서버 점검, DB 최적화, 캐싱 전략)

API 응답 속도 저하는 사용자 경험을 직접적으로 악화시키는 핵심 문제 중 하나입니다. 응답 시간이 길어지면 사용자는 서비스가 느리다고 인식하게 되며, 이는 이탈률 증가와 서비스 신뢰도 하락으로 이어질 수 있습니다.

API 응답이 느려졌을 때, 그냥 서버를 재시작하면 해결될 거라고 생각합니다. 그러나 재시작 후 10분도 안 돼서 똑같이 느려졌고, 원인을 찾는 데 반나절이 걸렸습니다. API 응답 속도 문제는 하나의 원인이 아니라 서버, DB, 네트워크가 뒤엉켜서 발생하는 경우가 대부분입니다. 이 글은 그 삽질을 줄이기 위한 점검 순서를 정리한 것입니다.

서버 점검, 어디서부터 봐야 할까요

API가 느려졌다는 신고를 받으면 가장 먼저 뭘 확인하시나요? 초반에 무조건 로그부터 뒤졌는데, 사실 그보다 먼저 봐야 할 게 있습니다. 바로 서버의 CPU 사용률과 메모리 점유율입니다.

CPU 사용률이 80% 이상을 지속적으로 유지하고 있다면, 요청 하나하나를 처리하는 데 이미 자원이 부족한 상태입니다. 메모리도 마찬가지입니다. 가용 메모리가 거의 없으면 운영체제가 디스크 스왑(swap, 부족한 메모리를 디스크로 대신 사용하는 방식)을 시작하는데, 이 순간부터 응답 속도는 눈에 띄게 떨어집니다. 스왑이 발생하는 서버에서는 평균 응답 시간이 평소의 3배 이상 늘어났습니다.

그 다음은 스레드 풀(Thread Pool) 상태입니다. 스레드 풀이란 서버가 동시에 처리할 수 있는 요청 작업자의 수를 미리 정해둔 것인데, 들어오는 요청 수가 이 한도를 넘으면 나머지 요청은 줄을 서서 기다리게 됩니다. 이 대기 시간이 응답 지연으로 직결됩니다. 이런 구조에서는 스레드 수를 늘리거나, 비동기 처리 방식으로 전환하는 것이 현실적인 해결책입니다.

애플리케이션 내부 로직도 빠뜨리면 안 됩니다. 특히 반복문 안에서 외부 API를 호출하거나 DB 쿼리를 실행하는 구조가 있다면, 요청 1건에 수십 번의 외부 호출이 발생할 수 있습니다. 이건 코드 리뷰에서도 쉽게 놓치는 부분이라 따로 프로파일링 툴로 잡아내는 게 낫습니다.

DB 최적화, 느린 쿼리 하나가 전체를 망친다

서버 자원이 멀쩡한데도 API가 느리다면, 거의 DB 문제일 가능성이 높습니다. "쿼리야 뭐 별거 있겠어"라고 생각했다가 느린 쿼리(Slow Query) 로그를 켜고 나서야 현실을 직면했습니다. 단 하나의 쿼리가 1초 넘게 걸리고 있었고, 그게 메인 API에서 매 요청마다 실행되고 있었습니다.

느린 쿼리의 원인은 대부분 인덱스 미설정입니다. 인덱스(Index)란 DB가 데이터를 빠르게 찾을 수 있도록 만들어둔 색인인데, 이게 없으면 테이블 전체를 처음부터 끝까지 다 뒤지는 풀 스캔(Full Scan)이 발생합니다. 데이터가 수십만 건만 넘어도 체감 속도 차이가 극명합니다. 출처: MySQL 공식 문서 - EXPLAIN을 활용하면 쿼리 실행 계획을 직접 확인할 수 있어서, 어느 테이블에서 풀 스캔이 발생하는지 바로 잡아낼 수 있습니다.

커넥션 풀(Connection Pool) 상태도 반드시 봐야 합니다. 커넥션 풀이란 DB 연결을 미리 만들어두고 재사용하는 방식인데, 이 풀이 꽉 차 있으면 새 요청은 빈 커넥션이 생길 때까지 기다려야 합니다. 반대로 커넥션을 너무 많이 열어두면 DB 서버 자체에 부하가 갑니다. 적정선을 잡는 게 중요한데, 보통 현재 최대 동시 요청 수를 기준으로 1.2~1.5배 정도를 권장합니다.

트랜잭션도 점검 대상입니다. 장시간 열려 있는 트랜잭션은 해당 데이터에 락(Lock, 다른 쿼리가 접근하지 못하도록 잠그는 것)을 걸어버립니다. 이 락이 걸린 데이터를 다른 쿼리가 건드리려 하면, 그 쿼리 전체가 락이 풀릴 때까지 멈춥니다. 특정 시간대에만 API가 느려지는 이상한 현상이었는데, 원인이 배치 작업에서 트랜잭션을 너무 길게 잡고 있었던 것이었습니다.

네트워크와 외부 의존성, 의외로 놓치기 쉬운 부분

서버도 멀쩡하고 DB도 괜찮은데 여전히 느리다면, 혹시 외부 API를 호출하고 있지는 않으신가요? 이게 생각보다 자주 범인입니다. 내 서버는 빠른데 외부 서비스가 느리면 결국 전체 응답이 느려집니다. 그리고 이건 내가 제어할 수 없다는 게 더 문제입니다.

이런 상황에서 가장 효과적인 방법은 외부 호출을 비동기로 처리하는 것입니다. 사용자한테 먼저 응답을 돌려주고, 외부 호출 결과는 나중에 처리하는 방식입니다. 물론 결과가 즉시 필요한 경우엔 쓸 수 없지만, 그렇지 않은 경우라면 사용자 체감 속도가 확연히 달라집니다.

네트워크 구간 자체의 지연도 있습니다. DNS 조회 시간, TLS 핸드셰이크(클라이언트와 서버가 암호화 통신을 시작하기 전에 서로 인증하고 키를 교환하는 과정) 등이 조금씩 시간을 잡아먹습니다. 이런 오버헤드를 줄이려면 CDN(콘텐츠 전송 네트워크, 사용자와 가까운 서버에서 데이터를 제공하는 방식)을 활용하거나 커넥션 재사용 설정을 확인해볼 필요가 있습니다. 출처: Cloudflare - Why is my website slow에서도 네트워크 레이어별 지연 원인을 잘 정리해두고 있습니다.

서버 코드를 만지다 보면 네트워크는 당연히 빠를 거라고 가정하게 되는데, 실제로 측정해보면 예상보다 큰 비중을 차지할 때가 많습니다.

캐싱 전략, 구조를 바꾸는 것이 근본적인 해결

지금까지 말한 점검들이 "무엇이 문제인가"를 찾는 과정이라면, 이제부터는 "어떻게 구조 자체를 바꿀 것인가"의 이야기입니다. 근본적인 개선 없이는 같은 문제가 반복될 수밖에 없습니다. 그중에서도 가장 먼저 도입해볼 만한 것이 캐싱 전략입니다.

Redis와 같은 인메모리 캐시(메모리에 데이터를 저장해두어 DB까지 가지 않고 바로 꺼내 쓰는 방식)를 적용하면, 자주 조회되는 데이터에 대한 응답 시간이 수십 밀리초에서 1밀리초 이하로 줄어드는 경우도 있습니다. 제가 직접 적용해봤을 때 가장 체감이 컸던 부분이 여기였습니다. 코드 변경도 크지 않은데 효과는 상당히 드라마틱했습니다.

그 외에도 구조적으로 확인해야 할 체크리스트를 정리하였습니다.

  1. 응답 데이터에 불필요한 필드가 포함되어 있지는 않은지 점검한다 (페이로드 최소화)
  2. 로드 밸런서(트래픽을 여러 서버에 나눠주는 장치)가 적용되어 있는지, 특정 서버에 편중이 없는지 확인한다
  3. 비동기 처리가 가능한 작업은 메시지 큐(Message Queue) 방식으로 분리했는지 점검한다
  4. 실제 트래픽과 유사한 조건으로 부하 테스트(Load Test)를 정기적으로 수행하고 있는지 확인한다

일반적으로 이 모든 걸 한 번에 적용하면 좋다고 알려져 있지만, 우선순위를 두는 것이 맞다고 봅니다. 느린 쿼리 개선과 캐싱 적용만으로도 응답 속도의 80%는 해결되는 경우가 많았습니다. 로드 밸런싱이나 메시지 큐는 그다음 단계로 넘어가도 늦지 않습니다.

API 응답 속도 문제는 어느 한 곳만 들여다봐서는 절대 답이 안 나옵니다. 서버, DB, 네트워크, 코드 구조까지 순서대로 좁혀가는 것이 결국 가장 빠른 길입니다. 처음부터 완벽하게 잡으려 하기보다는, 오늘 소개한 순서대로 하나씩 측정하고 기록하는 것부터 시작해보시길 권합니다. 측정 없이 감으로 고치는 건 제가 해봐서 압니다. 시간만 씁니다.


관련 글

댓글

이 블로그의 인기 게시물

HTTP 메서드의 필요성 (GET과 POST, PUT과 DELETE, API 보안)

API 없는 세상의 불편함 (로그인 연동, 서비스 구조, 디지털 인프라)

API 이해하기 (서비스 연결, 시스템 협력, 디지털 구조)