데이터 마이그레이션

필드 이름을 바꾸거나, 옮기거나, 나누는 코드 변경은 이미 저장된 문서를 옛 모양으로 남겨 둡니다. 마이그레이션은 그 문서를 새 모양으로 고치는 script/migration/ 속 파일이고, 같은 파일이 내 컴퓨터에서도 배포된 컨테이너에서도 실행됩니다.
  • 쓰기 전에 계획합니다. plan은 데이터를 바꾸지 않고 무엇이 바뀔지 보고하며, apply는 그 보고 뒤에 데이터가 바뀌었으면 거부할 수 있습니다.
  • 모든 실행이 기록됩니다. 앱의 데이터베이스가 어떤 마이그레이션을 언제, 어떤 파라미터로 실행했는지와 --actor를 주면 누가 실행했는지까지 남기므로, 환경마다 무엇이 남았는지 압니다.
  • 되돌릴 수 있습니다. 쓸 때마다 바꾸기 전 값을 남깁니다. 되돌리기는 그 값을 돌려놓고 마이그레이션이 넣거나 복사한 행은 영구히 지우되, 그사이 누가 고친 행은 건너뜁니다.

만들고 실행하기

워크스페이스 루트에서 하는 일을 이름으로 붙여 마이그레이션을 만듭니다:
Terminal
그러면 apps/koyo/script/migration/202610211000_orderToppings.ts가 생깁니다. 파일 이름이 마이그레이션의 id이고, 앞의 UTC 타임스탬프가 작성한 순서를 지킵니다. 실행은 akan migrate로 합니다:
Terminal
동작하는 일
list기본 동작입니다. 마이그레이션마다 pending, applied, partial 상태와 받는 파라미터를 보여 줍니다.
plan <id>plan을 실행해 요약, 앞쪽 항목 100개, planHash를 출력합니다.
apply <id>계획을 다시 세우고, 쓰고, 이어서 verify를 실행합니다.
verify <id>verify만 실행합니다.
rollback <id>변경 기록으로 apply를 되돌립니다.
  • 작성 중에 다시 돌리려면 되돌리고, 고치고, 다시 apply합니다.

작성하기

마이그레이션은 migration(title)에 세 단계를 이어 default로 export합니다. 다음 마이그레이션은 주문마다 topping을 toppings 목록으로 옮깁니다:
apps/koyo/script/migration/202610211000_orderToppings.ts
  • plan은 보고만 합니다. 사람이 검토할 보고를 돌려줍니다. summary는 읽을 요약이고, 선택인 items는 apply가 두 번째 인자로 받는 결정 목록입니다.
  • verify는 문제 목록을 돌려줍니다. 아직 잘못된 것 하나에 문자열 하나입니다. apply 직후마다 실행되며, 빈 목록이면 데이터가 의도대로라는 뜻입니다.
  • 이미 고친 행은 건너뜁니다. 위의 where가 topping이 없는 주문을 빼므로, 두 번 실행해도 바뀌는 것이 없습니다.
파라미터
코드가 아니라 실행하는 사람이 정할 값은 파라미터로 받습니다. String, ID 같은 스칼라나 enumOf 클래스를 받고, 값은 plan이 돌기 전에 검사됩니다:
apps/koyo/script/migration/202610221400_orderStore.ts
Terminal
.param()의 세 번째 인자는 다음을 받습니다:
descString
값을 채울 사람에게 보여 줄 뜻입니다.
enumString[]
String이 가질 수 있는 값입니다.
default
파라미터를 빼면 쓰는 값입니다.
nullableBoolean
빼면 오류 대신 null이 됩니다.

행과 필터

table(refName)은 모델의 행을 저장된 그대로 읽고 씁니다. 그래서 모델이 더는 선언하지 않는 필드에도 닿습니다. refName은 camelCase 모델 이름입니다:
호출
↳ 하는 일
find(where?, { after, limit })
id 순서로 행을 읽습니다. count, rewrite처럼 삭제된 행도 포함하며, { withRemoved: false }면 뺍니다.
get(id) · count(where?)
행 하나, 또는 맞는 행의 수입니다.
set(id, patch) · unset(id, paths)
행 하나의 _doc에서 점 경로를 쓰거나 지웁니다.
rewrite(fn, { where })
맞는 행을 500개씩(batchSize) 돕니다. fn이 patch를 돌려주면 쓰고, 아무것도 돌려주지 않으면 둡니다.
insert(doc) · copy(id, patch)
새 행을 만듭니다. copy는 다른 행의 _doc에서 시작합니다.
remove(id) · restore(id)
소프트 삭제와 그 복원입니다.
필터는 _doc 안의 점 경로나 removedAt 같은 열을 값, 또는 $ne, $in, $nin, $exists, $gt, $gte, $lt, $lte에 대응시킵니다. 모든 키가 맞아야 합니다.
  • $ne는 값이 없거나 null인 행을 뺍니다. { status: { $ne: "done" } }는 status가 없거나 null인 행을 건너뛰므로, 그런 행은 { status: null }로 찾습니다.
  • updatedAt은 그대로 둡니다. 마이그레이션은 데이터를 수정하는 일이 아니라 모양을 바꾸는 일이라, updatedAt 순 정렬이 흔들리지 않습니다.
  • 각 단계는 table 말고도 params, 새 행 id를 만드는 createId(), logger, 원시 조회용 sql, 부팅된 앱의 서비스를 꺼내는 get(srv.IcecreamOrderService)를 받습니다. srv는 ../../server에서 가져옵니다.

검토, 적용, 되돌리기

다른 사람이 쓰는 데이터라면 plan과 apply 사이에 검토를 둡니다. plan은 보고와 함께 planHash를 출력합니다. 이 값을 --expect로 apply에 넘기면, 다시 계산한 계획이 달라졌을 때 apply가 planChanged로 멈춥니다.
Terminal
  • 해시에는 파라미터도 들어갑니다. 그러니 계획할 때와 같은 파라미터로 apply합니다.
  • verify가 문제를 찾으면 명령은 종료 코드 3으로 실패하지만 마이그레이션은 적용된 채로 남습니다. 되돌려서 파일을 고치거나, 다른 환경에 이미 적용됐다면 새 마이그레이션을 씁니다.
  • 되돌리기는 그사이 고친 행을 건너뜁니다. 마이그레이션이 쓴 뒤 값이 바뀐 행은 그대로 두고 결과에 적으므로, 나중의 수정을 덮어쓰지 않습니다.
  • .rollback(fn)은 기본 되돌리기를 바꿉니다. 다른 행이 이미 가리키는 사본을 남겨 두는 것처럼 판단이 필요할 때 씁니다. for await로 changes를 최신 것부터 돌며 changes.revert(change)나 changes.skip(change, reason)을 부릅니다.
  • 한 번에 하나만 실행됩니다. 실행 중에는 다른 터미널이나 레플리카의 실행이 거부됩니다.

큰 테이블

apply는 트랜잭션 하나로 실행되므로, 실패해도 반쯤 된 상태가 남지 않습니다. SQLite에서는 그 트랜잭션이 하나뿐인 쓰기 잠금을 쥐고 있어서, 5초쯤 넘어가면 서비스 중인 서버의 쓰기가 실패하기 시작합니다. 그보다 오래 걸리는 테이블은 진행하면서 커밋합니다:
apps/koyo/script/migration/202610211000_orderToppings.ts
  • rewrite의 묶음마다 따로 커밋하고 어디까지 했는지 남깁니다. 실패한 apply는 마이그레이션을 partial로 남깁니다. 같은 파라미터로 다시 apply하면 그 지점부터 이어 가고(--expect를 쓴다면 먼저 다시 plan합니다), 되돌리면 쓴 만큼 되돌립니다.
  • 두 번 돌려도 바뀌는 것이 없게 씁니다. 이어 가는 apply도 apply를 처음부터 다시 실행합니다.
  • 변경 기록은 디스크를 씁니다. 바뀐 행마다 이전 값과 새 값을 남기며, 값 자체에 더해 행마다 160바이트쯤 듭니다.

컨테이너에서

빌드된 이미지에 모든 마이그레이션이 들어 있으므로 컨테이너에 따로 복사할 것이 없습니다. 컨테이너 안에서는 bun main.js ops migrate가 앱 이름 없이 akan migrate와 같은 동작과 옵션을 받습니다:
Docker
Terminal
Kubernetes
Terminal
  • --json은 마지막 줄에 JSON 한 줄을 출력합니다. ok와, 실패하면 이유를 담은 code가 들어 있습니다. 종료 코드는 성공이면 0, 명령을 잘못 쓰면 2, verify가 문제를 찾으면 3, alreadyApplied를 포함한 그 밖의 경우는 1입니다.
  • --actor는 실행한 사람을 실행 기록에 남깁니다.
  • 서비스 중인 서버 옆에 두 번째 서버를 띄웁니다. 포트와 스케줄 없이 떠서 동작이 끝나면 멈춥니다.

작성 규칙

마이그레이션은 되돌려야 할 수도 있는 운영 데이터를 바꿉니다. 네 가지 습관이 그 길을 지킵니다:
  1. 새 코드가 옛 데이터를 먼저 만납니다. 배포된 코드는 누군가 마이그레이션을 적용하기 전부터 요청을 받으므로, 적용될 때까지 두 모양을 모두 읽을 수 있어야 합니다.
  2. 쓰기는 서비스나 sql이 아니라 table()로 합니다. 서비스로 쓰면 사용자 알림 같은 모델 훅이 돌고, 둘 다 변경 기록에 남지 않아 되돌릴 수 없습니다.
  3. 데이터베이스 밖은 호출하지 않습니다. 되돌리기는 행을 돌려놓을 수는 있어도, 보낸 메일이나 결제는 되돌리지 못합니다.
  4. 예외는 평범한 Error로 던지고 운영자가 보고 조치할 수 있는 메시지를 씁니다. 실행 기록에 그대로 남습니다.

MIT 라이선스 하에 배포되었습니다.

내 AI에 이 문서 연결하기

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js 모든 권리 보유.시스템 관리자bassman