13 KiB
데이터베이스 마이그레이션
데이터베이스 스키마 변경은 설정·목록·상세 화면이나 공통 라이브러리의 일반 실행 경로에서 수행하지 않는다. 신규 설치의 최종 스키마는 install/gnuboard5.sql에 반영하고, 기존 설치 변경은 migrations 디렉터리의 버전형 SQL 파일로 관리한다.
신규 설치 이력
기존 테이블이 없는 신규 설치는 최신 스키마와 기본 데이터를 생성한 뒤, data/dbconfig.php를 만들기 전에 배포본의 모든 마이그레이션 ID·설명·체크섬을 성공 이력으로 등록한다. 적용 시각은 설치 시각이고 실행시간은 0이다. 과거 마이그레이션 SQL은 실행하지 않으며, 쇼핑몰 설치 선택 여부와 관계없이 해당 배포본을 기준선으로 기록한다. 설치 직후 DB 업그레이드에서 별도의 기존 상태 확인 이력 등록이 필요하지 않다.
마이그레이션 디렉터리가 비어 있거나 파일 읽기 실패 또는 이력 저장 실패 시 설치 완료를 중단한다. 해당 접두어의 기존 테이블이 있거나 기존 DB를 유지·재설치하는 경로는 일괄 완료로 기록하지 않는다. 남아 있는 쇼핑몰·SMS 등의 구버전 테이블을 완료로 오인하지 않도록 DB 업그레이드에서 실제 상태를 확인한다.
php tests/migration_install_history.php /path/to/isolated/mysql.sock은 root/빈 비밀번호인 격리 MySQL에 임시 DB를 생성하여 사용자 지정 접두어, 쇼핑몰 선택 여부, 설치 이력과 체크섬, 재실행 방지, 이력 저장 실패 및 잘못된 이력 테이블의 1054 오류와 실행 차단을 검증한다. 검사 종료 시 임시 DB를 삭제한다. python3 tests/migration_install_flow.py /path/to/isolated/mysql.sock은 임시 설치 디렉터리에서 실제 설치 코드를 실행하여 일반·쇼핑몰 신규 설치, 기존 테이블 보존과 마이그레이션 파일이 없을 때 설정 파일 생성 차단을 검증한다.
파일 규칙
- 파일명은
YYYYMMDD_NNN_설명.sql형식을 사용한다. - 파일명은 한 번 배포한 뒤 변경하거나 재사용하지 않는다.
-- @description에 관리자에게 표시할 설명을 작성한다.- 각 SQL 문 바로 앞에
-- @if-table-missing,-- @if-table-exists,-- @if-column-missing,-- @if-index-missing조건을 둘 수 있다. 조건이 여러 개면 모두 만족할 때만 실행한다. - 게시판별 글 테이블은
-- @foreach-write-table-if-column-missing column_name과{{write_table}}을 사용한다. - SQL에는 실제 접두어 대신 실행기가 지원하는 테이블 자리표시자를 사용한다.
- 한 파일은 가능한 한 하나의 목적만 포함한다.
자리표시자는 $g5에 등록된 *_table 키를 {{config_table}} 형식으로 사용한다. 실행기가 실제 테이블명으로 치환하며, 치환되지 않은 자리표시자는 오류로 처리한다.
실행 방법
공식 배포본에서는 관리자 화면으로 실행한다. bin/은 Git 추적 및 배포 대상에서 제외한 로컬 운영·검증 스크립트 디렉터리이므로 아래 검증 스크립트 설명은 해당 파일을 별도로 보유한 개발 환경에만 적용한다.
웹에서는 최고관리자가 환경설정 → DB업그레이드에서 전체 또는 특정 마이그레이션을 명시적으로 실행할 수 있다. 성공한 마이그레이션의 개별 실행 버튼은 비활성화되며, 전체 실행에서도 성공 이력과 체크섬이 일치하는 항목은 건너뛴다. 화면 조회만으로는 DDL이 실행되지 않는다.
기존 설치본처럼 이력이 비어 있으면 상태 조회 시 실제 실행 조건을 읽기 전용으로 검사한다. 일부 이력이 있는 경우에도 미기록 항목은 검사한다. 현재 조건에서 모든 SQL을 건너뛰는 항목은 실행 불필요 (미기록)로 표시하며 개별 실행 버튼을 비활성화한다. 이는 과거 실행 성공이나 컬럼 정의 전체의 일치를 보증하는 표시가 아니다. 게시판별 컬럼도 검사하며, 조건 없는 DDL·데이터 보정·검사 오류는 대기로 남긴다. 조회 요청 안에서는 메타데이터를 공유해 중복 조회를 줄이고, 다음 요청에서는 다시 검사하므로 외부 변경이나 선행 작업 후 상태가 반영된다.
기존 상태 확인 이력 등록 버튼은 실행 잠금 아래 다시 검사하고, 처음부터 연속해서 실행 불필요한 미기록 항목만 성공 이력으로 등록한다. 등록 시각은 확인한 시각이며 실제 과거 적용 시각이 아니다. 이 동작은 필요하면 이력 테이블을 생성하지만, 마이그레이션 SQL 자체는 실행하지 않는다. 변경이 필요한 항목이나 실패 이력을 만나면 중단하며, 성공 이력의 체크섬 불일치는 오류로 처리한다. 화면 진입만으로 등록하지 않으며 웹 등록은 최고관리자 권한과 POST 토큰 검증을 거친다. 전체 마이그레이션 실행도 기존처럼 실행 조건을 다시 확인하고 건너뛴 항목의 이력을 함께 기록한다.
전체 실행은 파일명 오름차순, 즉 오래된 마이그레이션부터 처리하며 하나가 실패하면 이후 실행을 중단한다. 개별 실행도 대상보다 앞선 모든 마이그레이션의 성공 이력과 체크섬이 유효해야 한다. 선행 항목이 누락·실패·변경된 경우 대상만 건너뛰기 성공으로 기록하지 않고 오류로 차단한다.
실행기는 g5_migrations 테이블에 마이그레이션 ID, 체크섬, 상태, 오류, 실행시간과 적용시각을 기록한다. 프로세스 간 잠금을 사용해 동시 실행을 막고, 실패하면 이후 마이그레이션을 중단한다. 적용이 끝난 파일의 체크섬이 바뀌면 오류로 처리한다.
현재 마이그레이션은 공통 실행 취소를 제공하지 않는다. 컬럼·테이블 삭제에 따른 데이터 손실과 후속 마이그레이션 의존성을 자동으로 안전하게 해결할 수 없기 때문이다. g5_migrations의 성공 이력만 삭제하는 것도 실제 스키마를 되돌리지 않으므로 취소로 취급하지 않는다. 롤백이 필요하면 데이터베이스 백업을 복원하거나, 해당 변경에 맞춘 별도 복구 절차를 검토한다.
check-migration-schema.php는 마이그레이션의 테이블·컬럼·인덱스가 신규 설치 SQL에 포함되는지와 최종 컬럼 정의가 일치하는지 검사한다. check-migration-runner.php는 격리된 임시 테이블로 부분 적용 스키마, 쇼핑몰 미설치 건너뛰기, 기존 데이터 보정을 검사한다. 마이그레이션을 추가하거나 설치 SQL을 변경한 뒤 두 검사를 반드시 실행한다.
check-migration-existing.php는 실제 DB 대신 SQL 응답을 제공해 조회 시 쓰기 금지, 순차 이력 등록, 재검사, 실패 이력·체크섬·잠금 보호를 검사한다. 기존 상태 검사나 이력 등록 로직을 변경한 뒤 실행한다.
쇼핑몰을 설치하지 않은 사이트에서는 DB 업그레이드 화면의 쇼핑몰 설치 버튼으로 최신 쇼핑몰 스키마를 추가할 수 있다. 같은 접두어의 쇼핑몰 테이블이 하나라도 있으면 기존 데이터 훼손을 막기 위해 설치를 중단하며, 기존 data/dbconfig.php의 DB 접속 정보와 토큰 키는 유지하고 쇼핑몰 설정만 추가한다. php bin/check-shop-install.php로 격리된 접두어에서 후설치, 설정 보존과 중복 실행 차단을 검사할 수 있다.
쇼핑몰 미설치 사이트의 업그레이드는 다음 두 방식으로 진행할 수 있다.
- 쇼핑몰 없이 업그레이드:
쇼핑몰 설치를 누르지 않고 기존 상태 이력 등록 또는 마이그레이션 실행을 진행한다. 쇼핑몰 관련 SQL은 기존 쇼핑몰 테이블의 존재 조건을 확인하므로 해당 테이블이 없는 설치에서는 실행하지 않는다. 공통 스키마와 쇼핑몰 변경이 한 파일에 있어도 쇼핑몰 문장만 건너뛴다. 쇼핑몰 항목의 성공 이력이 생겨도 쇼핑몰이 설치됐다는 뜻은 아니다. - 쇼핑몰을 추가하고 업그레이드:
쇼핑몰 설치로 최신 쇼핑몰 스키마를 생성한 뒤 공통 DB 마이그레이션을 진행한다. 쇼핑몰 없이 업그레이드를 먼저 완료한 뒤 후설치해도 최신 설치 SQL을 사용하므로 과거에 건너뛴 쇼핑몰 이력을 삭제하거나 다시 실행할 필요가 없다.
이 구분은 G5_USE_SHOP 값만으로 판단하지 않고 실제 쇼핑몰 테이블 존재 여부를 사용한다. 기존 쇼핑몰 테이블을 남겨 둔 채 기능만 비활성화한 사이트는 미설치 사이트와 다르며, 기존 테이블의 스키마 보정 대상이 될 수 있다. check-shop-install.php는 전체 마이그레이션의 쇼핑몰 SQL을 검사해 미설치 상태의 모든 문장이 건너뛰어지는지, 후설치 뒤에는 오류 없이 처리되는지도 검증한다.
check-runtime-ddl.php는 관리자·게시판·라이브러리·쇼핑몰 등 일반 실행 경로에 DDL 문이 다시 추가되지 않았는지 검사한다. 테이블이나 컬럼 변경은 이 검사를 우회하지 말고 버전형 마이그레이션으로 작성한다.
게시판 삭제 시 게시판별 테이블을 함께 삭제하는 것처럼 사용자가 명시적으로 요청한 데이터 생명주기 작업은 마이그레이션과 구분해 제한된 허용 목록으로 관리한다. 일반 레코드 생성 과정에서 AUTO_INCREMENT를 초기화하는 DDL은 허용하지 않는다.
권한과 배포
migrations/는 업그레이드 완료 후에도 유지한다. Apache 2.4용 migrations/.htaccess를 함께 배포하며, Nginx는 별도의 서버 설정이 필요하다. 공유호스팅 적용 조건, 하위 경로 설치 예시와 개별 SQL 파일의 차단 확인 방법은 마이그레이션 폴더 접근 차단을 참고한다. HTTP 접근 차단 후에도 PHP의 서버 내부 파일 읽기는 허용해야 한다.
관리자 DB업그레이드 실행 시에는 DB 계정에 필요한 DDL 권한이 있어야 한다. 평상시 DDL 권한을 제거한 운영 환경에서는 업그레이드 시점에 필요한 권한을 부여하고, 완료 후 운영 권한으로 복원한다.
배포 전에는 백업과 테이블 크기, 예상 잠금 시간을 확인한다. MySQL 계열 DB의 DDL은 트랜잭션으로 완전히 되돌릴 수 없으므로 실패한 마이그레이션의 오류를 확인하고 수동 복구 후 재실행한다.
기존 업그레이드 코드
과거 설치본을 화면 접근 시점에 보정하던 누적 DDL은 일반 실행 파일에서 제거했다. orderupgrade.php와 adm/dbupgrade.php에 누적됐던 배포 후 변경은 실제 도입 시점별 마이그레이션으로 이전했다. 조사 근거와 대응 목록은 database-migration-history.md에 기록한다.
이력 테이블 구조 오류 복구
Unknown column 'migration_id' in 'field list'는 실행기가 사용하는 이력 테이블에 migration_id 컬럼이 없을 때 발생한다. 현재 설치 SQL에는 이 컬럼이 포함되어 있다. 기존의 CREATE TABLE IF NOT EXISTS는 이미 존재하는 테이블의 구조를 고치지 않으며, 과거 실행기는 이력 조회 실패를 빈 이력으로 취급하여 첫 이력 저장에서 오류가 드러날 수 있었다. 실제 발생 환경에서 구조가 달라진 경위는 테이블 정의와 설치 파일을 확인해야 한다.
조회와 실행 시 필수 컬럼 및 migration_id 단일 기본키를 확인한다. 구조 또는 이력 조회에 실패하면 오류를 표시하고 마이그레이션 실행과 이력 등록을 중단한다. 화면 조회에서는 테이블을 변경하지 않는다.
- DB 백업 후
SHOW CREATE TABLE g5_migrations와SELECT COUNT(*) FROM g5_migrations로 구조와 기존 이력을 확인한다. 사용자 지정 접두어를 사용했다면 실제 테이블명으로 바꾼다. - 잘못된 구조이며 이력이 없는 테이블은 다른 용도로 사용되는 테이블이 아닌지 확인하고, 사용하지 않는 이름으로 보존한다. 예:
RENAME TABLE g5_migrations TO g5_migrations_backup_20260914. 이름이 중복되지 않는지 먼저 확인한다. - 최신 파일을 배포한 뒤 DB 업그레이드의 전체 실행 또는 기존 상태 확인 이력 등록을 다시 수행한다. 정상 이력 테이블을 생성하고 실제 DB 상태에 따라 처리한다. 기존 상태 등록은 선행 실행 필요 항목에서 중단하므로 남은 작업은 전체 실행으로 처리한다.
- 기존 이력이 있는 테이블은 컬럼 매핑과 체크섬을 검토하여 현재 정의로 복구한다. 정상 성공 이력을 잃으면 조건 없는 데이터 보정이 재실행될 수 있으므로, 이력을 임의 삭제하거나 빈 테이블로 대체하지 않는다.
install/gnuboard5.sql전체를 기존 DB에 실행하면 테이블이 삭제되므로 복구용으로 실행하지 않는다.