API가 같으면 같은 시스템일까?
2026-08-18 · DEV · 약 10분 읽기
내부 구현이 바뀐 뒤에도 같은 시스템이라고 판단할 수 있는 조건을 API 호환성, 계약 준수, 관찰 가능한 동작, 성능 SLO로 나누어 설명하고 검증 방법을 정리합니다.
Chromium에서는 IndexedDB의 저장 backend를 기존 LevelDB 기반 구현에서 SQLite 기반 구현으로 전환하는 작업이 진행되고 있습니다.
웹 애플리케이션이 사용하는 IndexedDB API는 그대로인데 그 아래의 저장 엔진이 바뀌는 것입니다. 그렇다면 이 변경 이후에도 이전과 같은 IndexedDB, 더 일반적으로는 이전과 같은 시스템이라고 할 수 있을까요?
IndexedDB 저장 backend 전환
IndexedDB와 저장 backend의 관계는 다음과 같습니다.
웹 애플리케이션
↓
IndexedDB API / 명세
↓
Chromium 구현
↓
저장 backend
(LevelDB → SQLite)웹 애플리케이션이 직접 사용하는 것은 IndexedDB API입니다.
예를 들어 다음 코드는 저장 backend가 무엇인지 알지 못합니다.
const transaction = db.transaction(['users'], 'readwrite');
transaction.objectStore('users').put({ id: 1, name: 'Kim' });코드에서 LevelDB가 사용되는지 SQLite가 사용되는지는 드러나지 않습니다. 그러나 보이지 않는 내부 구현이 달라졌다는 사실만으로 같은 시스템인지 판단할 수는 없습니다.
이 질문에 답하려면 먼저 시스템의 무엇이 같아야 하는지 정해야 합니다.
무엇이 같아야 같은 시스템인가
저장 엔진, 데이터 구조, 실행 모델처럼 시스템 내부의 구현을 바꾸는 일은 흔합니다. 비용을 줄이기 위해서일 수도 있고, 안정성을 높이거나 새로운 기능의 기반을 만들기 위한 선택일 수도 있습니다.
같은 시스템인지 판단할 때 소스 코드가 같은지는 좋은 기준이 아닙니다.
리팩터링이나 컴파일러 변경만으로도 실행 경로는 크게 달라질 수 있고, 데이터베이스나 저장 엔진을 교체하면 내부 구조는 더 많이 바뀝니다.
단순히 API가 그대로 있다고 해서 같은 시스템이라고 말하기는 어렵습니다. 반대로 저장 엔진이나 내부 코드가 완전히 달라졌다고 해서 다른 시스템이 된 것도 아닙니다.
중요한 것은 내부 구현이 아니라 외부에 했던 약속이 유지되고 있는가입니다.
내부가 같은가?
↓
외부에 약속한 결과와 품질이 계속 유지되는가?그렇다고 시스템이 제공하는 기능까지 반드시 달라지는 것은 아닙니다. 대부분의 경우 외부에서 보는 시스템은 특정 저장 엔진이 아니라 그 위에 있는 인터페이스와 동작에 대한 약속으로 정의됩니다.
하지만 함수 이름과 인자만 같다고 충분한 것도 아닙니다.
예를 들어 주문 생성 API가 이전과 같은 요청을 받더라도,
- 중복 주문을 막지 못하거나
- 이전에 실패로 처리하던 상황을 성공으로 응답하거나
- 성공 응답 이후 실제 데이터가 저장되지 않는다면
API의 형태는 같아도 같은 동작을 제공한다고 보기 어렵습니다.
반대로 저장 방식이나 캐시 구조가 바뀌었더라도 이런 약속이 그대로 유지된다면 구현이 달라졌다는 이유만으로 다른 시스템이라고 할 필요는 없습니다.
여기서 말하는 외부 사용자는 사람만 의미하지 않습니다. 다른 서비스, SDK, 자동화 프로그램도 시스템을 호출하고 결과를 해석합니다. 이들이 의존하는 동작 역시 시스템의 외부 약속에 포함됩니다.
그렇다고 관찰할 수 있는 모든 동작을 그대로 유지해야 하는 것은 아닙니다. 쿼리 플랜이나 메모리 배치, 내부 worker 번호처럼 제품이 의존하지 않는 값까지 고정하면 내부 구현을 개선하기 어려워집니다.
결국 먼저 구분해야 하는 것은
어떤 동작이 외부에 대한 약속이고, 어떤 동작이 내부 구현의 자유인가?
이 경계를 정해야 내부 변경이 실제 호환성 문제인지, 허용 가능한 구현 차이인지 판단할 수 있습니다.
이를 판단하기 위한 기준은 다음 네 가지로 나눌 수 있습니다.
- API 호환성
- 계약 또는 명세 준수
- 관찰 가능한 동작
- 성능 SLO
같은 시스템인지 판단하는 네 가지 기준
내부 구현을 변경했다면 다음 네 가지를 나누어 확인할 수 있습니다.
| 기준 | 확인하는 질문 | 유지되어야 하는 것 |
|---|---|---|
| API 호환성 | 기존 호출자가 그대로 사용할 수 있는가? | 인터페이스, 입력 형식, 반환 형식 |
| 계약 또는 명세 준수 | 약속한 의미를 그대로 지키는가? | 성공·실패 조건, 상태 변화 |
| 관찰 가능한 동작 | 사용자가 경험하는 결과가 같은가? | 결과, 오류 의미, 외부 부작용 |
| 성능 SLO | 사용할 만한 품질이 유지되는가? | 지연 시간, 처리량, 오류율, 자원 사용 |
이 네 가지는 비슷해 보이지만 서로 다른 문제를 다룹니다.
1. API 호환성
가장 먼저 확인할 수 있는 것은 API 자체가 유지되었는지이며, 메서드 이름, 인자, 응답 형태, 오류 타입 등이 여기에 해당합니다.
예를 들어 기존 코드가 다음과 같다고 해보겠습니다.
save(record);새 구현에서도 같은 방식으로 호출할 수 있고 같은 형태의 값을 반환한다면 API 수준에서는 호환된다고 볼 수 있습니다. 이 부분은 컴파일, 타입 검사, 통합 테스트 등을 통해 비교적 빠르게 확인할 수 있습니다.
하지만 API 호환성은 시작점일 뿐입니다.
save()라는 함수가 그대로 있어도 저장 실패를 숨기거나 기존에 보장하던 중복 방지 규칙이 사라진다면 실제 의미는 달라집니다.
응답의 형태는 같지만 의미가 바뀌는 경우도 있습니다.
{
"success": true
}기존에는 success: true가 처리 완료를 의미했는데, 새 구현에서는 단순히 요청 접수를 의미한다면 API 형태는 같아도 계약은 달라진 것입니다.
그래서 API 호환성만으로 두 구현이 같다고 판단할 수는 없습니다.
2. 계약 또는 명세 준수
그다음은 API가 약속한 의미가 유지되는지를 봐야 합니다. 여기에는 사전 조건과 사후 조건이 포함됩니다.
- 사전 조건: 호출자가 지켜야 하는 조건
- 사후 조건: 요청이 끝난 뒤 시스템이 보장해야 하는 상태
예를 들어 저장 API라면 다음과 같은 약속이 있을 수 있습니다.
유효한 데이터를 전달한다.
↓
저장 요청이 성공한다.
↓
이후 조회하면 해당 데이터를 확인할 수 있다.표준 명세가 있는 시스템이라면 명세가 기준이 되고, 사내 API라면 API 문서나 스키마, 테스트의 기대값이 계약 역할을 할 수 있습니다.
여기서 주의할 점은 기존 구현에서 우연히 관찰됐던 동작과 실제 계약을 구분하는 것입니다.
예를 들어 어떤 구현에서 작업 A가 항상 작업 B보다 먼저 끝났다고 해도 명세가 그 순서를 보장하지 않았다면 단순한 구현 특성일 수 있습니다.
기존 구현이 그랬다는 이유만으로 모든 행동을 계약으로 받아들이면 정상적인 내부 변경까지 오류로 판단하게 됩니다.
3. 관찰 가능한 동작
API와 계약을 넘어 실제 사용자가 경험하는 결과도 확인해야 합니다.
여기에는 단순한 반환값뿐 아니라 다음과 같은 요소도 들어갑니다.
- 오류가 어떤 의미로 전달되는지
- 외부 시스템으로 어떤 이벤트가 발송되는지
- 화면에 어떤 상태가 표시되는지
- 실패한 요청을 다시 시도할 수 있는지
- 여러 작업의 순서가 제품 동작에 영향을 주는지
예를 들어 결제 시스템을 생각해 볼 수 있습니다.
결제 완료
↓
결제 완료 이벤트 발생
↓
배송 요청 생성배송 요청이 결제 완료 이후에만 생성되어야 한다면 이 순서는 단순한 내부 구현이 아니라 제품 동작입니다.
반면 요청을 처리한 worker가 1번인지 3번인지는 일반적으로 중요하지 않습니다. 즉, 관찰할 수 있다는 사실만으로 모두 계약이 되는 것은 아닙니다.
제품이나 다른 시스템이 실제로 의존하고 있는지를 기준으로 판단해야 합니다. 이 경계는 가능하면 내부 변경 전에 정해 두는 편이 좋습니다.
그래야 변경 이후 차이가 발견됐을 때 그것이 버그인지, 원래 허용한 차이인지 빠르게 구분할 수 있습니다.
4. 성능 SLO
마지막은 기능이 아니라 품질의 문제입니다.
두 구현이 정확히 같은 결과를 반환하더라도 새 구현의 응답 시간이 몇 배 느려졌다면 실제 제품에서는 성공적인 교체라고 보기 어려울 수 있습니다.
예를 들어 다음과 같은 목표가 있을 수 있습니다.
p99 latency < 300ms
error rate < 0.1%
memory usage < 1GBAPI의 반환값과는 전혀 다른 종류의 약속입니다.
성능을 확인할 때 평균값만 보는 것도 주의해야 합니다. 일부 요청에서 반복적으로 긴 지연이 발생하더라도 평균 응답 시간에서는 잘 드러나지 않을 수 있기 때문에 p95, p99와 같은 백분위 지표와 오류율을 함께 보는 편이 좋습니다.
또한 기능 계약과 성능 문제는 구분해서 말할 필요가 있습니다.
결과가 잘못됨
→ 계약 위반
결과는 같지만 느려짐
→ 성능 회귀 또는 SLO 위반이렇게 구분하면 내부 변경 이후 발생한 문제의 성격도 더 명확하게 설명할 수 있습니다.
IndexedDB 사례에 기준 적용
LevelDB가 사용되는지 SQLite가 사용되는지는 일반적인 웹 코드의 계약 대상이 아닙니다. 저장 backend가 바뀌더라도 기존 코드가 그대로 실행된다면 우선 API 호환성은 유지되고 있다고 볼 수 있습니다.
하지만 API 호환성만으로는 충분하지 않습니다.
쓰기 작업이 성공했다면 이후 데이터를 다시 읽을 수 있어야 하고, 실패한 경우에는 애플리케이션이 올바르게 실패를 인식할 수 있어야 합니다.
이 부분은 IndexedDB의 계약과 관찰 가능한 동작에 해당합니다. 또 backend가 달라지면서 내부 자원 관리 방식이나 실행 경로가 바뀐다면 작업의 지연 시간이나 완료 순서가 이전과 다르게 나타날 수도 있습니다.
이때 중요한 질문은 애플리케이션이 그 차이에 실제로 의존하고 있었는가? 입니다.
제품이 특정 완료 순서에 의존한다면 관찰 가능한 동작의 문제가 됩니다.반대로 명세에서도 보장하지 않고 애플리케이션도 사용하지 않는 순서라면 허용 가능한 구현 차이일 수 있습니다.
따라서 IndexedDB의 전환을 볼 때 핵심 질문은
"SQLite가 LevelDB와 같은가?"
가 아닙니다.
다음 세 가지를 묻는 편이 더 정확합니다.
IndexedDB API와 명세는 그대로 유지되는가?
애플리케이션이 의존하는 결과와 부작용은 유지되는가?
실제 사용에 필요한 성능 수준은 유지되는가?IndexedDB API와 명세의 계약, 애플리케이션이 의존하는 동작, 실제 사용에 필요한 성능 SLO가 유지된다면 저장 backend가 달라져도 웹 애플리케이션 입장에서는 같은 IndexedDB를 사용하고 있다고 볼 수 있습니다.
정리
내부 구현이 바뀌었다고 해서 반드시 다른 시스템이 되는 것은 아닙니다.
저장 엔진, 자료구조, 스케줄링 방식처럼 외부 계약에 포함되지 않는 구현은 얼마든지 바뀔 수 있습니다.
대신 변경 이후에는 네 가지를 확인할 수 있습니다.
API를 그대로 사용할 수 있는가?
약속했던 의미를 그대로 지키는가?
사용자가 경험하는 동작이 유지되는가?
필요한 성능 수준을 유지하는가?이 네 가지가 유지된다면 내부 기술이 크게 달라져도 같은 시스템이라고 판단할 근거가 있습니다.
서비스는 앞으로도 계속 변화하고 발전합니다. 그 과정에서 내부 구현이 바뀌는 것은 자연스러운 일입니다. 중요한 것은 그 변화가 고객이 서비스를 이용하며 느끼는 경험과 기존에 기대하던 동작을 깨뜨리지 않는 것이라고 생각합니다.
결국 내부 구현을 자유롭게 개선하면서도, 외부에 약속한 경험은 지켜내는 것이 좋은 시스템 변경의 기준이 아닐까 합니다.
긴 글 읽어주셔서 감사합니다.
참고 자료
- Chromium blink-dev,
Web-Facing Change PSA: IndexedDB: SQLite backend - W3C,
Indexed Database API 3.0 - Google LevelDB,
Implementation notes - SQLite,
Isolation In SQLite - SQLite,
Transaction - SQLite,
Write-Ahead Logging
같은 카테고리의 글
DEV 글 더 읽기
이전 글
결제 시스템에서 상태 기반 테스트를 설계하는 방법
다음 글
이어지는 글이 없습니다