๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
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

ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์”ฉ

pnpm sonamu migrate down
์ฒซ ๋ฒˆ์งธ ์‹คํ–‰:
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 ๋ณ€๊ฒฝํ•จ
ํ•ด๊ฒฐ:
  1. DB ์ƒํƒœ ์ˆ˜๋™ ํ™•์ธ
  2. knex_migrations ํ…Œ์ด๋ธ” ์ˆ˜์ •
  3. 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

๋‹ค์Œ ๋‹จ๊ณ„