API 안정성 설계 (보호계층, 장애방지, 관측체계)

API 안정성은 단일 기술로 해결되는 문제가 아닙니다. 다양한 전략과 패턴이 결합되어야 비로소 안정적인 시스템을 구축할 수 있습니다. 지금까지 살펴본 다양한 요소들은 각각 독립적인 기능이 아니라, 서로 연결된 구조를 형성합니다. 이 글에서는 API 안정성을 구성하는 핵심 요소를 종합적으로 정리하고, 실무에서 반드시 구축해야 하는 기준을 제시합니다.

서비스가 갑자기 죽었을 때 가장 먼저 드는 생각은 "왜 미리 못 잡았지?"입니다. 저도 새벽에 슬랙 알림을 받고 노트북을 열었던 기억이 있습니다. 알고 보니 외부 결제 API 하나가 느려지면서 연결을 잡고 놓지 않아 전체 서버 스레드가 고갈된 케이스였습니다. Rate Limiting도 없었고, Timeout 설정도 기본값 그대로였습니다. 그때 처음으로 API 안정성 설계가 단순한 '선택 사항'이 아니라는 걸 몸으로 배웠습니다.

기본 보호 계층, 왜 설정하지 않는가

Rate Limiting, Timeout, Retry. 이 세 가지는 API 안정성의 가장 기초적인 보호 계층입니다. Rate Limiting은 단위 시간 내에 허용할 요청 수를 제한하는 방식으로, 트래픽 급증이나 악의적인 과부하 공격으로부터 서버를 지킵니다. Timeout은 응답을 기다리는 최대 시간을 설정하는 것인데, 이게 없으면 느린 외부 서비스 하나가 커넥션 풀 전체를 잠가버릴 수 있습니다.

Retry는 일시적 오류에 대해 요청을 자동으로 재시도하는 전략입니다. 그런데 여기서 주의할 점이 있습니다. Retry를 아무 생각 없이 붙이면 오히려 장애를 악화시킵니다. 이미 느린 서버에 재시도가 폭주하면 부하가 기하급수적으로 올라가기 때문입니다. 그래서 Exponential Backoff, 즉 재시도 간격을 점점 늘려가는 방식과 함께 써야 효과가 납니다. 이 조합을 적용하고 나서 저희 팀에서 일시적 오류로 인한 실패율이 체감상 절반 이하로 줄었습니다.

일반적으로 이 설정들은 기본값으로도 충분하다고 생각하는 분들이 있는데, 이건 위험한 생각입니다. AWS나 GCP의 SDK 기본 Timeout 값은 수십 초에 달하는 경우가 많고, 그 몇 십 초 동안 스레드는 아무것도 못 하고 기다리고 있습니다. 서비스 규모가 작을 때는 티가 안 나지만, 트래픽이 조금만 올라가면 바로 드러납니다.

장애 방지 구조, Circuit Breaker와 Bulkhead의 실제 역할

Circuit Breaker는 전기 회로 차단기에서 이름을 따온 패턴입니다. 특정 서비스로의 요청 실패율이 임계치를 넘으면 회로를 열어버려서(Open 상태) 이후 요청을 즉시 실패 처리합니다. 느린 응답을 기다리는 대신 빠르게 실패를 반환함으로써 자원 낭비를 막는 구조입니다. 회로가 열린 후 일정 시간이 지나면 일부 요청만 통과시켜 복구 여부를 확인하는 Half-Open 상태로 전환되고, 성공하면 다시 닫힙니다.

Bulkhead는 선박의 격벽 구조에서 온 개념입니다. 배가 침수될 때 한 구역만 막아 전체 침몰을 막듯이, 서비스별로 스레드 풀이나 커넥션 풀을 분리해 한 서비스의 장애가 다른 서비스로 번지지 않게 합니다. 제가 직접 적용해봤는데, 처음에는 설정이 복잡해서 귀찮다는 생각이 들었습니다. 그런데 외부 API 하나가 다운됐을 때 나머지 기능이 멀쩡히 살아있는 걸 보고 그 귀찮음이 완전히 사라졌습니다.

이 두 패턴을 함께 쓸 때 반드시 고민해야 할 게 있습니다. 임계값 설정입니다. Circuit Breaker가 너무 민감하면 멀쩡한 서비스도 차단해버리고, 너무 둔감하면 장애가 퍼진 뒤에야 반응합니다. 처음에 5xx 오류율 50%를 기준으로 잡았다가 너무 늦게 반응한다는 걸 알고 30%로 낮췄습니다. 이건 시스템마다 다르므로 실제 트래픽 패턴을 보고 조정하는 게 맞습니다.

관측 체계, 로그만으로는 부족한 이유

Observability(관측 가능성)는 요즘 많이 들리는 단어인데, 단순히 로그를 많이 쌓는 것과는 다릅니다. 시스템 내부 상태를 외부에서 추론할 수 있는 능력을 뜻합니다. 이를 위해 세 가지 신호가 필요합니다.

  1. 로그(Log): 특정 시점에 어떤 일이 일어났는지 기록합니다. 장애 원인을 사후에 파악하는 데 쓰입니다.
  2. 메트릭(Metric): 요청 수, 오류율, 응답 시간처럼 시간에 따라 변화하는 수치를 집계합니다. 이상 징후를 빠르게 탐지하는 데 유리합니다.
  3. 트레이스(Trace): 하나의 요청이 여러 서비스를 거치는 경로를 추적합니다. 마이크로서비스 구조에서 병목이 어디서 생기는지 파악할 때 필수입니다.

로그만 잘 남기면 충분하다고 생각했는데, 분산 시스템에서 로그만으로는 원인을 찾는 데 몇 시간씩 걸리는 경우가 생깁니다. Distributed Tracing, 즉 분산 추적을 도입하고 나서 장애 원인 파악 시간이 눈에 띄게 짧아졌습니다. 우리팀 기준으로는 평균 장애 대응 시간(MTTR, Mean Time To Recovery)이 약 40% 줄었다고 느꼈습니다.

알림 시스템도 Observability의 일부입니다. 그런데 알림이 너무 많으면 오히려 무감각해집니다. 중요하지 않은 알림이 쏟아지면 진짜 중요한 알림을 놓치게 됩니다. 이른바 Alert Fatigue, 알림 피로 현상입니다. 슬랙 알림 채널을 무음으로 해놓은 적이 있었는데, 그게 얼마나 위험한 습관인지는 나중에야 깨달았습니다. 알림은 꼭 행동이 필요한 것만 남기는 게 맞습니다.

SLO 기반 운영, 목표 없이 개선은 없다

SLO(Service Level Objective)는 서비스 품질 목표를 수치로 정의하는 것입니다. 예를 들어 "월간 가용성 99.9% 이상, p99 응답 시간 500ms 이하"처럼 구체적인 수치로 명시합니다. SLO를 정하지 않으면 팀이 어느 수준을 목표로 운영해야 하는지 기준이 없어지고, 개선 작업의 우선순위를 잡기도 어렵습니다.

SLO와 함께 쓰이는 개념이 Error Budget입니다. 허용 오류 예산이라고도 하는데, SLO를 달성하기 위해 허용 가능한 장애 시간이나 오류 횟수를 수치화한 것입니다. 99.9% 가용성을 목표로 하면 한 달에 약 43분의 다운타임이 허용됩니다. 이 예산이 빠르게 소진되면 신규 기능 배포를 멈추고 안정성 개선에 집중하는 식으로 운영합니다. 구글 SRE 팀이 이 방식으로 운영한다는 게 잘 알려져 있고, 실제로 이 개념을 도입한 조직에서 배포 실패로 인한 서비스 영향이 줄어드는 효과가 보고되고 있습니다(출처: Google SRE Book).

Postmortem, 즉 장애 회고도 빠질 수 없습니다. 장애가 나면 누군가를 탓하는 게 아니라, 시스템 구조의 어떤 부분이 취약했는지 분석하는 과정입니다. 제가 경험한 좋은 Postmortem은 "왜 사람이 실수했는가"보다 "왜 그 실수가 장애로 이어질 수 있는 구조였는가"를 파고들었습니다. 그 차이가 결국 시스템을 진짜로 개선하느냐 아니냐를 갈랐습니다.

API 안정성은 처음부터 완벽하게 갖출 수 없습니다. 보호 계층을 쌓고, 장애 확산을 막고, 관측 체계를 갖추고, 수치 기반으로 목표를 운영하는 이 네 가지가 서로 맞물려야 비로소 의미가 생깁니다. 처음 시작한다면 Timeout 설정과 기본 메트릭 수집부터 챙기는 것을 권합니다. 작은 것 하나를 제대로 붙이는 순간, 시스템이 보이기 시작합니다. 새벽 두 시에 슬랙 알림을 받는 일이 조금씩 줄어들 것입니다.


관련 글

댓글

이 블로그의 인기 게시물

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

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

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