다운타임 없이 테이블 이름 변경

GitLab에 내장된 데이터베이스 도우미 메서드를 사용하여 다운타임 없이 데이터베이스 테이블 이름을 변경할 수 있습니다.

이 기술은 데이터베이스 뷰를 기반으로 하며, 다음 단계를 사용합니다.

  1. 데이터베이스 테이블 이름 변경
  2. 새 테이블 이름을 가리키도록 이전 테이블 이름을 사용하여 데이터베이스 뷰 생성
  3. ActiveRecord의 스키마 캐시에 대한 해결 방법 추가

예를 들어, issues 테이블 이름을 tickets로 변경한다고 가정해보겠습니다. 다음 명령을 실행하세요:

BEGIN;
  ALTER TABLE issues RENAME TO tickets;
  CREATE VIEW issues AS SELECT * FROM tickets;
COMMIT;

데이터베이스 뷰는 기본값, not null 제약조건 및 인덱스와 같은 기본 테이블 스키마를 노출하지 않기 때문에, 응용 프로그램을 새 테이블 이름을 사용하도록 업데이트하기 위해 추가 단계가 필요합니다. 예를 들어, ActiveRecord는 이러한 데이터를 새 모델을 초기화하는 데 사용합니다.

이 한계를 극복하기 위해 ActiveRecord에게 이 정보를 새 테이블 이름을 사용하여 다른 테이블에서 가져오도록 지시해야 합니다.

마이그레이션 전략 분석

릴리스 N.M: ActiveRecord 모델의 테이블 표시

현재 릴리스를 “릴리스 N.M”이라고 가정합니다.

이 릴리스에서는 새 테이블 이름을 사용하여 데이터베이스 테이블을 등록하여, 데이터베이스 테이블 정보를 가져오도록 ActiveRecord에 지시합니다 (SchemaCache) (있는 경우). 그렇지 않은 경우 이전 테이블 이름으로 되돌아갑니다. 이것은 제로 다운타임 배포 중에 오류를 피하기 위해 필요합니다.

  1. lib/gitlab/database.rb에서 TABLES_TO_BE_RENAMED 상수 편집

    TABLES_TO_BE_RENAMED = {
      'issues' => 'tickets'
    }.freeze
    

    참고: 이 릴리스(N.M)에서는 tickets 데이터베이스 테이블이 아직 존재하지 않습니다. 이 단계는 실제 테이블 이름 변경을 위한 준비를 합니다.

릴리스 N.M+1: 데이터베이스 테이블 이름 변경

다음 릴리스를 “릴리스 N.M”이라고 가정합니다.

표준 마이그레이션(포스트 마이그레이션이 아님)을 실행하세요:

  def up
    rename_table_safely(:issues, :tickets)
  end
  
  def down
    undo_rename_table_safely(:issues, :tickets)
  end

중요 사항:

  • 다른 개발자들에게 테이블 이름이 변경될 예정임을 알립니다.
    • 합병 요청에서 @gl-database 그룹을 언급합니다.
    • 엔지니어링 주간 회고 문서에 노트를 추가합니다. table_name은 N.M 릴리스 및 N.M+1에서 수정될 수 없습니다.
  • 이 도우미 메서드는 테이블 이름을 변경하기 위해 표준 Rails의 rename_table 도우미를 사용합니다.
  • 이 도우미는 시퀀스 및 인덱스를 이름을 바꿉니다. 때로는 표준 Rails 규칙에서 벗어나 인덱스의 이름이 일관되지 않게 변경되는 경우가 있습니다. 로컬로 마이그레이션을 실행한 후 db/structure.sql에 일관되지 않은 이름의 인덱스가 있는지 확인하세요. 해당 인덱스는 별도의 마이그레이션에 매뉴얼으로 이름을 바꿀 수 있으며, 이는 릴리스 M.N+1의 일부가 될 수도 있습니다.
  • 외래 키 열은 여전히 이전 테이블 이름을 포함할 수 있습니다. 작은 테이블의 경우, 표준 열 이름 변경 프로세스를 따르세요.
  • 테이블 수정(열 추가 또는 삭제)은 이름 변경 프로세스 동안 허용되지 않습니다. 테이블에 대한 모든 변경 사항이 이름 변경 마이그레이션이 시작되기 전에(또는 다음 릴리스에서) 발생하는지 확인하세요.
  • 인덱스 이름이 변경될 수 있으므로 모델이 unique_by: index_name 옵션을 사용하여 대량 삽입(예: insert_allupsert_all)을 사용하지 않는지 확인하세요. 이러한 메서드를 사용하면 인덱스를 이름을 바꾸면 기능이 손상될 수 있습니다.
  • 모델 코드를 수정하여 새 데이터베이스 테이블을 가리키도록 합니다. 모델을 직접 이름을 바꾸거나 self.table_name 변수를 설정하여 수행합니다.

이 시점에서 우리의 쿼리에서 이전 데이터베이스 테이블 이름을 사용하는 애플리케이션이 없습니다.

  1. 포스트 마이그레이션을 통해 데이터베이스 뷰를 제거하세요:

      def up
        finalize_table_rename(:issues, :tickets)
      end
         
      def down
        undo_finalize_table_rename(:issues, :tickets)
      end
    
  2. 추가로, TABLES_TO_BE_RENAMED에서 테이블 정의를 반드시 제거해야 합니다.

    이를 위해 lib/gitlab/database.rb에서 TABLES_TO_BE_RENAMED 상수를 편집하세요:

    From:

    TABLES_TO_BE_RENAMED = {
      'issues' => 'tickets'
    }.freeze
    

    To:

    TABLES_TO_BE_RENAMED = {}.freeze
    

제로 다운타임 배포

응용 프로그램이 다운타임 없이 업그레이드될 때, 이전 코드를 실행 중인 응용 프로그램 인스턴스가 있을 수 있습니다. 이전 코드는 여전히 이전 데이터베이스 테이블을 참조합니다. 이 쿼리는 역호환성 있는 데이터베이스 뷰가 있기 때문에 문제없이 작동합니다.

이전 버전의 응용 프로그램을 재시작하거나 데이터베이스에 다시 연결해야 하는 경우, ActiveRecord는 쿼리할 때 열 정보를 다시 가져옵니다. 이때 이전에 표시한 테이블(M.N에 변경될 예정인 테이블)은 ActiveRecord에게 데이터베이스 테이블 정보를 가져올 때 새 데이터베이스 테이블 이름을 사용하도록 지시합니다.

새 버전의 응용 프로그램은 새 데이터베이스 테이블을 사용합니다.