여러분은 하위 호환성은 언제까지 유지해야 한다고 생각하시나요? 개발할 때 모든 변경을 이전 버전과 호환할 필요는 없습니다. 다만 다음과 같은 상황에서는 먼저 확인해야 합니다.
저는 최근 운영 중인 서비스에 새 기능을 배포하면서, 마이그레이션은 정상적으로 끝났는데 배포는 실패하는 상황을 실제로 겪었습니다. 기존 데이터베이스 필드를 새 필드로 바꾸고 API 규격도 함께 변경하는 배포였습니다. 약 10대의 인스턴스 중 첫 번째 서버가 새 버전으로 교체되면서 마이그레이션을 실행했고, 작업은 정상적으로 완료됐습니다. 하지만 롤링 배포가 끝나기 전까지 나머지 서버는 이전 버전으로 요청을 처리하고 있었습니다. 변경된 데이터 구조를 이해하지 못한 이전 버전 서버에서 약 10분 동안 오류가 반복됐습니다.

결국 하위 호환성은 외부 고객사의 API 연동, 늦은 앱 업데이트, 이벤트 재처리처럼 신·구 버전이 운영 환경에서 공존하는 상황의 문제입니다. 인프라와 배포 전략은 코드 밖에 있지만, 코드가 안전하게 동작할지를 결정합니다. AI 에이전트에게 기능 구현만 요청하면 이런 맥락을 빠뜨릴 수 있습니다.
이 문제는 특정 서비스나 배포 방식에서만 발생하는 예외가 아닙니다. 새로운 규격을 도입하더라도 이전 규격을 사용하는 대상이 한동안 남는 상황은 여러 소프트웨어 시스템에서 반복됩니다. 그렇다면 새로운 버전이 준비됐는데도, 왜 우리는 기존 사용자가 따라올 때까지 기다려야 할까요?
새 버전을 배포했다고 해서 모든 대상이 동시에 업데이트되는 것은 아닙니다. 내가 운영하는 서버는 롤링 배포가 끝날 때까지 이전 버전이 남고, 외부 기업은 각자의 일정에 따라 기존 API를 계속 사용할 수 있습니다. 웹에는 몇 년 전 작성된 문서가 그대로 남고, 사용자의 앱도 업데이트 시점이 제각각입니다.
모든 대상을 한 번에 바꿀 수 없다면, 새로운 규격을 도입하는 일과 기존 규격을 제거하는 일을 나누어 설계해야 합니다. 새 버전을 권장하는 순간 기존 버전을 바로 없앨 수 있는 것은 아니기 때문입니다.
이런 특성을 오래전부터 보여준 사례가 웹입니다. HTML 표준이 바뀌었다고 해서 웹에 공개된 모든 문서를 새로운 문법으로 다시 작성할 수는 없습니다. 새로운 브라우저도 과거에 작성된 문서를 계속 읽을 수 있어야 합니다.
이런 상황을 잘 보여주는 요소가 <center>입니다. 한때는 웹 페이지의 내용을 가운데 정렬하기 위해 자주 사용됐습니다.
<center>이 문장은 가운데 정렬됩니다.</center>
<center>는 HTML 3.2에 포함됐지만, HTML 4.0이 권고된 1997년부터 사용 중단 권고(deprecated) 요소로 분류됐습니다. 2026년 기준으로 약 29년째 새 문서에서 사용하지 않도록 권고되고 있는 셈입니다. (이후에는 CSS의 text-align을 사용하는 방식이 권장됐습니다.)
그렇다고 기존 문서의 <center>가 곧바로 작동을 멈춘 것은 아닙니다. 브라우저는 과거의 웹 문서를 깨뜨리지 않기 위해 이 요소를 계속 해석해 왔습니다. 새로 사용하지 않도록 권장하는 것과 기존 사용을 더 이상 지원하지 않는 것은 다른 문제입니다.
이 원리는 웹 서비스에도 그대로 적용됩니다. API 응답, 데이터 구조, 이벤트 형식 역시 다른 시스템이 의존하는 약속입니다. 새로운 규격을 권장하는 일과 기존 규격을 즉시 제거하는 일을 분리해야 하는 이유입니다.
기업용 API(B2B API)는 제공자가 새 규격을 배포한다고 해서 모든 고객사가 곧바로 코드를 바꿀 수 있는 구조가 아닙니다. 예를 들어 여러 기업이 같은 결제 API를 사용하고 있다면, 고객사마다 개발 일정과 검증 절차가 달라 새 버전으로의 전환 시점도 달라집니다.
그래서 외부에 제공하는 API를 변경할 때는 기존 필드를 바로 삭제하기보다 새 필드를 먼저 추가하고, 일정 기간 두 필드를 함께 처리하는 방식을 검토합니다. 이때 기존 필드를 언제까지 유지할지, 어떤 조건이 충족되면 제거할지도 미리 정해야 합니다.
지금까지 살펴본 사례를 정리하면, 다음 조건에서 하위 호환성을 먼저 확인해야 합니다.
이러한 조건에 해당한다면 새 버전이 기존 데이터를 읽을 수 있는지만 확인해서는 부족합니다. 이전 버전의 코드가 새 버전이 만든 데이터를 만났을 때도 기대한 대로 동작하는지 확인해야 합니다.
그렇다면 “호환된다”는 말은 정확히 무엇을 의미할까요? 코드를 실행할 수 있고, 서로 통신할 수 있으며, 이전과 같은 의미로 동작한다는 것은 각각 어떤 차이가 있을까요?
앞선 문단에서는 이전 규격을 사용하는 대상이 남아 있다면 새 버전을 바로 적용하기 어렵다는 점을 살펴봤습니다. 그렇다면 “호환된다”는 말은 정확히 무엇을 뜻할까요? 이전 코드가 오류 없이 응답을 받았지만, 그 안의 값이 이전과 다른 의미를 갖는다면 어떨까요?
Google API 설계 가이드의 하위 호환성 원칙에는 다음과 같은 문장이 있습니다.
APIs are fundamentally contracts with users.
API는 사용자와 맺은 계약입니다. 계약을 지킨다는 것은 함수 이름이나 JSON 필드가 남아 있다는 의미를 넘어, 상대방이 이전에 기대한 방식으로 계속 사용할 수 있어야 한다는 뜻입니다.
Google은 API 호환성을 세 가지 관점으로 나눠 설명합니다.
특히 세 번째로 설명한 의미 호환성은 테스트에서 놓치기 쉽습니다. 응답 코드가 200으로 돌아오고 JSON 파싱까지 성공하면 문제가 없다고 판단하기 쉽기 때문입니다. 하지만 클라이언트가 READY를 “결제 전”으로 해석하는 상황에서 서버가 같은 값을 “처리 중”으로 바꾸면, 통신은 성공해도 서비스의 동작은 달라집니다.
필드의 자료형과 길이, 기본값을 바꾸는 경우도 마찬가지입니다. 코드가 깨지지 않더라도 클라이언트가 받는 값과 사용자가 보는 결과가 달라질 수 있습니다.
그렇다면 새 필드를 추가하면 항상 안전할까요? 주문 조회 API가 order_id와 total_price만 반환해 왔다고 해보겠습니다. 여기에 discount_price를 추가했을 때 모르는 필드를 무시하는 클라이언트는 이전처럼 동작합니다. 반면 정의되지 않은 필드가 있으면 오류를 내는 클라이언트라면 같은 변경이 장애로 이어질 수 있습니다.

응답이 아니라 요청에 새 필드를 추가하는 경우는 더 조심해야 합니다. 새 필드를 필수로 만들면 이전 클라이언트는 값을 보낼 수 없습니다. 새로운 상태값 역시 오류로 처리되거나 기존 상태로 잘못 해석될 수 있습니다. 필드를 추가하는지 삭제하는지만으로 안전성을 판단하기 어려운 이유입니다.
변경 전에 다음 질문을 확인해야 합니다.
이 원칙은 데이터베이스의 기존 필드를 새 필드로 바꾸는 작업에도 적용됩니다. 새 버전이 새 필드를 읽는지만 볼 것이 아니라, 이전 버전이 기존 필드를 읽고 쓰는 동안 두 필드가 어떻게 공존할지도 확인해야 합니다.
결국 호환성은 변경의 모양이 아니라, 변경을 모르는 상대 시스템의 반응으로 판단해야 합니다. API의 상대가 클라이언트라면, 이벤트의 상대는 발행자와 소비자입니다. 이벤트에서는 이 관계가 어떻게 나타나는지 살펴봅니다.
앞선 문단에서 살펴본 API는 클라이언트와 서버가 서로 다른 버전으로 실행될 수 있다는 점에서 호환성이 문제였습니다. 이벤트는 여기에 한 가지 조건이 더 붙습니다. 한 서비스의 변화를 여러 서비스에 전달하고, 발행자와 소비자가 각자의 일정에 따라 바뀌기 때문입니다. 게다가 발행된 이벤트가 큐나 로그에 남아 나중에 다시 처리될 수도 있습니다.
API가 요청을 보낸 상대에게 응답하는 통로라면, 이벤트는 한 서비스에서 발생한 사실을 여러 서비스에 전달하는 매개체입니다. UI가 사용자의 행동을 특정 기능으로 연결하는 접점이라면, 이벤트는 서비스 사이에서 발생한 변화를 연결하는 접점에 가깝습니다. 예를 들어 주문 서비스가 결제 완료 사실을 발행하면, 이를 받은 서비스는 각자의 역할에 따라 후속 작업을 처리합니다. 발행자는 어떤 서비스가 이벤트를 읽는지 알지 못해도 되고, 소비자는 각자의 방식과 속도로 반응할 수 있습니다.
이런 구조에서는 발행자와 소비자가 같은 날 업데이트된다는 보장이 없습니다. 메시지 브로커에 이미 발행된 이벤트는 발행자나 소비자의 코드와 이벤트 규격이 바뀌어도 바로 사라지지 않을 수 있습니다. 따라서 새 발행자가 만든 이벤트를 이전 소비자가 받을 수도 있고, 새 소비자가 오래된 이벤트를 다시 읽을 수도 있습니다.
주문 서비스가 결제 완료 이벤트에 payment_method 필드를 추가했다고 해보겠습니다. 알림 서비스가 모르는 필드를 무시하면 기존처럼 동작하지만, 정해진 필드만 허용한다면 이벤트 처리가 실패할 수 있습니다.
새로운 상태값은 더 까다롭습니다. 알림 서비스가 PAID와 CANCELED만 알고 있는데 새 발행자가 REFUNDED를 보내면, 이를 오류로 처리하거나 기존 상태로 잘못 해석할 수 있습니다. 필드보다 상태값 하나가 더 위험할 때도 있는 이유입니다.

따라서 이벤트를 변경할 때는 “새 필드를 추가했으니 기존 소비자에게는 영향이 없다”고 단정하기 어렵습니다. 소비자가 모르는 필드를 무시하는지, 새로운 상태를 안전하게 처리하는지, 같은 이벤트를 다시 받아도 문제가 없는지까지 확인해야 합니다.
이벤트와 메시지의 호환성을 다룰 때는 “누가 먼저 바뀌는가”를 봐야 합니다. Confluent의 Schema Registry 문서에서는 이를 다음과 같이 구분합니다.

이 구분은 모든 시스템에 똑같이 적용되지는 않습니다. 그래도 새로운 계약을 도입할 때 “새 버전이 옛 데이터를 읽는가?”와 “옛 버전이 새 데이터를 읽는가?”를 나눠 묻는 데 유용합니다.
이 구분이 모든 시스템에 똑같이 적용되는 것은 아닙니다. 이벤트 형식과 직렬화 방식, 스키마를 관리하는 도구에 따라 허용되는 변경이 달라질 수 있습니다. 그래도 새로운 계약을 도입할 때 “새 버전이 옛 데이터를 읽는가?”와 “옛 버전이 새 데이터를 읽는가?”를 나눠 묻는 데에는 유용한 기준입니다.
이벤트를 변경할 때는 다음을 확인해야 합니다.
이벤트 변경은 새 형식을 만드는 데서 끝나지 않습니다. 이전 형식을 사용하는 소비자와 양쪽 형식을 처리할 기간, 기존 형식을 제거할 조건까지 함께 설계해야 합니다. 이제 이 원칙을 AI 활용에 적용해 보겠습니다.
앞서 살펴본 API와 이벤트에서 공통적으로 드러난 사실이 있습니다. 새 규격을 만들었다고 해서 이전 규격에 의존하는 코드와 데이터가 바로 사라지는 것은 아닙니다. 이전 버전의 서버와 클라이언트, 이미 발행된 이벤트와 기존 데이터가 남아 있는 동안에는 새 구조만 기준으로 변경할 수 없습니다.
호환성 있는 변경은 이전 규격을 무조건 보존하는 일이 아닙니다. 새로운 규격으로 이동할 시간을 확보하고, 그 시간이 끝났다는 근거를 확인한 뒤 정리하는 일입니다.
기존 형식을 바로 삭제하지 않고 변경을 여러 단계로 나누는 방법은 데이터베이스뿐 아니라 API와 이벤트에도 적용할 수 있습니다. 소프트웨어 설계와 리팩터링 분야에서 널리 알려진 마틴 파울러(Martin Fowler)는 이를 확장(expand), 이동(migrate), 축소(contract) 단계로 설명합니다.
이 순서의 핵심은 새 구조를 먼저 만드는 데 있지 않습니다. 이전 구조를 사용하는 상대가 남아 있는 동안에는 그 구조를 지워서는 안 된다는 데 있습니다.
AI를 예로 들면, 모델들도 지원 종료 시점을 미리 공유합니다. Google은 Gemini API 모델별 종료 일정을 공식 문서로 안내하고 있습니다.

새로운 모델로 이동할 시간을 사용자에게 제공한다는 점에서, AI API 역시 일정 기간의 호환성을 전제로 운영된다고 볼 수 있습니다.
호환 기간을 정하지 않으면 임시 코드가 영구 코드가 됩니다. 반대로 종료 조건을 미리 정해 두면, 호환성을 유지하는 일과 오래된 규격을 정리하는 일을 하나의 변경 계획 안에서 다룰 수 있습니다.
따라서 AI에게 구현을 요청할 때는 변경할 코드만 설명해서는 부족합니다. 아직 이전 형식을 사용하는 코드가 남아 있는지, 기존 데이터와 이벤트가 다시 처리될 수 있는지, 새 형식과 이전 형식을 언제까지 함께 지원할지까지 전달해야 합니다.
AI는 코드에서 변경 지점과 테스트 사례를 빠르게 찾을 수 있습니다. 하지만 코드 밖에 남아 있는 의존성과 지원 종료 시점까지 스스로 알 수는 없습니다. 호환성은 AI가 자동으로 보장하는 기능이 아니라, 사람이 설계하고 검증해야 할 조건입니다.
코드는 오늘 바꿀 수 있어도, 이미 배포된 코드와 데이터는 오늘 바뀌지 않습니다. 하위 호환성은 그 시간차를 안전하게 설계하는 일입니다.
하위 호환성을 생각하다 보면 결국 하나의 질문으로 돌아오게 됩니다. 새로운 코드를 만들 수 있는가가 아니라, 기존 코드와 새로운 코드가 함께 존재하는 시간을 어떻게 지나갈 것인가입니다.
서버를 배포하는 순간에도 이전 인스턴스가 남아 있을 수 있고, API를 변경한 뒤에도 기존 클라이언트는 계속 요청을 보낼 수 있습니다. 이벤트 규격을 바꿔도 과거에 발행된 메시지는 다시 처리될 수 있습니다. 새로운 버전이 준비됐다는 사실과 이전 버전을 제거해도 된다는 사실은 같지 않습니다.
AI는 이런 변경을 이전보다 훨씬 빠르게 구현해 줄 수 있습니다. 그러나 어떤 시스템이 아직 이전 규격에 의존하고 있는지, 얼마나 오래 함께 지원해야 하는지, 언제 제거해도 되는지는 결국 운영 환경을 이해하는 사람이 판단해야 합니다.
그래서 하위 호환성은 오래된 코드를 남겨 두는 기술이라기보다 변화가 시스템 전체에 도달할 때까지의 시간을 설계하는 기술에 가깝습니다. 코드는 빠르게 바꿀 수 있습니다. 중요한 것은 그 변화를 받아들일 준비가 되지 않은 대상까지 함께 바꾸려 하지 않는 것입니다.
*이 글은 AI의 도움을 받아 작성했습니다.
ⓒ요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.