-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reduce memory consumption when calling first & last on dirty association #39627
Reduce memory consumption when calling first & last on dirty association #39627
Conversation
3ced014
to
98b4980
Compare
8bad7ad
to
811e47e
Compare
Benchmarking memory usage, mostly for fun - Memory usage before (running the following script without the patched files) and after (with the patch):
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails"
gem "sqlite3"
gem "memory_profiler"
end
require "active_record"
require "minitest/autorun"
require "logger"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :posts, force: true do |t|
end
create_table :comments, force: true do |t|
t.integer :post_id
t.string :content
end
end
# patched files - refer to PR for details
require './collection_association'
require './collection_proxy'
require './finder_methods'
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
post = Post.create
1000.times { post.comments.create }
post.reload
post.comments.build
report = MemoryProfiler.report do
post.comments.first
end
report.pretty_print(detailed_report: false, scale_bytes: true) |
811e47e
to
303de7a
Compare
I'm still don't get about the use case that I'm not excited to change any behavior for such like unsure use case, at least this PR will possibly change returned record (I've added a regression test for that 60b43ab). |
Thank you for the feedback, @kamipo. I don't fully understand the use case for calling I recognize your concern about changing behavior, but I want to point out the existing behavior isn't consistent - # This test currently fails
def test_calling_first_on_association_returns_same_record_whether_loaded_or_not_loaded
topics = Topic.where(author_name: "Carl")
assert_equal topics.first, topics.reload.first
end This conflicts with the documentation, which states that I'm starting to wonder if a cleaner solution is to always use I would like to think about this some more, and maybe submit another revision for folks to look at in a few days, or open an issue on |
@alipman88 Thank you for looking into this issue! In our case we were checking that the new submission is not a duplicate, so after building a new record we were pulling the previous saved record and comparing some sets of values for similarity. To our suprise that check despite asking for A possibly more common use case I could foresee might be a form to create a new record + displaying some previous records on the same page. E.g. It was a five second fix for us with a manual |
I know the inconsistency ( Related: #30800 (comment), #31006, #24131 (comment), #33492. The inconsistency was introduced at #5153, personally I think it is hard to restore perfectly consistency unless reverting/opt-out #5153. |
I think you're right - we might consider using if loaded?
if order_values.present?
return records[index, limit] || []
else
return records
.min_by(index + limit) { |r| [r.id ? 0 : 1, r.attributes[implicit_order_column], r.id] }
.last(limit) || []
end
end But I'm not convinced that extra code is worth it. In practice, I don't think the current difference in ordering between loaded & unloaded relations causes that much confusion. (I can only find two issues, #39061 and #13743). I still would like to reduce the memory usage of calling |
d808a54
to
28877ef
Compare
@kamipo - I've created an alternate branch that satisfies your regression test (by not applying default ordering when calling But I'm not sure if preserving the existing behavior is worth the extra code complexity - it may be wise to hold off on this for now, and reconsider it for an upcoming minor release when we might excuse a behavior change. I intend to take a break from this issue. I respect your judgement either way. 🙂 |
28877ef
to
ef126d6
Compare
Avoid loading association when calling first, last or other finder methods (e.g. take, second, third) on a dirty association. Instead of loading all records in an association from the database, load no more than the requested limit. If the number of available database records falls short of the requested limit, pad as needed with unsaved records.
This reverts commit 60b43ab.
ef126d6
to
c165caa
Compare
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
Avoid loading association when calling first, last or other finder methods (e.g. take, second, third) on a dirty association. Instead of loading all records in an association from the database, load no more than the requested limit. If the number of available database records falls short of the requested limit, pad as needed with unsaved records.
Fixes #39455.