문자열과 텍스트 데이터 유형

문자열 또는 기타 텍스트 정보를 저장하는 새로운 열을 추가할 때:

  1. string 데이터 유형 대신에 항상 text 데이터 유형을 사용합니다.
  2. text 열은 테이블을 생성할 때 #text ... limit: 100 헬퍼(아래 참조)를 사용하거나 기존 테이블을 변경할 때 add_text_limit를 사용하여 항상 한도를 설정해야 합니다.

표준 Rails text 열 유형은 한도를 정의할 수 없지만, 우리는 create_table을 확장하여 limit: 255 옵션을 추가합니다. create_table 이외에도 add_text_limit를 사용하여 이미 존재하는 열에 검사 제약을 추가할 수 있습니다.

배경 정보

우리가 항상 string 대신 text를 사용하길 원하는 이유는 string 열은 한도를 업데이트하려면 ALTER TABLE ... 명령을 실행해야 한다는 단점이 있기 때문입니다.

항목이 추가되는 동안 ALTER TABLE ... 명령은 테이블에 EXCLUSIVE LOCK을 요구하며, 이는 해당 열을 업데이트하고 모든 기존 레코드를 유효성 검사하는 과정 동안 유지됩니다. 이는 큰 테이블에 대해 시간이 오래 걸릴 수 있는 과정입니다.

반면에 PostgreSQL에서는 텍스트가 더 나은 텍스트와 그다지 다를 바 없습니다. 이에 더해, 이미 존재하는 열에 한도를 추가하거나 해당 한도를 업데이트하는 것은 유효성 검사 단계 동안 EXCLUSIVE LOCK가 요구되지 않는 큰 비용이 드는 작업입니다. 첫 단계에서 옵션을 끄는 데 선언을 업데이트하는 데에 EXCLUSIVE LOCK이 요구되며, 이는 업데이트하는 동안에만 필요합니다. 이후에 VALIDATE CONSTRAINT를 사용하여 나중의 단계에서 유효성을 검사할 수 있으며, 이는 SHARE UPDATE EXCLUSIVE LOCK만 요구합니다(읽기 및 쓰기를 허용하면서 다른 유효성 검사 및 인덱스 생성과 충돌을 일으킬 수 있습니다).

참고: attr_encrypted 속성에는 텍스트 열을 사용하지 마세요. 대신 :binary을 사용하세요.

텍스트 열이 포함된 새 테이블 생성

새로운 테이블을 추가할 때는, 모든 텍스트 열에 대한 한도는 테이블 생성과 같은 마이그레이션에서 추가되어야 합니다. 이 열에 한도를 추가할 수 있게 하는 Rails의 #text 메소드에 limit: 속성을 추가합니다.

예를 들어, db/migrate/20200401000001_create_db_guides.rb에 두 개의 텍스트 열이 있는 테이블을 생성하는 마이그레이션을 고려해보세요.

class CreateDbGuides < Gitlab::Database::Migration[2.1]
  def change
    create_table :db_guides do |t|
      t.bigint :stars, default: 0, null: false
      t.text :title, limit: 128
      t.text :notes, limit: 1024
    end
  end
end

기존 테이블에 텍스트 열 추가

기존 테이블에 열을 추가하려면 해당 테이블에 대해 배타적 잠금이 필요합니다. 해당 잠금은 잠시 유지되지만, add_column이 실행되는데 필요한 시간은 테이블 액세스 빈도에 따라 다를 수 있습니다. 예를 들어, 매우 자주 액세스되는 테이블에 대해 배타적 잠금을 획들하는 것은 GitLab.com에서 몇 분이 걸릴 수 있으며, with_lock_retries를 사용해야 할 수도 있습니다.

텍스트 한도를 추가할 때는, 트랜잭션이 disable_ddl_transaction!로 비활성화되어야 합니다. 이는 마이그레이션이 나중에 실패할 경우 열이 롤백되지 않는다는 것을 의미합니다. 따라서 마이그레이션을 다시 실행하려고 하면 이미 존재하는 열 때문에 오류가 발생할 것입니다.

이러한 이유로, 기존 테이블에 텍스트 열을 추가하는 작업은 다음 중 하나로 수행될 수 있습니다:

별도의 마이그레이션에서 열 및 한도를 추가합니다

새로운 텍스트 열 extended_titlesprints 테이블에 추가하는 마이그레이션을 고려해보세요. db/migrate/20200501000001_add_extended_title_to_sprints.rb:

class AddExtendedTitleToSprints < Gitlab::Database::Migration[2.1]

  # rubocop:disable Migration/AddLimitToTextColumns
  # limit is added in 20200501000002_add_text_limit_to_sprints_extended_title
  def change
    add_column :sprints, :extended_title, :text
  end
  # rubocop:enable Migration/AddLimitToTextColumns
end

첫 번째 마이그레이션에 이어 두 번째 마이그레이션이 따라와야 합니다. extended_title에 추가된 한도가 있는 sprints에 대한 db/migrate/20200501000002_add_text_limit_to_sprints_extended_title:

class AddTextLimitToSprintsExtendedTitle < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  def up
    add_text_limit :sprints, :extended_title, 512
  end

  def down
    # `add_text_limit`은 뒤로 돌릴 수 없기 때문에 다운이 필요합니다
    remove_text_limit :sprints, :extended_title
  end
end

열과 제한을 동시에 추가하되 이미 열이 존재하는 경우 확인

sprints 테이블에 새로운 텍스트 열 extended_title을 추가하는 마이그레이션을 고려해보십시오. db/migrate/20200501000001_add_extended_title_to_sprints.rb:

class AddExtendedTitleToSprints < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  def up
    with_lock_retries do
      add_column :sprints, :extended_title, :text, if_not_exists: true
    end

    add_text_limit :sprints, :extended_title, 512
  end

  def down
    with_lock_retries do
      remove_column :sprints, :extended_title, if_exists: true
    end
  end
end

기존 열에 텍스트 제한 제약 추가

기존 데이터베이스 열에 텍스트 제한을 추가하려면 적어도 두 가지 다른 릴리스로 나누어 여러 단계가 필요합니다:

  1. 릴리스 N.M (현재 릴리스)

    • validate: false를 사용하여 텍스트 열에 제한을 추가하는 순방향 마이그레이션 추가
    • 기존 레코드를 수정하는 순방향 마이그레이션 추가

      참고: 테이블 크기에 따라, 다음 릴리스에서 정리를 위해 백그라운드 마이그레이션이 필요할 수 있습니다. 자세한 내용은 large tables에서 텍스트 제한 제약을 확인하십시오.

    • 다음 마일스톤에 텍스트 제한을 확인하기 위한 이슈 생성
  2. 릴리스 N.M+1 (다음 릴리스)

    • 순방향 마이그레이션을 사용하여 텍스트 제한 확인

예제

특정 릴리스 마일스톤(예: 13.0)에 issues.title_html1024 제한을 추가하고자 한다고 가정해봅시다.

issues는 행이 2500만 개가 넘는 상당히 바쁘고 큰 테이블이므로 업데이트를 실행하는 동안 모든 다른 프로세스가 잠기지 않도록 합니다.

또한, 프로덕션 데이터베이스를 확인한 후 제공된 제한보다 더 많은 문자를 포함하는 issues가 있는 것을 알기 때문에 제약을 추가하고 확인하는 것을 한 번에 할 수 없습니다.

참고: 제공된 제한보다 더 큰 제목을 가진 레코드가 없어도 다른 GitLab 인스턴스에는 해당 레코드가 있을 수 있으므로 어차피 동일한 프로세스를 따르게 됩니다.

새로운 유효하지 않은 레코드 방지 (현재 릴리스)

먼저, 테이블에 NOT VALID 검사 제약 조건으로 제한을 추가하여 새 레코드가 삽입되거나 현재 레코드가 업데이트될 때 일관성을 강제합니다.

위의 예에서 제공된 제한보다 더 큰 제목을 가진 기존 이슈에는 영향을 주지 않으며 issues 테이블의 레코드를 여전히 업데이트할 수 있습니다. 그러나 title_html을 1024 문자보다 더 많은 제목으로 업데이트하려고 하면 제약으로 인해 데이터베이스 오류가 발생합니다.

기존 속성에 제약을 추가하거나 제거하려면, 모든 애플리케이션 변경 사항이 먼저 배포되어야 합니다. 그렇지 않으면 여전히 이전 버전의 애플리케이션 서버가 유효하지 않은 값으로 속성을 업데이트하려고 시도할 수 있습니다. 이러한 이유로 add_text_limit는 순방향 마이그레이션에서 실행되어야 합니다.

여전히 우리 예제에서, 현재의 13.0 마일스톤을 위해 Issue 모델에 다음의 유효성 검사가 추가되었다고 가정합니다:

validates :title_html, length: { maximum: 1024 }

또한, validate: false로 텍스트 제한을 추가하는 순방향 마이그레이션인 db/post_migrate/20200501000001_add_text_limit_migration.rb을 동일한 마일스톤에 업데이트할 수 있습니다:

class AddTextLimitMigration < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  def up
    # 이는 확인하지 않고 제약을 추가합니다.
    add_text_limit :issues, :title_html, 1024, validate: false
  end

  def down
    # `add_text_limit`은 되돌릴 수 없으므로 다운이 필요합니다.
    remove_text_limit :issues, :title_html
  end
end

기존 레코드 수정을 위한 데이터 마이그레이션 (현재 릴리스)

여기서 접근 방법은 데이터 양과 정리 전략에 따라 다릅니다. GitLab.com에서 수정해야 하는 레코드 수는 백그라운드 데이터 마이그레이션을 사용해야 할 지 여부를 결정하는 데 도움이 되는 좋은 지표입니다.

  • 데이터 양이 1,000 레코드보다 적으면 데이터 마이그레이션을 순방향 마이그레이션 내에서 실행할 수 있습니다.
  • 데이터 양이 1,000 레코드보다 많으면 백그라운드 마이그레이션을 생성하는 것이 좋습니다.

어떤 옵션을 사용해야 하는지 확신이 없을 때는 데이터베이스 팀에게 문의하십시오.

예시에 다시 돌아와서, issues 테이블은 상당히 크고 자주 액세스되므로 13.0 마일스톤(현재)에 백그라운드 마이그레이션을 추가할 것입니다. db/post_migrate/20200501000002_schedule_cap_title_length_on_issues.rb:

class ScheduleCapTitleLengthOnIssues < Gitlab::Database::Migration[2.1]
  # GitLab.com에서 영향을 받는 레코드 수, 각 배치가 평균적으로 실행하는 데 필요한 시간 등 ...
  BATCH_SIZE = 5000
  DELAY_INTERVAL = 2.minutes.to_i

  # 백그라운드 마이그레이션은 1024 제한보다 긴 제목을 가진 issues를 업데이트합니다
  ISSUES_BACKGROUND_MIGRATION = 'CapTitleLengthOnIssues'.freeze

  disable_ddl_transaction!

  def up
    queue_batched_background_migration(
      ISSUES_BACKGROUND_MIGRATION,
      :issues,
      :id,
      job_interval: DELAY_INTERVAL,
      batch_size: BATCH_SIZE
    )
  end

  def down
    delete_batched_background_migration(ISSUES_BACKGROUND_MIGRATION, :issues, :id, [])
  end
end

본 가이드를 간략하게 유지하기 위해, 백그라운드 마이그레이션의 정의를 건너뛰고 일괄 배포를 예약하는 데 사용되는 순방향 마이그레이션의 고수준 예제만 제공했습니다. 데이터 혹은 이슈 배포에 대한 자세한 내용은 batched background migrations을 참고해주시기 바랍니다.

텍스트 제한 유효성 검사 (다음 릴리스)

텍스트 제한을 유효성 검사하면 전체 테이블을 스캔하여 각 레코드가 올바른지 확인합니다.

여전히 예시에서, 13.1 마일스톤(다음)에 대해 validate_text_limit 이주 도우미를 최종 배포 후 마이그레이션에서 실행합니다. db/post_migrate/20200601000001_validate_text_limit_migration.rb:

class ValidateTextLimitMigration < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  def up
    validate_text_limit :issues, :title_html
  end

  def down
    # no-op
  end
end

기존 열의 텍스트 제한 제약 조건 증가

기존 데이터베이스 열의 텍스트 제한을 증가시키는 것은 먼저 새로운 제약 조건(다른 이름으로)을 추가한 후 이전 제한을 삭제함으로써 안전하게 수행할 수 있습니다:

class ChangeMaintainerNoteLimitInCiRunner < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  def up
    add_text_limit :ci_runners, :maintainer_note, 1024, constraint_name: check_constraint_name(:ci_runners, :maintainer_note, 'max_length_1K')
    remove_text_limit :ci_runners, :maintainer_note, constraint_name: check_constraint_name(:ci_runners, :maintainer_note, 'max_length')
  end

  def down
    # no-op: 레코드가 길이(maintainer_note) > 255인 경우 실패할 수 있음
  end
end

대규모 테이블의 텍스트 제한 제약 조건

실제로 대규모 테이블의 텍스트 열을 정리해야 하는 경우(ci_buildsartifacts 예시), 백그라운드 마이그레이션이 한동안 실행되어야 하며 데이터 마이그레이션 추가 후에 추가 일괄 백그라운드 마이그레이션 정리가 필요합니다.

그 특별한 경우에는 데이터 마이그레이션을 추가한 발매로 3개의 발매가 필요합니다:

  1. 발매 N.M - 텍스트 제한과 기존 레코드를 수정하는 백그라운드 마이그레이션 추가.
  2. 발매 N.M+1 - 백그라운드 마이그레이션 정리.
  3. 발매 N.M+2 - 텍스트 제한을 유효성 검사함.