Skip to content
isdnetworks
Go back

코드와 데이터가 다른 길로 갔다

새 기능을 배포했는데 오류가 났다. 코드는 올라갔는데 테이블이 안 만들어져 있었다.

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 으로 막고 있지만 파일 자체가 여러 번 돌아도 되는 형태여야 안전했다.

운영 데이터를 넣는 것은 이 파일에 안 넣었다. 환경마다 달라야 하는 것을 코드와 같이 올리면 개발 값이 운영에 들어간다.

정리


Share this post on:

Previous Post
엑셀 업로드에 검사가 없었다
Next Post
반복문 안의 외부 요청