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

  • 소개 : GitLab 13.12에서 소개되었습니다.

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에게 새 테이블 이름을 사용하여 이 정보를 다른 테이블에서 획득하도록 지시해야 합니다.

마이그레이션 전략 분해

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

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

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

  1. lib/gitlab/database.rbTABLES_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 그룹에 언급합니다.
    • Engineering 주간 회고 문서에 노트 추가: ‘테이블 이름’은 N.M에서 이름이 변경될 예정입니다. N.M 및 N.M+1 릴리스에서 이 테이블의 수정이 허용되지 않습니다.
  • 도우미 메서드는 테이블 이름을 변경하기 위해 표준 Rails의 rename_table 도우미를 사용합니다.
  • 도우미는 시퀀스와 인덱스의 이름을 변경합니다. 때로는 일부 인덱스가 올바르게 변경되지 않을 수 있으므로 로컬로 마이그레이션을 실행한 후에 잘못된 이름의 인덱스(db/structure.sql)가 있는지 확인합니다. 해당 항목은 M.N+1 릴리스의 일부가 될 수 있는 별도의 마이그레이션에서 수동으로 변경할 수 있습니다.
  • 외래 키 열은 여전히 이전 테이블 이름을 포함할 수 있습니다. 작은 테이블의 경우 표준 열 이름 변경 프로세스를 따릅니다.
  • 테이블 변경(열 추가 or 제거)은 이름 변경 프로세스 중에 허용되지 않습니다. 이름 변경 마이그레이션을 시작하기 전 or 다음 릴리스에서 테이블에 모든 변경 사항이 발생하는지 확인합니다.
  • 인덱스 이름이 변경될 수 있으므로 모델이 unique_by: index_name 옵션을 사용하여 대량 삽입(for example, 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. 추가로 lib/gitlab/database.rbTABLES_TO_BE_RENAMED에서 테이블 정의가 반드시 제거되어야 합니다.

    다음을 편집하여 수행합니다. lib/gitlab/database.rb:

    From:

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

    To:

    TABLES_TO_BE_RENAMED = {}.freeze
    

제로 다운타임 배포

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

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

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