不要怂,就是干,撸起袖子干!

Commit a7e31040 by Daniel Sim Committed by Sushant

read-only transactions for use on read-replicas (#7324)

1 parent 21671949
# Future # Future
- [ADDED] Ability to run transactions on a read-replica by marking transactions as read only [#7323](https://github.com/sequelize/sequelize/issues/7323)
- [FIXED] Show a reasonable message when using renameColumn with a missing column [#6606](https://github.com/sequelize/sequelize/issues/6606) - [FIXED] Show a reasonable message when using renameColumn with a missing column [#6606](https://github.com/sequelize/sequelize/issues/6606)
- [PERFORMANCE] more efficient array handing for certain large queries [#7175](https://github.com/sequelize/sequelize/pull/7175) - [PERFORMANCE] more efficient array handing for certain large queries [#7175](https://github.com/sequelize/sequelize/pull/7175)
- [FIXED] Add `unique` indexes defined via options to `rawAttributes` [#7196] - [FIXED] Add `unique` indexes defined via options to `rawAttributes` [#7196]
......
...@@ -15,6 +15,8 @@ const Utils = require('./utils'); ...@@ -15,6 +15,8 @@ const Utils = require('./utils');
* @param {String} options.type=true Sets the type of the transaction. * @param {String} options.type=true Sets the type of the transaction.
* @param {String} options.isolationLevel=true Sets the isolation level of the transaction. * @param {String} options.isolationLevel=true Sets the isolation level of the transaction.
* @param {String} options.deferrable Sets the constraints to be deferred or immediately checked. * @param {String} options.deferrable Sets the constraints to be deferred or immediately checked.
* @param {String} options.readOnly=false Sets the read-only property of the transaction. Such transactions
* will use read replicas when available
* *
* @see {@link Sequelize.transaction} * @see {@link Sequelize.transaction}
*/ */
...@@ -30,7 +32,8 @@ class Transaction { ...@@ -30,7 +32,8 @@ class Transaction {
this.options = Utils._.extend({ this.options = Utils._.extend({
autocommit: transactionOptions.autocommit || null, autocommit: transactionOptions.autocommit || null,
type: sequelize.options.transactionType, type: sequelize.options.transactionType,
isolationLevel: sequelize.options.isolationLevel isolationLevel: sequelize.options.isolationLevel,
readOnly: false,
}, options || {}); }, options || {});
this.parent = this.options.transaction; this.parent = this.options.transaction;
...@@ -99,8 +102,19 @@ class Transaction { ...@@ -99,8 +102,19 @@ class Transaction {
} }
prepareEnvironment() { prepareEnvironment() {
let connectionPromise;
return Utils.Promise.resolve(this.parent ? this.parent.connection : this.sequelize.connectionManager.getConnection({ uuid: this.id })) if (this.parent) {
connectionPromise = Utils.Promise.resolve(this.parent.connection);
} else {
let acquireOptions = { uuid: this.id };
if (this.options.readOnly) {
acquireOptions.type = 'SELECT';
}
connectionPromise = this.sequelize.connectionManager.getConnection(acquireOptions);
}
return connectionPromise
.then(connection => { .then(connection => {
this.connection = connection; this.connection = connection;
this.connection.uuid = this.id; this.connection.uuid = this.id;
......
...@@ -7,11 +7,17 @@ const expect = chai.expect; ...@@ -7,11 +7,17 @@ const expect = chai.expect;
const Support = require(__dirname + '/support'); const Support = require(__dirname + '/support');
const DataTypes = require(__dirname + '/../../lib/data-types'); const DataTypes = require(__dirname + '/../../lib/data-types');
const dialect = Support.getTestDialect(); const dialect = Support.getTestDialect();
const sinon = require('sinon');
describe(Support.getTestDialectTeaser('Replication'), function() { describe(Support.getTestDialectTeaser('Replication'), function() {
if (dialect === 'sqlite') return; if (dialect === 'sqlite') return;
let sandbox;
let readSpy, writeSpy;
beforeEach(() => { beforeEach(() => {
sandbox = sinon.sandbox.create();
this.sequelize = Support.getSequelizeInstance(null, null, null, { this.sequelize = Support.getSequelizeInstance(null, null, null, {
replication: { replication: {
write: Support.getConnectionOptions(), write: Support.getConnectionOptions(),
...@@ -29,16 +35,50 @@ describe(Support.getTestDialectTeaser('Replication'), function() { ...@@ -29,16 +35,50 @@ describe(Support.getTestDialectTeaser('Replication'), function() {
} }
}); });
return this.User.sync({force: true}); return this.User.sync({force: true})
.then(() => {
readSpy = sandbox.spy(this.sequelize.connectionManager.pool.read, 'acquire');
writeSpy = sandbox.spy(this.sequelize.connectionManager.pool.write, 'acquire');
});
});
afterEach(() => {
sandbox.restore();
}); });
function expectReadCalls() {
chai.expect(readSpy.callCount).least(1);
chai.expect(writeSpy.notCalled).eql(true);
}
function expectWriteCalls() {
chai.expect(writeSpy.callCount).least(1);
chai.expect(readSpy.notCalled).eql(true);
}
it('should be able to make a write', () => { it('should be able to make a write', () => {
return this.User.create({ return this.User.create({
firstName: Math.random().toString() firstName: Math.random().toString()
}); })
.then(expectWriteCalls);
}); });
it('should be able to make a read', () => { it('should be able to make a read', () => {
return this.User.findAll(); return this.User.findAll()
.then(expectReadCalls);
});
it('should run read-only transactions on the replica', () => {
return this.sequelize.transaction({readOnly: true}, (transaction) => {
return this.User.findAll({transaction});
})
.then(expectReadCalls);
});
it('should run non-read-only transactions on the primary', () => {
return this.sequelize.transaction((transaction) => {
return this.User.findAll({transaction});
})
.then(expectWriteCalls);
}); });
}); });
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!