
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이 발생할 수 있다. 다만 이 확률은 매우 낮다고 하다.