- 대량 삽입을 위해
ApplicationRecord
준비하기 -
bulk_insert!
및bulk_upsert!
를 사용하여 레코드 삽입 has_many
관계를 대량으로 삽입- 알려진 제한 사항
대량 삽입을 위한 테이블에 삽입
가끔은 한꺼번에 대량의 레코드를 저장해야 할 때가 있는데, 컬렉션을 반복하고 각 레코드를 개별적으로 저장하는 것은 비효율적일 수 있습니다. Rails 6에서 insert_all
이 도입되면서 GitLab은 ActiveRecord
객체를 대량으로 삽입하기 위한 안전하고 간단한 API 세트를 추가했습니다.
대량 삽입을 위해 ApplicationRecord
준비하기
모델 클래스가 대량 삽입 API를 활용하려면 먼저 BulkInsertSafe
concern을 포함해야 합니다.
class MyModel < ApplicationRecord
# 다른 포함 사항 여기에
# ...
include BulkInsertSafe # 마지막에 이를 포함시킵니다
# ...
end
BulkInsertSafe
concern에는 두 가지 기능이 있습니다.
- 대량 삽입에 대해 안전하지 않은 ActiveRecord API를 사용하지 않도록 모델 클래스에 대한 검사를 수행합니다 (아래 내용 참조).
-
bulk_insert!
와bulk_upsert!
라는 새로운 클래스 메서드를 추가하며 한 번에 많은 레코드를 삽입할 수 있습니다.
bulk_insert!
및 bulk_upsert!
를 사용하여 레코드 삽입
대상 클래스가 BulkInsertSafe
에 의해 수행된 검사를 통과하면 다음과 같이 ActiveRecord 모델 객체 배열을 삽입할 수 있습니다.
records = [MyModel.new, ...]
MyModel.bulk_insert!(records)
bulk_insert!
를 호출하는 경우 항상 _새 레코드_를 삽입하려고 시도합니다. 대신 기존 레코드를 새 값으로 교체하고 기존 레코드가 없는 경우에만 삽입하려면 bulk_upsert!
를 사용할 수 있습니다.
records = [MyModel.new, existing_model, ...]
MyModel.bulk_upsert!(records, unique_by: [:name])
이 예에서 unique_by
는 레코드를 고유하게 여기고 그에 따라 존재하는 경우 업데이트되는 열을 지정합니다. 예를 들어, existing_model
에 name
속성이 있고 동일한 name
값을 갖는 레코드가 이미 존재한다면 해당 필드는 existing_model
의 내용으로 업데이트됩니다.
unique_by
매개 변수는 Symbol
로 전달되어 데이터베이스 색인을 지정할 수도 있는데, 이 경우 해당 색인으로 열이 고유하게 여겨집니다.
MyModel.bulk_insert!(records, unique_by: :index_on_name)
레코드 유효성 검사
bulk_insert!
메서드는 records
가 트랜잭션 내에서 삽입되도록 보장하고 삽입 전에 각 레코드에 대해 유효성 검사를 실행합니다. 레코드가 유효성 검사에 실패하는 경우 오류가 발생하고 트랜잭션이 롤백됩니다. :validate
옵션을 사용하여 유효성 검사를 끌 수 있습니다.
MyModel.bulk_insert!(records, validate: false)
일괄 크기 구성
records
수가 주어진 임계값을 초과하는 경우 삽입이 여러 일괄로 발생합니다. 기본 일괄 크기는 BulkInsertSafe::DEFAULT_BATCH_SIZE
에서 정의됩니다. 기본 임계값이 500이라고 가정하면 950개의 레코드를 삽입하면 크기가 각각 500과 450인 두 개의 일괄이 순차적으로 작성됩니다. :batch_size
옵션을 사용하여 기본 일괄 크기를 재정의할 수 있습니다.
MyModel.bulk_insert!(records, batch_size: 100)
950개의 레코드와 동일한 수의 경우, 이 경우 10개의 일괄이 작성됩니다. 이는 발생하는 INSERT
문 수에도 영향을 미치므로 코드에 미치는 성능 영향을 메트릭하는 것이 좋습니다. 처리할 INSERT
문 수와 각 INSERT
의 크기와 비용 사이에는 균형이 있습니다.
중복 레코드 처리
bulk_insert!
에만 적용됩니다. 기존 레코드를 업데이트하려는 경우 bulk_upsert!
를 사용하십시오.삽입하려는 일부 레코드가 이미 존재하는 경우 기본 키 충돌이 발생할 수 있습니다. 이러한 문제를 해결하는 두 가지 방법이 있습니다. 오류를 발생시켜 곧바로 실패하거나 중복 레코드를 건너뛸 수 있습니다. bulk_insert!
의 기본 동작은 빠르게 실패하여 ActiveRecord::RecordNotUnique
오류를 발생시키는 것입니다.
이를 원치 않는 경우 skip_duplicates
플래그를 사용하여 중복 레코드를 건너뛸 수 있습니다.
MyModel.bulk_insert!(records, skip_duplicates: true)
안전한 대량 삽입 요구 사항
ActiveRecord의 많은 부분은 콜백을 중심으로 구축되어 있습니다. 이러한 콜백의 많은 부분은 save
또는 create
와 같은 모델 생명 주기 이벤트에 응답하여 발생합니다. 이러한 콜백은 모든 인스턴스에 대해 호출되도록 의도되었기 때문에 대량 삽입에 사용할 수 없습니다. 이벤트가 대량으로 삽입될 때 호출되지 않으므로 현재 이러한 사용을 방지하고 있습니다.
명시적으로 허용된 콜백에 대한 자세한 내용은 BulkInsertSafe
에서 정의됩니다. 클래스가 안전하게 지정되지 않은 콜백을 사용하고 BulkInsertSafe
를 포함하면 애플리케이션에서 오류가 발생합니다.
BulkInsertSafe
대 InsertAll
내부적으로 BulkInsertSafe
는 InsertAll
에 기반하고 있으며, 언제 BulkInsertSafe
를 선택해야 하는지 궁금할 수 있습니다. 이 결정을 내릴 수 있도록 이러한 클래스 사이의 주요 차이점을 아래 표에 나열했습니다.
입력 유형 | 입력 유효성 검사 | 일괄 크기 지정 | 콜백 우회 가능 | 트랜잭션 처리 | |
---|---|---|---|---|---|
bulk_insert!
| ActiveRecord 객체 | 예 (선택 사항) | 예 (선택 사항) | 안 됨 (안전하지 않은 콜백 사용 방지) | 가능 |
insert_all!
| 속성 해시 | 아니오 | 아니오 | 가능 | 가능 |
요약하면, BulkInsertSafe
는 대량 삽입을 일반적인 ActiveRecord 객체와 삽입에 가깝게 이동시킵니다. 그러나 단순히 대량으로 원시 데이터를 삽입하는 경우에는 insert_all
이 더 효율적입니다.
has_many
관계를 대량으로 삽입
일반적인 사용 사례로는 관계 소유자의 소유된 관계를 통해 관련된 관계를 컬렉션으로 저장하는 것입니다. 소유된 관계는 has_many
클래스 메서드를 통해 관계 소유자에 연관되어 있으며 다음과 같이 선언할 수 있습니다.
owner = OwnerModel.new(owned_relations: array_of_owned_relations)
# 'owned_relations'를 하나씩 저장
owner.save!
이 방식은 array_of_owned_relations
이 크기가 크다면, owned_relations
의 각 레코드에 대해 단일 INSERT
와 트랜잭션이 발생하여 비효율적일 수 있습니다. 이 문제를 해결하기 위해 BulkInsertableAssociations
concern을 사용하여 소유자가 대량 삽입에 안전한 관계를 정의한다고 선언할 수 있습니다.
class OwnerModel < ApplicationRecord
# 다른 포함 사항 여기에
# ...
include BulkInsertableAssociations # 마지막에 이를 포함시킵니다
has_many :my_models
end
여기서 my_models
는 이전에 설명한 대량 삽입에 안전하게 선언되어야 합니다. 이제 다음과 같이 아직 저장되지 않은 레코드를 삽입할 수 있습니다.
BulkInsertableAssociations.with_bulk_insert do
owner = OwnerModel.new(my_models: array_of_my_model_instances)
# 단일 대량 삽입을 통해 'my_models'를 저장합니다 (여러 일괄로 이루어질 수도 있음)
owner.save!
end
이 블록 내에서 BulkInsertSafe
가 아닌 관계를 저장할 수도 있습니다. 이러한 관계는 블록 외부에서 save
를 호출한 것과 같이 처리됩니다.
알려진 제한 사항
이 API들의 사용에는 몇 가지 제한 사항이 있습니다:
-
BulkInsertableAssociations
:- 현재
has_many
관계와만 호환됩니다. -
has_many through: ...
관계는 아직 지원되지 않습니다.
- 현재
또한 입력 데이터는 최대 1000개의 레코드로 제한되어야 하거나,
이미 일괄 처리되어야 합니다. INSERT
문은 단일 트랜잭션에서 실행되므로 많은 양의 레코드의 경우 데이터베이스 안정성에 부정적인 영향을 줄 수 있습니다.