Conversation
There was a problem hiding this comment.
We need to do some gymnastics here to be backwards compatible.
However if we deprecate the establish_connection from the model, eventually we would be able to remove this fallback into the Class.name method.
The way I envision this, is we would establish the connection straight to the handler, as this connection is not attached to the model anymore.
There was a problem hiding this comment.
Could spare an array allocation by calling each_value if Concurrent::Map supports it 😁
|
@tenderlove @matthewd @rafaelfranca would ❤️ your review here too. |
|
Doesn't this break the current inheritance behaviour? |
It should not. the |
There was a problem hiding this comment.
Should we put id ahead of the pack here as well?
ConnectionHandler will not have any knowlodge of AR models now, it will only know about the specs. Like that we can decouple the two, and allow the same model to use more than one connection. Historically, folks used to create abstract AR classes on the fly in order to have multiple connections for the same model, and override the connection methods. With this, now we can override the `specificiation_id` method in the model, to return a key, that will be used to find the connection_pool from the handler.
| def retrieve_connection_pool(spec_name) | ||
| owner_to_pool.fetch(spec_name) do | ||
| if ancestor_pool = pool_from_any_process_for(spec_name) | ||
| # A connection was established in an ancestor process that must have |
After PR rails#24844 the documentation for `#retrieve_connection_pool` was out of date. This commit changes: - the reference from `@class_to_pool` to `@owner_to_pool`. - with newer Rubies, `#fetch` isn't significantly slower than `#[]`. Since Rails 5 requires Ruby >= 2.2.2, we can just use `#fetch` here.
See rails/rails#24844 for the changes on Rails.
Follow up to rails#20818. `retrieve_connection` is passed `spec_name` instead of `klass` since rails#24844.
|
@arthurnn sorry for commenting on a closed issue, but looking through this thread + the changes in the code, I'm not sure I understand how I can make use of these changes. Basically, my scenario is that I would like to be able to have multiple databases configured at runtime (e.g. new user signs up -> make the Rails app "aware" of a new database -> make a new Pool for that database), and I can switch on each request which database to use. To my understanding, this PR would make the above use-case possible, including using a multi-threaded server like Puma - it seems though that it eludes me how I can actually define the "specifications" for each DB. Could you please give me some pointers in the right direction? To summarize:
Thanks a lot! |
|
Hey @rzv01 I am glad you asked. I am planning to write a few blog posts on how people can use this, but I didn't have time yet. I did a talk that touch into this changes too, here is the link: https://youtu.be/-41V0qETxmE?list=PLo3sQWbYZbskTlzH7Ert-FucR7QmIuwE5
The idea is to not use the same connection pool for different database. Each database should have its own connection pool.
You can create the connection pools passing a specification name, and in your model you would extend the method |
|
Hey @arthurnn - thanks a lot for your response. I've watched the video you linked - very informative, thanks! and it got me closer to understanding how multiple DBs should work. Now, in your talk you mentioned that the future direction for Rails 5.1 and onward would be to have all the DB configs in the database.yml file and to initialize all connections at app startup. However, this means the config is now static. Would it be possible to make the app "aware" of a new DB config during runtime? Not sure how this would work to be honest, maybe write to the database.yml file (say, an external process which knows when a new DB is needed) and send a signal to the Rails server process? which would in turn reload the database.yml, which contains the new DB config as well. The use case I'm thinking of is:
here comes the problem: How would the Rails app know about the new DB? :) Ideally, without having to restart the app - I guess it wouldn't be the end of the world, especially with a phased restart of a multi-threaded server, but still, not that elegant, hence me asking if a new DB config can be "injected" in the app dynamically. Btw, sorry if I missed something and the answer to my question above is obvious! Thanks a lot, once again! 🍻 |
|
I'm also interested in use case where there is one DB per user/customer. |
|
the database connection details don't technically need to be in database.yml, you should be able to create connection pools yourself; the code could be something like this (untested): def self.connection_specification_name
if !connection_handler.retrieve_connection_pool(Thread.current['customer_db_name'])
spec = database_spec.merge(name: Thread.current['customer_db_name'])
connection_handler.establish_connection(spec)
end
Thread.current['customer_db_name']
endonly looked briefly though, and i might be missing something basic there :). i assume, as we're creating a pool per database we'll end up with edit: would probably be better do do the connection pool initialisation out of band though (per request?) as this method will be called a ton 😅 |
|
@mikecmpbll is right. |
|
@mikecmpbll @arthurnn thanks a ton for your help, you guys are great :) I think right now i got all the info i need. I'll give it a try sometime in the next few days and see how it goes. |
|
Is there a thread safe way to remove idle connection pools? With some testing it looks like I just can't use ConnectionHandler's remove_connection, because it removes pool from all threads? |
|
@arthurnn Is there a way to avoid using an instance variable to store |
|
Cool @njakobsen. Please do contribute a PR to no longer refer to the ivar directly. And a PR with your dynamic connection swapping would be wonderful as well! 😍 |
|
I'm definitely just crashing around, but I'll show you my monkeypatch and maybe you can let me know if there's a better way to store information about which connection the model should use on a per-thread basis. See https://github.com/culturecode/stagehand/blob/5c2d1815738e602584633ef48c7aa2b62ba79ce6/lib/stagehand/active_record_extensions.rb#L28-L53. With some feedback, I'll see what sort of PR I can make from all of this. |
|
Do you have a use case that involves calling |
|
I don't have a use case for removing the connection, but if someone using my patch were to call Basically @arthurnn's suggestion of how his PR can be used opened up a nice avenue for my Gem to use a lighter touch to swap connections dynamically by changing the connection spec name instead of calling |
|
I am also not happy with my solution of storing the connection spec name for each model on |
|
i think |
|
If you've overwritten the getter/setter, then I think it's pretty common to need to thread-scope the behaviour.. the only quick observation I'd make is that you don't necessarily need to use the setter: you could define |
|
@matthewd that's an interesting idea, but I was trying to keep the changes to AR:B isolated from the inner machinations of the rest of the gem. If I could simply make it threadsafe, then any other code (including subclasses) calling My gem seems to work with my monkeypatches, but at @jeremy's urging, my questions are now more about how to apply this threadsafety to a PR for Rails itself.
|
|
@jeremy @matthewd I think I'm going to tackle this with a very light touch. Instead of making a bunch of decisions about how the connection should be tracked independently per-model, per-thread (which could cause issues in the general case where we don't want independent tracking per thread). I'll make a PR which simply uses the |
Uh oh!
There was an error while loading. Please reload this page.