laravel migration 안전하게 수행하기


schema migration 이란?

SW 개발시 Database Schema 를 기술한 DDL(Data Definition Language) 파일은 버전 관리 시스템을 통해 변경 이력을 관리할 수 있지만 DDL 이 DBMS 에 적용되었는지 여부는 제대로 관리되지 않는 경우가 많습니다. 


더구나 local, 개발, 테스트, QA, staging, production 등 여러 시스템이 혼재하는 경우 DB 스키마와 DBMS 의 일치여부를 관리하는 프로세스를 구축해 놓지 않았다면 각 시스템마다 스키마의 일관성을 유지하기 어려우며 이는 운영 배포시 장애를 유발할 수 있습니다.


특히 작은 변경을 자주 배포하기 힘든 환경에서 하나의 배포로 많은 변경 사항을 반영할수록 문제가 발생할 확률이 높아지게 됩니다.


이런 문제를 해결하기 위해 ruby on rails 나 laravel 은 schema Migration 이라는 방법을 통해 스키마의 적용 여부를 DB table 에 기록하고 변경된 스키마만 DBMS 에 반영하고 있습니다.


하지만 schema migration 은 쉽고 빠르고 변경 사항을 체계적으로 관리할 수 있는 장점이 있지만 다음과 같이 잘못된 명령어 한 방으로 비즈니스의 근간인 데이타를 날려버릴수도 있는 아주 위험한 방법이기도 합니다.

$ php artisan migrate:fresh



이렇게 유용하지만 위험한 명령을 잘 사용하기 위해 제가 생각하는 베스트 프랙티스를 정리해 보았습니다.


베스트 프랙티스

1. 운영 접근 가능 장비를 물리적으로 분리

운영 시스템은 매우 중요하므로 접근 가능한 네트워크와 인원을 제한하고 접근할 수 있는 장비도 물리적으로 분리해 놓는 게 필요합니다.


개발용 노트북에서 운영까지 접근할 수 있다면 운영 배포 작업시 혼동으로 인해 실수할 여지가 있습니다.

이를 방지하기 위해 운영에서 변경 작업시 조심하라고 해도 사람의 주의력은 한계가 있으므로 명확하게 대상 서버를 알수 있도록 운영 작업용 장비를 별도로 구성하고 네트워크나 방화벽까지 격리해 놓는 것이 필요합니다.


만약 예산이나 장소의 협소등으로 물리적 장비 구성이 어려워서 하나의 장비만 사용해야 한다면 논리적으로 작업 환경을 구분하는 방법도 있습니다.

예로 한 장비에 Windows 와 Ubuntu Linux 를 설치하고 평소에는 Windows 를 사용하다가 운영 서버에서 수행하는 작업은 Ubuntu 에서 수행하면 어느 환경에서 작업하는지 비교적 명확하게 인지할 수 있습니다.

2. DB migration 작업은 2인 이상 수행

DB migration 은 중요하고 위험한 작업이므로 독자적으로 하지 말고 2인 이상으로 작업하고 각 단계별로 교차 확인하는게 좋습니다.

3. 서비스 계정은 Drop 권한을 Revoke

서비스를 구동할 때 사용하는 DB 계정은 혹시 모를 실수를 방지하기 위해 drop 권한을 제거하는 게 좋습니다. MySQL 의 경우 DBMS 이름이 laravel8 이고 사용자가 laravel8 일 경우 다음 DDL 문으로 drop 권한을 제거할 수 있습니다.

REVOKE drop on laravel8.* from 'laravel8'@'localhost';


만약 DROP Table 이 필요하다면 해당 권한이 있는 별도의 계정으로 작업하면 됩니다.

Column DROP 은 ALTER 권한이며 ALTER 권한을 제거하면 컬럼 이름 변경, 크기 및 유형 변경이 불가능해집니다. 


4. APP Env 를 production 으로 설정

운영 환경이라면 반드시 laravel 의 app 환경 설정 파일인 .env 에 다음과 같이 APP_ENV 를 production 으로 설정합니다. 

APP_ENV=production


production 으로 설정하면 migratedb:seed 같이 Database 에서 작업하는 명령어는 아래와 같이 명시적으로 실행 동의를 입력해야 동작합니다.

$ php artisan migrate

**************************************
*     Application In Production!     *
**************************************

 Do you really wish to run this command? (yes/no) [no]:
 > 

Command Canceled!

실행하려면 yes를 입력하면 되며(y 만 입력해도 됩니다.) 이외 다른 문자를 입력하거나 입력이 없으면 migration 을 실행하지 않으므로 실수로 migrate 를 실행하고 DBMS 에 바로 반영되는 것을 방지할 수 있습니다.


5. 작업전에 pretend 로 확인

migration 전에 --pretend 명령을 실행하면 현재 migration 명령으로 수행할 SQL 을 화면에 출력해 줍니다.


특히 새로운 테이블을 생성하는 CREATE 구문은 영향이 적지만 기존 테이블을 수정하는 ALTER 구문은 운영에서 실행할 경우 어떤 side effect 가 발생할 지 모르므로 매우 신중하게 실행해야 하며 이때 --pretend 를 사용해서 변경 사항을 다시 검토해 보는 게 좋습니다.

├─migrations
│      2014_10_12_000000_create_users_table.php
│      2014_10_12_100000_create_password_resets_table.php
│      2019_08_19_000000_create_failed_jobs_table.php
│      2021_02_08_014247_create_policies_table.php
│      2021_02_08_072653_add_policy_name_on_policies_table.php
│


예로 위와 같은 migration 파일이 있고 2개의 migration을 적용해야 한다고 가정해 보겠습니다.


2021_02_08_014247_create_policies_table

class CreatePoliciesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('policies', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }


2021_02_08_072653_add_policy_name_on_policies_table

class AddPolicyNameOnPoliciesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('policies', function (Blueprint $table) {
            $table->text('name')->nullable();
            $table->boolean('agree')->default(false);
        });
    }


migrate 명령어에 --pretend 옵션을 주고 실행하면 다음과 같이  명령어를 실행하면 DDL 문을 출력하므로 반영전에 다시 한 번 리뷰하고 이상유무를 확인할 수 있습니다.

$ php artisan migration --pretend


CreatePoliciesTable: create table `policies` (`id` bigint unsigned not null auto_increment primary key, `created_at` timestamp null, `updated_at` timestamp null) default character set utf8mb4 collate 'utf8mb4_unicode_ci'
AddPolicyNameOnPoliciesTable: alter table `policies` add `name` text null, add `agree` tinyint(1) not null default '0'

6. 작은 변경을 자주 배포

개발/테스트는 schema migration 시 문제가 발생하면 새로 DDL 을 적용해서 깔끔한 상태로 schema migration 이 되었을 수 있지만 오래전부터 서비스해온 운영 DB 는 그런 상태가 아닐수 있습니다.

특히 ALTER 작업은 데이타가 있을 경우 실패할 수 있으며 많은 변경 사항을 한 번에 몰아서 배포할 경우 작은 문제가 연쇄 작용으로 큰 문제를 일으킬 수 있습니다.


schema migration 같이 자동화되고 위험한 작업을 잘 수행하려면 작은 변경을 자주 배포해서 운영과 개발/테스트간 차이를 좁혀야 합니다. 

같이 보기

Ref