Part 7: Multi db improvements, Add ability to block writes to a database - #34505
Conversation
|
We do have code to do the same thing. So this would be useful for us. We define it as "is a query that is not reading" (ie not select, etc)", and we use: as definition. |
|
I like the idea of it, because it'd provide a good foundation to run a read/write-split application against a single database in development. I don't think it's practical to detect read-only queries via string matching, though: even something that explicitly starts with "SELECT" can end up writing. I wonder about using On a specific API note, I'd prefer a synonym for 'blocking', just because it conflicts with "[thread] blocking I/O". |
775c0ca to
132baad
Compare
|
Changed |
|
Oh right I also need to move this to execute. 😄 |
|
I agree with @matthewd that detecting read-only queries via string matching might be problematic. For example |
f313a51 to
392d51e
Compare
|
Ok I moved this to execute and added tests for create/delete/update/where.first to ensure these were all getting caught. I updated the changelog to note that the purpose of preventing write queries when choosing readonly mode is for testing, catching accidental writes, and for switching to a readonly connection without opening a second connection. It's purpose isn't to catch ALL writes to a database - it's not meant as a replacement for a replica. |
This PR adds the ability to prevent writes to a database even if the
database user is able to write (ie the database is a primary and not a
replica).
This is useful for a few reasons: 1) when converting your database from
a single db to a primary/replica setup - you can fix all the writes on
reads early on, 2) when we implement automatic database switching or
when an app is manually switching connections this feature can be used
to ensure reads are reading and writes are writing. We want to make sure
we raise if we ever try to write in read mode, regardless of database
type and 3) for local development if you don't want to set up multiple
databases but do want to support rw/ro queries.
This should be used in conjunction with `connected_to` in write mode.
For example:
```
ActiveRecord::Base.connected_to(role: :writing) do
Dog.connection.while_preventing_writes do
Dog.create! # will raise because we're preventing writes
end
end
ActiveRecord::Base.connected_to(role: :reading) do
Dog.connection.while_preventing_writes do
Dog.first # will not raise because we're not writing
end
end
```
392d51e to
f39d72d
Compare
|
@eileencodes what about copy to CSV ? in this implementation it is going to be writable but it isn't |
| end | ||
| end | ||
|
|
||
| READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp.call(:select, :show, :set) # :nodoc: |
There was a problem hiding this comment.
Explain is on master.
This feature is not meant to be a catch all. If you want to actually block all writes you should use a readonly user. |
|
@eileencodes I don't want to block writes, just expect using copy to csv will not raise error created issue #39166 |
|
I'm curious, why was this approach preferred over read-only transactions, which @matthewd had mentioned in #34505 (comment)? Extending Active Record transactions to support a new option e.g. |
|
I answered @janko here https://discuss.rubyonrails.org/t/active-record-while-preventing-writes-api-versus-read-only-transactions/76443/2. Can we keep the discussion in one place if there are followup questions? Thanks! |
So is this merge now safe against a |
We use this at GH but I'm not 100% sure whether others will find this useful. Thoughts?
The other open question I have is whether the
write_query?should be defined by "is a query that writes" or "is a query that is not reading" (ie not select, etc). For now I chose some common write queries to demonstrate the goals of this feature but I'd love input into what @rafaelfranca @tenderlove and @matthewd think about this.This PR adds the ability to block writes to a database even if the
database user is able to write (ie the database is a primary and not a
replica).
This is useful for a few reasons: 1) when converting your database from
a single db to a primary/replica setup - you can fix all the writes on
reads early on, 2) when we implement automatic database switching or
when an app is manually switching connections this feature can be used
to ensure reads are reading and writes are writing. We want to make sure
we raise if we ever try to write in read mode, regardless of database
type.
This should be used in conjunction with
connected_toin write mode.For example: