Migration์ ๋กค๋ฐฑํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ด์ ์ํ๋ก ๋๋๋ฆด ์ ์์ต๋๋ค.
๋กค๋ฐฑ ๋ช
๋ น์ด
rollback ์ต๊ทผ batch ๋กค๋ฐฑ ํ ๋ฒ์ ์ฌ๋ฌ ๊ฐ
rollback --all ๋ชจ๋ Migration ๋กค๋ฐฑ ์ด๊ธฐ ์ํ๋ก
down ํ ๋ฒ์ ํ๋์ฉ ๋จ๊ณ๋ณ ๋กค๋ฐฑ
status ๋กค๋ฐฑ ์ ์ํ ํ์ธ batch ํ์ธ
๊ธฐ๋ณธ ๋กค๋ฐฑ
์ต๊ทผ batch ๋กค๋ฐฑ
pnpm sonamu migrate rollback
์คํ ๊ฒฐ๊ณผ :
Batch 2 rolled back: 1 migration
โ
20251220143200_alter_users_add1.ts
migrate rollback์ ๊ฐ์ฅ ์ต๊ทผ batch์ ๋ชจ๋ Migration์ ๋กค๋ฐฑํฉ๋๋ค.
ํ์ฌ ์ํ ํ์ธ
pnpm sonamu migrate status
์ถ๋ ฅ :
Executed migrations (Batch 1):
โ
20251220143022_create__users.ts
โ
20251220143100_create__posts.ts
Executed migrations (Batch 2):
โ
20251220143200_alter_users_add1.ts โ ์ด batch๊ฐ ๋กค๋ฐฑ๋จ
Pending migrations:
โณ 20251220143300_alter_posts_add1.ts
๋กค๋ฐฑ ๋ฐฉ์
Batch ๋จ์ ๋กค๋ฐฑ
# ์ํ ํ์ธ
pnpm sonamu migrate status
# Batch 3 (๊ฐ์ฅ ์ต๊ทผ)
โ
20251220150000_alter_users_add2.ts
โ
20251220150100_alter_posts_add2.ts
# Batch 2
โ
20251220143200_alter_users_add1.ts
# ๋กค๋ฐฑ (Batch 3๋ง)
pnpm sonamu migrate rollback
# ๊ฒฐ๊ณผ: Batch 3์ 2๊ฐ Migration ๋กค๋ฐฑ
Batch 3 rolled back: 2 migrations
โ
20251220150000_alter_users_add2.ts
โ
20251220150100_alter_posts_add2.ts
ํ ๋ฒ์ ํ๋์ฉ
์ฒซ ๋ฒ์งธ ์คํ :
Batch 2 rolled back: 1 migration
โ
20251220143200_alter_users_add1.ts
๋ ๋ฒ์งธ ์คํ :
Batch 1 rolled back: 1 migration
โ
20251220143100_create__posts.ts
์ ์ฒด ๋กค๋ฐฑ
pnpm sonamu migrate rollback --all
์คํ ๊ฒฐ๊ณผ :
Batch 2 rolled back: 1 migration
โ
20251220143200_alter_users_add1.ts
Batch 1 rolled back: 2 migrations
โ
20251220143100_create__posts.ts
โ
20251220143022_create__users.ts
All migrations rolled back successfully
rollback --all์ ๋ชจ๋ ํ
์ด๋ธ์ ์ญ์ ํฉ๋๋ค!
ํ๋ก๋์
์์๋ ์ ๋ ์ฌ์ฉํ์ง ๋ง์ธ์.
down ํจ์
๊ธฐ๋ณธ ๊ตฌ์กฐ
export async function down ( knex : Knex ) : Promise < void > {
// up์ ์ญ์์
์ํ
}
CREATE TABLE ๋กค๋ฐฑ
// up: ํ
์ด๋ธ ์์ฑ
export async function up ( knex : Knex ) : Promise < void > {
await knex . schema . createTable ( "users" , ( table ) => {
table . increments (). primary ();
table . string ( "email" , 255 ). notNullable ();
});
}
// down: ํ
์ด๋ธ ์ญ์
export async function down ( knex : Knex ) : Promise < void > {
return knex . schema . dropTable ( "users" );
}
ALTER TABLE ๋กค๋ฐฑ
// up: ์ปฌ๋ผ ์ถ๊ฐ
export async function up ( knex : Knex ) : Promise < void > {
await knex . schema . alterTable ( "users" , ( table ) => {
table . string ( "phone" , 20 ). nullable ();
});
}
// down: ์ปฌ๋ผ ์ญ์
export async function down ( knex : Knex ) : Promise < void > {
await knex . schema . alterTable ( "users" , ( table ) => {
table . dropColumns ( "phone" );
});
}
FOREIGN KEY ๋กค๋ฐฑ
// up: ์ธ๋ ํค ์ถ๊ฐ
export async function up ( knex : Knex ) : Promise < void > {
return knex . schema . alterTable ( "employees" , ( table ) => {
table
. foreign ( "user_id" )
. references ( "users.id" )
. onUpdate ( "CASCADE" )
. onDelete ( "CASCADE" );
});
}
// down: ์ธ๋ ํค ์ญ์
export async function down ( knex : Knex ) : Promise < void > {
return knex . schema . alterTable ( "employees" , ( table ) => {
table . dropForeign ([ "user_id" ]);
});
}
๋กค๋ฐฑ ์๋๋ฆฌ์ค
์๋๋ฆฌ์ค 1: ์๋ชป๋ Migration ์์
# 1. Migration ์คํ
pnpm sonamu migrate run
โ
20251220143200_alter_users_add1.ts
# 2. ๋ฌธ์ ๋ฐ๊ฒฌ (์๋ชป๋ ์ปฌ๋ผ ํ์
)
# 3. ๋กค๋ฐฑ
pnpm sonamu migrate rollback
# 4. Migration ํ์ผ ์์
vim src/migrations/20251220143200_alter_users_add1.ts
# 5. ๋ค์ ์คํ
pnpm sonamu migrate run
์๋๋ฆฌ์ค 2: ํ๋ก๋์
๊ธด๊ธ ๋กค๋ฐฑ
# 1. ํ๋ก๋์
๋ฐฐํฌ ํ ๋ฌธ์ ๋ฐ๊ฒฌ
# 2. ์ฆ์ ๋กค๋ฐฑ
NODE_ENV = production pnpm sonamu migrate rollback
# 3. ์ด์ ๋ฒ์ ์ ํ๋ฆฌ์ผ์ด์
๋ฐฐํฌ
# 4. ๋ฌธ์ ์์ ํ ์ฌ๋ฐฐํฌ
์๋๋ฆฌ์ค 3: ๋ถ๋ถ ๋กค๋ฐฑ
# ํ์ฌ ์ํ
Batch 3: alter_users_add2.ts, alter_posts_add2.ts
Batch 2: alter_users_add1.ts
Batch 1: create__users.ts, create__posts.ts
# Batch 3๋ง ๋กค๋ฐฑ (Batch 2, 1์ ์ ์ง)
pnpm sonamu migrate rollback
# ๊ฒฐ๊ณผ
Batch 2: alter_users_add1.ts โ ์ ์ง
Batch 1: create__users.ts, create__posts.ts โ ์ ์ง
๋ฐ์ดํฐ ๋ณด์กด
๋กค๋ฐฑ ์ ๋ฐ์ดํฐ ์์ค
// up: ์ปฌ๋ผ ์ถ๊ฐ
export async function up ( knex : Knex ) : Promise < void > {
await knex . schema . alterTable ( "users" , ( table ) => {
table . string ( "phone" , 20 ). nullable ();
});
}
// down: ์ปฌ๋ผ ์ญ์ (๋ฐ์ดํฐ ์์ค!)
export async function down ( knex : Knex ) : Promise < void > {
await knex . schema . alterTable ( "users" , ( table ) => {
table . dropColumns ( "phone" ); // โ phone ๋ฐ์ดํฐ ๋ชจ๋ ์ญ์ ๋จ
});
}
๋ฐ์ดํฐ ์์ค ์ฃผ์! ์ปฌ๋ผ/ํ
์ด๋ธ ์ญ์ ์ ๋ฐ์ดํฐ๋ ๋ณต๊ตฌ๋์ง ์์ต๋๋ค.
ํ๋ก๋์
์์๋ ๋ฐฑ์
ํ์!
์์ ํ ๋กค๋ฐฑ ํจํด
// Step 1: ์ปฌ๋ผ ์ถ๊ฐ (nullable)
export async function up ( knex : Knex ) : Promise < void > {
await knex . schema . alterTable ( "users" , ( table ) => {
table . string ( "new_phone" , 20 ). nullable ();
});
}
// Step 2: ๋ฐ์ดํฐ ๋ง์ด๊ทธ๋ ์ด์
await knex ( "users" ). update ({
new_phone: knex . raw ( "old_phone" ),
});
// Step 3: old_phone ์ปฌ๋ผ ์ ์ง (๋กค๋ฐฑ ๋๋น)
// โ ๋์ค์ ๋ณ๋ Migration์ผ๋ก ์ญ์
๋กค๋ฐฑ ๋ถ๊ฐ๋ฅํ ๊ฒฝ์ฐ
๋ฐ์ดํฐ ๋ณํ
// up: ํ์
๋ณ๊ฒฝ
export async function up ( knex : Knex ) : Promise < void > {
await knex . raw (
`ALTER TABLE users
ALTER COLUMN age TYPE integer
USING age::integer`
);
}
// down: ์๋ณธ ๋ฐ์ดํฐ ๋ณต๊ตฌ ๋ถ๊ฐ๋ฅ
export async function down ( knex : Knex ) : Promise < void > {
await knex . raw (
`ALTER TABLE users
ALTER COLUMN age TYPE varchar(255)`
);
// "25" โ 25 โ "25" (์๋ณธ์ด "025"์๋ค๋ฉด ์์ค)
}
๋ฐ์ดํฐ ์ญ์
// up: ํน์ ๋ฐ์ดํฐ ์ญ์
export async function up ( knex : Knex ) : Promise < void > {
await knex ( "users" )
. where ( "is_deleted" , true )
. delete ();
}
// down: ์ญ์ ๋ ๋ฐ์ดํฐ ๋ณต๊ตฌ ๋ถ๊ฐ๋ฅ
export async function down ( knex : Knex ) : Promise < void > {
// ๋ณต๊ตฌ ๋ถ๊ฐ๋ฅ!
}
๋กค๋ฐฑ ์ ๋ต
1. ๋ฐฑ์
์ฐ์
# PostgreSQL ๋ฐฑ์
pg_dump -U postgres -d mydb > backup_before_rollback.sql
# ๋กค๋ฐฑ
pnpm sonamu migrate rollback
# ๋ฌธ์ ๋ฐ์ ์ ๋ณต์
psql -U postgres -d mydb < backup_before_rollback.sql
2. ์คํ
์ด์ง ํ
์คํธ
# ์คํ
์ด์ง์์ ๋กค๋ฐฑ ํ
์คํธ
NODE_ENV = staging pnpm sonamu migrate rollback
# ๋ฌธ์ ์์ผ๋ฉด ํ๋ก๋์
NODE_ENV = production pnpm sonamu migrate rollback
3. ์ ์ง์ ๋กค๋ฐฑ
# ํ ๋ฒ์ ํ๋์ฉ ๋กค๋ฐฑ
pnpm sonamu migrate down
# ์ํ ํ์ธ
pnpm sonamu migrate status
# ์ ํ๋ฆฌ์ผ์ด์
ํ
์คํธ
# ๋ค์ ๋กค๋ฐฑ
pnpm sonamu migrate down
์๋ฌ ์ฒ๋ฆฌ
๋กค๋ฐฑ ์คํจ
pnpm sonamu migrate rollback
์๋ฌ :
โ Error rolling back 20251220143200_alter_users_add1.ts
Error: column "phone" does not exist
์์ธ :
Migration์ด ๋ถ๋ถ์ ์ผ๋ก๋ง ์ ์ฉ๋จ
down ํจ์๊ฐ up๊ณผ ์ผ์นํ์ง ์์
์๋์ผ๋ก DB ๋ณ๊ฒฝํจ
ํด๊ฒฐ :
DB ์ํ ์๋ ํ์ธ
knex_migrations ํ
์ด๋ธ ์์
down ํจ์ ์์ ํ ์ฌ์๋
๊ฐ์ ๋กค๋ฐฑ
-- knex_migrations์์ ์ ๊ฑฐ
DELETE FROM knex_migrations
WHERE name = '20251220143200_alter_users_add1.ts' ;
-- ์๋์ผ๋ก ๋ณ๊ฒฝ ์ฌํญ ๋๋๋ฆฌ๊ธฐ
ALTER TABLE users DROP COLUMN phone;
๋กค๋ฐฑ ๋ก๊ทธ
์์ธ ๋ก๊ทธ
pnpm sonamu migrate rollback --verbose
์ถ๋ ฅ :
Rolling back migration: 20251220143200_alter_users_add1.ts
SQL: ALTER TABLE "users" DROP COLUMN "phone"
โ
Completed in 23ms
Batch 2 rolled back: 1 migration
๋กค๋ฐฑ ๊ธฐ๋ก ์กฐํ
-- ์ต๊ทผ ๋กค๋ฐฑ๋ Migration ํ์ธ
SELECT * FROM knex_migrations
WHERE batch = ( SELECT MAX (batch) + 1 FROM knex_migrations);
์ค์ ํ
1. down ํจ์ ๊ฒ์ฆ
// ํ
์คํธ: up โ down โ up
test ( "migration reversibility" , async () => {
await migration . up ( knex );
await migration . down ( knex );
await migration . up ( knex ); // ๋ค์ ์คํ ๊ฐ๋ฅํด์ผ ํจ
});
2. ๋กค๋ฐฑ ๊ณํ ๋ฌธ์ํ
# Rollback Plan
## Migration: 20251220143200_alter_users_add1.ts
### Changes
- Add column: users.phone
### Rollback Impact
- Data loss: users.phone ๋ฐ์ดํฐ ์ญ์
- Estimated time: 5 seconds
- Downtime: None (nullable ์ปฌ๋ผ)
### Rollback Steps
1. `pnpm sonamu migrate rollback`
2. Deploy previous app version
3. Verify phone field not used
3. ์๋ ๋กค๋ฐฑ ์คํฌ๋ฆฝํธ
#!/bin/bash
# auto-rollback.sh
echo "Creating backup..."
pg_dump -U postgres -d mydb > backup.sql
echo "Rolling back..."
pnpm sonamu migrate rollback
if [ $? -eq 0 ]; then
echo "โ
Rollback successful"
else
echo "โ Rollback failed, restoring backup..."
psql -U postgres -d mydb < backup.sql
fi
๋ค์ ๋จ๊ณ