-
Notifications
You must be signed in to change notification settings - Fork 460
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
transactions and different db servers #264
Comments
Different db servers Cross tenant transactions |
different db serversok I understand cross tenant transactionsYes for tenants stored as schemas, its quite straight forward: everything can be wrapped in an
I tried this way too:
But it seems |
Ah, sorry I see what you're doing. Ya you can't do cross-connection transactions. When you're not using We're likely going to sunset the connection based strategies soon and strip them out to a separate gem for legacy systems as it's really not the recommended way of doing things. |
Different db servers Whenever I wish there was a roadmap concerning the issue you opened on cross tenant transactions |
yup, you're right, it's literally making a new connection so it would just be a matter of switching those credentials to a new machine. So there's nothing stopping us, it's just never been implemented. You're welcome to submit a PR for something if you like. Basically we'd need a map of 'tenant_name' => 'db credentials' so that we can lookup the config for that tenant. Might not be too hard. |
Ok I'll look into it :) Thanks for the lightning quick answers |
I've just made another test just to check for curiosity's sake. Then these work (and rollback properly if I raise an error in the blocks):
I'm not sure I get the difference with the case where we have two connections on different tenants. |
That shouldn't work, but this is all just ActiveRecord w/ multiple connections, nothing to do with Apartment. I wouldn't rely on it. |
I suggest to add the following config:
If |
Wow that's a lot of changes. What if |
Actually only the Doing your way, we'd simply not need additional config, and we'd have: 1- I guess we still should check |
If your Unless i'm missing something? |
Some clients of mine want their data to be fully isolated from the rest (HIPAA constraint). |
Hi @apneadiving I have the same constraint! From our pentesters. Wanna go and hack on this together? It would be awesome if switching from fully isolated to schema would be frictionless! |
@emilebosch once we agree about the api with @bradrobertson we could start. Pretty cool you're in the same timezone. We could definitely work together on this, I intend to have it done this week. |
Awesome Ok i have to go run back in a meeting but we'll be shortly back. |
@apneadiving I'm not sure why you'd need to redeploy. Here's how I'd do it:
Apartment.configure do |config|
config.tenant_names = ->{ Hash[Tenant.pluck(:name, :connection)] }
end And it would immediately be available |
This is super awesome! A couple of things functional requirements from my side: I don't know if it applies to you guys but:
Are these good points? |
Also the actual requirements are: prevent leaking data from other tenants in case of a SQL injection breach |
One of the biggest problems you'll face is making it behave with threads for folks using a threaded app server or things like sidekiq. |
ActiveRecord connection pool is already thread safe. |
How will you be switching connections between tenants? Unless you know something I don't (which is entirely likely 😄) you're not able to use the same AR class with two different database servers concurrently, due to the way that the connection pool is inextricably linked to the AR class and multiple DB servers need different connection pools. This is exactly the problem in #180 afaict. |
@bradrobertson ah ok, yes you're right @emilebosch you have a lot of requirements, most of them join mine, but I'm not sure some are possible (like frictionless schema or db). I'll start with my scope to get things started. |
@mikecmpbll You may be entirely correct. I don't have a firm knowledge of the AR connection handling other than I've read that it's thread safe. I was under the impression that there was actually a pool of pools, 1 for each |
I'm not sure I understand the limitation: apartment already switches connections between db. |
What Mike is suggesting is that ActiveRecord connection pooling is not actually thread safe, ie that if 2 different threads call switch to 2 different physical hosts at the same time, it's possible that 1 thread could actually use the connection of the other. I haven't verified this in any way, but it's definitely something for ActiveRecord to solve, not Apartment. |
ConnectionPool is threadsafe; threads will pick up their own connections from the connection pool and everybody's happy. However, ConnectionPool holds it's own ConnectionSpecification, with the connection info. If we want to connect to some other database, we have to establish a new connection pool. The ConnectionPool that AR uses is resolved hierarchically from the class you are querying, so if you query The short implementation of establish_connection makes it pretty obvious to see how it links the class (owner) with the newly established pool. What this means in practice is that establish_connection is not threadsafe because all our threads are looking to our AR class for the connection pool. Rails does all their threadsafe connection handling within the pool, so they need the pool to be shared across threads, which is fine most of the time. The real problem as far as I can see it is the rigidity of the mapping between class and connection specification. I hope that makes sense, and if anyone's an expert on this and wants to tell me I've got it all wrong I'd be the first to welcome that too! Like Brad says though, this is a Rails issue and needs to be sorted there. I've been meaning to hack on it for a few months, maybe this weekend will be the start. |
Ok I guess I kind of understand thanks to the explanations :) So the problem can arise whenever we use multithreaded processes like Sidekiq or Puma/Unicorn. But it would be fine with single threaded one like Thin or DelayedJob? It fails no matter what the value of |
ok @mikecmpbll guess you're now captain awkward thread bugs. Is a connection pool per tenant what you're suggesting? |
Yes, indeed.
No, there is no threading problem with postgres schemas, or multiple databases on the same server with mysql (both termed 'schemas' in Apartment for their similarities). The problem is with the adapters that use @emilebosch : what I'm suggesting is that this is extremely difficult to do safely without some changes to ActiveRecord connection handling. |
@mikecmpbll If we monkey hack |
I don't think a monkey patch of Rails will be a suitable solution, not least one that inserts Apartment logic into it. I'll fork AR and see if I can make any tracks with it this weekend but can't guarantee I'll come up with anything.. heh |
Actually rereading @mikecmpbll explanations, I understand my expectations cant be met because of active record's pooling. What confuses me is that using Postgresql + In order to be able to have tenants on different servers, maybe I could try to have them inherit from a
If I understand properly, its meant to let us have a separated pool of connections.
It wouldnt solve:
|
Have you tried the test in #180? The multiple database model (use_schemas = false) for Postgresql uses establish_connection so it should be susceptible. You could split different models between different servers using the abstract class technique, but I don't see how you could do tenant sharding. |
@mikecmpbll My assumption was: an abstract class would have its own connection. So I'd have Global connection and Tenant connection. Maybe I've got it wrong though :) |
@bradrobertson Because of it, all excluded models have a different connection. Doesn't it somehow prevent from pooling? I feel like its not that desirable, why did you make this choice please? |
That doesn't prevent pooling no. Each establish connection will establish a pool of connections. Again we don't use connection based adapters at all so I can't really comment much here. Our Sorry guys I don't really have the time to support you on this one. If you come up with something I'm happy to review it but I'm otherwise too busy to keep fielding these questions. |
@bradrobertson no problem I completely understand and am already happy to find a solid base to work on. I'll try to organize the |
ya I can review PRs |
For history's sake, each call to I havent found a more recent resource but it feels like the principle will remain. So I think we'd rather avoid this. |
@apneadiving yep, I touched on that previously. there's nothing particularly wrong or bothersome with creating a new connection pool for each excluded model and it would introduce some quite considerable complexity to do it any other way; I don't see it providing any great value. |
@mikecmpbll ah, interesting, you dont think there would be benefit in having one abstract superclass for all tenanted models and one for all shared models? |
I don't see any real terms advantage of that, no. |
@mikecmpbll ok :) I guess I dont understand it properly then. Say if you call |
perhaps, but connections aren't very expensive. it might be able to be improved but I don't know whether that would be worth the added complexity. also, it's not a real world problem that anyone has so in terms of priorities it doesn't really feature for me, i'm more interested in addressing this multi-database threading issue. probably easiest to hit me up on #rubyonrails on freenode if you wanna discuss more (same name) 😊 |
right. thanks @apneadiving for discussing with me on IRC. we had a bit of a breakthrough and it looks like we can solve the threading issue without any changes to AR. however, to say I'm not a lover of rspec would be an understatement—and this means I can't work out how to create a failing test for #180 :( if someone can help me with writing an rspec test where i can switch between multiple tenants, then I'll be off and running. |
@mikecmpbll here is one directly inspired from #180 https://gist.github.com/apneadiving/6adcb32ce7fec82d0653 It fails with:
I'm sure we can remove some looping to gain time. |
@mikecmpbll Would you be so kind to tell what the revelation/breaktrhough was? |
@emilebosch mike found out that we can actually change the connection handler at will (thus create one per thread => thread safe but more connections). |
@apneadiving Awesome. Thus making your own connection handler is the way to go i guess? Good work! |
Could someone help me for a naming question please? Basically to create another db on another server we must connect to this server first. I try to name for the config which would provide the db name to use in order to connect to the server before creating a tenant. |
Primary? / Landlord? / Metada I recon you’re lookoing for the database where we can collect all shared entities right? Emile Bosch
|
@emilebosch No :) Say you have your localhost and want to create a tenant on a server hosted on amazon. To create the tenant db on this other server, you must first connect to it but you cant connect directly (thats not the way it works), you have to connect to one of its dbs (for postgresql the default name is often |
Yes, Ok. Got it. The tenant host db basically, we don’t have AWS so i could be totally wrong. From my perspective what happens is:
A landlord isn’t that wrong actually since he creates apartments/tenants. :) Else its more like a jump database/hop? Emile Bosch
|
FYI the code is done apneadiving@01cf288 I now have to figure out how to test it. Basically its tough to have two different db servers on the same machine. Maybe I'll just test if my options are properly taken into account |
Hi and thanks for the gem :)
Here are some questions I have concerning its use. Please be aware it would be possible to see them as a freelancing mission.
Store tenants in different databases servers
In the current state of the gem, it seems the assumption is that all tenants are stored in the same db server. Am I right to assume this? Is it planned to support different servers?
Cross tenant transactions
I realized transactions work fine whenever ran across different schemas of the same db.
But I cant make them work whenever tenants are in different db (
undefined method 'rollback' for #<ActiveRecord::ConnectionAdapters::ClosedTransaction:0x007ffef036a320>
).Could you please provide some guidance?
Thanks in advance
The text was updated successfully, but these errors were encountered: