-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Find a way to generate schema cache: db/schema_cache.yml
#862
Comments
If you want to do this manually instead of automatically you can hook into
Then you also need to have the The reason this works is it guarantees your schema cache is always updated right after the current migrations have been run. Then even if your slug is promoted from staging to production and your production database is migrated without generating a new schema cache, it should still hold all the values from the last time it was generated since that came from the same migration. |
@schneems Thank you for looking into this. Rake::Task["assets:precompile"].enhance do
Rake::Task["db:migrate"].invoke
Rake::Task["db:schema:cache:dump"].invoke
end Got some questions to make sure I understand:
|
@schneems about the first suggestion for running the schema cache during the build phase- if you could add that to the buildpack it would a win for every rails-on-heroku developer out there. |
If you're deploying with Rails, then we force the build to stop if the
By "manually" I meant hooking into
I am certainly considering it. I wouldn't expect it to come any time soon though. We need a solution that will work for 100% of the cases and until I can figure out the best way to make that happen, explicitly adding this in right after |
Thank you for @schneems the elaborate response. |
db/schema_cache.yml
Related issue rails/rails#35770, you might need to remove the
Call from |
Is it possible to have a deploy specific Rakefile
deploy script
|
Related ticket https://heroku.support/918801 |
@jeffblake removing columns is going to result in exceptions even without preboot if you have any request volume because AR will raise exceptions if the shape of a table has changed when you read or write (even if those columns aren't directly referenced). One way to get around this is by deploying a commit where you add the column to |
This article suggests you can run multiple rake commands in the release phase like this:
So I edited my Procfile to do both
Will this work? |
@dalezak by the time the release phase runs, it's too late. The slug has already been compiled. The workaround above only works because it does the schema cache dumping before the slug is packaged. |
@geoffharcourt where do you extend assets:precompile block so that it's registered? Inside the
Will this get called during deployment to Heroku? Or does it manually need to be called locally? |
@dalezak we do this in
|
Thanks @geoffharcourt, where does this live? Inside a rake file like |
It's in |
Hi everybody! I must be missing something. What's wrong with including and having CI also generate the file and failing CI if the generated file is different from the one on Github? Note, this strategy required us to monkey patch the SchemaCache class so that Hashes and Arrays are always sorted so that YAML output shows as ordered. Any disadvantage to that? |
@justin808 Nothing wrong with it, it simply means that as a developer you would need to run the command to generate the schema cache every time you run rake db:migrate. |
@justin808 I was doing this is the past, but a few times forgot to manually recreate the
|
@schneems Should that URL still work? |
It's for internal use only. It's helpful to link both ways between support tickets so later engineers can see real-world use cases to get a fuller context on the issue. |
@schneems can you expand on why enhancing |
Wondering how we could resolve this in 2022 😆, the issue seems to persist ? |
We recently had to come up with a novel way to implement this, and I'd like to share it. Add a new rake task that generates the schema file and stores it in Rails Cache (Redis in our case), and run it on release after migrations. Then, modify our Procfile to run another new rake task that reads the schema file from Rails Cache and stores it in the local filesystem, before running the actual Rails process. The main caveat we're aware of is a possible race condition: if two releases are running at roughly the same time, and the second one has a DB migration, the it's possible for the first one to finish later and overwrite the cache value with an older schema version. However, this would be resolved with the next release anyway, so we chose not to address this at the moment. Following this approach allows us to keep build and release tasks separate, while benefitting from a fresh ActiveRecord cache most of the time. @schneems I'm not sure if it doing something similar on the buildpack level might be feasible (perhaps not, because we're modifying the filesystem at process runtime), but as we haven't seen this approach anywhere yet, we thought it'd be useful to share. Sample code: Rake tasks namespace :active_record_schema_cache do
desc 'Dumps AR schema cache, then writes it to the Rails cache store (Redis)'
task dump: :environment do
exit unless ENV.fetch('AR_SCHEMA_CACHING_ENABLED', 'false') == 'true'
Rake::Task['db:schema:cache:dump'].invoke
dump_data = File.read('db/schema_cache.yml')
Rails.cache.write('active_record_schema_cache_data', dump_data)
rescue => e
# RedisCacheStore swallows any Redis errors: https://github.com/rails/rails/blob/7-1-stable/activesupport/lib/active_support/cache/redis_cache_store.rb#L478-L483
# this is here as an additional failsafe to prevent blocking releases and process startups
Rails.logger.error(message: e)
end
desc 'Gets the AR schema cache from the Rails cache store (Redis) and saves it to filesystem'
task load: :environment do
exit unless ENV.fetch('AR_SCHEMA_CACHING_ENABLED', 'false') == 'true'
dump_data = Rails.cache.read('active_record_schema_cache_data')
exit unless dump_data
File.write('db/schema_cache.yml', dump_data)
rescue => e
# RedisCacheStore swallows any Redis errors: https://github.com/rails/rails/blob/7-1-stable/activesupport/lib/active_support/cache/redis_cache_store.rb#L478-L483
# this is here as an additional failsafe to prevent blocking releases and process startups
Rails.logger.error(message: e)
end
end Procfile
|
There's a cool feature called the schema cache: https://kirshatrov.com/2016/12/13/schema-cache/
Here's some problems with using this feature on Heroku.
Problem 1
The officially sanctioned method to run database migrations is via release phase:
That is because this fires on slug promotion from staging to production and other times when no build is triggered. In short it fires every time your app is released.
The big limitation is that modifications to disk are not preserved in the release phase. So if you run migrations and then generate a schema cache, welp, that schema cache isn't going to do anything because it's saved as a disk modification which is discarded via release phase.
Problem 2See update at the bottomOriginal:
We could generate the schema dump at build time instead of at release time, however what happens if you are deploying with a new migration. What will happen is this:
Update: It turns out this is not correct. The schema cache falls back to the old behavior if it detects that the versions are not up to date https://github.com/rails/rails/blob/1d2f553d16d8e3ee1dd6622b96ad98a72ea98d2d/activerecord/lib/active_record/railtie.rb#L136-L141.
So in the best case, after deploys that migrate the database you'll not take advantage of the schema cache. In the worst case if you're deploying with a migration on every deploy, you would be no worse off.
The text was updated successfully, but these errors were encountered: