GN⁺: Postgres에서 흔히 발생하는 DB 스키마 변경 실수
(postgres.ai)Here is a summary of the common database schema change mistakes, translated and structured in Korean:
동시성 관련 실수
- 락 획득 실패
- 한 번에 너무 많은 행 업데이트
- 배타적 락 획득 후 트랜잭션을 오래 열어두기
단계의 정확성 관련 실수 - 논리적 이슈
- 예상치 못한 스키마 편차
- 스키마/앱 코드 불일치
- 예상치 못한 데이터
기타 실수
- statement_timeout 도달
- 증가할 수 있는 테이블에 4바이트 정수 기본키 사용
- VACUUM 동작과 bloat 위험 무시
Case 1. 스키마 불일치
- 개발/테스트 환경에서는 동작했지만 QA/Staging/Production에서는 실패
- 원인 파악 후 워크플로우 개선으로 해결해야 함
Case 2. IF [NOT] EXISTS 오용
- 스키마 불일치 에러를 IF NOT EXISTS로 무시하려 하지 말 것
- 문제의 근본 원인을 파악하고 해결해야 함
Case 3. statement_timeout 도달
- 모든 변경 사항을 대용량 데이터로 테스트해서 미리 파악할 것
Case 4. 무제한 대규모 변경
- 너무 많은 행을 한 트랜잭션에서 변경하면 다른 트랜잭션에 영향
- Checkpointer 튜닝 안되어있으면 WAL 데이터 과도 생성
- 디스크 쓰기 포화로 전반적인 성능 저하 발생 가능
- VACUUM/Bloat 이슈 발생 가능
- 배치로 쪼개서 처리하고 VACUUM 관리할 것
Case 5. 배타적 락 획득 후 트랜잭션 내 대기
- BEGIN/ALTER TABLE/COMMIT 사이에 다른 작업하면 락이 오래 유지됨
- 배타적 락 획득 후에는 가능한 한 빨리 트랜잭션을 마쳐야 함
Case 6. DDL + 대량 DML이 포함된 트랜잭션
- DDL 단계에서 획득한 락이 DML 단계까지 오래 유지됨
- DDL과 DML을 별도 트랜잭션/마이그레이션 단계로 분리할 것
Case 7. 배타적 락 획득 대기로 인한 다른 세션 블로킹
- 오토베큠이 wraparound 방지 모드일 때 DDL에 yield 안함
- 락 획득 대기 중에 SELECT마저 블로킹됨
- lock_timeout을 낮게 설정하고 재시도 로직 만들 것
Case 8. FK 생성시 주의사항
- 큰 테이블에 FK 생성시 referenced 테이블 스캔으로 시간 소요
- not valid 옵션으로 FK 정의 후 별도 트랜잭션에서 validate
Case 9. FK 삭제시 주의사항
- 두 테이블 락 필요하므로 lock_timeout 재시도 로직 필요
Case 10. CHECK 제약 조건 추가시 주의사항
- 전체 테이블 스캔 발생하므로 FK와 유사한 2단계 접근법 사용
Case 11. NOT NULL 추가시 주의사항
- Postgres 11 미만에서 새 컬럼에 NOT NULL 추가시 테이블 스캔 발생
- Postgres 11부터는 NOT NULL DEFAULT 컬럼 추가로 해결 가능
- Postgres 12부터는 CHECK 제약조건 추가로 NOT NULL 설정 가능
Case 12. 컬럼 데이터 타입 변경시 주의사항
- 전체 테이블 재작성 발생할 수 있음
- 새 컬럼 추가 후 트리거로 데이터 복사하는 접근법 필요
Case 13. CREATE INDEX시 주의사항
- OLTP에서는 CREATE INDEX CONCURRENTLY 사용해야 함
- 유니크 인덱스 생성이 실패하면 invalid 인덱스 정리 필요
Case 14. DROP INDEX시 주의사항
- 락 획득 이슈 있으므로 DROP INDEX CONCURRENLTY 사용
Case 15. 객체 이름 변경시 주의사항
- 앱 코드와 DB 스키마 간 불일치 피하도록 배포 순서 조정 필요
Case 16. DEFAULT 값이 있는 컬럼 추가
- PG 11 이전엔 전체 테이블 재작성 발생
- PG 11부터는 DEFAULT 값 있는 컬럼 추가가 빨라짐
Case 17. CREATE INDEX CONCURRENTLY 실패시 잔여물 처리
- 실패하면 invalid 인덱스가 남으므로 재시도 전에 정리 필요
Case 18. 큰 테이블에 4바이트 정수 기본키 사용
- int8을 써야 함. 대부분의 프레임워크가 이미 int8 사용중.
권장사항
- 현실적인 데이터 크기로 테스트하기
- 배타적 락 유지 시간 체크하기
- 배포 자동화 개선하기
- 다른 사람에게 배우고 지식 공유하기
GN⁺의 의견
이 글은 실제 DB 스키마 변경 시 겪을 수 있는 여러 실수와 주의사항을 잘 정리해주고 있습니다. 특히 배타적 락과 관련된 이슈가 많이 언급되는데, 이는 대용량 데이터베이스일수록 더 심각한 문제를 일으킬 수 있는 사안입니다.
개발자들이 흔히 간과하기 쉬운 FK, NOT NULL, 인덱스 등을 다룰 때 주의사항도 구체적으로 잘 설명하고 있습니다. Postgres의 버전별 개선 사항을 이해하고 활용하는 것도 도움이 될 것 같네요.
무엇보다 현실적인 데이터 크기로 철저히 테스트하고, 배포 자동화를 개선해나가는 것이 스키마 변경의 위험을 최소화하는데 핵심이라는 점에 공감합니다. 테스트와 배포 자동화를 위해 Database Lab Engine 같은 도구를 활용해보는 것도 좋겠습니다.
이런 유용한 팁들을 공유해주는 기술 블로그 글들이 더 많아지면 좋겠습니다. 이런 정보가 널리 퍼질수록 데이터베이스를 다루는 개발자들의 역량 향상에 분명 도움이 될 것 같네요.