Skip to content

Commit 402b551

Browse files
callmehiphopstephenplusplus
authored andcommitted
spanner: add object caching (#2362)
1 parent 9edeba3 commit 402b551

File tree

11 files changed

+211
-46
lines changed

11 files changed

+211
-46
lines changed

src/database.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,10 @@ Database.formatName_ = function(instanceName, name) {
232232
* });
233233
*/
234234
Database.prototype.close = function(callback) {
235+
var self = this;
236+
235237
this.pool_.clear().then(function() {
238+
self.parent.databases_.delete(self.id);
236239
callback(null);
237240
}, function(err) {
238241
callback(err || new Error('Unable to close database connection.'));
@@ -327,9 +330,11 @@ Database.prototype.createTable = function(schema, callback) {
327330
* });
328331
*/
329332
Database.prototype.delete = function(callback) {
330-
return this.api.Database.dropDatabase({
333+
var reqOpts = {
331334
database: this.formattedName_
332-
}, callback);
335+
};
336+
337+
return this.api.Database.dropDatabase(reqOpts, callback);
333338
};
334339

335340
/**

src/index.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var Instance = require('./instance.js');
4343

4444
var v1 = require('./v1');
4545

46+
4647
/**
4748
* [Cloud Spanner](https://cloud.google.com/spanner) is a highly scalable,
4849
* transactional, managed, NewSQL database service. Cloud Spanner solves the
@@ -91,6 +92,8 @@ function Spanner(options) {
9192
};
9293

9394
commonGrpc.Service.call(this, config, options);
95+
96+
this.instances_ = new Map();
9497
}
9598

9699
util.inherits(Spanner, commonGrpc.Service);
@@ -464,7 +467,11 @@ Spanner.prototype.instance = function(name) {
464467
throw new Error('A name is required to access an Instance object.');
465468
}
466469

467-
return new Instance(this, name);
470+
if (!this.instances_.has(name)) {
471+
this.instances_.set(name, new Instance(this, name));
472+
}
473+
474+
return this.instances_.get(name);
468475
};
469476

470477
/**

src/instance.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ function Instance(spanner, name) {
142142
spanner.createInstance(self.formattedName_, options, callback);
143143
}
144144
});
145+
146+
this.databases_ = new Map();
145147
}
146148

147149
util.inherits(Instance, commonGrpc.ServiceObject);
@@ -283,7 +285,11 @@ Instance.prototype.database = function(name, poolOptions) {
283285
throw new Error('A name is required to access a Database object.');
284286
}
285287

286-
return new Database(this, name, poolOptions);
288+
if (!this.databases_.has(name)) {
289+
this.databases_.set(name, new Database(this, name, poolOptions));
290+
}
291+
292+
return this.databases_.get(name);
287293
};
288294

289295
/**
@@ -312,9 +318,19 @@ Instance.prototype.database = function(name, poolOptions) {
312318
* });
313319
*/
314320
Instance.prototype.delete = function(callback) {
315-
return this.api.Instance.deleteInstance({
321+
var self = this;
322+
323+
var reqOpts = {
316324
name: this.formattedName_
317-
}, callback);
325+
};
326+
327+
return this.api.Instance.deleteInstance(reqOpts, function(err, resp) {
328+
if (!err) {
329+
self.parent.instances_.delete(self.id);
330+
}
331+
332+
callback(err, resp);
333+
});
318334
};
319335

320336
/**

src/session-pool.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ SessionPool.prototype.getNextAvailableSession_ = function(options, callback) {
500500
return;
501501
}
502502

503-
this.pollForSession_(callback);
503+
this.pollForSession_(options, callback);
504504
};
505505

506506
/**
@@ -511,8 +511,9 @@ SessionPool.prototype.getNextAvailableSession_ = function(options, callback) {
511511
* @param {function} callback - The callback function to be executed when a
512512
* session is available.
513513
*/
514-
SessionPool.prototype.pollForSession_ = function(callback) {
514+
SessionPool.prototype.pollForSession_ = function(options, callback) {
515515
this.pendingAcquires.push({
516+
options: options,
516517
callback: callback,
517518
timeout: this.acquireTimeout
518519
});
@@ -531,7 +532,8 @@ SessionPool.prototype.pollForSession_ = function(callback) {
531532
self.writePool && self.writePool.free;
532533

533534
if (hasFreeSession) {
534-
self.getNextAvailableSession_(self.pendingAcquires.shift().callback);
535+
var acquireReq = self.pendingAcquires.shift();
536+
self.getNextAvailableSession_(acquireReq.options, acquireReq.callback);
535537
}
536538

537539
if (!self.pendingAcquires.length) {

src/transaction.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,6 @@ Transaction.prototype.requestStream = function(config) {
371371
return;
372372
}
373373

374-
requestStream.end();
375374
userStream.destroy();
376375

377376
if (self.shouldRetry_(err)) {

system-test/spanner.js

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3070,7 +3070,75 @@ var spanner = new Spanner(env);
30703070
});
30713071
});
30723072

3073-
it('should retry an aborted transaction', function(done) {
3073+
it('should retry an aborted txn when reading fails', function(done) {
3074+
var query = `SELECT * FROM ${table.name}`;
3075+
var attempts = 0;
3076+
3077+
var expectedRow = {
3078+
Key: 'k888',
3079+
NumberValue: null,
3080+
StringValue: 'abc'
3081+
};
3082+
3083+
database.runTransaction(function(err, transaction) {
3084+
assert.ifError(err);
3085+
3086+
transaction.run(query, function(err) {
3087+
assert.ifError(err);
3088+
3089+
var action = attempts++ === 0 ? runOtherTransaction : wrap;
3090+
3091+
action(function(err) {
3092+
assert.ifError(err);
3093+
3094+
transaction.run(query, function(err, rows) {
3095+
assert.ifError(err);
3096+
3097+
transaction.insert(table.name, {
3098+
Key: generateName('key'),
3099+
StringValue: generateName('val')
3100+
});
3101+
3102+
transaction.commit(function(err) {
3103+
assert.ifError(err);
3104+
3105+
var lastRow = rows.pop().toJSON();
3106+
3107+
assert.deepEqual(lastRow, expectedRow);
3108+
assert.strictEqual(attempts, 2);
3109+
3110+
done();
3111+
});
3112+
});
3113+
});
3114+
});
3115+
});
3116+
3117+
function runOtherTransaction(callback) {
3118+
database.runTransaction(function(err, transaction) {
3119+
if (err) {
3120+
callback(err);
3121+
return;
3122+
}
3123+
3124+
transaction.run(query, function(err) {
3125+
if (err) {
3126+
callback(err);
3127+
return;
3128+
}
3129+
3130+
transaction.insert(table.name, expectedRow);
3131+
transaction.commit(callback);
3132+
});
3133+
});
3134+
}
3135+
3136+
function wrap(callback) {
3137+
setImmediate(callback);
3138+
}
3139+
});
3140+
3141+
it('should retry an aborted txn when commit fails', function(done) {
30743142
var query = `SELECT * FROM ${table.name}`;
30753143
var attempts = 0;
30763144

test/database.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ describe('Database', function() {
7878

7979
var INSTANCE = {
8080
api: {},
81-
formattedName_: 'instance-name'
81+
formattedName_: 'instance-name',
82+
databases_: new Map()
8283
};
8384

8485
var NAME = 'table-name';
@@ -199,15 +200,31 @@ describe('Database', function() {
199200

200201
describe('close', function() {
201202
describe('success', function() {
202-
it('should close the database', function(done) {
203+
beforeEach(function() {
204+
database.parent = INSTANCE;
203205
database.pool_ = {
204206
clear: function() {
205207
return Promise.resolve();
206208
}
207209
};
210+
});
208211

212+
it('should close the database', function(done) {
209213
database.close(done);
210214
});
215+
216+
it('should remove the database cache', function(done) {
217+
var cache = INSTANCE.databases_;
218+
219+
cache.set(database.id, database);
220+
assert(cache.has(database.id));
221+
222+
database.close(function(err) {
223+
assert.ifError(err);
224+
assert.strictEqual(cache.has(database.id), false);
225+
done();
226+
});
227+
});
211228
});
212229

213230
describe('error', function() {

test/index.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ describe('Spanner', function() {
121121
});
122122

123123
describe('instantiation', function() {
124+
it('should localize an instance map', function() {
125+
assert(spanner.instances_ instanceof Map);
126+
});
127+
124128
it('should promisify all the things', function() {
125129
assert(promisified);
126130
});
@@ -639,11 +643,28 @@ describe('Spanner', function() {
639643
}, /A name is required to access an Instance object\./);
640644
});
641645

642-
it('should return an Instance object', function() {
646+
it('should create and cache an Instance', function() {
647+
var cache = spanner.instances_;
648+
649+
assert.strictEqual(cache.has(NAME), false);
650+
643651
var instance = spanner.instance(NAME);
652+
644653
assert(instance instanceof FakeInstance);
645654
assert.strictEqual(instance.calledWith_[0], spanner);
646655
assert.strictEqual(instance.calledWith_[1], NAME);
656+
assert.strictEqual(instance, cache.get(NAME));
657+
});
658+
659+
it('should re-use cached objects', function() {
660+
var cache = spanner.instances_;
661+
var fakeInstance = {};
662+
663+
cache.set(NAME, fakeInstance);
664+
665+
var instance = spanner.instance(NAME);
666+
667+
assert.strictEqual(instance, fakeInstance);
647668
});
648669
});
649670

0 commit comments

Comments
 (0)