rails 6.0 버전부터 create_or_find_by
라는 메서드가 추가됐다. 해당 메서드에 사용 방법에 대해서 알아보자!
레코드를 먼저 생성하려고 시도하고 이미 기존에 있는 데이터가 있다며 기존 데이터를 반환하는 메서드다. 구현된 소스 코드를 살펴보니 생성하는 과정에서 RecordNotUnique
예외가 발생하면 find_by
메서드를 실행하는 것 같다.
def create_or_find_by(attributes, &block)
transaction(requires_new: true) { create(attributes, &block) }
rescue ActiveRecord::RecordNotUnique
find_by!(attributes)
end
해당 기능을 사용하려면 RecordNotUnique
예외를 발생하기 위해서 데이터베이스의 유니크 제약을 걸어줘야 한다. migrate 파일 기준으로 작성하였습니다.
def change
create_table :users do |t|
t.string :name, index: { unique: true }
end
end
레코드가 존재하지 않으면 새로운 레코드를 생성해야 할 때 find_or_create_by
를 사용한다. 이 경우 select
-> insert
사이에 시차로 인해 여러 스레드 및 프로세스 환경에서 실행 시 race condition
이 발생할 수 있다. 하지만 create_or_find_by
의 경우 insert
를 먼저 시도하고 데이터베이스 유니크 제약을 통해 흐름을 제어함으로써 race condition
의 확률을 줄인다.
데이터베이스 유니크 제약이 걸린 상황에서 find_or_create_by
를 사용한다고 했을 때 race_condition
이 발생하면 RecordNotUnique
예외를 반환하기 때문에 애플리케이션에 오류가 발생할 것이다. create_or_find_by
는 예외가 발생할 때 해당 예외를 catch하고 find_by
를 하므로 오류가 발생하지 않는다.
find_or_create_by
보다 4배 정도 느리다는 것 같다.insert
-> select
하는 과정에서 다른 프로세스에서 delete가 실행 시 race_conditon
이 발생할 수 있다. 다만 이 확률은 매우 낮다고 하다.