[LARAVEL] 마이그레이션

김세연·2025년 9월 23일

Laravel

목록 보기
5/14
post-thumbnail

마이그레이션

  • 마이그레이션(Migration)은 한마디로 데이터베이스의 버전 관리 시스템이다.
  • 코드를 관리하기 위해 Git을 사용하듯이, 데이터베이스의 구조(스키마) 변경 내역을 관리하기 위해 마이그레이션을 사용한다.
    즉, SQL 쿼리를 직접 실행하는 대신, PHP 코드로 작성된 파일들을 통해 데이터베이스를 생성하고 수정하는 방식이다.
  • 데이터베이스 구조 변경에 대한 설계도라고 생각하면 쉽다.

up, down

up() 메서드데이터베이스에 변경사항을 적용(실행)하는 역할을 하고,
down() 메서드는 그 변경사항을 원래대로 되돌리는(취소) 역할을 한다.
이 둘은 항상 한 쌍으로 움직이며, 데이터베이스의 버전을 관리하는 '타임머신'과 같은 기능을 제공한다.

up() 메서드

up() 메서드는 마이그레이션의 핵심으로, 데이터베이스 스키마(구조)를 변경하고 싶을 때 실행할 코드를 담고 있다.

  • 역할:
    새로운 테이블 생성, 기존 테이블에 컬럼 추가, 인덱스 설정 등 데이터베이스에 '긍정적인' 또는 '순방향' 변경을 가하는 작업을 정의한다.

  • 실행 시점:
    터미널에서 php artisan migrate 명령을 실행하면, 아직 실행되지 않은 모든 마이그레이션 파일의 up() 메서드가 순서대로 호출된다.

// database/migrations/..._create_tasks_table.php

/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    // 'tasks' 라는 이름의 테이블을 생성한다.
    Schema::create('tasks', function (Blueprint $table) {
        $table->id(); // id 컬럼 추가
        $table->string('title'); // title 컬럼 추가
        $table->timestamps(); // created_at, updated_at 컬럼 추가
    });
}

migrate 명령을 실행하면 라라벨은 이 up() 메서드를 읽어서 실제로 데이터베이스에 tasks 테이블을 만듭니다.

down() 메서드

down() 메서드는 up() 메서드에서 했던 모든 작업을 정확히 반대로 수행하여 변경사항을 취소하는 역할을 한다.

  • 역할:
    up()에서 생성했던 테이블을 삭제하거나, 추가했던 컬럼을 제거하는 등 데이터베이스를 '이전 상태'로 되돌리는 작업을 정의한다.

  • 실행 시점:
    터미널에서 php artisan migrate:rollback 명령을 실행하면, 가장 최근에 실행되었던 마이그레이션 배치의 down() 메서드가 호출된다.

// database/migrations/..._create_tasks_table.php

/**
 * Reverse the migrations.
 *
 * @return void
 */
public function down()
{
    // 'tasks' 라는 이름의 테이블이 존재하면 삭제한다.
    Schema::dropIfExists('tasks');
}

rollback 명령을 실행하면, 라라벨은 이 down() 메서드를 읽어서 up()에서 만들었던 tasks 테이블을 데이터베이스에서 완전히 삭제한다.

왜 up()과 down()이 항상 함께 있어야 하는가?

이 둘은 데이터베이스를 안전하고 유연하게 관리하기 위한 필수적인 안전장치이다.

  • 안전한 롤백 (Safe Rollback):
    새로 추가한 기능이나 변경사항에 문제가 발생했을 때, migrate:rollback 명령 한 번으로 데이터베이스를 아무 일도 없었던 것처럼 이전 상태로 되돌릴 수 있다. 수동으로 데이터베이스에 접속해서 테이블이나 컬럼을 삭제하는 위험한 작업을 할 필요가 없다.
  • 협업 (Collaboration):
    여러 개발자가 함께 작업할 때, 다른 사람이 만든 마이그레이션을 내 로컬 환경에 적용했다가(migrate) 필요에 따라 다시 이전 상태로 되돌릴(rollback) 수 있다.
  • 명확한 히스토리 관리:
    마이그레이션 파일 자체가 데이터베이스 구조가 어떻게 변해왔는지를 보여주는 명확한 '설계도'이자 '역사 기록'이 된다. up()은 '무엇을 했는지', down()은 '그것을 어떻게 되돌리는지'를 명시하므로 코드만 봐도 변경 이력을 쉽게 파악할 수 있다.

down() 메서드가 필요한 이유

down() 메서드는 컴퓨터 작업에서 'Ctrl + Z' (실행 취소)와 똑같은 역할을 데이터베이스에 해준다고 생각하면 된다.

실수했을 경우

// up() 메서드
$table->string('priorty'); // 'priority'를 'priorty'로 오타 발생

php artisan migrate를 실행하고 나서야 오타를 발견했을 때

  • down()이 없다면?
    데이터베이스 관리 툴(HeidiSQL, Sequel Pro 등)을 켠다.
    -> todo_db 데이터베이스에 접속한다.
    -> tasks 테이블을 찾는다.
    -> priorty 컬럼을 직접 손으로 삭제(DROP)한다.
    -> 마이그레이션 파일 이름은 priority로 수정하고 다시 migrate를 실행한다.
    이 과정은 번거롭고, 위험하며(실수로 다른 컬럼을 지울 수도 있음), 시간이 오래 걸린다.

  • down()이 있다면? (올바른 방법)
    터미널에 php artisan migrate:rollback 딱 한 줄만 입력한다.
    라라벨이 알아서 down() 메서드(dropColumn('priorty'))를 실행해 잘못 만든 컬럼을 안전하고 깔끔하게 지워준다.
    마이그레이션 파일의 오타를 priority로 수정하고 다시 php artisan migrate를 실행하면 끝이다.
    훨씬 빠르고, 안전하며, 자동화되어 있다.

기능을 빼기로한 경우

이미 priority 컬럼을 추가하는 마이그레이션은 실행한 상태에서

  • down()이 없다면?
    데이터베이스에 쓰레기 컬럼(priority)이 그대로 남게 된다.
    당장은 문제가 없겠지만, 나중에 데이터베이스 구조가 복잡해지고 지저분해지는 원인이 된다.

  • down()이 있다면?
    php artisan migrate:rollback 명령으로 해당 마이그레이션을 취소해서, 데이터베이스를 '중요도' 기능이 추가되기 전의 깨끗한 상태로 되돌릴 수 있다.

결론적으로 down() 메서드가 필요한 이유는 다음과 같다.

  • 안전성:
    언제든 이전 상태로 돌아갈 수 있다는 확신을 준다. 덕분에 개발자는 실수를 두려워하지 않고 과감하게 데이터베이스 구조를 변경해 볼 수 있다.

  • 자동화:
    데이터베이스 변경과 취소 과정을 모두 코드로 관리하고 명령어로 실행할 수 있게 해준다.
    수동 작업으로 인한 실수를 원천적으로 차단한다.

  • 관리 용이성:
    데이터베이스의 모든 변경 이력(History)을 되돌릴 수 있는 형태로 명확하게 관리할 수 있다.

down() 메서드는 단순히 '테이블 삭제' 기능이 아니라, 데이터베이스의 변경사항을 체계적으로 관리하고, 문제가 생겼을 때 즉시 과거로 돌아갈 수 있게 해주는 아주 중요한 보험이자 안전장치이다.


마이그레이션 관련 주요 Artisan 명령어들

  • php artisan migrate:status
    현재 모든 마이그레이션 파일의 실행 상태를 확인한다.
    어떤 마이그레이션이 실행되었고 어떤 것이 아직 실행되지 않았는지 목록으로 보여준다.
    협업하거나 배포 상태를 확인할 때 매우 유용하다.

  • php artisan migrate:refresh
    모든 마이그레이션을 롤백(down)한 다음, 처음부터 다시 전부 실행(up)한다.
    데이터베이스를 완전히 초기화하고 모든 테이블을 새로 만들고 싶을 때 사용한다.
    테스트 환경에서 데이터베이스 구조를 처음부터 다시 구축할 때 편리하다.

  • php artisan migrate:fresh
    refresh와 비슷하지만 더 확실하게 데이터베이스를 초기화한다.
    down 메서드를 실행하는 대신, 데이터베이스의 모든 테이블을 무조건 삭제한 후 처음부터 migrate를 실행한다.
    down 메서드가 복잡하거나 꼬였을 때 유용하다.

명령어동작 방식주요 사용 사례
migrate:rollback가장 최근에 실행된 마이그레이션만 취소방금 적용한 변경사항에 실수가 있을 때
migrate:refresh모든 down 실행 → 모든 up 실행DB 스키마 전체를 처음부터 다시 구축할 때
migrate:fresh모든 테이블 삭제 → 모든 up 실행down 로직 없이 DB를 가장 깨끗하게 초기화할 때

기존 컬럼 수정

이미 제품 데이터베이스에 적용된 테이블의 컬럼을 수정해야 할 때가 있다.
예를 들어, tasks 테이블의 title 컬럼 길이를 늘려야 할 수 있다.
이런 작업은 down 메서드로 간단히 되돌릴 수 없으므로 특별한 주의가 필요하다.

  • 패키지 설치
    기존 컬럼을 수정하려면 doctrine/dbal이라는 패키지가 필요하다.
    다음 명령어로 설치할 수 있다.
composer require doctrine/dbal
  • 수정 마이그레이션 생성
    새 마이그레이션 파일을 만들어 컬럼을 수정하는 코드를 작성한다.
    파일 이름은 어떤 작업을 하는지 명확하게 짓는 것이 좋다.
php artisan make:migration change_title_column_in_tasks_table
  • 마이그레이션 파일 작성
    Schema::table을 사용하고, change() 메서드를 호출하여 컬럼의 속성을 변경한다.
// up() 메서드
public function up()
{
    Schema::table('tasks', function (Blueprint $table) {
        // title 컬럼을 varchar(255)에서 varchar(500)으로 변경
        $table->string('title', 500)->change();
    });
}

// down() 메서드 (이전 상태로 되돌리기)
public function down()
{
    Schema::table('tasks', function (Blueprint $table) {
        $table->string('title', 255)->change();
    });
}

기존 테이블을 수정하는 마이그레이션은 데이터 손실의 위험이 있으므로, down 메서드를 통해 이전 상태로 되돌릴 수 있도록 신중하게 작성해야 합니다.


시딩(Seeding): 초기 데이터 채우기

마이그레이션이 데이터베이스의 '구조'를 만든다면, 시딩(Seeding)은 그 구조 안에 초기 테스트용 데이터나 기본 데이터를 채워 넣는 역할을 한다.

  • 왜 필요한가?:
    개발 초기 단계에서 애플리케이션 기능을 테스트하려면 데이터가 필요하다.
    매번 수동으로 데이터를 추가하는 것은 비효율적이므로, 시더(Seeder)를 만들어두면 명령어 한 번으로 필요한 데이터를 자동으로 채울 수 있습니다

  • 시더 생성

php artisan make:seeder TaskSeeder
  • 시더 파일 작성 (database/seeders/TaskSeeder.php)
    run 메서드 안에 데이터를 생성하는 코드를 작성한다.
use App\Models\Task;
use Illuminate\Database\Seeder;

class TaskSeeder extends Seeder
{
    public function run()
    {
        Task::create(['title' => 'Laravel 공부하기']);
        Task::create(['title' => '운동하기']);
        Task::create(['title' => '장보기']);
    }
}
  • 시더 실행
    db:seed 명령어로 시더를 실행할 수 있습니다.
php artisan db:seed --class=TaskSeeder
  • 마이그레이션과 시딩 한번에 실행
    데이터베이스를 초기화하고 초기 데이터까지 한 번에 넣고 싶을 때 --seed 옵션을 사용하면 편리하다.
php artisan migrate:fresh --seed

외래 키 제약 조건 (Foreign Key Constraints)

외래 키는 테이블 간의 관계를 설정하는 것입니다.
예를 들어, To-Do List의 각 할 일(Task)은 특정 사용자(User)에게 속해 있어야 한다.

  • 역할:
    데이터의 무결성을 보장한다.
    예를 들어, 존재하지 않는 user_id를 가진 task가 생성되는 것을 데이터베이스 수준에서 막아준다.

  • 마이그레이션 작성법:
    tasks 테이블을 만들 때, 어떤 users 테이블을 참조할 것인지 명시해야 한다.

// database/migrations/..._create_tasks_table.php

public function up()
{
    Schema::create('tasks', function (Blueprint $table) {
        $table->id();
        $table->string('title');

        // 1. users 테이블의 id를 참조하는 user_id 컬럼 생성
        $table->foreignId('user_id')->constrained();

        // 2. 만약 해당 유저가 삭제되면, 관련된 task도 함께 삭제 (CASCADE)
        // $table->foreignId('user_id')->constrained()->onDelete('cascade');

        $table->boolean('completed')->default(false);
        $table->timestamps();
    });
}
  • foreignId('user_id'):
    unsignedBigInteger 타입의 user_id 컬럼을 생성한다.

  • constrained():
    이 컬럼이 users 테이블의 id 컬럼을 참조하는 외래 키임을 자동으로 설정해주는 편리한 메서드이다.

  • onDelete('cascade'):
    만약 부모 데이터(예: users 테이블의 특정 유저)가 삭제되면, 관련된 자식 데이터(tasks 테이블의 해당 유저가 쓴 글들)도 함께 삭제하는 옵션이다.

외래 키를 설정할 때는 마이그레이션 실행 순서가 중요하다.
users 테이블이 tasks 테이블보다 먼저 생성되어야 한다.
라라벨은 보통 파일 이름의 타임스탬프 순서로 실행하므로 대부분 자동으로 처리된다.


익명 마이그레이션 (Anonymous Migrations)

최신 라라벨(8.x 버전 이후)에서는 마이그레이션을 만들면 클래스 이름이 없는 파일을 볼 수 있다.

// 예전 방식
class CreateTasksTable extends Migration { ... }

// 최신 방식 (익명 클래스)
return new class extends Migration
{
    public function up() { ... }
    public function down() { ... }
};
  • 왜 필요한가?:
    여러 개발자가 동시에 작업할 때, 실수로 똑같은 이름의 마이그레이션 클래스(CreateSomethingTable)를 만드는 경우가 있었다.
    이럴 경우 클래스 이름이 중복되어 에러가 발생한다.
    익명 마이그레이션은 클래스 이름 자체를 없애 이런 이름 충돌 문제를 원천적으로 방지한다.

실제 서버(Production) 환경에서의 주의사항

개발 환경에서는 migrate:fresh 같은 명령어를 자유롭게 사용하지만, 실제 사용자가 있는 서버에서는 절대 함부로 사용하면 안 된다.

  • php artisan migrate --force:
    실제 서버 환경에서 migrate 명령어를 실행하려고 하면, 라라벨은 "정말로 실행하시겠습니까?"라고 물어보며 한번 더 확인한다.
    이는 실수로 운영 데이터베이스를 건드리는 것을 막기 위한 안전장치이다.
    스크립트를 통해 자동으로 배포하는 과정에서는 이 확인창에 응답할 수 없으므로, --force 플래그를 붙여 "네, 확실히 실행하겠습니다." 라고 명시해주어야 한다.

  • 점검 모드 활용:
    중요한 데이터베이스 변경이 있을 때는 마이그레이션을 실행하기 전에 php artisan down 명령어로 사이트를 잠시 점검 모드로 전환하는 것이 안전하다.
    작업이 끝난 후 php artisan up으로 다시 정상화시킬 수 있습니다.


순수 SQL 사용하기 (Raw SQL Expressions)

라라벨의 스키마 빌더는 매우 강력하지만, 가끔은 스키마 빌더가 지원하지 않는 복잡한 SQL 구문(예: VIEW 생성, 특정 함수 정의)을 실행해야 할 때가 있다.
이때는 DB::statement를 사용할 수 있다.

use Illuminate\Support\Facades\DB;

public function up()
{
    DB::statement('
        CREATE VIEW completed_tasks AS
        SELECT *
        FROM tasks
        WHERE completed = TRUE
    ');
}

public function down()
{
    DB::statement('DROP VIEW IF EXISTS completed_tasks');
}

알아두면 좋은 심화 주제들

마이그레이션과 데이터베이스 트랜잭션 (Migrations & Transactions)

  • 자동 안전장치:
    라라벨은 php artisan migrate를 실행할 때, 각 마이그레이션 파일을 하나의 트랜잭션(Transaction)으로 감싸서 실행한다.
  • 왜 중요한가?:
    만약 up() 메서드 안에 10개의 작업을 정의했는데, 5번째 작업에서 에러가 발생하면 트랜잭션 덕분에 라라벨은 앞서 성공했던 1~4번째 작업까지 모두 자동으로 되돌려버린다. 즉, 마이그레이션은 '전부 성공'하거나 '전부 실패'만 존재하므로, 데이터베이스가 어중간한 상태로 남는 것을 막아주는 강력한 안전장치가 내장되어 있다.

스키마 덤프 (Schema Dumping)

  • 문제:
    프로젝트가 아주 커져서 마이그레이션 파일이 수백 개가 되면, php artisan migrate:fresh를 실행할 때 수백 개의 파일을 하나씩 실행하느라 시간이 매우 오래 걸린다.
  • 해결:
    php artisan schema:dump 명령어를 사용하면, 현재 데이터베이스의 최종 구조를 database/schema/mysql-schema.sql 같은 단 하나의 SQL 파일로 만들어준다.
    그 후 migrate:fresh를 실행하면 수백 개의 마이그레이션 파일을 실행하는 대신, 이 SQL 파일 하나만 실행하여 데이터베이스를 훨씬 빠르게 구축한다.
    새롭게 프로젝트에 참여한 팀원이 초기 세팅을 할 때 매우 유용하다.

결론

마이그레이션의 핵심 철학은 "데이터베이스를 코드로 관리하여 모든 변경 내역을 추적하고, 팀원들과 안전하게 공유하는 것"이다.

profile
공부 재밌따

0개의 댓글