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
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 |
@@ -67,11 +67,21 @@ def query(sql, name = nil) #:nodoc: | |||
end | |||
end | |||
|
|||
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp.call(:select, :show, :set) # :nodoc: |
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.
explain
?
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.
Explain is on master.
DEFAULT_READ_QUERY = [:begin, :commit, :explain, :release, :rollback, :savepoint, :select, :with] # :nodoc: |
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_to
in write mode.For example: