/to @raymondfeng @altsang
/cc @bajtos
In the interest of LoopBack reliability, I'd like to start a discussion on making LoopBack ACID compliant. After brushing off earlier attempts to look into the issue I had an idea.
I think Transactions + Multiversioning will get us 80% or more to ACID.
Transactions
function Transaction(dataSource) {
this.domain = domain.create();
this.dataSource = dataSource;
this.id = this.getHashCode();
this.snapshots = [];
domain.transaction = this;
domain.on('error', this.onError.bind(this));
}
Transaction.prototype.commit = function(callback) {
this.domain.run(function run() {
this.dataSource.startTransaction(this);
}.bind(this));
}
Transaction.prototype.snapshot = function(name, callback) {
this.snapshots.push(this.dataSource.snapshot(name, this, callback));
}
Transaction.prototype.rollback = function(name, fn) {
this.currentSnapshot = name ? this.getSnapshot(name) : this.snapshots.pop();
if(this.currentSnapshot) {
this.dataSource.applySnapshot(this.currentSnapshot)
}
}
Transaction.prototype.onError = function() {
this.rollback(function rollback(err) {
this.dispose();
throw err;
}.bind(this));
}
Transaction.prototype.dispose = function(callback) {
this.domain.dispose();
this.dataSource.endTransaction(this, callback);
}
Transaction.getCurrent = function() {
return domain.active.transaction;
}
// strong-remoting hooking
remotes.before('**', function(ctx, next, method) {
var t = ctx.transaction = new Transaction(method.constructor.dataSource);
t.domain.add(ctx.req);
t.domain.add(ctx.res);
t.commit(next);
});
remotes.after('**', function(ctx, next) {
ctx.transaction.dispose(next);
});
The above requires the dataSource / connector to implement methods that delegate to driver transactions, or multiversioning. To support multiversioning, an outer wrapper of the connector could be devised that essentially tracks each method call and model instance. As well as changing the behavior slightly.
Multiversioning
By default all update operations during a transaction would create copies of the given model instance and tag them with the transaction.id. Model's created during the transaction would also be tagged with the transaction.id.
By default, all queries would exlclude instances tagged with transaction.ids.
Failure / Cleanup
If the transaction fails. All models tagged with a transaction.id are removed. This could also be ensured on startup / by a job of some sort to essentially garbage collect tagged instances.
If the transaction completes without error all tagged model instances would replace matching untagged models.
Concerns
During the final phase of a succesful transaction, since transactions aren't isolated by locking, there is a potential for cleanup to happen out of order. If the transaction.id is sortable we might be able to avoid this.
/to @raymondfeng @altsang
/cc @bajtos
In the interest of LoopBack reliability, I'd like to start a discussion on making LoopBack ACID compliant. After brushing off earlier attempts to look into the issue I had an idea.
I think Transactions + Multiversioning will get us 80% or more to ACID.
Transactions
The above requires the
dataSource/connectorto implement methods that delegate to driver transactions, or multiversioning. To support multiversioning, an outer wrapper of the connector could be devised that essentially tracks each method call and model instance. As well as changing the behavior slightly.Multiversioning
By default all update operations during a transaction would create copies of the given model instance and tag them with the
transaction.id. Model's created during the transaction would also be tagged with thetransaction.id.By default, all queries would exlclude instances tagged with
transaction.ids.Failure / Cleanup
If the transaction fails. All models tagged with a
transaction.idare removed. This could also be ensured on startup / by a job of some sort to essentially garbage collect tagged instances.If the transaction completes without error all tagged model instances would replace matching untagged models.
Concerns
During the final phase of a succesful transaction, since transactions aren't isolated by locking, there is a potential for cleanup to happen out of order. If the
transaction.idis sortable we might be able to avoid this.