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

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+1: 데이터베이스 테이블 이름 변경

다음 릴리즈를 “릴리즈 N.M”으로 간주합니다.

표준 마이그레이션 (포스트 마이그레이션이 아님)을 실행합니다.

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

중요한 참고 사항:

  • 다른 개발자에게 해당 테이블이 이름이 변경될 예정임을 알려야 합니다.
    • Merge Request에서 @gl-database 그룹을 언급합니다.
    • 엔지니어링 주간 리뷰 문서에 메모를 추가합니다. table_name이 N.M에서 이름이 변경될 예정입니다. 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
    

Zero-downtime 배포

애플리케이션이 다운타임없이 업그레이드될 때, 구현 중인 응용 프로그램 인스턴스가 실행될 수 있습니다. 이전 코드는 여전히 이전 데이터베이스 테이블을 참조합니다. 쿼리는 여전히 문제없이 작동하지만, 하위 호환성 데이터베이스 뷰가 준비되어 있습니다.

만약 이전 버전의 애플리케이션이 다시 시작되거나 데이터베이스에 다시 연결해야 하는 경우, ActiveRecord는 다시 열 정보를 가져옵니다. 이때 우리가 이전에 표시한 테이블 (TABLES_TO_BE_RENAMED)은 ActiveRecord에게 새 데이터베이스 테이블 이름을 가져오도록 지시합니다.

새 버전의 애플리케이션은 새 데이터베이스 테이블을 사용합니다.