15

Performance regression in CollectionAssocation#build by ghiculescu · Pull Reques...

 3 years ago
source link: https://github.com/rails/rails/pull/42524
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Copy link

Member

ghiculescu commented 11 days ago

edited

#40379 caused a big regression in one of our apps when upgrading to Rails 6.1. If you have an association with lots of records and you call build on it, the build method takes a long time because it has to iterate over every object on the target to check for duplicates.

This is necessary because of how has_many_inversing works, but could be implemented to run more quickly. I've attempted to do that in this PR, by keeping a separate cache of records that were added to the target through has_many_inversing and only searching for the matching record in that case.

Replication script for the bug, this fails against Rails 6.1 and main, passes against Rails 6, passes when run against this branch:

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  gem "rails", "~> 6.1.0"
  gem "sqlite3"
end

require "active_record"
require "minitest/autorun"
require "logger"

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :authors, force: true do |t|
  end
  create_table :posts, force: true do |t|
    t.references :author
  end
end

class Author < ActiveRecord::Base
  has_many :posts
end

class Post < ActiveRecord::Base
  belongs_to :author
end

class BugTest < Minitest::Test
  def test_association_stuff
    author = Author.create!

    posts = 100_000.times.map { |n| { author_id: author.id } }
    Post.insert_all(posts)

    author = Author.find(author.id)
    author.posts.load_target

    5000.times do |n|
      time = Benchmark.ms { author.posts.build }
      assert time < 30, "iteration #{n}: #{time}" # takes about 200ms in Rails 6.1
    end
  end
end

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK