웹 개발
데이터베이스 스키마를 바꾸기 전 확인 순서
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
마이그레이션의 호환성 확인
애플리케이션이 현재 버전과 다음 버전을 동시에 돌릴 수 있는가?
- 컬럼 추가: 대부분 호환성 있음. 코드가 새 컬럼을 사용하지 않으면 된다.
- 컬럼 이름 변경: 위험. 기존 코드가 깨진다.
- 컬럼 삭제: 위험. 기존 코드가 접근할 수 없다.
- 데이터 타입 변경: 매우 위험. 타입 캐스팅이 실패할 수 있다.
안전한 순서:
- 새 컬럼 추가 → 코드 배포 → 기존 컬럼 제거
또는 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
마이그레이션은 자동화하기 쉬운 작업처럼 보이지만, 실제로는 가장 신중해야 하는 작업이다. 로컬에서 충분히 테스트하고, 운영 전에 백업을 확보하는 습관이 중요하다.