-
Notifications
You must be signed in to change notification settings - Fork 17
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
[reflection] [idea] #26
Comments
Really interesting thoughts. Personally, I am glad that we are finally discussing on these. We could really use your help and insight into taking this further forward. These are a few thoughts from our notes:
Like you mentioned, there is a chance with the present API that developer forgets to commit/rollback. The only counter we have for this is auto-rollback after a time interval. :-/ PS: I am putting some time to implement this new syntax. I love the fact that the proposed syntaxt totally abstracts the concept if commit and rollback!! |
Thoughts on our present implementation of transaction. The majority of transaction logic is written within this adapter. We wanted to have the API handled at the waterline level by adding additional methods for adapter interface. However, that became very tedious as it was super difficult to carry forward any information from userland to the adapterland due to the number of deep clones that happen during the information flow. |
Things we intend to do next:
|
Sam thanks for the detailed follow up. When you say "across multiple sources", are you taking about the same db? e.g.
Here's what I think we shouldn't try to solve in core right now (based on my experience trying the last time): ( As you can see, what I'm getting at is that we don't want to have to roll a durable commit log :p ) Those caveats aside, I think it would totally be doable to support the following: You guys have gotten a good start on W, X, and Y in this module. To get to Z, we'll want to define standard functions (e.g. (cc @particlebanana) |
(Oops, didn't mean to close was trying to type in my phone sorry) |
@shamasis please, tell me more |
I think I see what you're saying but to clarify:
That totally makes sense, hadn't thought of it. Great idea! I think that even means we could do B, C, and D (since PostgreSQL supports transactions) |
The only issue I see is that I don't think the actual commit calls would be truly atomic-- eg if db1 committed but db2 didn't. In other words an unsuccessful commit would mean inconsistent data, whereas in a single-db setup it just means the db thinks that particular transaction never happened. I think we'd just need to be clear about that in docs, and it would be good |
Yes. I agree to your commit failure related inconsistency. I was of the opinion that it is a very rare case. |
I think so too. |
@mikermcneil - in our 0.7.0 we bettered support for postmanlabs/waterline-sequel@ce23238 Would it be an appropriate PR to waterline? |
I think so- let me loop in @particlebanana on that |
Only issue I'd see is that we'd need to clarify mongo support in the docs, but seeing as how projections aren't in the docs now anyways.. :p |
@particlebanana - what's your thought? |
Hey @shamasis so the projection stuff that is there is completely by accident. I don't think anyone ever specifically added it, it works by overriding some stuff we do internally. The api is pretty nasty as well: Model.find({ where: { age: { '<': 30 } }, select: ['name'] }) If you have stuff to bring this up to Deferred.js so it looks something like this: User.find().select(['name', 'age']).exec(cb); That would be cool. There is this we can target for Basically my goal is to re-write the "interchange" format between Waterline and the adapters so that it encodes the entire query rather than just the criteria. I have a start of what that may look like here. With that we can use something like Knex to build the query and pass it directly to mysql, postgresql, etc. So projections should be fully supported there. Chime in if you have anything specifically you would like to see. |
@shamasis we've got the beginnings of a plan hatched for this-- will follow up on Slack. In the mean time, I wanted to toss an idea for a tweak to the proposed usage out there to take into account what we talked about on Skype (not needing to know the models involved): // ...
foo: function (req, res){
sails.transaction('prodMySQL', function (transaction, done){
transaction.models.onemodel.find().exec(function(err, allRecords) {
if (err) return done(err);
return done(null, allRecords);
// There would also be: `transaction.connection`
// provided as the _actual_ raw database connection-- provided for lower-level usage
// (but should not be closed, aborted, or committed manually-- calling `cb` does that)
}, function (err, literallyAnything) {
// (note that at this point, either way, we know the raw connection was released to the
// pool or never summoned in the first place)
if (err) {
// means the transaction was aborted or it never begun
return res.negotiate(err);
}
// if `err` is falsy, then we know the transaction was committed.
// second arg is optional, but if you passed in a second arg to the callback,
// its passed through here for convenience, e.g. so you can do
return res.json(literallyAnything);
});
});
},
// ... e.g. so basically just expose the atomic equivalent of the subset of In addition to the above, summarized here:
...there'd be something more or less like the following methods:
So! The actual implementation of this is definitely one of the last steps in a multi-step project, but after talking to @particlebanana and @sgress454 about this on Friday, I'm starting to feel pretty good about how we get there. That said, before we're too far down this road, I want to check that what I posted above would be a workable interface for y'all's use case. (I realize you'd still need something above that for a ~transaction across multiple db connections, but I think, realistically given our timeline, that would need to live in a userland service for now) |
Apologies for this brief and delayed reply. I'm out of town till 27th with limited connectivity. @particlebanana - with a specific Also, you are forcing the fluid style API instead of the callback model by doing this. |
@mikermcneil - this new proposal sounds super cool. Its future proof too! |
That sounds doable for 1.0 to me unless Im missing something. @particlebanana I know it'd affect the operation builder (pretty sure it's covered in the integrator already, unless we ripped that out, cant remember offhand) @shamasis awesome. We're all pumped about finally getting to a workable plan for this. Thanks for your help. Almost got that orm hook ready for you-- keep getting distracted with other documentation tasks. |
@shamasis I am having sails application interacting with the mysql database. I am using default ORM waterline and sails-mysql adapter. But i need mysql transactions support which sails-mysql doesnt support currently. I am planning to use sails-mysql-transactions instead of sails-mysql , Will it be a good idea to use it in prod env and is it in a stable form ? And also i have some time constraints, I wanted to know if it will be a lot of rework if i change the adapter? |
@sapnajindal - changing the adapter is simply changing the name of the adapter in connections.js, followed by updating package.json to install this adapter. We're using this adapter in production under real heavy load. Also, note that SailsJS is also planning to introduce native transaction support in waterline very soon. |
Update:
// Fetches a preconfigured deferred object hooked up to the sails-mysql adapter
// (and consequently the appropriate driver)
sails.datastore('larrysDbCluster')
// Provided custom metadata is sent through (as a direct reference; i.e. no cloning)
// to ALL driver methods called as a result of the built-in transaction logic.
.meta({})
// This function will be run when the transaction begins. If it breaks,
// the transaction will be rolled back automatically.
.transaction(function during (T, done) {
Location.findOne({id: locationId})
.usingConnection(T.connection)
.exec(function (err, location) {
if (err) {return done(err);}
if (!location) {return done.notFound();}
// Get all products at the location
ProductOffering.find({location: locationId})
.populate('productType')
.usingConnection(T.connection)
.exec(function(err, productOfferings) {
if (err) {return done(err);}
var mush = _.indexBy(productOfferings, 'id');
return done(undefined, mush);
});
});
}).exec(/* … */); |
( btw for more on how this impacts MySQL in particular, see also https://github.com/mikermcneil/machinepack-mysql/blob/master/SYNTAX_NOTES.js ) |
@shamasis Does this adapter create a transactionId field for all the models? And is it a mandatory field and we need to keep it in our database? |
Yes it auto creates the field. You can turn off auto creation and manually create the field by setting This is needed. Otherwise, there's now way to correlate queries that are generated during populates and other actions. |
@shamasis Is there any other change in sails-mysql-adapter with respect to sails-mysql ? We are currently using sails-mysql adapter, it would be great if you can share the impact of changing adapter from sails-mysql to sails-mysql-adapter. |
There are a number of changes. All of them are outlined in README. However there's no breaking change except the installation process. sails-mysql-transactions does not work unless installed using the postinstall script. This is a limitation for which we have a fix and intend to work on it sometime next month. |
@shamasis I checked the Readme, which is saying we get an additional field when we do update for a model and it contains all the previous values. I am facing two problems here: If i run the above i get an array of json in updated but if i run Model.update({id:1},{name:'test'}).then(function(updated,prev){}).catch(function(err){}) updated :[ [ { id:1,name:'test' } ], [{}] ] prev : undefined Why is this behavioural change if i use then instead of exec ? Its creating lot of complications for me. -- and why is the prev always empty? |
@shamasis Is there any way i can use the transaction in models? I mean i am creating entry in one model using transactions, on the basis of this order another model is getting created, but there if some stuff which gets checked in before Create of the model. As the transaction is not yet committed, in before create it is not able to find the linked other models id. I want to use the same transaction in model which i started in controller. |
@shamasis I tried passing transaction object in values to the model like below
In Model.js
But by doing this i am getting an error in beforecreate self._transaction.id is not a function |
@sapnajindal - I will go through your comments in details and reply in a separate issue. This issue was not intended to be discussed what we are discussing here. The original issue creator might not be comfortable with the issue topic being hijacked. |
@shamasis Agreed! Hoping to get a reply soon as its blocking my work. |
Obviously I realize there's things you guys will need to do to be able to make the switch, but I just wanted to let you know the usage is in place and documented 👍 |
@shamasis is there a way we can support replica configuration for Postgres adapter. @mikermcneil support for database replica configuration in sails/waterline's roadmap? |
First of all, this module is awesome; amazing work (particularly the depth you went to w/ making instance methods do their thing when amidst a transaction).
I wanted to leave a note here with some reflections on the history and future of transactions in WL core, and also share an idea for some syntactic sugar based on what I learned from WL2.
Transactions in WL core
Bad news first: We will probably never have true atomic, durable, consistent, and isolated cross-adapter transactions in Waterline core.
The good news is that we will definitely implement transactions across the same connection to the same database. The question is just when-- and you guys have done an amazing job hastening that along.
An idea re: syntax
The #1 thing I've always been worried about w/ transactions is forgetting to rollback under rare error conditions (i.e. that never get tested). The #2 thing has always been forgetting to use the proper transaction instance and accidentally using models directly.
When I was actively working on WL2 last summer, the simplest possible usage I could come up with which also addressed both issues (at least to some degree) was something like this:
Below, I put together a hypothetical remapping of the example in the README of this module if it was wrapped up in something like this (in addition to
.start()
, not instead of it). Please take it with a grain of salt-- I'm not saying I think it should be this way in this module necessarily (it's more in-line with what we'd need in core Waterline anyway-- i.e. triggering a usage error if the models are not part of the same database connection). I just want to make sure and share this thinking with you guys in case its helpful or spurs any ideas for folding transaction support into Sails/Waterline core.Here goes nothing:
The text was updated successfully, but these errors were encountered: