Move SQLite pragma configuration to the database configuration file#49691
Move SQLite pragma configuration to the database configuration file#49691suwyn wants to merge 1 commit intorails:mainfrom
Conversation
How they will not be affected? The defaults that were applied will just not be applied anymore. |
|
Ah, I get what you mean now, this code can't be released in 7.1 anymore. It will only be released in 7.2, so not applying defaults automatically with affect any app that is upgrading from 7.1. We need a way to apply it by default if it isn't configured explicitly in the |
For other client requirements we do defaults like this: https://github.com/rails/rails/blob/main/activerecord/lib/active_record/database_configurations/hash_config.rb#L71-L73 But I feel a bit weird having sqlite3 specific defaults set in the hash_config.rb. None of the other options specified are db specific. 🤔 |
Yes, sorry poorly worded. The WAL journal mode is persistent, so the database will remain in WAL even if the
I could see the case for applying the defaults on a new database, but not sure about applying persistent changes implicitly to an existing database... |
|
Got it. I think this patch is ok then. |
That's interesting @eileencodes I hadn't thought about that. There is evidence of other configuration options that are not represented in the HashConfig. My preference would be to keep as generalized and not introduce anything db specific. |
This allows developers to fine tune their SQLite pragma through additional configuration options available in the database configuration file.
76293cd to
1fd33bf
Compare
|
Updated the PR description with another use case where the changes in 7.1 are problematic. tl;dr; Rails 7.1 is no longer compatible with readonly sqlite databases. Also - although I preserved the performance optimizations of #49349 by putting them as defaults in the database configuration file generator, my preference would be to remove them from there as well and require any pragma beyond the FK statement (which existed before 7.1) be explicitly set by the developer. Looking forward to others thoughts and opinions on this. VERY excited on the momentum growing behind SQLite and Rails! :itshappening: |
| raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA") | ||
|
|
||
| (@config.keys & PRAGMA_STATEMENTS).each do |key| | ||
| @raw_connection.send "#{key}=", @config[key] |
There was a problem hiding this comment.
I preferred using the setters offered by the sqlite gem rather than executing the PRAGMA statements directly because the gem provides normalization of the values before setting them.
There was a problem hiding this comment.
If I understand this code correctly, it will also accept something like journal_mode=(:wal). Which I think is nicer than journal_mode: WAL in the config.
There was a problem hiding this comment.
Yep, same could be done for for synchronous
|
Yeah, we don't want to unwind the new defaults or force them into the user's configuration. We should set defaults (which might change over time) that we think are the most appropriate in general, and then allow the user to override them via their config where circumstance dictates. If we want to get fancy, I'd be open to considering a basic check for a filesystem-readonly DB file when choosing them. (Or in extreme, even rescue an attempt to journalize a currently-not-journalled DB and just keep going? 🤔) That is (broadly) what we do in the other adapters too -- though more rarely, because most "how your database operates" config lives in the server in those cases. Following that precedent, I suspect SQLite pragmas are probably most equivalent to session variables, which would mean these values should live under a I'd rather we not list out the possible pragma names... @flavorjones @tenderlove can we rely on |
I don't have a strong preference either way, but I believe we should be able to unset them. The goal of this PR is to allow me to connect to ActiveRecord to SQLite databases without forcing it to use WAL, and to allow activerecord to connect to databases that are readonly. Both were possible before 7.1.
I'd be happy to move them to the variables section if that's desired. I would like to point out that there are two types of pragmas. Ones that persist beyond the connection (e.g.
Perhaps that constant is badly named. It's a list of pragmas that are available to be configured via the database config, not meant to be all the pragmas that exist. I've found it difficult to discern which configuration options were available with each adapter so this approach was meant to make that more discoverable.
I read that as the module should not be included elsewhere, not that the methods shouldn't be used. |
@matthewd Yep, that module is included in the I will note, though, that not all pragmas supported by SQLite3 have an accessor, and as new pragmas are added upstream the gem will likely always lag unless and until someone needs to use the pragma and takes the time to write and test the accessor with the correct type coercion. So: @suwyn maybe it's a good idea to use the accessor if one exists, but fall back to using |
|
@flavorjones The challenge i have with the fallback is that we'd still need a way to be explicit on which configuration options are intended to be set as PRAGMAs, otherwise we'd potentially be attempting to set invalid pragma options. Some options that come to mind -
or (within a variables subkey)
I think I prefer the first option, but indifferent on whether to use The bigger concern for me still is that with the changes in 7.1 (forcing WAL & the setting the other pragma defaults), there is no way to unset them i.e. how do we use defaults that are compiled with SQLite? The only way would be to explicitly copy them into the configuration. That's another reason why I think all pragma's should be specified in the database configuration rather than hard coding them into the adapter and forcing developers to overwrite them. So to push this PR forward, I need direction on:
|
I think the read-only incompatibility is a good reason to prefer this PR over #49349 and to revert that PR if/when this is merged (especially because of the good defaults being declared in
I know remarkably little about the database adapters and the design principles they follow, but maybe @eileencodes can either give an opinion or tag someone else close to those classes.
My preference is to only support the pragmas supported by the setters in the gem; and then we can push users to request (or write) new feature code and tests for any pragmas they want to use. Especially since any pragma that users want to experiment with can be set via |
fractaledmind
left a comment
There was a problem hiding this comment.
@suwyn I like the direction of this PR. I confess that when I opened the original PR with these configuration changes, I overlooked the possibility that a Rails app might want to connect to a read-only database. Thanks for pointing that oversight out and pushing for a solution that still provides healthy defaults for apps, but allows devs the sharp knives to make changes as they need.
I made a few comments and suggestions for how I think this PR could be improved at the margins, but in general, I'm a fan.
| journal_mode: WAL | ||
| synchronous: NORMAL | ||
| mmap_size: 134217728 | ||
| journal_size_limit: 67108864 | ||
| cache_size: 2000 |
There was a problem hiding this comment.
I think we should put back the comments that were originally in the configure_connection code to explain what these pragmas are doing and like to the SQLite docs.
There was a problem hiding this comment.
IMHO the comments don't add value. Comments are code, in that they need to be maintained. One seeking to understand what the pragma's do should reference the official documentation as the behavior may change.
| timeout: 5000 | ||
| journal_mode: WAL | ||
| synchronous: NORMAL | ||
| mmap_size: 134217728 |
There was a problem hiding this comment.
We should make these numbers more human-meaningful
| mmap_size: 134217728 | |
| mmap_size: <%%= 128.megabytes %> |
| journal_mode: WAL | ||
| synchronous: NORMAL | ||
| mmap_size: 134217728 | ||
| journal_size_limit: 67108864 |
There was a problem hiding this comment.
Ditto here
| journal_size_limit: 67108864 | |
| journal_size_limit: <%%= 64.megabytes %> |
| # https://www.sqlite.org/mmap.html | ||
| raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA") | ||
|
|
||
| (@config.keys & PRAGMA_STATEMENTS).each do |key| |
There was a problem hiding this comment.
Why limit the PRAGMAs that can be passed from the database.yml file? If we are already moving the SQLite configuration into the file, we should grant developers the power to configure other SQLite PRAGMA statements as well.
There was a problem hiding this comment.
We shouldn't limit them, but the thought behind this approach is to only allow the pragma's supported by the adapter and avoid arbitrarily executing database statements.
|
Closing this in favor of #50460 authored by the contributor who introduced the change. |
Motivation / Background
A recent PR #49349 introduced a number of SQLite optimizations to improve database performance.
Those optimizations were hard coded in the SQLite adapter which left developers in the same position they were before - that if they wanted to configure SQLite beyond that default configuration, they still needed to rely on monkey patching the adapter.
Hard coding those parameters also introduce the potential for breakage. For example, the write ahead log is now the default journal mode. The side effect of this is that SQLite will use three files instead of one for the database which may break any operational scripts (e.g. the backup strategy must change).
Another issue is that WAL mode is an optimization for writing to databases, and now with Rails 7.1 it makes a false assumption that you'll always want to write to any SQLite database.
Rails 7.1 now throws an error connecting to read-only sqlite database that isn't already in WAL mode.
A use case where that is problematic. Let's say on each build of my application I embed the latest version of a Zipcode database that we receive from a 3rd party. The database, since it is readonly, has the correct file permissions (readonly) and is embedded with journal mode turned off (no -shm or -wal files). 7.1 will throw an error now since it can't change the database to use WAL.
Detail
This Pull Request moves those performance optimizations into the database configuration file. Developers will now have the option to tune their SQLite configurations by specifying PRAGMA values along with the rest of their database configuration, in
config/database.yml.The defaults that were introduced in 7.1 have been moved to the database.yml generator so new applications can benefit from them while existing applications upgrading 7.1+ won't be impacted side effects.
Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]