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

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에게 지시합니다(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

중요 사항:

  • 테이블 이름이 변경될 예정임을 다른 개발자에게 알립니다.
    • 병합 요청에서 @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
    

다운타임 없는 배포

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

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

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