Skip to content

Commit 87aa8e3

Browse files
committed
⚡️ Deduplicate Backend.getOps() calls
Calls to `Backend.getOps()` can be quite expensive. In order to minimize the impact of these calls on the server, this change deduplicates concurrent calls with the same arguments, and makes only a single call, then invoking all the callbacks with the single result.
1 parent 297ce5d commit 87aa8e3

File tree

3 files changed

+52
-1
lines changed

3 files changed

+52
-1
lines changed

lib/backend.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ function Backend(options) {
5757
function(error, context) {
5858
logger.error(error);
5959
};
60+
61+
var backend = this;
62+
this._dbGetOps = util.deduplicateRequests(function() {
63+
backend.db.getOps.apply(backend.db, arguments);
64+
});
6065
}
6166
module.exports = Backend;
6267
emitter.mixin(Backend);
@@ -324,7 +329,7 @@ Backend.prototype._getSanitizedOps = function(agent, projection, collection, id,
324329
var backend = this;
325330
if (!opsOptions) opsOptions = {};
326331
if (agent) opsOptions.agentCustom = agent.custom;
327-
backend.db.getOps(collection, id, from, to, opsOptions, function(err, ops) {
332+
this._dbGetOps(collection, id, from, to, opsOptions, function(err, ops) {
328333
if (err) return callback(err);
329334
backend._sanitizeOps(agent, projection, collection, id, ops, function(err) {
330335
if (err) return callback(err);

lib/util.js

+29
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,35 @@ exports.clone = function(obj) {
9898
return (obj === undefined) ? undefined : JSON.parse(JSON.stringify(obj));
9999
};
100100

101+
exports.deduplicateRequests = function(fn) {
102+
var callbacksByArgs = {};
103+
return function() {
104+
var callback = arguments[arguments.length - 1];
105+
var args = [];
106+
for (var i = 0; i < arguments.length - 1; i++) {
107+
args.push(arguments[i]);
108+
}
109+
var argString = JSON.stringify(args);
110+
111+
var callbacks = exports.digOrCreate(callbacksByArgs, argString, function() {
112+
return [];
113+
});
114+
callbacks.push(callback);
115+
116+
if (callbacks.length > 1) return;
117+
118+
args.push(function() {
119+
while (callbacks.length) {
120+
var cb = callbacks.shift();
121+
cb.apply(null, arguments);
122+
}
123+
delete callbacksByArgs[argString];
124+
});
125+
126+
fn.apply(null, args);
127+
};
128+
};
129+
101130
var objectProtoPropNames = Object.create(null);
102131
Object.getOwnPropertyNames(Object.prototype).forEach(function(prop) {
103132
if (prop !== '__proto__') {

test/backend.js

+17
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,23 @@ describe('Backend', function() {
9292
done();
9393
});
9494
});
95+
96+
it('deduplicates concurrent requests', function(done) {
97+
var getOps = sinon.spy(backend.db, 'getOps');
98+
var count = 0;
99+
var callback = function(error, ops) {
100+
if (error) return done(error);
101+
expect(ops).to.have.length(2);
102+
expect(ops[0].create.data).to.eql({title: '1984'});
103+
expect(ops[1].op).to.eql([{p: ['author'], oi: 'George Orwell'}]);
104+
count++;
105+
expect(getOps).to.have.been.calledOnce;
106+
if (count === 2) done();
107+
}
108+
109+
backend.getOps(agent, 'books', '1984', 0, null, callback);
110+
backend.getOps(agent, 'books', '1984', 0, null, callback);
111+
});
95112
});
96113

97114
describe('getOpsBulk', function() {

0 commit comments

Comments
 (0)