API를 설계할 때는 새 엔드포인트를 만드는 이야기보다 없애는 이야기가 더 어렵습니다. 새 API는 내부 팀이 준비되면 열 수 있지만, 기존 API는 이미 누군가의 배치, 모바일 앱, 파트너 연동, 오래된 백오피스 화면에 박혀 있을 수 있습니다. 그래서 API 제거는 단순한 코드 삭제가 아니라 사용자와 시스템의 계약을 종료하는 운영 절차입니다.

백엔드 실무에서 API deprecation은 “문서에 deprecated라고 써두기"로 끝나지 않습니다. 언제부터 경고할지, 어느 클라이언트가 아직 쓰는지, 어떤 버전은 얼마나 더 유지할지, 강제 차단 전에 어떤 지표를 볼지 정해야 합니다. 이 글은 API 버전 관리, Consumer-Driven Contract Testing, Feature Flag, 배포 런북을 API 종료 관점으로 묶어 정리합니다.

이 글에서 얻는 것

  • API deprecation, sunset, removal을 구분하고 각 단계에서 해야 할 일을 설명할 수 있습니다.
  • 사용량·클라이언트 분포·계약 중요도 기준으로 sunset window를 숫자로 잡을 수 있습니다.
  • 호환성 레이어, 경고 헤더, 관측 지표, 차단 플래그를 조합해 API 제거 리스크를 낮출 수 있습니다.
  • “바로 삭제”, “장기 유지”, “호환 변환”, “새 버전 강제” 중 어떤 선택이 맞는지 의사결정 기준을 세울 수 있습니다.

핵심 개념/이슈

1) Deprecation은 제거가 아니라 제거 예고다

Deprecation은 “이 API는 앞으로 권장하지 않는다"는 상태입니다. 아직 동작해야 합니다. Sunset은 “이 날짜 이후에는 동작을 보장하지 않는다"는 종료 일정입니다. Removal은 실제 코드와 라우팅을 제거하거나 요청을 차단하는 단계입니다. 이 세 단계를 섞으면 사고가 납니다.

실무에서는 아래처럼 상태를 나눠 두는 편이 안전합니다.

상태의미클라이언트 영향운영 액션
Active정상 지원없음일반 변경 관리
Deprecated새 사용 금지, 기존 사용 허용경고 노출문서/헤더/로그 경고, 대체 API 안내
Sunset Scheduled종료일 확정이전 계획 필요클라이언트별 사용량 추적, 마이그레이션 캠페인
Restricted일부 클라이언트 차단 또는 write 금지기능 제한allowlist, feature flag, rate limit 적용
Removed더 이상 지원하지 않음실패 응답410 Gone, 문서 제거, 코드 정리

중요한 점은 deprecated 상태가 removal의 면죄부가 아니라는 것입니다. 사용자가 아직 남아 있는데 코드만 지우면 API 팀의 정리가 아니라 장애입니다. 특히 모바일 앱, 외부 파트너, 임베디드 장비처럼 배포 주기가 느린 클라이언트는 서버 팀의 릴리스 속도로 움직이지 않습니다.

2) 사용량이 0처럼 보여도 실제 0이 아닐 수 있다

API 제거 전 가장 흔한 실수는 access log만 보고 “아무도 안 쓰네"라고 판단하는 것입니다. 로그 샘플링, CDN 캐시, 프록시 경유, 배치 주기, 지역별 트래픽 차이 때문에 실제 사용량이 가려질 수 있습니다. 특히 월 1회 정산 배치나 분기별 리포트 API는 일주일만 보면 0처럼 보입니다.

제거 후보 API는 최소 아래 지표를 함께 봐야 합니다.

  • 최근 30일/90일 요청 수와 고유 클라이언트 수
  • 인증 주체별 사용량: user, service account, partner, mobile app version
  • 응답 코드 분포: 2xx, 4xx, 5xx, timeout
  • 호출 시간대와 주기: 매일, 매주, 월말, 배치 window
  • downstream 영향: DB, 메시지 발행, 외부 API 호출 여부
  • 대체 API 사용 전환율

기준은 API 성격에 따라 다르지만, 외부 공개 API는 최소 90일, 내부 서비스 API도 최소 30일은 보는 편이 안전합니다. 요청 수가 낮아도 결제, 권한, 정산, 알림처럼 비즈니스 영향이 큰 API는 단순 트래픽 기준으로 삭제하면 안 됩니다.

3) Sunset window는 배포 주기와 계약 책임으로 정한다

모든 API를 1년씩 유지할 필요는 없습니다. 반대로 모든 API를 2주 만에 없앨 수도 없습니다. 핵심은 클라이언트가 실제로 바꿀 수 있는 시간입니다.

초기 기준은 아래처럼 잡을 수 있습니다.

API 종류권장 sunset window이유
같은 저장소 내부 API1~2주producer/consumer를 한 팀이 조정 가능
사내 서비스 간 API4~8주배포 일정과 회귀 테스트 필요
웹 프론트엔드 전용 API2~4주강제 배포가 비교적 쉬움
모바일 앱 API3~6개월앱 심사, 사용자 업데이트 지연 존재
외부 파트너 API6~12개월계약·문서·파트너 개발 일정 필요
결제/정산/인증 API영향도에 따라 별도 승인장애 비용이 크고 롤백이 어렵다

의사결정 우선순위는 계약 영향 > 클라이언트 배포 가능성 > 트래픽 크기 > 코드 정리 이득 순서가 좋습니다. 호출량이 작아도 외부 파트너가 쓰고 있으면 긴 window가 필요합니다. 반대로 호출량이 커도 같은 팀이 관리하는 내부 프론트엔드라면 짧게 가져갈 수 있습니다.

4) 경고는 문서보다 런타임에서 더 잘 전달된다

문서 업데이트는 필요하지만 충분하지 않습니다. 실제 호출하는 클라이언트가 경고를 받아야 합니다. HTTP API라면 응답 헤더를 적극적으로 쓰는 편이 좋습니다.

예시는 아래와 같습니다.

Deprecation: true
Sunset: Fri, 31 Jul 2026 23:59:59 GMT
Link: </docs/api/v2/orders>; rel="successor-version"
Warning: 299 - "This endpoint will be removed on 2026-07-31. Use /v2/orders."

여기에 애플리케이션 로그와 metrics를 붙입니다.

api.deprecated.request.count{endpoint="/v1/orders", client_id="partner-a"}
api.deprecated.unique_clients{endpoint="/v1/orders"}
api.sunset.days_remaining{endpoint="/v1/orders"}
api.successor.adoption_ratio{from="/v1/orders", to="/v2/orders"}

경고는 너무 조용해도 문제고 너무 시끄러워도 문제입니다. 운영 로그에 모든 요청마다 error를 남기면 노이즈가 됩니다. 추천은 첫 2주는 info, sunset 30일 전부터 warning, 7일 전부터 클라이언트 owner에게 직접 알림을 보내는 식입니다. API 응답 헤더는 매 요청에 넣되, 서버 로그는 rate limit을 걸어 집계 중심으로 남기는 편이 낫습니다.

5) 호환성 레이어는 부채지만, 삭제 사고보다 싸다

새 API와 옛 API의 데이터 모델이 조금 다를 때는 compatibility adapter를 둘 수 있습니다. 예를 들어 /v1/ordersstatus: "PAID"를 반환하고 /v2/orderspayment.status: "captured"를 반환한다면, 일정 기간 v1 응답을 v2 모델에서 변환해 줄 수 있습니다.

호환성 레이어를 둘 때의 원칙은 세 가지입니다.

  1. 단방향 변환만 허용: v1을 유지하기 위해 v2 내부 모델을 계속 오염시키지 않는다.
  2. 만료일을 코드와 설정에 박는다: adapter에 owner, removal date, 지표를 둔다.
  3. 새 기능은 old API에 추가하지 않는다: deprecated API는 안정화 상태이지 성장 대상이 아니다.

호환성 레이어는 오래 살수록 부채가 됩니다. 하지만 클라이언트 전환 기간 동안 장애를 막아주는 보험이기도 합니다. API Composition과 Aggregation처럼 여러 downstream을 묶는 API라면 호환성 레이어가 단순 필드 변환보다 복잡해질 수 있으므로, 변환 비용과 유지 기간을 숫자로 관리해야 합니다.

실무 적용

1) Deprecation RFC를 작게라도 만든다

API 종료는 작은 변경처럼 보여도 영향 범위가 넓습니다. 최소 RFC에는 아래 항목이 있어야 합니다.

Endpoint: DELETE /v1/orders/{id}
Replacement: POST /v2/order-cancellations
Owner: order-platform
Reason: cancellation audit trail and policy validation
Deprecated from: 2026-05-15
Sunset date: 2026-08-31
Removal target: 2026-09-15
Known clients: web-admin, partner-a, settlement-batch
Risk class: medium/high
Rollback: feature flag route-back to legacy handler
Success metric: v1 traffic < 0.1% of order write traffic for 14 consecutive days

RFC가 거창할 필요는 없습니다. 핵심은 “왜 없애는가”, “누가 쓰는가”, “언제 실패하기 시작하는가”, “되돌릴 수 있는가"를 한곳에 모으는 것입니다. 이 문서가 있어야 배포 중 문제가 생겨도 판단이 빨라집니다.

2) 단계별 롤아웃을 feature flag로 제어한다

API 제거는 코드 배포와 정책 적용을 분리하는 편이 안전합니다. 코드는 미리 배포하고, 실제 차단은 feature flag나 gateway rule로 천천히 켭니다.

권장 순서는 아래입니다.

  1. Observe mode: 아무 동작도 바꾸지 않고 사용량만 측정한다.
  2. Warn mode: 응답 헤더와 로그에 deprecation 경고를 넣는다.
  3. Shadow reject: 차단했을 경우 실패했을 요청을 집계하지만 실제로는 통과시킨다.
  4. Canary restrict: 내부 클라이언트나 1% 트래픽부터 410/403을 반환한다.
  5. Client allowlist: 전환 완료 클라이언트는 차단, 미전환 핵심 클라이언트는 임시 허용한다.
  6. Full removal: 라우팅과 legacy handler를 제거한다.

이 흐름은 Feature Flag배포 런북의 기본 원칙과 같습니다. 위험한 변경은 배포 시점에 한 번에 터뜨리지 않고, 관측 가능한 정책 변경으로 쪼갭니다.

3) Contract test로 successor API를 검증한다

기존 API를 없애려면 대체 API가 실제로 같은 업무를 처리할 수 있어야 합니다. 문서상 대체가 아니라 consumer 관점의 대체여야 합니다. 그래서 consumer-driven contract test가 중요합니다.

예를 들어 파트너가 /v1/invoices에서 아래 필드를 사용하고 있었다면:

{
  "invoiceId": "inv_123",
  "amount": 12000,
  "currency": "KRW",
  "paidAt": "2026-05-15T10:00:00+09:00"
}

/v2/billing/invoices가 같은 의미를 제공하는지 contract test로 확인해야 합니다. 필드명이 바뀌어도 괜찮지만 의미가 사라지면 안 됩니다. 특히 금액 단위, 시간대, nullable 정책, 정렬 순서, pagination 방식은 깨지기 쉽습니다.

대체 API 전환 완료 기준은 아래처럼 잡을 수 있습니다.

  • 알려진 consumer의 contract test 통과율 100%
  • v1 대비 v2 응답 의미 차이 documented exception 0~N개 명시
  • 전환 대상 클라이언트의 v2 error rate가 v1 대비 1.2배 이하
  • 핵심 endpoint의 p95 latency가 v1 대비 150% 이하 또는 명시적 예외 승인
  • pagination/cursor/정렬 기준 변경 시 샘플 데이터 비교 테스트 통과

Contract test가 없으면 전환은 결국 “문서 보고 바꿔주세요"가 됩니다. 내부 API라도 최소한 golden response fixture를 남겨야 제거 후 회귀를 잡을 수 있습니다.

4) 관측 대시보드는 endpoint가 아니라 client 기준으로 본다

API deprecation 대시보드는 endpoint별 요청 수만 보면 부족합니다. 제거 결정은 “누가 아직 남았는가"를 알아야 하기 때문입니다. 최소한 아래 차원이 필요합니다.

  • endpoint, method, version
  • client_id, app_version, user_agent, partner_id
  • auth principal type: user/service/partner
  • region, environment
  • response code, latency, error class
  • replacement endpoint adoption

예를 들어 /v1/orders 요청이 하루 10만 건에서 1만 건으로 줄었다고 해도, 남은 1만 건이 특정 대형 파트너 한 곳이면 제거하면 안 됩니다. 반대로 100개 클라이언트가 각각 테스트 환경에서 몇 번 호출하는 수준이면 차단을 앞당길 수 있습니다.

초기 알림 기준은 이렇게 잡을 수 있습니다.

  • sunset 60일 전: known client owner에게 전환 일정 요청
  • sunset 30일 전: 미전환 client_id가 5개 이상이면 리스크 리뷰
  • sunset 14일 전: 핵심 client 미전환 시 sunset 연기 또는 allowlist 결정
  • sunset 7일 전: deprecated traffic이 전체 endpoint traffic의 1% 이상이면 full removal 금지
  • removal 당일: 410 응답률, support ticket, error budget burn을 30분 단위로 확인

5) 실패 응답도 마이그레이션 도구다

제거 후 404를 반환하면 클라이언트 입장에서는 “경로가 틀렸나? 서버 버그인가?“를 구분하기 어렵습니다. 의도적으로 제거한 API는 410 Gone을 쓰고, 가능한 경우 대체 경로를 함께 알려주는 편이 좋습니다.

{
  "error": "endpoint_sunset",
  "message": "This endpoint was sunset on 2026-08-31.",
  "replacement": "/v2/order-cancellations",
  "docs": "https://example.com/docs/api-migration/orders-v2",
  "requestId": "req_abc123"
}

실패 응답에는 requestId가 있어야 지원팀과 API 팀이 같은 요청을 추적할 수 있습니다. 단, 민감한 내부 정책이나 client별 사용량을 응답에 노출하면 안 됩니다. 에러는 친절해야 하지만 과도하게 상세하면 보안 정보가 됩니다.

트레이드오프/주의점

1) 너무 긴 deprecation은 플랫폼을 늙게 만든다

호환성을 지키는 것은 중요하지만, 영원한 v1 지원은 비용이 큽니다. 오래된 API는 인증 방식, 데이터 모델, 에러 코드, pagination, rate limit 정책이 새 기준과 어긋날 가능성이 높습니다. 신규 기능을 만들 때마다 legacy 분기를 넣으면 개발 속도가 느려지고 테스트 매트릭스가 폭발합니다.

그래서 sunset 없는 deprecation은 피해야 합니다. 정말 종료일을 못 박기 어렵다면 최소한 재검토 날짜를 둡니다. 예를 들어 “2026-08-31 sunset"이 어렵다면 “2026-08-31에 usage와 client readiness로 재승인"처럼 관리해야 합니다. 무기한 deprecated는 사실상 active와 다르지 않습니다.

2) 너무 빠른 removal은 신뢰를 깎는다

반대로 빠른 삭제는 팀 신뢰를 크게 해칩니다. API는 사용자의 코드와 연결된 계약입니다. 특히 외부 파트너 API에서 예고 없는 removal이 발생하면 기술 부채 정리보다 관계 비용이 더 큽니다. 내부에서도 플랫폼 팀이 임의로 API를 없앤다는 인식이 생기면, 다음 변경부터 팀들이 비공식 우회로를 만들기 시작합니다.

삭제를 앞당기고 싶다면 세 가지 조건이 필요합니다.

  • known client가 모두 전환했거나 owner가 명시적으로 승인했다.
  • 30일 이상 deprecated traffic이 실질적으로 0에 가깝다.
  • 410/route-back/allowlist 같은 복구 수단이 준비되어 있다.

이 조건 없이 “코드가 복잡해서"만으로 제거하면 운영 리스크가 큽니다.

3) 버전만 올리면 문제가 해결되는 것은 아니다

/v2를 만들었다고 deprecation이 자동으로 끝나지는 않습니다. v2가 더 느리거나, 필드 의미가 애매하거나, migration guide가 부족하면 클라이언트는 전환하지 않습니다. API 팀이 봐야 할 것은 “새 버전 출시"가 아니라 “업무 흐름 전환 완료"입니다.

특히 아래 변경은 문서만으로는 부족합니다.

  • offset pagination에서 cursor pagination으로 변경
  • sync API에서 async request-reply로 변경
  • enum 값의 의미 변경
  • monetary amount 단위 변경
  • timezone 기준 변경
  • nullable 필드의 required 전환

이런 변경은 샘플 코드, 변환 가이드, contract fixture, staged rollout을 같이 제공해야 합니다.

4) Gateway 차단과 애플리케이션 제거는 다른 단계다

API Gateway에서 먼저 차단하면 애플리케이션 코드를 건드리지 않고 removal 효과를 볼 수 있습니다. 하지만 gateway rule만 제거하고 코드가 남아 있으면 테스트 환경이나 내부 경로에서 다시 살아날 수 있습니다. 반대로 애플리케이션 코드를 먼저 지우면 gateway allowlist로 되돌리기 어렵습니다.

추천은 gateway restrict → 관측 → 애플리케이션 removal → gateway rule cleanup 순서입니다. 이렇게 하면 차단을 되돌릴 수 있는 기간을 확보하면서도 최종 부채 정리까지 갈 수 있습니다.

체크리스트 또는 연습

운영 체크리스트

  • 제거 대상 API의 owner, replacement, sunset date가 문서화되어 있다.
  • 최근 30일/90일 사용량을 client_id, app_version, partner_id 기준으로 확인했다.
  • 응답 헤더 또는 런타임 경고로 deprecation이 실제 호출자에게 전달된다.
  • 대체 API의 contract test 또는 golden response 비교가 준비되어 있다.
  • sunset 30일 전 미전환 클라이언트 목록과 owner가 확인된다.
  • removal은 feature flag, gateway rule, allowlist 중 하나로 단계 제어 가능하다.
  • 410 Gone 응답에 replacement, docs, requestId가 포함된다.
  • 제거 후 30~60분 동안 error rate, ticket, deprecated traffic을 모니터링한다.
  • legacy handler와 문서, SDK, 샘플 코드까지 정리하는 후속 작업이 잡혀 있다.

연습: /v1/search를 없애야 한다면

상황을 가정해 봅시다. /v1/search는 offset pagination을 쓰고, /v2/search는 cursor pagination과 relevance score를 제공합니다. v1은 하루 20만 건 호출되고, 그중 70%는 웹 프론트엔드, 20%는 모바일 앱, 10%는 외부 파트너입니다. 서버 팀은 6주 안에 v1 코드를 지우고 싶어 합니다.

이때 바로 6주 sunset을 잡으면 위험합니다. 웹 프론트엔드는 2~4주 안에 전환할 수 있지만, 모바일 앱과 외부 파트너는 더 길게 봐야 합니다. 합리적인 계획은 아래에 가깝습니다.

  1. 오늘부터 deprecation header와 usage dashboard를 켠다.
  2. 웹 프론트엔드는 4주 안에 v2 전환을 목표로 한다.
  3. 모바일 앱은 최소 3개월 window를 둔다.
  4. 외부 파트너는 계약 조건을 확인하고 6개월 또는 별도 allowlist를 검토한다.
  5. 서버 코드는 v2 기반 adapter로 v1 응답을 만들어 내부 중복을 줄인다.
  6. v1 full removal은 deprecated traffic이 1% 미만이고 known client가 모두 전환된 뒤 실행한다.

핵심은 코드 삭제 일정이 아니라 클라이언트 전환 가능성을 기준으로 계획을 나누는 것입니다. API deprecation을 잘하는 팀은 오래된 코드를 무작정 끌고 가지도, 사용자 계약을 무시하고 지우지도 않습니다. 숫자와 단계로 종료합니다.