사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
데이터 마이그레이션
필드 이름을 바꾸거나, 옮기거나, 나누는 코드 변경은 이미 저장된 문서를 옛 모양으로 남겨 둡니다. 마이그레이션은 그 문서를 새 모양으로 고치는
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는 실행한 사람을 실행 기록에 남깁니다.- 서비스 중인 서버 옆에 두 번째 서버를 띄웁니다. 포트와 스케줄 없이 떠서 동작이 끝나면 멈춥니다.
작성 규칙
마이그레이션은 되돌려야 할 수도 있는 운영 데이터를 바꿉니다. 네 가지 습관이 그 길을 지킵니다:
- 새 코드가 옛 데이터를 먼저 만납니다. 배포된 코드는 누군가 마이그레이션을 적용하기 전부터 요청을 받으므로, 적용될 때까지 두 모양을 모두 읽을 수 있어야 합니다.
- 쓰기는 서비스나
sql이 아니라table()로 합니다. 서비스로 쓰면 사용자 알림 같은 모델 훅이 돌고, 둘 다 변경 기록에 남지 않아 되돌릴 수 없습니다. - 데이터베이스 밖은 호출하지 않습니다. 되돌리기는 행을 돌려놓을 수는 있어도, 보낸 메일이나 결제는 되돌리지 못합니다.
- 예외는 평범한
Error로 던지고 운영자가 보고 조치할 수 있는 메시지를 씁니다. 실행 기록에 그대로 남습니다.


모든 환경에 적용된 마이그레이션은 지워도 됩니다. 데이터베이스마다 기록이 남고 파일이 없어도 변경 기록으로 되돌릴 수 있지만 기본 되돌리기로만 되돌리므로,
.rollback(fn)이 아직 필요할 수 있는 파일은 남겨 둡니다.