API 트래픽이 늘어나면 우리는 어떤 선택들을 하게 될까?
- 개발
- 2026. 2. 3. 23:33
특정 API의 개발과 배포가 완료되고, 시간이 어느 정도 지난 뒤 트래픽이 몰리는 상황은 생각보다 자주 발생합니다. 이런 상황이 오면 자연스럽게 여러 선택지를 떠올리게 됩니다. 특정 값을 캐싱할 수도 있고, 메시지 큐를 도입해 비동기 처리를 할 수도 있으며, 아예 비동기 API 구조로 전환하는 방법도 있습니다. 레이턴시를 줄이기 위한 방법은 분명 많습니다. 그리고 각각의 방법은 그럴듯해 보입니다. 하지만 여기서 한 번 더 생각해볼 필요가 있습니다. "과연 어떤 방법을 사용하는 것이 효율적인 선택일까?" 이 질문은 "캐시를 쓸까, MQ를 쓸까, 비동기로 바꿀까"를 묻는 질문처럼 보이지만, 사실은 그보다 앞선 문제를 묻고 있는 질문일지도 모릅니다.
사실 이전에 이거와 비슷하게 글을 작성한적이 있습니다.
특명: 지연시간을 줄여라!
개발을 완료한 이후에도, 사용자는 여전히 서비스가 느리다고 느낄 수 있습니다. 이는 절대적인 처리 속도의 문제가 아니라, 지연 시간이 불안정하게 발생하기 때문입니다. 사용자 경험은 단순
b-programmer.tistory.com
우연인지는 모르겠지만 이번에도 비슷한 주제네요.
Stateless Architecture
Stateless Architecture를 흔히 서버가 상태를 가지지 않는 구조라고 말합니다.
조금 더 정확하게 말하면, 상태가 있느냐 없느냐의 문제가 아니라 상태를 의존하고 있느냐의 문제라고 보는 게 맞습니다.
즉, 이런 질문을 던질 수 있습니다.
이 요청이 반드시 서버가 이전 상태를 알고 있어야만 처리되어야 할 이유가 있는가?
이 질문에 YES라면, 그 API는 이미 Stateless하다고 보기 어렵습니다.
이 개념을 설명할 때 보통 세션을 예시로 많이 듭니다. 로그인 방식에는 세션 방식과 토큰 방식이 존재합니다.
세션 방식의 경우, 로그인 이후의 상태를 서버가 직접 관리합니다. 어떤 사용자가 로그인되어 있는지에 대한 정보가 서버 쪽에 저장됩니다.
이 구조에서는 요청을 처리하기 위해 서버가 해당 상태를 알고 있어야 합니다. 즉, 요청이 서버의 상태에 의존하게 됩니다.
반면 토큰 방식에서는 서버가 로그인 이후의 상태를 유지하지 않습니다.
요청마다 토큰이 함께 전달되고, 서버는 그 토큰을 검증하는 역할만 수행합니다.
요청 하나만으로 처리에 필요한 정보가 모두 전달된다면, 이 구조는 Stateless하다고 볼 수 있습니다.
이것이 문제가 되어어지는 이유
API 트래픽이 늘어나면 우리는 서버를 늘리게 됩니다. 이것은 굉장히 자연스러운 선택입니다.
하지만 서버를 늘리는 순간, 다음과 같은 문제들이 바로 튀어나옵니다.
- 어떤 서버가 이 사용자의 상태를 가지고 있는가?
- 이전 요청 정보는 어디에 있는가?
- 이 요청을 다른 서버가 처리해도 되는가?
이 질문들에 매번 답을 해야 하는 구조라면, 서버를 늘리는 것만으로는 문제를 해결할 수 없습니다.
Stateless Architecture는 이 질문 자체를 구조적으로 없애기 위한 선택이라고 볼 수 있습니다.
Stateless가 의미하는 상태는 다음과 같습니다. 요청 하나만 봐도 처리할 수 있고,
이전 요청을 기억하지 않아도 되며, 어떤 서버가 요청을 받아도 항상 같은 결과를 반환합니다.
즉, 요청이 서버 인스턴스에 묶이지 않는 것, 이것이 Stateless Architecture의 핵심입니다.
그럼 어떻게 처리할 수 있을까요? 답은 간단합니다. 상태 관리를 서버에서 직접 관리하지 않고, 외부에서 관리하도록 만들면 됩니다.
예를 들면 DB 같은 저장소를 사용하는 방식이 있죠. 다만 여기서 중요한 점이 하나 있습니다.
이전 값을 사용해야 하는 경우에 그 값을 서버 메모리에 저장해두고 사용한다면,
그 구조를 Stateless하다고 말하기는 어렵습니다.
Load Balancing
Stateless 구조를 만들었다고 해서 문제가 모두 해결되는 것은 아닙니다.
여전히 API 요청은 어딘가로 들어와야 하고, 그 요청을 누군가는 처리해야 합니다.
트래픽이 늘어나는 상황에서 서버 하나로 모든 요청을 처리하는 데에는 명확한 한계가 있습니다.
그래서 우리는 서버를 여러 대로 늘리는 선택을 하게 됩니다.
하지만 여기서 한 가지 문제가 생깁니다.
서버를 늘렸다고 해서 요청이 자동으로 나뉘지는 않는다는 점입니다.
클라이언트의 요청은 여전히 하나의 진입 지점으로 들어오고,
그 요청을 어떤 서버가 처리할지는 어디선가 결정되어야 합니다. 이 역할을 담당하는 것이 로드 밸런서입니다.
로드 밸런서는 들어오는 요청을 여러 서버로 나누어 전달함으로써, 특정 서버에 부하가 몰리는 상황을 방지합니다.
결국, 부하를 분산하는 역할을 하게 되는 것이죠.
서버 입장에서는 요청이 분산되어 들어오기 때문에, 더 많은 트래픽을 안정적으로 처리할 수 있게 되고,
그 결과 API의 확장성도 자연스럽게 높아진다고 볼 수 있습니다.

Rate Limiting
로드 밸런서를 통해 부하를 나눴음에도 불구하고, 여전히 트래픽이 몰리는 경우가 있습니다.
이때는 들어오는 요청 자체를 어디까지 받아줄 것인지 제한할 필요가 생깁니다.
요청으로 들어오는 트래픽의 양을 의도적으로 제한하는 것, 이것이 바로 Rate Limiting입니다.
하지만 모든 트래픽을 무조건 제한해서는 안 됩니다.
만약 다수의 사용자가 동시에 로그인을 시도하고 있는 상황에서, 특정 구간에서 요청이 일괄적으로 제한된다면
그 이후에 들어오는 사용자들은 정상적인 로그인조차 할 수 없는 상황이 발생할 수 있습니다.
즉, Rate Limiting은 트래픽을 단순히 줄이는 것이 아니라,
어떤 트래픽을 제한하고 어떤 트래픽은 반드시 허용할 것인지를 구분하는 문제라고 볼 수 있습니다.
Rate Limiting 같은 경우에는 인프라 레벨에서 적용할 수도 있고, 어플리케이션 레벨에서도 적용이 가능합니다.
다만 이 두 방식은 같은 일을 하는 것처럼 보이지만, 각자가 맡는 역할은 다릅니다.
인프라 레벨의 Rate Limiting은 요청이 API 서버에 도달하기 전에 트래픽을 거르는 역할에 가깝습니다.
과도한 요청이나 비정상적인 트래픽으로부터 시스템 전체를 보호하는 것이 목적입니다.
반면 어플리케이션 레벨의 Rate Limiting은 비즈니스 맥락을 기준으로 요청을 제어합니다.
어떤 사용자가, 어떤 API를, 어느 정도까지 사용할 수 있는지를 어플리케이션이 직접 판단합니다.
즉, 인프라 레벨은 시스템 보호에 가깝고 어플리케이션 레벨은 정책과 규칙의 문제에 가깝다고 볼 수 있습니다.
그래서 Rate Limiting은 한 곳에서만 처리하기보다는, 각 레벨의 역할에 맞게 나누어 적용하는 경우가 많습니다.
이렇게 역할을 분리했을 때에야 Rate Limiting은 API 확장 과정에서 의미 있는 선택이 됩니다.
Async
비동기... 일반적인 API 방식은 대부분 동기 방식입니다.
동기 방식에서는 요청을 발송하면 응답이 올 때까지 계속 기다려야 합니다.
이 과정에서 요청을 처리하는 데 시간이 오래 걸리면, 그만큼 레이턴시도 자연스럽게 길어질 수 있습니다.
비동기는 여기서 다릅니다. 요청이 처리되는 순서 자체는 동기든 비동기든 크게 다르지 않습니다.
다만 차이점은, 비동기는 응답을 기다리지 않는다는 점입니다.
API 요청을 발송한 뒤, 그 요청이 언제 끝나는지를 지금 당장 알 필요가 없습니다.
요청은 보내고, 그 시점에서 현재 요청 흐름은 종료됩니다.
이 차이로 인해 비동기 구조에서는 하나의 요청이 서버를 오래 붙잡고 있지 않게 되고,
그만큼 더 많은 요청을 동시에 받아들일 수 있게 됩니다.
그렇다고해서 모든 api를 비동기로 생성하면 안됩니다.
비동기 방식은 요청을 빠르게 흘려보내는 데에는 유리하지만, 모든 상황에 적합한 구조는 아닙니다.
특히 사용자가 즉시 결과를 알아야 하는 요청의 경우에는, 비동기 방식이 오히려 불편한 구조가 될 수 있습니다.
예를 들어, 로그인 요청 조회 API 결과를 바로 화면에 보여줘야 하는 요청등등
이런 경우에는 요청을 보내고 응답을 바로 받는 동기 방식이 더 자연스럽습니다.
또 한 가지 고려해야 할 점은 복잡도입니다.
비동기 구조를 도입하면, 처리 상태를 별도로 관리해야 하고 실패 여부를 추적해야 하며 재시도나 보상 로직도 필요해집니다.
즉, 단순한 요청까지 모두 비동기로 처리하면, 구조는 오히려 더 복잡해질 수 있습니다.
MSA
MSA는 요청을 더 잘 나누는 방법이라기보다는, 시스템 자체를 나누는 선택에 가깝습니다.
모놀리식에서 MSA로 전환하는 방법
모놀리식으로 개발된 제품을 마이크로서비스 아키텍처(MSA)로 전환한다고 하면,흔히 도메인 단위로 코드를 분리하고, 서비스를 나누는 작업부터 떠올리기 쉽습니다. 실제로 복사·붙여넣기 방식
b-programmer.tistory.com
지금까지의 선택들이 "하나의 API를 어떻게 확장할 것인가"였다면, MSA는 "API 자체를 나눌 수 없을까?" 라는 질문에서 출발합니다.
모놀리식 구조에서는 서버를 아무리 늘려도 결국 하나의 애플리케이션이 모든 책임을 지게 됩니다.
- 하나의 배포 단위
- 하나의 장애 지점
- 하나의 확장 전략
이 구조에서는 특정 기능만 트래픽이 몰려도, 전체 시스템이 영향을 받게 됩니다.
결론
이번 글은 초반에 어떻게 적어야 할지 조금 난감했던 글이었습니다. 이전에 이미 레이턴시, 처리량, 대역폭과 관련된 글들을 작성한 적이 있었기 때문에, 같은 이야기를 반복하지 않으면서 어떻게 풀어가야 할지 고민을 많이 했습니다. 이 글의 핵심은 API의 성능을 높이는 방법이 아니라, API에 몰리는 부하를 어떻게 나눌 수 있는지에 초점을 두는 것이었습니다. Stateless에서 시작해서, 로드 벨런싱, Rate Limiting, Async, 그리고 MSA까지 흐름을 따라가다 보면, 결국에는 서버를 어떻게 보호할 수 있는가라는 질문에 도달하게 된다고 생각합니다. 다만, 이 다섯 가지를 모두 API 트래픽을 위해 반드시 적용해야 하는 것은 아닙니다. 상황에 따라, 그리고 시점에 따라 필요한 선택을 적절하게 하는 것이 오히려 더 중요하다고 생각합니다. API 확장은 정답을 외우는 문제가 아니라, 그때그때 가장 합리적인 선택을 하는 문제이기 때문입니다.
'개발' 카테고리의 다른 글
| 어떻게 하면 마스킹 부분을 확장하여 개발할 수 있을까? (2) | 2026.02.02 |
|---|---|
| 리트라이 전략과 서킷 브레이커와의 관계 (0) | 2026.01.31 |
| API 프로토콜은 REST뿐만이 아니라고?! (0) | 2026.01.29 |
| Retry 전략 - 수치는 어떻게 지정해야 할까 (1) | 2026.01.28 |
| 특명: 지연시간을 줄여라! (0) | 2026.01.27 |