다운타임 없이 테이블 이름 바꾸기

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

이 기술은 다음 단계로 데이터베이스 뷰를 기반으로 합니다:

  1. 데이터베이스 테이블 이름을 변경합니다.
  2. 이전 테이블 이름을 사용하여 새로운 테이블 이름을 가리키는 데이터베이스 뷰를 만듭니다.
  3. ActiveRecord의 스키마 캐시에 대한 우회 방법을 추가합니다.

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

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

데이터베이스 뷰는 기본 테이블 스키마(기본값, null이 아닌 제약 조건, 인덱스 등)를 노출하지 않으므로 애플리케이션을 업데이트하여 새로운 테이블 이름을 사용하기 위한 추가 단계가 필요합니다. ActiveRecord는 이 데이터를 많이 사용하므로, 예를 들어 새로운 모델을 초기화하는 데 필요합니다.

이 제한을 우회하기 위해 ActiveRecord에 새로운 테이블 이름을 사용하여 다른 테이블에서 이 정보를 가져오도록 지시해야 합니다.

마이그레이션 전략 분석

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

현재 릴리스를 “릴리스 N.M”으로 간주합니다.

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

  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

중요 사항:

  • 다른 개발자들에게 테이블 이름이 변경될 것임을 알려주세요.
    • 병합 요청에서 @gl-database 그룹에 핑을 보냅니다.
    • Engineering Week-in-Review 문서에 메모를 추가합니다: 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에서 테이블 정의 must 제거해야 합니다.

    이를 위해 lib/gitlab/database.rb에서 TABLES_TO_BE_RENAMED 상수를 편집합니다:

    이전:

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

    이후:

    TABLES_TO_BE_RENAMED = {}.freeze
    

무중단 배포

애플리케이션이 다운타임 없이 업그레이드될 때, 구버전 코드가 실행되고 있는 애플리케이션 인스턴스가 있을 수 있습니다. 구버전 코드는 여전히 구 데이터베이스 테이블을 참조합니다. 쿼리는 여전히 문제 없이 작동합니다. 왜냐하면 하위 호환 가능한 데이터베이스 뷰가 설정되어 있기 때문입니다.

구버전 애플리케이션을 다시 시작하거나 데이터베이스에 다시 연결해야 하는 경우, ActiveRecord는 다시 열 정보(column information)를 가져옵니다. 이때, 우리 이전에 마크한 테이블(TABLES_TO_BE_RENAMED)은 ActiveRecord에게 데이터베이스 테이블 정보를 가져올 때 새 데이터베이스 테이블 이름을 사용하도록 지시합니다.

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