← 전체 글로 돌아가기

웹 개발

데이터베이스 스키마를 바꾸기 전 확인 순서

SQL 데이터베이스의 스키마를 변경할 때 로컬과 운영 환경의 차이를 고려하고 마이그레이션을 안전하게 진행하는 방법을 정리했다.

데이터베이스 스키마 변경(컬럼 추가, 타입 변경, 인덱스 생성 등)은 실제 데이터가 들어있는 테이블이므로 신중해야 한다. 한 번의 실수로 데이터가 손상되거나 서비스 중단이 될 수 있기 때문이다.

먼저 로컬 환경에서 완전히 테스트한다

로컬 데이터베이스에서 마이그레이션을 미리 돌려본다:

# Prisma를 쓰는 경우
npx prisma migrate dev --name add_column_to_users

# Rails를 쓰는 경우
rails generate migration AddColumnToUsers
rails db:migrate

# 순수 SQL의 경우
psql -U postgres -d mydb -f migration.sql

실행 후 데이터를 확인해서 예상대로 되었는지 본다:

SELECT column_info FROM information_schema.columns WHERE table_name = 'users';
SELECT * FROM users LIMIT 5;

마이그레이션의 진행 과정을 기록한다

어떤 변경을 하는지, 왜 하는지 명확히 해야 나중에 롤백할 때 빠르다:

# 마이그레이션 파일 예시 (Prisma)
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String
  age   Int?    // ← 새로 추가된 컬럼
}

이 마이그레이션은 npx prisma migrate dev --name add_age_to_users로 실행되고, .prisma/migrations/ 폴더에 기록된다.

기존 데이터에 대한 기본값(default) 처리

새 컬럼을 추가할 때 기존 행들에 뭔가를 채워야 한다:

-- 1. NULL 허용하는 방법 (가장 안전)
ALTER TABLE users ADD COLUMN age INT;

-- 2. 기본값을 지정하는 방법
ALTER TABLE users ADD COLUMN age INT DEFAULT 0;

-- 3. 기본값을 지정하고 NOT NULL로 강제하는 방법 (위험)
ALTER TABLE users ADD COLUMN age INT DEFAULT 0 NOT NULL;

기본값을 지정하면 추가 쿼리가 필요 없지만, NOT NULL을 동시에 쓰면 매우 느릴 수 있다. 아주 큰 테이블은 다음 방법이 낫다:

-- Step 1: NULL 허용으로 추가
ALTER TABLE users ADD COLUMN age INT;

-- Step 2: 데이터 채우기
UPDATE users SET age = 0 WHERE age IS NULL;

-- Step 3: NOT NULL 제약 추가
ALTER TABLE users ALTER COLUMN age SET NOT NULL;

큰 테이블에서의 성능 문제

백만 개 이상의 행이 있는 테이블은 락(lock)이 길어지면 다른 쿼리가 블로킹된다:

-- PostgreSQL의 경우, CONCURRENTLY 옵션으로 논블로킹 인덱스 생성
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);

-- MySQL의 경우, 별도의 도구(pt-online-schema-change)를 사용해야 한다

먼저 운영 환경의 테이블 크기를 확인하자:

-- PostgreSQL
SELECT pg_size_pretty(pg_total_relation_size('users'));

-- MySQL
SELECT
  table_schema,
  table_name,
  round(((data_length + index_length) / 1024 / 1024), 2) as size_mb
FROM information_schema.TABLES
WHERE table_schema != 'information_schema'
ORDER BY (data_length + index_length) DESC;

마이그레이션 전에 백업을 확보한다

데이터베이스 백업은 필수다:

# PostgreSQL
pg_dump -U postgres -d mydb > backup_before_migration.sql

# MySQL
mysqldump -u root -p mydb > backup_before_migration.sql

이 파일이 실제로 restore 가능한지 테스트하는 것도 좋은 습관이다:

# 복구 테스트는 별도의 테스트 DB에서
psql -U postgres -d test_db < backup_before_migration.sql

마이그레이션의 호환성 확인

애플리케이션이 현재 버전과 다음 버전을 동시에 돌릴 수 있는가?

  • 컬럼 추가: 대부분 호환성 있음. 코드가 새 컬럼을 사용하지 않으면 된다.
  • 컬럼 이름 변경: 위험. 기존 코드가 깨진다.
  • 컬럼 삭제: 위험. 기존 코드가 접근할 수 없다.
  • 데이터 타입 변경: 매우 위험. 타입 캐스팅이 실패할 수 있다.

안전한 순서:

  1. 새 컬럼 추가 → 코드 배포 → 기존 컬럼 제거

또는 Feature Flag로 마이그레이션 시간을 제어한다.

마이그레이션 중 모니터링

실제 마이그레이션 중에는:

# 진행 상황 모니터링
WATCH -n 1 'SELECT * FROM pg_stat_activity WHERE query LIKE "%ALTER%";'

# 느린 쿼리 로그 확인
tail -f /var/log/mysql/slow.log

롤백이 필요하면 백업에서 복구하거나, 반대 마이그레이션을 실행한다:

# Prisma
npx prisma migrate resolve --rolled-back <migration_name>

# Rails
rails db:rollback

마이그레이션은 자동화하기 쉬운 작업처럼 보이지만, 실제로는 가장 신중해야 하는 작업이다. 로컬에서 충분히 테스트하고, 운영 전에 백업을 확보하는 습관이 중요하다.