Rails 마이그레이션 시 add_foreign_key로 인한 SQLite 데이터 손실 문제

A mere add_foreign_key can wipe out your whole Rails+SQLite production table

작성자
HackerNews
발행일
2025년 09월 27일

핵심 요약

  • 1 SQLite의 `ALTER TABLE` 제약으로 인해 `add_foreign_key`는 테이블을 재구축하며, 이 과정에서 `on_delete: :cascade` 설정된 다른 외래 키가 작동하여 데이터가 삭제될 수 있습니다.
  • 2 문제 해결을 위해 `add_foreign_key` 대신 `add_column`과 `add_index`를 사용하고, 애플리케이션 레벨에서 유효성 검사를 통해 외래 키 제약을 구현했습니다.
  • 3 SQLite 사용 시 `on_delete: :cascade`는 신중하게 적용하고, 마이그레이션 테스트 및 백업을 통해 데이터 손실 위험을 최소화해야 합니다.

도입

Rails 마이그레이션에서 `add_foreign_key`를 사용할 때 SQLite 데이터베이스에서 예기치 않은 데이터 손실이 발생할 수 있는 문제를 다룹니다. 저자는 `Telebugs`라는 자체 호스팅 오류 추적기에서 `add_foreign_key`를 추가한 후, 종속 테이블의 데이터가 완전히 사라지는 심각한 상황을 겪었습니다. 이 문제는 `add_foreign_key :groups, :users, column: :owner_id, on_delete: :nullify`와 같은 일반적인 마이그레이션 코드에서 비롯되었습니다.
  • 문제의 원인: SQLite의 테이블 재구축 방식 SQLite는 ALTER TABLE로 외래 키를 직접 추가하는 것을 지원하지 않습니다. 따라서 add_foreign_key 명령이 실행되면 Rails는 기존 테이블을 임시 테이블 생성, 데이터 복사, 원본 테이블 삭제, 새 스키마로 재생성, 데이터 재복사 과정을 통해 재구축합니다. 이 과정에서 원본 groups 테이블이 삭제될 때, reports 테이블에 설정된 on_delete: :cascade 외래 키가 작동하여 reports 테이블의 모든 레코드가 함께 삭제되는 치명적인 데이터 손실이 발생했습니다.

  • 해결책: add_foreign_key 회피 및 애플리케이션 레벨 제약 저자는 이 문제를 해결하기 위해 add_foreign_key 대신 add_column :groups, :owner_id, :integeradd_index :groups, :owner_id를 사용하여 컬럼과 인덱스만 추가했습니다. 데이터베이스 레벨의 외래 키 제약은 Group 모델에 validates :owner_id, inclusion: { in: ->(record) { User.pluck(:id) + [nil] } }, allow_nil: true와 같은 유효성 검사를 추가하여 애플리케이션 레벨에서 구현했습니다. 이 방법으로 SQLite의 위험을 피하면서도 데이터 무결성을 유지할 수 있었습니다.

  • 배운 점

    • SQLite의 테이블 재구축 방식은 on_delete: :cascade와 결합될 때 데이터 손실을 야기할 수 있으므로 주의해야 합니다.
    • 단순 컬럼 추가 시 add_columnadd_index를 사용하고, SQLite 환경에서는 애플리케이션 레벨에서 제약을 강제하는 것이 더 안전합니다.
    • on_delete: :cascade 대신 on_delete: :nullify를 고려하여 데이터 손실을 방지하고, 애플리케이션에서 고아 레코드를 처리해야 합니다.
    • 마이그레이션은 실제 데이터로 충분히 테스트하고, 실행 전 데이터베이스를 반드시 백업해야 합니다.

결론

이 사례는 Rails와 SQLite를 함께 사용할 때 `add_foreign_key` 마이그레이션이 `on_delete: :cascade` 외래 키와 상호작용하여 예기치 않은 데이터 손실을 초래할 수 있음을 명확히 보여줍니다. SQLite의 특성을 이해하고, 마이그레이션 설계 시 `add_foreign_key` 대신 `add_column` 및 애플리케이션 레벨 유효성 검사를 활용하는 등 신중한 접근이 필요합니다. 데이터 손실 방지를 위해 `on_delete` 옵션 선택, 철저한 테스트, 그리고 데이터베이스 백업의 중요성을 강조합니다. SQLite는 여전히 강력하지만, 그 함정을 인지하고 사용해야 합니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!