REST API 개발할 때 왜 자꾸 장애가 날까요?

profile_image
작성자 API설계자 윤새봄
댓글 0건 조회 42회

잘 돌아가던 API가 배포 후 갑자기 깨지는 이유

화면에 맞춰 즉흥적으로 만든 엔드포인트

로컬에서는 멀쩡했던 REST API가 배포 직후 장애를 일으키는 장면은 드물지 않습니다. 프론트엔드 화면 하나를 빨리 완성하려고 /getUserList, /saveNewOrder처럼 기능 이름을 그대로 URL에 붙이면 당장은 편하지만, 화면과 요구사항이 늘어날수록 규칙을 예측하기 어려워집니다. 새로 합류한 개발자는 비슷한 기능을 새 API로 만들어야 하는지 기존 API를 확장해야 하는지 판단하지 못합니다.

실패한 프로젝트를 살펴보면 API가 데이터 자원을 표현하지 않고 특정 화면의 버튼이나 개발자의 구현 방식에 종속된 경우가 많습니다. 예를 들어 회원 목록을 GET /users로 조회하고 회원 한 명을 GET /users/{id}로 조회하면 의미가 선명합니다. 반면 POST /userSearch 하나에 조회, 필터, 상세 보기까지 몰아넣으면 캐시와 권한 정책을 적용하기 어려워지고 테스트 조건도 폭발적으로 늘어납니다.

코딩은 단순히 문법을 입력하는 행위가 아니라 요구사항을 컴퓨터가 처리할 수 있는 구조로 바꾸는 작업입니다. 기본 개념이 낯설다면 코딩의 용어 정의를 먼저 확인해도 좋습니다. REST API에서도 중요한 것은 멋진 URL이 아니라 팀원이 같은 규칙으로 읽고 수정할 수 있는 구조입니다.

  • 하지 말아야 할 실수: 화면 버튼마다 새로운 동사형 URL을 추가합니다.
  • 바꿀 방법: 사용자, 주문, 상품처럼 자원을 명사로 표현합니다.
  • 확인 질문: 다른 화면에서도 이 자원을 같은 URL과 규칙으로 사용할 수 있는지 묻습니다.
  • 실패 신호: 비슷한 이름의 API가 세 개 이상 생겼는데 차이를 문서 없이는 설명하지 못합니다.
URL을 보고 대상 자원과 동작을 짐작할 수 없다면 구현보다 이름부터 다시 설계하는 편이 빠릅니다.

HTTP 메서드를 무시하면 재시도 한 번이 사고가 됩니다

GET 요청으로 데이터를 바꾼 실패 사례

한 쇼핑몰 개발팀은 적립금 지급 기능을 빠르게 연결하기 위해 GET /points/add?userId=10을 만들었습니다. 브라우저에서 주소를 입력해 시험하기 편하다는 이유였습니다. 문제는 모니터링 도구와 검색 봇이 해당 주소를 자동으로 호출하면서 일부 회원에게 적립금이 중복 지급됐다는 점입니다. GET은 조회에 사용하고 서버 상태를 바꾸지 않아야 한다는 원칙을 편의 때문에 무시한 결과입니다.

POST, PUT, PATCH도 아무렇게나 섞으면 장애 복구가 어려워집니다. 주문 전체 정보를 교체하는 PUT과 일부 필드만 수정하는 PATCH의 차이를 팀이 합의하지 않으면, 프론트엔드가 보내지 않은 필드가 빈값으로 덮일 수 있습니다. 네트워크가 끊겨 클라이언트가 같은 요청을 다시 보냈을 때 결제가 두 번 처리되는 문제도 흔합니다. 결제나 쿠폰 발급처럼 중복 실행이 위험한 작업에는 요청 식별자인 멱등성 키를 두는 것이 안전합니다.

모든 동작을 POST 하나로 처리하면 초기 개발 속도는 빨라 보입니다. 그러나 CDN 캐시, 브라우저 동작, 접근 로그 분석과 보안 정책이 HTTP 의미를 활용하지 못해 운영 비용이 커집니다. 여러분의 API 문서에서 메서드만 가린 뒤에도 요청의 성격을 설명할 수 있나요? 설명이 어렵다면 자원 모델과 동작 경계를 다시 살펴봐야 합니다.

  1. 조회는 GET으로 만들고 본문보다 경로와 쿼리 조건을 사용합니다.
  2. 새 자원 생성은 POST로 처리하고 생성된 위치나 식별자를 응답합니다.
  3. 전체 교체는 PUT, 일부 수정은 PATCH로 구분하되 팀 규칙을 문서화합니다.
  4. 삭제는 DELETE로 표현하고 반복 호출했을 때의 응답 정책을 정합니다.
  5. 결제처럼 민감한 POST에는 멱등성 키와 서버 측 중복 검사 로직을 둡니다.

상태 코드가 모두 200이면 장애를 더 늦게 발견합니다

성공처럼 보이는 실패 응답의 함정

서버에서 오류가 발생했는데도 HTTP 상태 코드는 200이고 본문에 {"success":false}만 넣는 API가 있습니다. 프론트엔드 개발자가 본문을 확인하면 되니 문제없어 보이지만, 로드밸런서와 모니터링 시스템은 이를 정상 요청으로 집계할 수 있습니다. 실제 사용자는 결제에 실패하고 있는데 운영 대시보드에는 성공률 100%가 찍히는 황당한 상황이 만들어집니다.

400은 잘못된 요청 형식, 401은 인증 정보가 없거나 유효하지 않은 상황, 403은 인증됐지만 권한이 없는 상황, 404는 자원을 찾지 못한 상황에 주로 사용합니다. 서버 내부 예외는 500 계열로 분리해야 합니다. 단, 상태 코드만 맞춘다고 충분하지는 않습니다. 클라이언트가 대응할 수 있도록 안정적인 오류 코드, 사용자를 위한 메시지, 추적용 요청 ID를 함께 제공해야 합니다.

반대로 내부 예외 메시지와 데이터베이스 쿼리를 그대로 응답하는 것도 위험합니다. 개발자는 디버깅하기 편하지만 테이블명, 라이브러리 버전, 서버 경로 같은 정보가 외부에 노출될 수 있습니다. 사용자 응답에는 필요한 정보만 담고 상세 스택 추적은 서버 로그에서 요청 ID로 찾는 구조가 좋습니다. 프로그래밍의 범위와 처리 절차를 이해하려면 프로그래밍 개념 설명도 설계 원칙을 잡는 데 참고할 수 있습니다.

  • 200: 요청이 실제로 성공했고 약속한 결과가 반환됐을 때 사용합니다.
  • 201: 자원이 생성됐으며 가능하면 생성된 자원의 주소를 알려줍니다.
  • 400 계열: 사용자가 요청을 고치거나 인증·권한을 확인해야 할 때 사용합니다.
  • 500 계열: 클라이언트 수정으로 해결되지 않는 서버 문제를 나타냅니다.
  • 오류 본문: code, message, requestId처럼 일관된 필드를 유지합니다.
오류 응답은 개발자를 혼내는 문장이 아니라 다음 행동을 알려주는 인터페이스입니다. 상태 코드와 오류 코드를 분리해 설계하세요.

데이터를 전부 보내는 친절함이 성능 장애를 부릅니다

페이지네이션과 필드 제한을 미룬 대가

사내 관리자 화면이라 사용자가 많지 않다는 이유로 GET /orders에서 주문 전체를 반환한 팀이 있었습니다. 주문이 500건일 때는 빨랐지만 몇 달 뒤 20만 건이 쌓이자 응답 생성에 수십 초가 걸렸고, 서버 메모리도 급격히 증가했습니다. 결국 관리자 한 명이 목록을 열 때마다 다른 고객의 주문 요청까지 느려졌습니다. 데이터가 아직 적다는 사실은 페이지네이션을 생략할 근거가 아닙니다.

페이지 번호 방식은 구현과 이해가 쉽지만 데이터가 자주 추가되면 다음 페이지에서 항목이 중복되거나 빠질 수 있습니다. 실시간 피드나 주문 이력에는 마지막으로 본 항목의 식별자나 생성 시각을 기준으로 넘기는 커서 방식이 더 안정적입니다. 어떤 방식을 쓰든 기본 크기와 최대 크기를 서버가 강제해야 합니다. 클라이언트가 size=1000000을 보내도 그대로 허용해서는 안 됩니다.

목록 응답에 상세 설명, 내부 메모, 첨부파일 원문까지 모두 담는 실수도 피해야 합니다. 목록에는 식별자, 제목, 상태, 수정 시각처럼 화면에 필요한 요약 필드만 제공하고 상세 정보는 개별 조회에서 반환하는 편이 낫습니다. 다만 요청마다 다시 조회하는 N+1 문제가 생기지 않도록 데이터베이스 쿼리 횟수와 응답 크기를 함께 측정해야 합니다. 작은 JSON처럼 보여도 수천 건이 모이면 직렬화와 네트워크 비용이 커집니다.

  • 목록의 기본 크기는 서비스 성격에 맞춰 20~50개 정도로 시작합니다.
  • 최대 요청 크기는 100~200개처럼 서버에서 제한하고 초과 요청을 거절합니다.
  • 정렬 기준에는 고유 식별자를 함께 넣어 결과 순서를 안정적으로 유지합니다.
  • 응답에는 다음 커서나 전체 개수 등 화면에 실제로 필요한 탐색 정보를 제공합니다.
  • 압축 전후 응답 용량, 데이터베이스 실행 시간, 직렬화 시간을 따로 관찰합니다.

인증만 붙이고 권한 검사를 빼면 남의 데이터가 보입니다

ID 하나 바꿨는데 주문서가 열린 사고

로그인한 사용자가 /orders/481을 열 수 있다는 사실만 확인하고, 그 주문의 소유자가 누구인지는 검사하지 않는 코드가 있습니다. 공격자가 주소의 숫자를 482로 바꿨을 때 다른 사람의 주문서가 보이면 전형적인 객체 수준 권한 검증 실패입니다. 인증은 누구인지 확인하는 과정이고, 인가는 그 사람이 해당 자원에 무엇을 할 수 있는지 판단하는 과정입니다. 둘 중 하나만 구현해서는 안전한 웹개발이 되지 않습니다.

프론트엔드에서 관리자 버튼을 숨기는 것은 보안 통제가 아닙니다. 사용자는 개발자 도구나 별도 프로그램으로 API를 직접 호출할 수 있기 때문입니다. 서버는 모든 요청에서 사용자, 자원, 행위의 조합을 확인해야 합니다. 특히 조직형 서비스에서는 같은 회사에 속했는지, 프로젝트 참여자인지, 읽기와 수정 중 어떤 권한이 있는지를 계층적으로 검사해야 합니다.

또 다른 흔한 실수는 액세스 토큰을 오래 유지하고 로그에 그대로 남기는 것입니다. 토큰은 비밀번호처럼 다루고 URL 쿼리 문자열에 넣지 않아야 합니다. 짧은 수명의 액세스 토큰과 안전하게 보관된 갱신 토큰을 구분하며, 로그에는 토큰과 주민등록번호, 카드 정보 같은 민감값을 마스킹합니다. AI 개발 도구가 빠르게 발전하더라도 보안 판단까지 자동으로 완성된다고 기대하기는 어렵습니다. 관련 흐름은 AI 기술 변곡점에 관한 기사처럼 변화의 맥락을 살펴보되, 생성된 코드는 반드시 사람이 권한 경계와 데이터 노출 여부를 검토해야 합니다.

  1. 요청을 보낸 사용자의 신원을 서버에서 검증합니다.
  2. 조회 대상 자원이 어느 사용자나 조직에 속하는지 확인합니다.
  3. 읽기, 생성, 수정, 삭제 권한을 동작별로 나눕니다.
  4. 일반 사용자가 관리자 API를 직접 호출하는 통합 테스트를 추가합니다.
  5. 토큰, 쿠키, 개인 정보가 로그와 오류 응답에 남지 않는지 점검합니다.
  6. 권한 거절 이벤트가 반복되면 탐지할 수 있도록 별도 보안 지표를 수집합니다.

API 한 줄을 아끼려다 잃는 개발 시간과 운영비

현실적인 숫자로 잡는 최소 방어선

일정이 촉박하면 문서, 테스트, 모니터링을 나중으로 미루기 쉽습니다. 하지만 엔드포인트 하나를 30분 빨리 만드는 대신 장애 원인을 찾는 데 개발자 세 명이 두 시간씩 쓰면 이미 6인시를 소비한 셈입니다. 고객 문의 대응과 긴급 배포까지 더하면 절약했다고 생각한 시간의 열 배가 사라질 수 있습니다. 그래서 모든 것을 완벽하게 준비하기보다 실패 비용이 큰 지점에 최소 방어선을 먼저 세워야 합니다.

작은 팀이라면 API 하나를 추가할 때 정상 요청, 입력 오류, 인증 실패, 권한 실패, 중복 요청의 다섯 경로만 우선 자동화해도 효과가 큽니다. 각 테스트가 1초라면 100개를 실행해도 약 100초이며 병렬 실행으로 더 줄일 수 있습니다. 여기에 요청 ID, 처리 시간, 상태 코드, 사용자나 조직의 비식별 키를 구조화 로그로 남기면 장애 조사에서 재현에 쓰는 시간을 크게 줄일 수 있습니다. 단, 개인 정보와 토큰은 기록하지 않습니다.

API 문서는 거대한 문서 프로젝트로 시작할 필요가 없습니다. 요청과 응답 예시, 필수 필드, 오류 코드, 권한 조건, 페이지네이션 규칙을 엔드포인트당 10~15분 동안 기록하는 것부터 충분합니다. 코드 리뷰에서도 변경 줄 수만 보지 말고 호환성이 깨지는지 확인하세요. 이미 사용 중인 필드를 갑자기 삭제하거나 자료형을 문자열에서 숫자로 바꾸면 작은 수정이 여러 클라이언트의 동시 장애로 번집니다.

  • API 설계 검토: 엔드포인트당 15~30분을 확보해 URL, 메서드, 상태 코드를 확인합니다.
  • 핵심 테스트: 정상·입력 오류·인증·권한·중복 요청 등 최소 5개 시나리오를 작성합니다.
  • 응답 제한: 목록 기본 20~50개, 최대 100~200개처럼 상한을 명시합니다.
  • 성능 목표: 서비스 기준에 따라 일반 조회의 95백분위 응답 시간을 예컨대 300~500밀리초 이내로 관찰합니다.
  • 로그 보관: 처음에는 7~30일 범위에서 저장량과 개인정보 위험을 계산해 결정합니다.
  • 장애 훈련: 월 1회 30분 동안 실패 요청 하나를 requestId로 추적해 복구 경로를 확인합니다.
  • 비용 판단: 예방 작업 2시간과 장애 대응 예상 6~20시간을 비교해 우선순위를 정합니다.

시간이 단 하루뿐이라면 첫 2시간은 API 규칙과 권한 경계에, 다음 3시간은 구현에, 1시간은 다섯 가지 실패 테스트에, 마지막 1시간은 문서와 로그 확인에 배분해 보세요. 총 7시간 안에서도 무계획한 개발보다 예측 가능한 결과를 만들 수 있습니다. 유료 모니터링 도구를 바로 도입하기 어렵다면 서버 로그와 상태 코드 집계부터 시작하고, 장애 빈도와 조사 시간이 월 구독료를 넘어서는 시점에 도구 비용을 검토하는 방식이 현실적입니다.

REST API 개발할 때 왜 자꾸 장애가 날까요?

댓글목록

등록된 댓글이 없습니다.