API 게이트웨이 도입 후 겪은 일 (병목, 책임 분리, 중앙화)
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
솔직히 저는 API 게이트웨이를 처음 도입할 때, 이게 단순히 트래픽을 분산시켜주는 편리한 도구 정도로만 생각했습니다. 인증이나 로깅 같은 공통 기능을 한 곳에서 처리하니 얼마나 좋냐고 팀원들을 설득했었죠. 그런데 막상 운영에 들어가니 예상과 다른 문제들이 하나둘 터지기 시작했습니다. 모든 요청이 게이트웨이를 거쳐야 하니 그곳이 병목이 되고, 점차 게이트웨이에 이것저것 기능을 추가하다 보니 어느새 복잡한 괴물이 되어버렸습니다.
단일 진입점이라는 구조적 선택
API 게이트웨이는 클라이언트와 내부 마이크로서비스 사이에 위치한 추상화 계층입니다. 외부에서 들어오는 모든 요청을 이곳에서 받아 적절한 서비스로 전달하는 방식이죠. 이 구조의 가장 큰 장점은 내부 서비스 구조가 바뀌어도 클라이언트는 그 변화를 알 필요가 없다는 점입니다.
제가 참여했던 프로젝트에서도 초기에는 이 점이 정말 유용했습니다. 서비스를 분리하거나 통합할 때 게이트웨이의 라우팅 규칙만 수정하면 됐으니까요. 클라이언트 입장에서는 여전히 같은 엔드포인트로 요청을 보내면 됐습니다. 하지만 이게 모든 트래픽이 한 곳을 통과한다는 의미이기도 했습니다. 서비스가 10개든 100개든, 모든 요청이 게이트웨이라는 관문을 반드시 거쳐야 했죠.
일반적으로 단일 진입점 구조는 보안 정책 적용이나 모니터링 측면에서 효율적이라고 알려져 있지만, 실제로 운영해보니 이게 양날의 검이었습니다. 트래픽이 증가하면서 게이트웨이 자체의 성능이 전체 시스템의 상한선이 되어버리는 상황이 발생했거든요.
공통 기능을 한곳에 모으면 편할까
처음 게이트웨이를 설계할 때 저희는 인증(Authentication), 로깅(Logging), 요청 제한(Rate Limiting) 같은 공통 기능을 여기에 모아두기로 했습니다. 각 마이크로서비스마다 이런 코드를 중복으로 구현할 필요가 없으니 정말 효율적이라고 생각했죠. 실제로 초반에는 개발 속도가 빨라지는 효과가 있었습니다.
그런데 시간이 지나면서 문제가 생겼습니다. "이것도 공통 기능이니까 게이트웨이에 넣자"는 식의 논리가 계속 확장됐거든요. 요청 데이터를 변환하는 로직, 응답 형식을 가공하는 코드, 심지어 일부 비즈니스 규칙까지 게이트웨이에 들어가기 시작했습니다. 결국 게이트웨이 코드베이스가 점점 비대해지고 복잡해졌죠.
제 경험상 이런 상황은 정말 조심해야 합니다. 게이트웨이가 단순한 라우터에서 벗어나 미니 애플리케이션 서버로 변질되면, 배포할 때마다 긴장하게 됩니다. 게이트�eway에 버그가 생기면 모든 서비스가 영향을 받으니까요. 실제로 저희 팀은 한 번은 게이트웨이 배포 중 설정 오류로 30분간 전체 서비스가 마비된 적도 있었습니다.
- 인증 및 인가: JWT 토큰 검증, OAuth 처리 등 보안 관련 공통 로직
- 로깅 및 모니터링: 요청/응답 로그, 성능 메트릭 수집
- 트래픽 제어: Rate Limiting, Throttling, Circuit Breaker 패턴 적용
- 프로토콜 변환: REST를 gRPC로, 또는 그 반대로 변환하는 어댑터 역할
이 정도 기능까지는 게이트웨이의 책임 범위 안에 있다고 봅니다. 하지만 여기에 비즈니스 로직이 섞이기 시작하면 경계가 무너집니다.
병목 지점이 되는 순간
게이트웨이를 운영하면서 제가 가장 크게 느낀 위험은 병목 현상입니다. 마이크로서비스 아키텍처를 선택한 이유 중 하나가 서비스별로 독립적인 확장이 가능하다는 점인데, 정작 모든 요청이 게이트웨이를 통과하니 그곳이 단일 장애 지점(Single Point of Failure)이 되어버렸습니다.
저희 서비스는 특정 시간대에 트래픽이 급증하는 패턴을 가지고 있었습니다. 처음에는 개별 서비스들을 스케일 아웃해서 대응하려고 했는데, 게이트웨이 레벨에서 응답 시간이 느려지는 현상이 발생했습니다. 아무리 뒷단 서비스를 늘려도 게이트웨이가 요청을 처리하는 속도가 따라가지 못하면 소용이 없었죠.
이 문제를 해결하려고 저희는 게이트웨이 자체를 다중 인스턴스로 구성하고, 로드 밸런서 앞단에 배치했습니다. 오토 스케일링 정책도 적용했고요. 그런데 여기서도 함정이 있었습니다. 게이트웨이 인스턴스를 늘리면 각 인스턴스가 유지해야 하는 상태 정보나 캐시 동기화 문제가 생기더라고요. 무상태(Stateless) 설계 원칙을 지키지 않았던 초기 구조의 문제점이 드러난 거죠.
결국 저희는 게이트웨이를 완전히 무상태로 재설계하고, 세션 정보는 외부 Redis 클러스터에 저장하는 방식으로 전환했습니다. 이 과정에서 많은 시행착오를 겪었지만, 지금은 트래픽 급증 시에도 안정적으로 대응할 수 있게 됐습니다.
책임 범위를 어디까지 설정할 것인가
게이트웨이를 운영하면서 가장 어려웠던 부분은 "어디까지 게이트웨이의 책임으로 볼 것인가"를 정하는 일이었습니다. 팀 내에서도 의견이 갈렸거든요. 어떤 개발자는 "공통 기능이니까 게이트웨이에서 처리하는 게 맞다"고 했고, 다른 개발자는 "그러다가 게이트웨이가 너무 무거워진다"고 반대했습니다.
제 생각에는, 게이트웨이는 횡단 관심사(Cross-Cutting Concerns)만 처리해야 합니다. 인증, 로깅, 트래픽 제어처럼 모든 서비스에 공통으로 적용되는 정책 말이죠. 반면 특정 도메인에 종속된 비즈니스 로직은 절대 게이트웨이에 두면 안 됩니다. 예를 들어 "사용자의 등급에 따라 할인율을 계산한다"는 로직은 명백히 비즈니스 영역이니 해당 서비스에 있어야 합니다.
실제로 저희 프로젝트에서도 이 원칙을 정하고 나니 구조가 훨씬 명확해졌습니다. 게이트웨이에 새 기능을 추가할 때 "이게 정말 모든 서비스에 필요한 공통 기능인가?"를 먼저 자문하게 됐죠. 대부분의 경우 답은 "아니오"였고, 그런 기능들은 개별 서비스로 이관했습니다.
마이크로서비스 아키텍처에서는 서비스 간 경계를 명확히 하는 것이 핵심입니다. 게이트웨이도 마찬가지입니다. 책임 범위를 명확히 정하지 않으면, 결국 모놀리스 시스템의 문제를 다시 반복하게 됩니다. 중앙화된 거대한 컴포넌트가 생기고, 그곳의 변경이 전체 시스템에 영향을 미치게 되는 거죠.
지금 저희 팀은 게이트웨이의 책임을 최소화하는 방향으로 계속 개선하고 있습니다. 불필요하게 들어간 기능은 빼고, 정말 필요한 공통 기능만 남기려고 노력 중입니다. 아직 완벽하지는 않지만, 초기보다는 훨씬 명확한 구조를 가지게 됐다고 자부합니다. API 게이트웨이는 분명 필수적인 아키텍처 요소지만, 그 역할을 어떻게 정의하느냐에 따라 독이 될 수도, 약이 될 수도 있다는 걸 직접 경험으로 배웠습니다.
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기