새 기능을 배포했는데 오류가 났다. 코드는 올라갔는데 테이블이 안 만들어져 있었다.
Table of contents
Open Table of contents
증상 — 순서가 어긋났다
화면에 뜬 것은 이 메시지였다.
Table 'app.member_grade' doesn't exist
급하게 만들었고 그 사이 5분 동안 오류가 났다. 테이블 추가는 손으로 해야 하는 것인데 잊은 것이다.
관리 방식부터 달랐다. 코드는 SVN 에 올리면 배포 스크립트가 받아 가는데 MySQL 변경은 각자 손으로 쳤다. 누가 언제 무엇을 바꿨는지 기록이 없었다.
개발과 시험과 운영 세 곳의 스키마가 조금씩 달라져 있었다.
$ mysqldump --no-data -h dev app > dev.sql
$ mysqldump --no-data -h real app > real.sql
$ diff dev.sql real.sql | grep -c "^[<>]"
88
mysqldump --no-data 로 구조만 뽑아 비교하니 88줄이 달랐다.
변경을 파일로 남겼다
스키마 변경을 파일로 적고 SVN 의 코드와 같은 자리에 뒀다.
db/
001_create_member_grade.sql
002_add_index_order_paydate.sql
003_alter_product_name_length.sql
번호를 붙여서 순서대로 돌게 했다.
-- 001_create_member_grade.sql
CREATE TABLE member_grade (
no int NOT NULL AUTO_INCREMENT,
name varchar(50) NOT NULL,
PRIMARY KEY (no)
);
코드와 같이 올라가니 잊을 자리가 줄었다. 파일만 있으면 어디까지 돌렸는지 모르므로 적용 이력도 남겼다.
CREATE TABLE db_migration (
file varchar(200) NOT NULL,
applied datetime NOT NULL,
PRIMARY KEY (file)
);
foreach (glob('db/*.sql') as $f) {
$name = basename($f);
if ($db->count("SELECT COUNT(*) FROM db_migration WHERE file=?", $name)) continue;
$db->exec(file_get_contents($f));
$db->insert('db_migration', ['file' => $name, 'applied' => date('Y-m-d H:i:s')]);
echo "적용: $name\n";
}
db_migration 에 있는 파일은 건너뛰니 두 번 돌려도 한 번만 적용된다. 실행을 망설이지 않게 됐다.
더하기만 하게 썼다
되돌리는 파일도 같이 두려다 안 했다. CREATE TABLE 을 되돌리는 것은 DROP TABLE 인데 이미 데이터가 들어간 뒤면 되돌릴 수가 없다.
대신 되돌릴 필요가 없게 쓰기로 했다.
- 컬럼을 지우지 않는다. 안 쓰게만 한다
- 컬럼 이름을 바꾸지 않는다. 새로 만들고 옮긴다
- NOT NULL 을 새로 걸 때는 기본값을 준다
ADD COLUMN 만 쓰고 DROP COLUMN 이나 이름 변경은 안 하는 방식이다. 지우는 것은 코드가 다 옮겨간 뒤에 별도로 했다.
배포 중에는 옛 코드와 새 코드가 잠시 같이 도는 순간이 있다. 그때 컬럼이 사라지면 옛 코드가 깨지는데 더하기만 하면 그 순간에도 양쪽이 다 돈다.
올리는 순서와 환경 차이
코드와 MySQL 중 무엇을 먼저 올릴지 정했다.
1. DB 변경 (옛 코드가 돌아도 안 깨지는 것만)
2. 코드 배포
3. 필요하면 정리용 DB 변경 (다음 배포에서)
MySQL 을 먼저 올린다. 컬럼이 하나 더 있어도 옛 코드는 안 깨지지만 반대로 코드를 먼저 올리면 없는 컬럼을 읽는다.
88줄 차이도 하나씩 봤다.
운영에만 있는 것 12개 (급하게 만들고 개발에 반영 안 함)
개발에만 있는 것 31개 (시험하다 만들고 안 지움)
타입이 다른 것 9개
운영에만 있는 12개가 위험했다. 개발에서 시험을 못 한 상태로 운영에 있었기 때문이다. 하나씩 확인해서 필요한 것은 파일로 만들고 안 쓰는 것은 지웠다.
기본 데이터를 넣어야 하는 경우도 같은 방식으로 담았다.
-- 004_insert_default_grade.sql
INSERT INTO member_grade (no, name) VALUES (1, '일반'), (2, 'VIP')
ON DUPLICATE KEY UPDATE name = VALUES(name);
ON DUPLICATE KEY 를 붙인 것은 두 번 돌아도 안 깨지게 하기 위해서다. db_migration 으로 막고 있지만 파일 자체가 여러 번 돌아도 되는 형태여야 안전했다.
운영 데이터를 넣는 것은 이 파일에 안 넣었다. 환경마다 달라야 하는 것을 코드와 같이 올리면 개발 값이 운영에 들어간다.
정리
- 코드와 데이터를 다른 길로 올리면 순서가 어긋난다
- 어떤 변경이 적용됐는지 알 방법이 없어진다
- 변경을
.sql로 남기고SVN에 코드와 같이 올린다 db_migration에 무엇까지 돌았는지 기록해 두 번 돌려도 한 번만 적용되게 한다- 되돌리는 것을 만들기보다 되돌릴 필요가 없게 쓴다
- 컬럼을 지우거나 이름을 바꾸지 않는다. 배포 중에 두 판이 같이 도는 순간이 있다
MySQL을 먼저 올린다. 컬럼이 더 있어도 옛 코드는 안 깨진다- 환경 사이 차이를 재 본다. 운영에만 있는 것이 가장 위험하다
- 자료를 넣는 파일도 여러 번 돌아도 되는 형태로 쓴다