TIL - 20261002

juni·4일 전

TIL

목록 보기
468/472

1002 운영 자동화/AI 워크플로우 심화 (26/N): API·Event·DB Versioning, Backward Compatibility와 Zero-Downtime Migration


✅ 1. 배포 순간에는 구버전과 신버전이 동시에 존재할 수 있다

배포를 단순하게 생각하면:

v1 서버 종료
↓
v2 서버 시작

처럼 보인다.

하지만 실제 운영에서는 잠깐이라도:

Old Version
+
New Version

이 동시에 존재할 수 있다.

예:

Instance A
v1

Instance B
v2

또는:

Frontend
구버전 캐시

Backend
신버전

일 수도 있다.


✅ 2. 그래서 “새 코드에서만 동작하면 된다”는 생각이 위험하다

예를 들어 Backend Response를:

{
  "phone": "010..."
}

에서 갑자기:

{
  "phoneNumber": "010..."
}

로 바꿨다고 하자.

신규 Frontend는 정상이다.

하지만 아직 구버전 Frontend가:

response.phone

을 사용하고 있다면 바로 깨진다.


✅ 3. Backward Compatibility란?

쉽게 말하면:

새로운 버전이 기존 Client·Consumer·데이터와도 일정 기간 정상적으로 동작할 수 있는 성질

이다.

즉:

New Backend
+
Old Frontend

도 동작하고,

가능하다면:

Old Backend
+
New Frontend

도 일정 범위에서 동작하도록 설계한다.


✅ 4. Forward Compatibility와는 조금 다르다

개념적으로:

Backward Compatibility
신버전이 구버전을 이해함
Forward Compatibility
구버전이 미래 데이터를 어느 정도 견딤

으로 볼 수 있다.

실무에서는 우선 Backward Compatibility를 훨씬 자주 신경 쓴다.


✅ 5. Zero-Downtime Deployment의 핵심도 호환성이다

서버를 끄지 않는다고 자동으로 무중단 배포가 되는 것은 아니다.

실제로는:

Old Code
+
New Code
+
Current DB Schema

조합이 모두 잠시 공존할 수 있어야 한다.


✅ 6. 가장 흔한 문제는 DB Schema 변경이다

예:

orders.customer_phone

Column을:

orders.phone_number

로 Rename했다고 하자.

신버전은:

phone_number

을 읽는다.

하지만 구버전 서버는 계속:

customer_phone

을 읽는다.

Rename 순간 구버전이 깨질 수 있다.


✅ 7. 그래서 Production에서는 “바로 Rename”을 피하는 경우가 많다

대신:

Add
↓
Migrate
↓
Switch
↓
Remove

순서로 진행한다.

이게 0927에서 언급한:

Expand
→ Migrate
→ Contract

패턴과 연결된다.


✅ 8. Expand 단계

기존 Column은 유지하고 새 Column만 추가한다.

기존:

customer_phone

추가:

phone_number

이제 DB에는 둘 다 존재한다.


✅ 9. 이 단계에서는 구버전도 계속 동작한다

구버전:

customer_phone

사용.

신버전:

phone_number

을 사용할 준비를 한다.

즉 아직 Breaking Change가 없다.


✅ 10. Dual Read / Dual Write가 필요할 수 있다

Migration 중에는 잠깐:

Old Column
+
New Column

둘 다 다뤄야 할 수 있다.

예:

phoneNumber =
  row.phoneNumber ??
  row.customerPhone;

처럼 Read fallback을 둔다.


✅ 11. Dual Write

새 데이터 저장 시:

customer_phone

phone_number

둘 다 동일하게 저장할 수도 있다.

예:

await tx.order.update({
  data: {
    customerPhone: phone,
    phoneNumber: phone,
  },
});

✅ 12. Dual Write는 영구 구조가 아니다

Migration 기간에만 사용하는 임시 호환 계층이다.

장기간 유지하면:

두 값 불일치

코드 복잡도 증가

어느 Column이 Source of Truth인지 혼란

이 생긴다.


✅ 13. Source of Truth를 정한다

예:

Migration 초기
customer_phone = Source of Truth

이후:

Migration 완료
phone_number = Source of Truth

로 전환한다.


✅ 14. Backfill

기존 Row의 새 Column을 채운다.

예:

UPDATE orders
SET phone_number = customer_phone
WHERE phone_number IS NULL;

하지만 Production에서는 한 번에 모든 Row를 갱신하면 위험할 수 있다.


✅ 15. 대량 Backfill은 Batch 처리한다

예:

1,000 rows
↓
Commit
↓
1,000 rows
↓
Commit

형태로 처리한다.


✅ 16. Batch Migration 이유

대규모 Update는:

Long Transaction

DB Lock

WAL 증가

CPU/IO 증가

API Latency 상승

을 만들 수 있다.


✅ 17. Migration도 운영 Job으로 본다

예:

MIGRATION_JOB

Total
1,000,000

Processed
250,000

Remaining
750,000

처럼 관리할 수 있다.


✅ 18. Backfill Resume가 가능해야 한다

중간에 서버가 죽더라도:

0부터 다시

하지 않는다.

마지막 Cursor나 조건을 기준으로 이어간다.


✅ 19. Migration도 Idempotent하면 좋다

예:

UPDATE orders
SET phone_number = customer_phone
WHERE phone_number IS NULL;

은 여러 번 실행해도 이미 채운 Row는 건너뛴다.

이런 형태가 안전하다.


✅ 20. Backfill 완료 검증

단순히 Job이 성공했다고 끝내지 않는다.

확인:

phone_number IS NULL
0건

Old / New 불일치
0건

같은 검증이 필요하다.


✅ 21. Read Switch

Backfill이 완료되면 신버전 코드에서:

phone_number

을 Primary Read로 사용한다.

필요하다면 잠깐 fallback을 유지한다.


✅ 22. Write Switch

이후 새 Column만 쓰게 바꿀 수 있다.

단 아직 구버전 Instance가 존재한다면 Old Column Write를 너무 빨리 중단하면 안 된다.


✅ 23. Contract 단계

충분히 안정화된 뒤:

customer_phone

을 제거한다.

이게 마지막 단계다.


✅ 24. Column 제거가 가장 마지막이어야 하는 이유

삭제는 되돌리기 어렵다.

새 코드 문제
→ 이전 코드 Rollback

을 하려는데 Old Column이 이미 삭제됐다면 Rollback이 깨질 수 있다.


✅ 25. Zero-Downtime Migration 핵심 원칙

Additive Change 먼저

Destructive Change 나중

이다.


✅ 26. Additive Change 예시

안전한 편:

새 Column 추가

새 Table 추가

새 Index 추가

새 Event Field 추가

새 API Response Field 추가

이다.


✅ 27. Destructive Change 예시

위험:

Column 삭제

Column Rename

Table 삭제

기존 API Field 제거

기존 Event Field 제거

Status 값 의미 변경

이다.


✅ 28. API Versioning도 같은 문제다

기존 API:

GET /api/orders

Response:

{
  "id": "...",
  "status": "WAITING"
}

새 요구사항으로 status 구조를 바꾸고 싶다고 하자.


✅ 29. 기존 Field 의미를 갑자기 바꾸면 안 된다

나쁜 변경:

{
  "status": {
    "code": "WAITING",
    "label": "대기"
  }
}

기존 Client는 문자열을 기대하고 있다.


✅ 30. 더 안전한 변경

기존 Field는 유지한다.

{
  "status": "WAITING",
  "statusInfo": {
    "code": "WAITING",
    "label": "대기"
  }
}

신규 Client는 statusInfo를 사용한다.


✅ 31. 이후 사용처가 모두 전환된 뒤 Old Field 제거를 검토한다

즉 API에서도:

Expand
→ Migrate Clients
→ Deprecate
→ Remove

흐름이 있다.


✅ 32. API Version을 무조건 /v2로 만들 필요는 없다

작은 Additive Change는 기존 API에 추가해도 된다.

예:

새 Optional Field 추가

정도는 Version 증가 없이 가능할 수 있다.


✅ 33. API Version이 필요한 경우

예:

Response 구조 대규모 변경

Field 의미 변경

필수 Request 구조 변경

행동 자체 변경

같은 Breaking Change가 있을 때다.


✅ 34. URL Versioning

예:

/api/v1/orders

/api/v2/orders

가 가장 이해하기 쉬운 방식 중 하나다.


✅ 35. Header Versioning도 가능하다

예:

Accept-Version: 2

같은 형태도 가능하지만 현재 규모에서는 URL Versioning이 더 단순할 수 있다.


✅ 36. Version을 너무 많이 만들면 유지보수가 어려워진다

예:

v1
v2
v3
v4

를 모두 장기간 지원하면:

Controller 증가

Service 분기

Test 증가

Bug Fix 중복

이 발생한다.


✅ 37. Versioning보다 Compatibility가 먼저다

작은 변경마다 v2를 만드는 것보다:

Additive Change

로 해결 가능한지 먼저 본다.


✅ 38. API Deprecation

기존 API를 바로 삭제하지 않고:

Deprecated

상태를 둔다.

예:

GET /api/v1/orders

는 아직 동작하지만 신규 개발에서는 사용하지 않는다.


✅ 39. Deprecation Metadata

내부 문서에:

Deprecated At

Replacement

Removal Target

Known Consumers

를 적는다.


✅ 40. 내부 Admin API라도 Consumer를 확인한다

현재 Frontend만 쓴다고 생각했는데:

Excel Script

Chrome Extension

Automation Tool

Partner Integration

이 사용할 수 있다.

삭제 전에 실제 Consumer를 확인한다.


✅ 41. API Usage Logging

Deprecated Endpoint 사용량을 측정할 수 있다.

예:

/api/v1/orders

최근 7일 요청
0

이면 제거 후보가 된다.


✅ 42. 사용량 0이어도 바로 삭제하지는 않는다

특정 월말 Batch처럼 드물게 호출되는 Consumer가 있을 수 있다.

업무 주기를 고려한다.


✅ 43. Request Compatibility

새 Field를 필수로 추가하면 기존 Client가 깨질 수 있다.

기존:

{
  "orderId": "123"
}

새 API:

{
  "orderId": "123",
  "source": "NAVER"
}

에서 source를 필수로 만들면 Old Client가 실패한다.


✅ 44. Migration 기간에는 Optional로 둔다

source absent
→ DEFAULT / legacy behavior

로 처리한다.

Client가 모두 전환된 뒤 필수화를 검토한다.


✅ 45. Default 의미가 안전해야 한다

예:

source 미입력
→ UNKNOWN

처럼 의미가 명확해야 한다.

잘못된 비즈니스 값을 임의 Default로 넣지 않는다.


✅ 46. Enum 변경도 Breaking Change가 될 수 있다

기존:

WAITING
DONE

신규:

WAITING
PROCESSING
DONE

을 추가했다고 하자.

Old Frontend가:

switch (status) {
  case 'WAITING':
  case 'DONE':
}

만 처리한다면 새 값에서 UI가 깨질 수 있다.


✅ 47. Consumer는 Unknown Enum을 견디는 것이 좋다

예:

default:
  return '처리 중';

처럼 안전한 fallback을 둔다.


✅ 48. 하지만 중요한 상태에서는 무조건 fallback도 위험하다

예:

REFUNDED

CANCELLED

을 모두:

UNKNOWN

으로 숨기면 운영자가 중요한 상태를 놓칠 수 있다.

따라서 Unknown 상태를 명시적으로 보여주는 편이 낫다.


✅ 49. Event Versioning은 API보다 더 중요할 수 있다

1001에서 Outbox Event를 다뤘다.

Event Producer와 Consumer는 배포 시점이 다를 수 있다.

즉:

Producer v2

Consumer v1

조합이 잠시 존재할 수 있다.


✅ 50. Event Payload Additive Change

기존:

{
  "version": 1,
  "orderId": "123",
  "status": "DONE"
}

신규:

{
  "version": 1,
  "orderId": "123",
  "status": "DONE",
  "carrier": "LGU"
}

Consumer가 Unknown Field를 무시할 수 있다면 호환 가능하다.


✅ 51. 기존 Field 삭제는 위험하다

Consumer v1이:

status

를 요구하는데 Producer가 없애버리면 DLQ가 발생할 수 있다.


✅ 52. Breaking Event는 새 Version으로 만든다

예:

ORDER_STATUS_CHANGED_V1

ORDER_STATUS_CHANGED_V2

또는:

{
  "eventType": "ORDER_STATUS_CHANGED",
  "version": 2
}

형태다.


✅ 53. Consumer는 지원 Version을 명확히 한다

예:

switch (event.version) {
  case 1:
    return handleV1(event);

  case 2:
    return handleV2(event);

  default:
    throw new UnsupportedEventVersionError();
}

✅ 54. Producer부터 v2만 내보내면 위험할 수 있다

먼저 Consumer가 v2를 이해하도록 배포한다.

즉:

Consumer First

↓

Producer Second

전략이다.


✅ 55. Event Migration 순서

예:

1. Consumer가 V1 + V2 지원

2. Producer가 V2 발행 시작

3. V1 Event backlog/DLQ 소진 확인

4. V1 Producer 중단

5. 충분한 기간 후 V1 Consumer 제거

이다.


✅ 56. 이것이 Expand/Contract를 Event에 적용한 형태다

Schema뿐 아니라 Event에서도 동일하다.


✅ 57. Event Consumer가 여러 개라면 더 신중해야 한다

예:

Notification

Analytics

CRM

세 Consumer 모두 v2를 지원해야 한다.

하나라도 v1만 지원하면 Producer 전환 시 깨질 수 있다.


✅ 58. Consumer Compatibility Matrix

예:

ConsumerV1V2
NotificationOO
AnalyticsOO
CRMOX

이 경우 아직 v2 전환이 완료된 것이 아니다.


✅ 59. Event Registry에 지원 Version을 기록할 수 있다

예:

ORDER_STATUS_CHANGED

Producer
v2

Consumers
notification: v1,v2
analytics: v1,v2
crm: v1

같이 본다.


✅ 60. 지금은 자동 Registry까지 만들 필요는 없다

문서나 코드 상수로도 충분하다.

핵심은 Breaking Change가 누구에게 영향을 주는지 파악하는 것이다.


✅ 61. Database Schema Version과 Application Version

앱 버전:

release_20261002_03

DB Migration 상태:

migration_20261002_02

를 별도로 추적하면 좋다.


✅ 62. Health Endpoint에서 Migration 상태 확인

예:

{
  "releaseId": "release_20261002_03",
  "dbMigrationVersion": "2026100202"
}

정도다.

민감 정보를 노출할 필요는 없다.


✅ 63. App이 기대하는 최소 Schema Version

예:

minimumDbVersion
2026100202

보다 DB가 오래되면 서버 시작을 차단할 수도 있다.


✅ 64. 반대로 DB가 너무 앞선 경우도 문제다

예:

DB는 destructive migration 완료

Old App rollback

하려는데 Old App이 필요한 Column이 사라졌다.

그래서 Migration과 Rollback Compatibility를 함께 봐야 한다.


✅ 65. Migration Compatibility Window

DB Schema가 일정 기간:

Old App
+
New App

둘 다 지원하도록 유지한다.

이게 무중단 배포에서 중요하다.


✅ 66. Nullable → NOT NULL 변경도 단계적으로

기존 Column:

source NULL 허용

새 Requirement:

source NOT NULL

이라고 하자.

바로 Constraint를 추가하면 기존 데이터 때문에 실패할 수 있다.


✅ 67. 안전한 순서

1. Application에서 신규 Row에 source 저장

2. 기존 NULL Backfill

3. NULL 남은 Row 검증

4. NOT NULL Constraint 추가

이다.


✅ 68. Constraint도 Migration 마지막 쪽에서 강화한다

처음부터 강하게 넣으면 Old App이 여전히 NULL을 넣을 수 있다.


✅ 69. Default 추가도 주의한다

대규모 Table에 Default + NOT NULL을 한 번에 적용하면 DB 버전/상황에 따라 비용이 클 수 있다.

현재 Table 규모와 실제 SQL을 확인한다.


✅ 70. Index 추가도 Zero-Downtime 관점이 있다

대형 Table에서 일반 Index 생성이 Write를 방해할 수 있다.

Production에서는 Database가 지원하는 온라인/동시 생성 방식을 검토할 수 있다.


✅ 71. Prisma Migration도 생성 SQL을 확인한다

ORM을 사용한다고 안전성이 자동 보장되는 것은 아니다.

특히:

DROP

RENAME

NOT NULL

UNIQUE

INDEX

변경은 실제 Migration SQL을 확인한다.


✅ 72. Migration Review Checklist

Table Lock 가능성

Full Table Scan

대량 Rewrite

기존 데이터 호환

Rollback 가능성

Old App Compatibility

를 확인한다.


✅ 73. Rename은 Add + Backfill + Remove로 풀 수 있다

즉:

RENAME COLUMN

보다:

ADD new_column

Backfill

Application Switch

DROP old_column

가 더 안전할 수 있다.


✅ 74. Table Rename도 비슷하다

기존 Table을 바로 Rename하는 대신 새 Table로 Migration 후 점진적으로 전환할 수 있다.

다만 작은 내부 프로젝트에서는 과도할 수 있으므로 실제 위험도에 맞춘다.


✅ 75. Data Type 변경도 Breaking Change다

예:

price
INTEGER

를:

BIGINT

으로 바꾸는 건 비교적 단순할 수 있지만,

VARCHAR
→ JSON

같은 변경은 훨씬 크다.


✅ 76. 새 Column로 변환하는 방법

예:

options_text

를:

options_json

으로 추가하고 Backfill한다.

기존 Column은 유지한다.


✅ 77. Data Migration Error를 격리한다

기존 데이터가 모두 정상일 거라고 가정하지 않는다.

예:

100만 건 중
57건 파싱 실패

할 수 있다.


✅ 78. 실패 Row를 별도 기록한다

예:

migration_errors

또는 Migration Report에:

id

reason

sourceValue

를 기록한다.

민감 데이터는 주의한다.


✅ 79. 57건 때문에 전체 Migration을 무조건 멈출지 결정한다

업무 중요도에 따라:

ALL_OR_NOTHING

BEST_EFFORT

MANUAL_REVIEW

정책이 다를 수 있다.


✅ 80. Migration Progress

예:

Total
1,000,000

Processed
943,000

Failed
57

Remaining
56,943

를 볼 수 있으면 운영하기 쉽다.


✅ 81. Migration 중 신규 Write도 계속 들어온다

Backfill만 생각하면 놓치기 쉬운 부분이다.

기존 Row Backfill 중

동시에 신규 주문 생성

이 계속 발생한다.


✅ 82. 그래서 먼저 New Write Path를 배포한다

순서:

1. 새 Column 추가

2. 신규 Write가 Old + New 모두 기록

3. 기존 데이터 Backfill

이어야 Backfill 종료 후 다시 누락이 생기지 않는다.


✅ 83. Dual Write 일관성 검증

Migration 기간 동안:

oldValue != newValue

인 Row를 주기적으로 찾는다.


✅ 84. Divergence Metric

예:

phone_column_mismatch
3

같은 Gauge를 둘 수도 있다.

0이 유지되는지 확인한다.


✅ 85. Read Shadowing

신규 Read Path를 실제 Response에 쓰기 전 검증할 수도 있다.

예:

실제 응답은 Old Query

백그라운드에서 New Query도 실행

결과 비교

한다.


✅ 86. Shadow Read

예:

Old Result
100 orders

New Result
98 orders

라면 아직 전환하면 안 된다.


✅ 87. Shadow Read는 Production 부하를 고려한다

Query를 두 번 실행하므로 DB 부하가 증가한다.

일부 요청에만 적용하거나 샘플링한다.


✅ 88. Dark Launch

신규 기능을 실제 사용자 결과에는 반영하지 않고 백그라운드에서 실행해 검증하는 방식이다.

Feature Flag/Canary와 연결된다.


✅ 89. API v2 Dark Read

예:

Client에는 v1 결과 반환

v2 Query도 실행

차이만 Metric 기록

할 수 있다.


✅ 90. 결과가 안정적이면 실제 Read를 v2로 전환한다

이 방식은 위험한 조회 로직 변경에 유용하다.


✅ 91. Write Shadowing은 훨씬 위험하다

실제 Side Effect가 두 번 발생할 수 있기 때문이다.

Write는 단순히 Shadow하지 않는다.


✅ 92. Write Migration은 Idempotency가 필수

예:

Old Writer

New Writer

가 동시에 동작하면 중복 Side Effect가 생길 수 있다.

업무 Key를 기준으로 중복을 막는다.


✅ 93. API Consumer Migration

새 API를 만들었다면 Consumer를 단계적으로 이동한다.

예:

관리자 v2
개발자 계정만

↓

일부 관리자

↓

전체 관리자

Feature Flag를 활용할 수 있다.


✅ 94. Consumer가 모두 v2로 이동한 뒤 v1 Deprecation

즉:

Create v2

Migrate Consumer

Observe

Deprecate v1

Remove v1

이다.


✅ 95. API Contract Test

API Response가 기존 Client Contract를 깨지 않는지 테스트한다.

예:

status field 존재

id string

items array

같은 계약이다.


✅ 96. Snapshot Test만으로는 부족할 수 있다

Response 전체 Snapshot은 작은 변경에도 자주 깨진다.

중요 Contract 중심 테스트가 더 낫다.


✅ 97. Consumer-Driven Contract 개념

Consumer가:

나는 이 Field가 필요하다

라는 계약을 명시하는 방식이다.

현재 규모에서는 전문 도구까지 도입하지 않아도 된다.


✅ 98. 최소 Contract Test

예:

expect(order).toMatchObject({
  id: expect.any(String),
  status: expect.any(String),
});

정도로도 핵심 Field 삭제를 잡을 수 있다.


✅ 99. Event Contract Test도 필요하다

Producer가 생성하는 Event가 Consumer Schema와 호환되는지 테스트한다.


✅ 100. Producer Test

예:

ORDER_STATUS_CHANGED v2

Event가 Required Field를 모두 포함하는지 검증한다.


✅ 101. Consumer Test

v1/v2 Event Sample을 각각 넣어 정상 처리되는지 확인한다.


✅ 102. Golden Event Fixture

예:

test/fixtures/events/
order-status-changed-v1.json

order-status-changed-v2.json

같이 유지할 수 있다.


✅ 103. 오래된 Event Fixture가 중요한 이유

현재 Producer가 더 이상 v1을 만들지 않아도 DLQ/Retry에 v1이 남아 있을 수 있다.

Consumer가 계속 처리해야 하는 기간에는 Fixture도 유지한다.


✅ 104. DB Migration Fixture

Migration 전 Sample Data를 만들어:

Migration 적용

↓

예상 Schema/Data 확인

테스트할 수 있다.


✅ 105. Production Snapshot 전체를 Test에 복사하지 않는다

개인정보 문제도 있고 무겁다.

대표적인 형태만 익명화해서 Fixture로 만든다.


✅ 106. Schema Compatibility Test

예:

Old App Query

New Schema

조합이 동작하는지 Staging에서 확인할 수 있다.


✅ 107. 배포 조합 Matrix

예:

AppDB결과
v1schema 1O
v1schema 2O
v2schema 2O
v2schema 3O
v1schema 3X

이 경우 schema 3 적용 이후에는 v1 Rollback이 불가능하다.


✅ 108. 이 Matrix가 Rollback Safety를 결정한다

0927의:

SAFE

CONDITIONAL

UNSAFE

분류에 사용할 수 있다.


✅ 109. Safe Rollback

예:

새 Column만 추가

했다면 이전 App은 무시하므로 Rollback이 쉽다.


✅ 110. Unsafe Rollback

예:

Old Column 삭제

후에는 이전 App이 바로 깨질 수 있다.


✅ 111. Migration Gate에 Compatibility Check 추가

Release Gate에서:

파괴적 Migration인가?

Old App이 새 Schema에서 동작하는가?

Rollback 가능한가?

를 확인한다.


✅ 112. Breaking Migration은 한 Release에 몰아넣지 않는다

예:

Column Rename

Data Transform

API 변경

Event V2

를 한 번에 배포하면 문제 발생 시 원인 추적이 어렵다.


✅ 113. 단계별 Release

예:

Release A

새 Column 추가
Dual Write

Release B

Backfill
Read Switch

Release C

Old Column 제거

처럼 나눈다.


✅ 114. 배포 횟수가 늘지만 위험은 줄어든다

무중단 Migration은 보통:

한 번에 빨리

보다:

여러 번에 나눠 안전하게

하는 쪽이다.


✅ 115. Feature Flag로 Read Path 전환

예:

order_phone_v2_read

Flag를 두고:

개발자 계정
↓
일부 Traffic
↓
전체

로 확대할 수 있다.


✅ 116. 문제가 생기면 Flag OFF

DB Schema는 그대로 두고 Read만 Old Path로 돌린다.

전체 Rollback보다 빠르다.


✅ 117. Write Path Flag는 더 조심한다

Old/New Write가 서로 다른 데이터를 만들 수 있기 때문이다.

Write 전환은 Data Consistency 검증을 더 강하게 한다.


✅ 118. Feature Flag Cleanup은 Migration 완료 후 한다

예:

new_phone_read_enabled

가 100% ON으로 안정화되고 Old Path를 제거했다면 Flag도 삭제한다.

0928의 Flag Lifecycle과 연결된다.


✅ 119. Event Consumer Kill Switch와 Version Migration

v2 Consumer에 문제가 생기면:

v2_consumer_enabled = false

로 멈출 수 있다.

단 Event는 버리지 않는다.


✅ 120. Event Backlog와 Version

Consumer v2를 멈춘 동안 Event가 쌓이면 나중에 복구 시 해당 Version을 계속 이해할 수 있어야 한다.


✅ 121. Version 제거 전에 Queue / Inbox / DLQ를 확인한다

예:

V1 Event Pending
0

V1 DLQ
0

인지 확인한다.


✅ 122. Long-running Workflow Version도 같은 문제다

0930에서 Workflow Version을 Run에 고정한다고 했다.

예:

Workflow v1
3일째 WAITING

인 상태에서 코드가 v2로 배포될 수 있다.


✅ 123. 기존 Workflow가 v1 Step을 Resume할 수 있어야 한다

짧은 Workflow라면 배포 전에 모두 종료시키는 방법도 있다.

긴 Workflow라면 Version Compatibility가 필요하다.


✅ 124. Workflow Definition 제거를 서두르지 않는다

v1 Run이 모두:

SUCCESS

FAILED

CANCELLED

같은 Terminal State가 될 때까지 v1 Resume 로직을 유지할 수 있다.


✅ 125. Workflow Version Retirement

예:

v1 active runs
0

확인 후 v1 Definition 제거한다.


✅ 126. AI Workflow에서도 Versioning이 중요하다

AI Run에는:

workflowVersion

promptVersion

modelVersion

policyVersion

이 들어간다.


✅ 127. 실행 중 Prompt가 바뀌어도 기존 Run에 새 Prompt를 적용하지 않는다

예:

Run 시작
prompt v4

중간에 시스템 Prompt가 v5로 바뀌었다고 Retry 시 v5를 쓰면 같은 Workflow가 다른 규칙으로 이어질 수 있다.


✅ 128. Retry는 가능하면 같은 Version 유지

prompt v4

policy v7

model A

를 그대로 사용한다.


✅ 129. 동일 Version을 더 이상 사용할 수 없는 경우

예:

Model 삭제

API 폐기

등이면 기존 Run을 새 Version으로 Migration할지 Manual 처리할지 결정한다.


✅ 130. AI Run Migration을 자동으로 하지 않는다

Prompt/Policy 변경은 결과 의미를 바꿀 수 있다.

새 Run 생성

이 더 안전한 경우가 많다.


✅ 131. Cache도 Versioning 영향을 받는다

API Response 구조가 바뀌었는데 Old Cache가 남아 있을 수 있다.

예:

Backend v2
+
Cache v1 payload

조합이다.


✅ 132. Cache Key Version

예:

product:v1:{id}

product:v2:{id}

처럼 Version을 Key에 포함할 수 있다.


✅ 133. 이렇게 하면 Old/New Cache가 충돌하지 않는다

전환 후 v1 Cache는 TTL로 자연스럽게 사라지게 둘 수 있다.


✅ 134. CDN Cache도 고려한다

Frontend Asset나 API Cache가 남아:

Old Frontend
+
New API

조합이 발생할 수 있다.

그래서 API Compatibility가 더 중요하다.


✅ 135. Frontend Asset은 Hash 기반 파일명이 유리하다

예:

app.abc123.js

app.def456.js

처럼 새 Asset을 별도 파일로 배포한다.


✅ 136. 배포 중 Old HTML이 Old JS를 계속 참조할 수도 있다

기존 Asset을 즉시 삭제하면 Old Client가 404를 볼 수 있다.

일정 기간 이전 Asset을 유지한다.


✅ 137. Client 업데이트가 강제되지 않는 환경도 고려한다

브라우저 Tab을 며칠간 열어둘 수 있다.

Old Frontend가 며칠 뒤 New API를 호출할 수도 있다.


✅ 138. 그래서 Backend Compatibility Window를 잡는다

예:

구버전 Frontend
최소 7일 지원

같은 내부 정책을 정할 수 있다.

정확한 기간은 서비스 특성에 맞춘다.


✅ 139. Admin 서비스도 열린 Tab 문제가 있다

관리자가 오래된 관리자 페이지를 열어둔 채 작업할 수 있다.

Breaking API 변경 직후 저장을 누르면 실패할 수 있다.


✅ 140. Frontend Version Detection

Frontend가 현재 Release Version을 가지고:

Client Version

Server Min Supported Version

을 비교할 수도 있다.


✅ 141. 매우 오래된 Admin Client에는 새로고침 안내

예:

새 버전이 배포되었습니다.
페이지를 새로고침해주세요.

같은 UX를 제공할 수 있다.


✅ 142. 강제 새로고침은 저장 중인 Form을 날릴 수 있다

즉시 새로고침보다:

현재 작업 저장

새로고침

을 유도하는 편이 낫다.


✅ 143. API Error Code도 안정적으로 관리한다

기존 Client가:

ORDER_NOT_FOUND

를 처리하고 있는데 갑자기:

NOT_FOUND

으로 바꾸면 Frontend 동작이 달라질 수 있다.


✅ 144. Error Contract도 API Contract다

다음도 Versioning 대상이다.

HTTP Status

errorCode

error shape

validation errors

✅ 145. Error Response Additive Change

기존:

{
  "code": "INVALID_ORDER"
}

신규:

{
  "code": "INVALID_ORDER",
  "details": {}
}

정도는 비교적 안전하다.


✅ 146. Error Code Rename은 Breaking Change일 수 있다

Frontend나 자동화에서 분기할 수 있기 때문이다.


✅ 147. Internal Automation API도 Contract가 있다

Chrome Extension, RPA, Local Script가 호출한다면 일반 Frontend와 마찬가지다.

“내부용”이라고 Breaking Change가 안전한 것은 아니다.


✅ 148. Database View를 호환 계층으로 사용할 수도 있다

예를 들어 Table 구조가 바뀌었는데 구버전 Query를 잠깐 지원하기 위해 View를 둘 수 있다.

다만 현재 규모에서는 복잡해질 수 있으므로 필요할 때만 고려한다.


✅ 149. Compatibility Layer는 임시라는 점을 명확히 한다

예:

LegacyAdapter

DeprecatedFieldMapper

DualWrite

에는 제거 Issue와 시점을 남긴다.


✅ 150. 그렇지 않으면 영구 기술 부채가 된다

몇 년 후에도:

v1

v2

legacy

temp

코드가 다 남게 된다.


✅ 151. Compatibility Debt

삭제되지 않은 호환 코드도 기술 부채다.

예:

Deprecated API

Old Event Handler

Dual Write

Fallback Read

Legacy Column

이다.


✅ 152. Cleanup 조건을 명확히 한다

예:

Old API Usage = 0

Old Event Pending = 0

Old Workflow Active = 0

Old Column Read = 0

이면 제거 후보가 된다.


✅ 153. Telemetry가 Cleanup 판단에 중요하다

감으로:

아마 이제 안 쓰겠지

라고 삭제하지 않는다.

실제 Usage Metric을 본다.


✅ 154. Deprecated Field Read Tracking

가능하다면:

legacy_field_read_total

같은 Metric으로 사용 여부를 추적할 수 있다.


✅ 155. 하지만 모든 Property 접근을 Instrumentation할 필요는 없다

API Endpoint 사용량, Event Version 소비량 등 큰 단위부터 본다.


✅ 156. Zero-Downtime Migration의 실제 목표

절대 0ms 장애

보다 현실적으로는:

배포 중 구/신 버전 공존으로 인해
사용자 요청이 실패하지 않게 함

에 가깝다.


✅ 157. Deployment 순서가 중요하다

예:

DB Expand

↓

Backward-Compatible App

↓

Backfill

↓

Read Switch

↓

Observe

↓

DB Contract

순서다.


✅ 158. 잘못된 순서

Old Column Delete

↓

New App Deploy

이면 Delete와 Deploy 사이에 기존 App이 깨진다.


✅ 159. Migration Runbook 예시

1. 신규 Schema 추가

2. Migration 적용 확인

3. Backward-Compatible App 배포

4. Dual Write 확인

5. Existing Data Backfill

6. Consistency Check

7. New Read Path Canary

8. 전체 Read 전환

9. 일정 기간 Monitoring

10. Old Write 중단

11. Old Column 사용량 확인

12. Old Column 제거

✅ 160. Contract 단계는 며칠 뒤 별도 Release로 진행해도 된다

급하게 같은 날 삭제할 필요 없다.

안정성을 위해 호환 기간을 둔다.


✅ 161. Production Migration은 되돌릴 수 있는 단계부터 진행한다

초반 단계일수록:

Feature Flag OFF

Old App Rollback

이 가능하도록 한다.


✅ 162. Point of No Return

Migration 중 특정 지점부터 단순 Rollback이 불가능해질 수 있다.

예:

Old Column 삭제

이다.

이를 명시한다.


✅ 163. Release Plan에 Point of No Return 기록

예:

Step 1~7
Rollback Safe

Step 8
Old Column Drop

After Step 8
Roll Forward Only

처럼 관리할 수 있다.


✅ 164. 이를 모르고 자동 Rollback하면 더 큰 장애가 난다

0927의 Auto Rollback이 무조건 안전한 것이 아닌 이유다.


✅ 165. Migration 전 Backup이 Rollback은 아니다

Backup이 있다고:

쉽게 되돌릴 수 있음

을 의미하지 않는다.

Production 전체 DB Restore는 매우 큰 작업이다.


✅ 166. Backup은 최후 복구 수단에 가깝다

Schema 변경의 일반 Rollback 전략은:

Backward Compatibility
+
Roll Forward

가 더 현실적일 수 있다.


✅ 167. Roll Forward를 고려한다

Migration 후 New App에 Bug가 있더라도 Schema가 이미 전진했다면:

Hotfix v2.1

을 배포하는 것이 이전 v1로 돌아가는 것보다 안전할 수 있다.


✅ 168. Schema Migration도 Incident Timeline에 연결

예:

09:00
Migration 시작

09:10
Backfill 50%

09:18
Latency 상승

09:20
Backfill Pause

같은 기록을 남긴다.


✅ 169. Backfill Pause 기능

대량 Migration은:

PAUSE

RESUME

할 수 있으면 좋다.

DB 부하가 증가하면 잠시 중지한다.


✅ 170. Migration Rate Limit

예:

500 rows/sec

처럼 속도를 제한할 수 있다.

Production Traffic을 우선한다.


✅ 171. Adaptive Migration까지는 필요 없다

현재는 Metric 보면서 사람이 Batch Size를 조절하는 정도면 충분하다.


✅ 172. Migration 상태

예:

PLANNED

RUNNING

PAUSED

VERIFYING

COMPLETED

FAILED

MANUAL_REQUIRED

정도로 관리할 수 있다.


✅ 173. Migration Job과 Schema Migration은 구분한다

Prisma Migration:

Schema 구조 변경

Backfill Job:

기존 데이터 변환

이다.

둘을 하나의 긴 SQL Transaction으로 만들지 않는 편이 안전한 경우가 많다.


✅ 174. Deploy 전에 Schema Expand

가능하면:

Schema가 먼저 신버전 코드를 받아들일 준비

를 한다.


✅ 175. 단 App이 Old Schema에서도 동작해야 배포 순서 선택 폭이 넓다

상황에 따라:

App First

가 필요한 Migration도 있다.

따라서 무조건 한 규칙이 아니라 Compatibility Matrix로 판단한다.


✅ 176. Feature Flag는 DB Migration에도 강력하다

새 Column Read를 배포해놓고:

flag = false

로 유지한다.

Backfill/검증 후 Flag ON한다.


✅ 177. Problem 발생 시 DB를 Rollback하지 않고 Flag OFF

빠르게 이전 Read Path로 돌아갈 수 있다.


✅ 178. Data Consistency Dashboard

Migration 동안:

Backfill %

Mismatch Count

NULL Count

Error Count

DB CPU

Query Latency

를 본다.


✅ 179. Migration 성공 기준

예:

Backfill 100%

Mismatch 0

New Read Error 0

p95 정상

Old Path Usage 0

등이다.


✅ 180. “Migration Script 종료 = 성공”이 아니다

Data가 실제 올바른지 검증해야 한다.


✅ 181. API Migration Dashboard

예:

v1 requests
4%

v2 requests
96%

v1 error rate
0.1%

v2 error rate
0.1%

를 보고 전환 상황을 판단한다.


✅ 182. Event Migration Dashboard

V1 produced
0

V1 consumed
12

V1 DLQ
0

V2 consumed
4,291

처럼 확인한다.


✅ 183. Version Retirement 조건

예:

V1 Producer = 0

V1 Queue Pending = 0

V1 DLQ = 0

V1 Consumer Usage = 0

을 만족하면 v1 제거를 검토한다.


✅ 184. Deployment와 Compatibility Test 연결

CI에서 최소한:

Current Client Contract

Current Event Fixtures

Migration Test

를 돌린다.


✅ 185. Changed Files에 따라 Test 추가

예:

prisma/
변경
→ migration compatibility test
events/
변경
→ event contract test
api/
DTO 변경
→ API contract test

를 실행한다.


✅ 186. AI Release Review에도 Compatibility 항목 추가

AI가 Diff를 분석해 다음을 표시할 수 있다.

Field 제거

Enum 변경

Migration DROP

Rename

Event Payload 변경

Required Request Field 추가

등이다.


✅ 187. AI는 Breaking Change 후보를 찾는 데 유용하다

예:

OrderResponse.status
string → object

변경을 보고:

기존 Client Compatibility 확인 필요

라고 지적할 수 있다.


✅ 188. 하지만 최종 판단은 Contract Test가 더 강하다

AI가:

안전해 보입니다

라고 해도 실제 Test가 실패하면 Release를 막는다.


✅ 189. Deterministic Gate 우선

Schema Diff

Contract Test

Migration Test

Runtime Validation

을 AI 의견보다 우선한다.


✅ 190. AI Migration Plan 생성

AI에게 실제 Diff 기반으로:

Expand 단계

Backfill

Read Switch

Cleanup

순서를 초안으로 만들게 할 수 있다.


✅ 191. AI에게 Production Migration 실행 권한을 바로 주지는 않는다

특히:

DROP

DELETE

대량 UPDATE

는 Approval이 필요하다.


✅ 192. Migration SQL Review에 AI 활용

예:

Lock 위험

Full Table Rewrite 가능성

Rollback 위험

Old App Compatibility

를 체크리스트 형태로 분석하게 할 수 있다.


✅ 193. 현재 프로젝트에서 우선 적용할 곳

가장 먼저 다음 세 영역에 적용하면 좋다.

Prisma Migration

Admin API Response

Outbox Event Payload

이다.


✅ 194. 특히 Admin API는 자주 바뀔 수 있다

주문관리 UI가 계속 발전하면:

Filter

Pagination

Status

Risk Flags

Response 구조가 바뀔 수 있다.

기존 화면과 호환 가능한 Additive Change를 우선한다.


✅ 195. Table Migration도 중요하다

Orders처럼 핵심 Table은:

Rename

Drop

Type Change

를 한 번에 하지 않는다.


✅ 196. Notification Event도 중요하다

현재 알림톡 자동화가 발전하면:

ORDER_STATUS_CHANGED

Event Payload가 늘어날 수 있다.

기존 Consumer를 깨지 않게 Field를 추가한다.


✅ 197. Local LLM 자동화에도 Versioning이 필요하다

Production 수준 DB Migration까지는 아니지만:

Report Manifest

Workflow State File

Config Format

Prompt Version

변경 시 기존 Run을 읽을 수 있어야 한다.


✅ 198. State File Version

예:

{
  "version": 2,
  "runId": "...",
  "status": "FAILED"
}

을 둔다.


✅ 199. Config Format Migration

기존:

{
  "model": "shn-coder"
}

신규:

{
  "llm": {
    "model": "shn-coder"
  }
}

로 바뀐다면 기존 Config를 자동 변환하거나 Deprecated 기간을 둘 수 있다.


✅ 200. CLI도 Compatibility가 있다

기존:

report generate

를 갑자기 없애면 사용자가 깨진다.

새 명령을 추가한 뒤 기존 명령을 Alias/Deprecated로 유지할 수 있다.


✅ 201. Internal Tool도 Versioning 대상이다

API만 Version 관리하는 것이 아니다.

CLI

Config

File Format

Event

Workflow

DB

모두 Contract가 있다.


✅ 202. Version을 어디에 붙일지 선택한다

모든 객체마다 Version이 필요하지는 않다.

Breaking Change 가능성이 높고 오래 보관되는 데이터에 우선 붙인다.


✅ 203. 우선순위 높은 Version 대상

Integration Event

Workflow Run Definition

Persistent Config

File Format

Public/Internal API Contract

이다.


✅ 204. 현재 프로젝트에서 지나치게 과한 것

당장은:

GraphQL Schema Registry

전문 Contract Platform

복잡한 API Gateway Version Router

DB Blue/Green Cluster

Multi-region Migration

까지는 필요 없다.


✅ 205. 가장 ROI 높은 규칙

1. 기존 Field 바로 삭제 금지

2. DB Column Rename 대신 Add → Migrate → Remove

3. Event Breaking Change는 Version 증가

4. Migration 전에 Rollback Compatibility 확인

5. Old Usage가 0인지 확인 후 Cleanup

이 다섯 가지가 핵심이다.


✅ 206. Zero-Downtime Migration 상태 흐름

EXPAND

↓

DUAL_WRITE

↓

BACKFILL

↓

VERIFY

↓

READ_SWITCH

↓

MONITOR

↓

OLD_PATH_DISABLED

↓

CONTRACT

로 볼 수 있다.


✅ 207. 각 단계에 Gate를 둔다

예:

BACKFILL → VERIFY

Backfill 100%

VERIFY → READ_SWITCH

Mismatch 0

READ_SWITCH → CONTRACT

Old Path Usage 0
+
안정화 기간 통과

이다.


✅ 208. 자동 Contract는 추천하지 않는다

Old Column Usage 0
→ 즉시 DROP

까지 자동화할 필요는 없다.

삭제는 Manual Approval이 안전하다.


✅ 209. Migration Runbook을 코드와 함께 관리

예:

/docs/migrations/

2026-10-order-phone-v2.md

에 계획을 기록한다.


✅ 210. Migration 문서 템플릿

# Migration

## 목적

## 현재 구조

## 목표 구조

## Breaking Risk

## Expand

## Dual Write

## Backfill

## Verification

## Read Switch

## Rollback Plan

## Point of No Return

## Contract

## Monitoring

✅ 211. Release와 Migration ID 연결

예:

release_20261002_03

migration
order-phone-v2

를 연결한다.

Incident 발생 시 어떤 Migration이 함께 진행됐는지 알 수 있다.


✅ 212. Config Version도 함께 기록

Migration 중 Feature Flag가 바뀌면:

Release

DB Version

Config Version

Flag Version

을 같이 봐야 한다.


✅ 213. Observability Context

예:

{
  "releaseId": "release_20261002_03",
  "dbSchemaVersion": "2026100202",
  "flagVersion": "v44"
}

처럼 주요 Context를 로그에 연결할 수 있다.


✅ 214. 오류 급증 시 Version별 비교

예:

release v2
Error 5%

release v1
Error 0.1%

라면 신규 Release 문제 가능성을 빠르게 볼 수 있다.


✅ 215. Canary와 Versioning

v2 API/Read Path를 일부 사용자에게만 노출하면:

Old Contract
+
New Contract

를 동시에 유지해야 한다.


✅ 216. Progressive Migration

예:

Internal Admin
↓
10%
↓
50%
↓
100%

로 새 Read Path를 확대한다.


✅ 217. DB Migration도 Progressive하게 생각할 수 있다

Backfill:

Carrier = LGU 먼저

검증 후:

SKT

KT

순으로 확장할 수도 있다.


✅ 218. Scope를 잘게 나누면 Blast Radius가 줄어든다

전체 100만 Row보다:

10,000 Row

부터 시작한다.


✅ 219. Migration Failure Injection

0925의 Reliability Test와 연결한다.

예:

Backfill 중 Worker Crash

DB Timeout

중간 Batch 실패

Old/New 값 불일치

를 테스트한다.


✅ 220. Expected Result

완료 Batch 보존

중복 Update 안전

Resume 가능

Mismatch 탐지

여야 한다.


✅ 221. API Compatibility Test Scenario

예:

Old Frontend Payload
→ New Backend

이 여전히 정상인지 확인한다.


✅ 222. Event Compatibility Test Scenario

V1 Event
→ New Consumer

V2 Event
→ New Consumer

모두 성공해야 Migration 기간을 안전하게 운영할 수 있다.


✅ 223. DB Compatibility Test Scenario

Old App Query
→ Expanded Schema

가 성공해야 한다.


✅ 224. Rollback Test도 실제로 해본다

Staging에서:

Schema Expand

New App

↓

Old App Rollback

후 정상인지 확인한다.


✅ 225. Migration이 성공한 뒤 Cleanup Issue를 바로 만든다

예:

[Cleanup]
Remove customer_phone legacy column

을 생성한다.

그렇지 않으면 Legacy가 영구화될 가능성이 높다.


✅ 226. Cleanup 조건을 Issue에 명시

- Old read usage = 0
- Old write disabled
- Mismatch = 0
- 7일 안정화

같이 적는다.


✅ 227. Compatibility Window 종료

조건이 충족되면:

Old API

Old Event

Old Column

Old Flag

을 순서대로 제거한다.


✅ 228. 삭제도 Release다

Cleanup이라고 위험이 없는 것이 아니다.

오히려 삭제는 Destructive Change다.

Build/Test/Migration Gate를 거친다.


✅ 229. Cleanup Release에서 특히 확인

Legacy Consumer 없음

Old Workflow 없음

Old Event 없음

Rollback 필요 여부

이다.


✅ 230. Codex 구현 프롬프트

현재 NestJS + Prisma + PostgreSQL 프로젝트에
Backward Compatibility와 Zero-Downtime Migration을 고려한
기본 운영 규칙과 검증 구조를 추가해줘.

목표는 복잡한 Schema Management Platform을 만드는 것이 아니라,
API / Event / DB Schema가 변경될 때
구버전과 신버전이 잠시 공존해도
Production이 깨지지 않도록 하는 것이다.

현재 프로젝트 구조와 실제 Migration 방식을 먼저 분석하고,
기존 기능을 과도하게 재설계하지 않는다.

1. DB Migration 변경을 다음 유형으로 분류할 수 있게 한다.

- ADDITIVE
- BACKFILL_REQUIRED
- DESTRUCTIVE
- BREAKING

예:
- Column 추가 → ADDITIVE
- 기존 데이터 변환 → BACKFILL_REQUIRED
- Column 삭제 → DESTRUCTIVE
- Type 의미 변경 → BREAKING

2. Prisma Migration을 검토할 때
다음 변경을 위험 변경으로 표시할 수 있는
간단한 검증 Script 또는 Review 절차를 만든다.

- DROP TABLE
- DROP COLUMN
- RENAME
- NOT NULL 추가
- UNIQUE 추가
- 대규모 Type 변경

자동으로 Migration을 수정하지 않는다.

3. Destructive Migration은
별도 Manual Approval 없이는
Production Gate를 통과하지 않게 할 수 있는 구조를 만든다.

4. DB Migration은
Expand → Migrate → Contract 흐름을 기본 원칙으로 한다.

5. Rename이 필요한 경우
가능하다면 다음 단계로 나누는 설계안을 제시한다.

- 신규 Column 추가
- Dual Write
- Existing Data Backfill
- Consistency Verification
- New Read Switch
- Old Path Disable
- Old Column Remove

6. Dual Write가 필요한 경우
Source of Truth를 명시할 수 있게 한다.

7. Backfill Job은
한 번의 대형 Transaction으로 처리하지 않고
Batch 방식으로 실행할 수 있게 한다.

필요 기능:
- processed count
- failed count
- resume
- pause
- batch size

8. Backfill은 Idempotent하게 설계한다.

이미 Migration이 완료된 Row는
재실행 시 안전하게 Skip할 수 있어야 한다.

9. Migration Verification 구조를 만든다.

예:
- NULL count
- old/new mismatch count
- failed row count

10. Migration 중 발생한 실패 Row를
개인정보 노출 없이 추적할 수 있게 한다.

11. 새 Read Path 전환은
가능하면 Feature Flag와 연결할 수 있게 한다.

12. New Read Path를 일부 관리자 또는 일부 Traffic에서
먼저 테스트할 수 있는 구조를 유지한다.

13. Migration 완료 직후
Old Column을 자동 삭제하지 않는다.

Contract 단계는 별도 Release와
Manual Approval 대상으로 둔다.

14. API Contract 변경 시
Additive Change를 우선한다.

예:
기존 Field 유지 + 신규 Field 추가

15. 기존 Request Field를 갑자기 필수화하지 않도록 한다.

Migration 기간에는 Optional/Fallback 정책을 고려한다.

16. Enum 값 추가가
기존 Frontend에 영향을 줄 수 있다는 것을 고려한다.

Unknown Enum을 처리할 수 있는
Frontend/Consumer 정책을 점검한다.

17. Breaking API 변경이 필요한 경우
v2 Endpoint 또는 동등한 Versioning 전략을 사용할 수 있게 한다.

18. Deprecated API에 다음 정보를 관리할 수 있게 한다.

- replacement
- deprecatedAt
- removalTarget
- usage metric

19. Event Schema에도 version을 적용한다.

Event에는 최소:
- eventType
- version
- eventId
를 유지한다.

20. Breaking Event 변경은
기존 Version을 즉시 덮어쓰지 않는다.

21. Event Migration은 다음 순서를 기본으로 한다.

- Consumer가 V1 + V2 지원
- Producer가 V2 발행
- V1 backlog/DLQ 확인
- V1 Producer 중단
- V1 Consumer 제거

22. Unsupported Event Version은
무한 Retry하지 않고
기존 DLQ 흐름으로 보낸다.

23. Event Contract Test Fixture를 만든다.

예:
test/fixtures/events/
- order-status-changed-v1.json
- order-status-changed-v2.json

24. Consumer가 기존 Event Version을
Migration 기간 동안 계속 처리할 수 있는지 테스트한다.

25. API Contract Test를 추가한다.

중요 Response Field가
의도 없이 삭제되거나 Type이 변경되는 것을
테스트로 감지할 수 있게 한다.

26. Cache Payload 구조가 바뀌는 경우를 위해
Cache Key Versioning을 적용할 수 있는 구조를 고려한다.

예:
order:v1:{id}
order:v2:{id}

27. Release Metadata에 가능하다면 다음을 연결한다.

- releaseId
- commitSha
- dbSchemaVersion
- configVersion

28. Health/Internal Version Endpoint에서
민감정보 없이 다음을 확인할 수 있게 한다.

- releaseId
- commitSha
- dbSchemaVersion

29. Deployment Gate에서
Rollback Compatibility를 판단할 수 있는
체크리스트 또는 결과를 남긴다.

예:
- SAFE
- CONDITIONAL
- UNSAFE

30. Point of No Return이 있는 Migration은
명시적으로 기록한다.

예:
Legacy Column Drop 이후
Old App Rollback 불가

31. Cleanup 대상 Legacy를 추적할 수 있게 한다.

예:
- Deprecated API
- Old Event Version
- Legacy Column
- Temporary Feature Flag

32. 실제 Usage가 0인지 확인하지 않고
자동 Cleanup하지 않는다.

33. 다음 Integration Test를 작성한다.

- Old Request → New API
- New Response 추가 Field가 Old Client Contract를 깨지 않음
- V1 Event → New Consumer
- V2 Event → New Consumer
- Unsupported Event Version
- Old App Query → Expanded Schema
- Backfill 중 중단 후 Resume
- Backfill 재실행 Idempotency
- Old/New 데이터 Mismatch 검출
- Feature Flag OFF 시 Old Read Path 복귀

34. 현재 프로젝트의 실제 Prisma Schema와 API를 먼저 분석하고,
전 시스템에 Version을 무조건 추가하지 말고
Breaking Change 가능성이 높은 핵심 영역부터 적용해줘.

✅ 231. Migration Plan용 Codex 프롬프트

현재 Git Diff와 Prisma Migration을 분석해서
Zero-Downtime Migration Plan을 작성해줘.

실제 변경사항에서 확인되는 내용만 사용하고,
Production 구조를 추정해서 사실처럼 작성하지 않는다.

다음 형식으로 작성한다.

## 변경 요약

## Breaking Change 후보

## Old App 영향

## New App 영향

## DB 영향

## Expand 단계

## Dual Write 필요 여부

## Backfill 계획

## Verification Query

## Read Switch 계획

## Feature Flag 사용 여부

## Rollback 가능 범위

## Point of No Return

## Contract / Cleanup 단계

## 배포 후 Monitoring

특히 다음 변경은 강조한다.

- DROP
- RENAME
- NOT NULL
- UNIQUE
- Type 변경
- API Field 삭제
- Required Field 추가
- Enum 변경
- Event Payload Breaking Change

Migration SQL을 자동 실행하거나
Production 데이터를 직접 수정하지 않는다.

✅ 232. Compatibility Review 프롬프트

현재 변경사항이 기존 Consumer와 호환되는지 검토해줘.

검토 대상:

1. React Frontend
2. Admin Frontend
3. NestJS API
4. Prisma Schema
5. Queue/Event Consumer
6. Webhook
7. Chrome Extension 또는 내부 자동화 Script가 있다면 해당 Consumer

각 변경에 대해:

- 기존 Contract
- 신규 Contract
- Breaking 여부
- 영향을 받는 Consumer
- Backward-Compatible하게 변경하는 방법
- 필요한 Migration 단계
- 필요한 Test

를 정리한다.

결과는 다음 Priority로 구분한다.

P0
배포 시 즉시 장애 가능

P1
구버전 Consumer 깨질 가능성

P2
Cleanup/Deprecation 필요

단순 Field 추가 등 안전한 변경은
과도하게 위험하다고 분류하지 않는다.

✅ 233. 실무 체크리스트

API

  • 기존 Field를 바로 삭제하지 않는가?
  • 새 Request Field를 갑자기 필수화하지 않는가?
  • Error Contract가 유지되는가?
  • Enum 추가를 Old Client가 견디는가?
  • Deprecated API 사용량을 확인할 수 있는가?

Event

  • Event Version이 있는가?
  • 기존 Consumer가 새 Event를 처리할 수 있는가?
  • Breaking Change는 새 Version으로 분리했는가?
  • V1 backlog/DLQ를 확인했는가?
  • Old Consumer 제거 시점이 명확한가?

DB

  • Additive Change부터 시작하는가?
  • Rename 대신 Add → Migrate → Remove를 고려했는가?
  • 대량 Backfill을 Batch 처리하는가?
  • Backfill이 Resume 가능한가?
  • Backfill이 Idempotent한가?
  • Mismatch 검증이 있는가?
  • Old App이 Expanded Schema에서 동작하는가?

Deployment

  • Old/New App이 잠시 공존해도 안전한가?
  • Rollback Compatibility를 확인했는가?
  • Point of No Return을 알고 있는가?
  • 파괴적 Migration은 별도 Release인가?
  • Contract 단계 전 안정화 기간이 있는가?

Feature Flag

  • New Read Path를 Flag로 전환 가능한가?
  • 문제 시 Old Path로 복구 가능한가?
  • Migration 완료 후 Temporary Flag를 제거하는가?

Backfill

  • Progress를 볼 수 있는가?
  • Pause / Resume가 가능한가?
  • DB 부하를 제한하는가?
  • 실패 Row를 추적할 수 있는가?
  • 완료 후 Verification을 수행하는가?

Workflow / AI

  • Workflow Version이 Run에 고정되는가?
  • Prompt Version이 Retry 중 바뀌지 않는가?
  • 기존 Run이 구버전 Definition으로 Resume 가능한가?
  • Version Migration이 필요하면 자동으로 덮어쓰지 않는가?

📌 요약

1001에서는:

DB 변경
↓
Outbox
↓
Event
↓
Consumer

를 안전하게 연결하는 방법을 다뤘다.

하지만 실제 운영에서는 배포할 때:

Old Producer
New Producer

Old Consumer
New Consumer

Old Frontend
New Frontend

Old App
New App

이 잠시 동시에 존재할 수 있다.

그래서 1002의 핵심은:

새 버전만 정상 동작하게 만드는 것이 아니라, 전환 기간 동안 구버전과 신버전이 함께 있어도 깨지지 않게 만드는 것

이다.

가장 중요한 기본 원칙은:

Add
↓
Migrate
↓
Verify
↓
Switch
↓
Remove

이다.

DB에서는:

Expand
→ Dual Write
→ Backfill
→ Verify
→ Read Switch
→ Contract

형태가 된다.

특히:

DROP

RENAME

NOT NULL

기존 Field 삭제

같은 Destructive Change를 처음부터 수행하지 않는다.

API에서도:

기존 Field 유지
+
신규 Field 추가

형태의 Additive Change를 우선한다.

Event도 마찬가지다.

Consumer V1+V2 지원
↓
Producer V2 전환
↓
V1 backlog 제거
↓
V1 제거

순서로 진행한다.

무중단 배포에서 중요한 것은:

New App

만 테스트하는 것이 아니다.

반드시:

Old App
+
New Schema

조합도 확인해야 한다.

그래야 문제가 생겼을 때 이전 Release로 Rollback할 수 있다.

그리고 Migration에는 어느 순간:

Point of No Return

이 생길 수 있다.

예를 들어 Old Column을 삭제한 뒤에는:

Old App Rollback

이 더 이상 안전하지 않을 수 있다.

따라서 Release 전에:

Rollback Safe

Conditional

Unsafe

를 판단해야 한다.

현재 프로젝트 규모에서는 거대한 Schema Registry나 전문 Migration Platform까지 만들 필요는 없다.

우선:

Prisma Migration Review

Additive Change 원칙

Batch Backfill

Feature Flag Read Switch

API/Event Contract Test

Old Usage 확인 후 Cleanup

정도만 적용해도 Production 안정성은 크게 올라간다.

지금까지 흐름을 연결하면:

Change
↓
Expand
↓
Backward Compatibility
↓
Deploy Old + New
↓
Backfill
↓
Verify
↓
Progressive Switch
↓
Observe
↓
Contract
↓
Cleanup

이 된다.

결국 Zero-Downtime Migration의 핵심은

“한 번에 완벽하게 바꾸는 것”이 아니라, 구버전과 신버전이 잠시 공존할 수 있는 호환 구간을 의도적으로 만들고, 실제 사용량과 데이터 일관성을 확인한 뒤 마지막에 낡은 구조를 제거하는 것

이다.

0개의 댓글