-
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
Cleanup define_attribute_methods
initializer
#48743
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -171,23 +171,35 @@ class Railtie < Rails::Railtie # :nodoc: | |
end | ||
|
||
initializer "active_record.define_attribute_methods" do |app| | ||
# For resiliency, it is critical that a Rails application should be | ||
# able to boot without depending on the database (or any other service) | ||
# being responsive. | ||
# | ||
# Otherwise a bad deploy adding a lot of load on the database may require to | ||
# entirely shutdown the application so the database can recover before a fixed | ||
# version can be deployed again. | ||
# | ||
# This is why this initializer tries hard not to query the database, and if it | ||
# does, it makes sure to any possible database error. | ||
check_schema_cache_dump_version = config.active_record.check_schema_cache_dump_version | ||
config.after_initialize do | ||
ActiveSupport.on_load(:active_record) do | ||
if app.config.eager_load | ||
# In development and test we shouldn't eagerly define attribute methods because | ||
# db:test:prepare will trigger later and might change the schema. | ||
if app.config.eager_load && !Rails.env.local? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not super happy with this |
||
begin | ||
descendants.each do |model| | ||
# If the schema cache was loaded from a dump, we can use it without connecting | ||
if schema_reflection = model.connection_pool.schema_reflection | ||
# TODO: this is dirty, can we find a better way? | ||
schema_reflection = schema_reflection.bind(nil) | ||
elsif model.connected? | ||
# If there's no connection yet, we avoid connecting. | ||
schema_reflection = model.connection.schema_reflection | ||
end | ||
|
||
# If the schema cache doesn't have the columns | ||
# hash for the model cached, `define_attribute_methods` would trigger a query. | ||
if schema_reflection && schema_reflection.columns_hash?(model.table_name) | ||
# If the schema cache doesn't have the columns for this model, | ||
# we avoid calling `define_attribute_methods` as it would trigger a query. | ||
# | ||
# However if we're already connected to the database, it's too late so we might | ||
# as well eagerly define the attributes and hope the database timeout is strict enough. | ||
# | ||
# Additionally if `check_schema_cache_dump_version` is enabled, we have to connect to the | ||
# database anyway to load the schema cache dump, so we might as well do it during boot to | ||
# save memory in pre-forking setups and avoid slowness during the first requests post deploy. | ||
schema_reflection = model.connection_pool.schema_reflection | ||
if check_schema_cache_dump_version || schema_reflection.cached?(model.table_name) || model.connected? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @casperisfine, I'm seeing this trigger DB connections on boot even when It's quite possible I'm missing something, but since the newly added Or alternatively, would adding There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So I'm not a great writer, but I tries to explain that in the last part of the comment. There are competing concerns here:
The only way to achieve all the above is to have schema cache dumps (which few users do) and to turn off So for users that don't have a schema cache persisted we're kinda stuck between a rock and a hard place here. We can either not trigger queries on boot and degrade memory performance and first requests performance, or trigger queries on boot and degrade resiliency. I chose the later. Previously we'd only do it if somehow we were already connected, but after reflection I think it's a bad behavior, because if you fix your app to no longer connect on boot, you will suddenly experience a performance degradation. So yeah, all this is tricky, and as long as schema cache dumps remain hard to integrate, I don't think we can do much better here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @byroot, thanks for your thoughtful reply! If I'm understanding correctly, this PR intentionally changes the default Rails behavior to always attempt connecting to the DB at boot, except when a schema cache dump is both present and version checks are disabled. And, if that DB connect fails, it always displays a warning. Again assuming that's correct, then it seems that the For background, I discovered this because we have a test in our apps that runs We don't use Would it be worth replacing If this new behavior is indeed kept, then it would seem like it warrants a changelog entry? BTW, while I'm here, thanks so much for your work on many of Rails' gnarlier internals. Often not visible, but still highly appreciated! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That is correct.
I understand it might be a bit annoying in your use case, but I don't think it's a huge deal and is probably valuable for users in general.
So we've discussed this further with @matthewd. He'll do a more thorough summary of what was said and concluded, but it's likely we'll partially revert to the older behavior here, as in prioritize not connecting to the database (resiliency) over eager loading attributes (performance). In the end I'm not fully satisfied with either solution. I really wish we didn't have to make such a silly tradeoff. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, @byroot and I discussed this, and while we both feel that the ideal choice is to define attributes based on the schema cache during boot without touching the database, if we have to choose between connecting early to verify the schema version or deferring attribute definition, retaining the historical sacrifice of performance is probably the safer way to go. In particular, in my view, that allows Ultimately, only eagerly defining attributes when a schema dump is present and the version check is disabled is Not Great: defining attributes is expensive, and it saves both memory and initial-request runtime to do it at the same time we're eager loading everything else. In the slightly longer term, we should look into other ways to make this work better:
Footnotes
|
||
model.define_attribute_methods | ||
end | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor typo here @casperisfine
Is it supposed to be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, fixed in 47d0f4e