Behaviour of ActiveRecord `find_or_create_by` is changing in Rails 7.1.0, introducing subtransactions

GitLab is currently on 7.0.8, and the Rails version 7.1.0 changes the behaviour of the method find_or_create_by

7.0.8 version:

    def find_or_create_by(attributes, &block)
      find_by(attributes) || create(attributes, &block)
    end

7.1.0 version:

    def find_or_create_by(attributes, &block)
      find_by(attributes) || create_or_find_by(attributes, &block)
    end

The introduction of create_or_find_by in this method is worthy of attention because that method introduces subtransactions:

 def create_or_find_by(attributes, &block)
      transaction(requires_new: true) { create(attributes, &block) }
    rescue ActiveRecord::RecordNotUnique
      find_by!(attributes)
    end

This is now similar to our custom method, safe_find_or_create_by, which we already deem as unsafe in our guides because it opens a new sub-transaction.

Both methods [create_or_find_by & safe_find_or_create_by] use subtransactions internally if executed within the context of an existing transaction. This can significantly impact overall performance, especially if more than 64 live subtransactions are being used inside a single transaction.

TODO:

Before we update our Rails version to 7.1.0, we should

Using a pattern of

find_by(attributes) || upsert(attributes, on_duplicate: :skip)

could be a good alternative. upsert with on_duplicate: :skip also prevents the race condition without using a subtransaction. Noticed during the code review of !142600 (comment 1747335749)

Edited by Manoj M J