Skip to content

Commit

Permalink
APIv3: Cache invalidation + refactoring (nightscout#6688)
Browse files Browse the repository at this point in the history
* APIv3: isolating documents from tests (not allowing clashes of calculated identifiers)

* removing unused async keyword

* fixing api v3 swagger and moving it to /api3-docs

* APIv3: adding cachedCollection stub of cachedCollection storage implementation

* APIv3: mongo cachedCollection storage implementation

* APIv3: testing and debugging cache updates

* APIv3: more testing on cache updates

* APIv3: fixing bad async functions

* APIv3: finishing cache invalidation tests

Co-authored-by: Petr Ondrusek <[email protected]>
Co-authored-by: Petr Ondrůšek <[email protected]>
Co-authored-by: Sulka Haro <[email protected]>
  • Loading branch information
4 people authored and tanja3981 committed Jan 6, 2021
1 parent c44ae2b commit 1911c7e
Show file tree
Hide file tree
Showing 18 changed files with 539 additions and 119 deletions.
22 changes: 13 additions & 9 deletions lib/api3/generic/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const apiConst = require('../const.json')
, dateTools = require('../shared/dateTools')
, opTools = require('../shared/operationTools')
, stringTools = require('../shared/stringTools')
, CollectionStorage = require('../storage/mongoCollection')
, MongoCollectionStorage = require('../storage/mongoCollection')
, CachedCollectionStorage = require('../storage/mongoCachedCollection')
, searchOperation = require('./search/operation')
, createOperation = require('./create/operation')
, readOperation = require('./read/operation')
Expand All @@ -26,13 +27,16 @@ function Collection ({ ctx, env, app, colName, storageColName, fallbackGetDate,
fallbackDateField }) {

const self = this;

self.colName = colName;
self.fallbackGetDate = fallbackGetDate;
self.dedupFallbackFields = app.get('API3_DEDUP_FALLBACK_ENABLED') ? dedupFallbackFields : [];
self.autoPruneDays = app.setENVTruthy('API3_AUTOPRUNE_' + colName.toUpperCase());
self.nextAutoPrune = new Date();
self.storage = new CollectionStorage(ctx, env, storageColName);

const baseStorage = new MongoCollectionStorage(ctx, env, storageColName);
self.storage = new CachedCollectionStorage(ctx, env, colName, baseStorage);

self.fallbackDateField = fallbackDateField;

self.mapRoutes = function mapRoutes () {
Expand Down Expand Up @@ -89,9 +93,9 @@ function Collection ({ ctx, env, app, colName, storageColName, fallbackGetDate,
};



/**
* Fetch modified date from document (with possible fallback and back-fill to srvModified/srvCreated)
* Fetch modified date from document (with possible fallback and back-fill to srvModified/srvCreated)
* @param {Object} doc - document loaded from database
*/
self.resolveDates = function resolveDates (doc) {
Expand Down Expand Up @@ -125,12 +129,12 @@ function Collection ({ ctx, env, app, colName, storageColName, fallbackGetDate,
* in the background (asynchronously)
* */
self.autoPrune = function autoPrune () {

if (!stringTools.isNumberInString(self.autoPruneDays))
return;

const autoPruneDays = parseFloat(self.autoPruneDays);
if (autoPruneDays <= 0)
if (autoPruneDays <= 0)
return;

if (new Date() > self.nextAutoPrune) {
Expand Down Expand Up @@ -190,4 +194,4 @@ function Collection ({ ctx, env, app, colName, storageColName, fallbackGetDate,
}
}

module.exports = Collection;
module.exports = Collection;
6 changes: 3 additions & 3 deletions lib/api3/generic/history/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ async function history (opCtx, fieldsProjector) {

if (filter !== null && limit !== null && projection !== null) {

const result = await col.storage.findMany(filter
const result = await col.storage.findMany({ filter
, sort
, limit
, skip
, projection
, onlyValid
, logicalOperator);
, logicalOperator });

if (!result)
throw new Error('empty result');
Expand Down Expand Up @@ -150,4 +150,4 @@ function historyOperation (ctx, env, app, col) {
};
}

module.exports = historyOperation;
module.exports = historyOperation;
7 changes: 3 additions & 4 deletions lib/api3/generic/search/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ async function search (opCtx) {


if (filter !== null && sort !== null && limit !== null && skip !== null && projection !== null) {

const result = await col.storage.findMany(filter
const result = await col.storage.findMany({ filter
, sort
, limit
, skip
, projection
, onlyValid);
, onlyValid });

if (!result)
throw new Error('empty result');
Expand Down Expand Up @@ -76,4 +75,4 @@ function searchOperation (ctx, env, app, col) {
};
}

module.exports = searchOperation;
module.exports = searchOperation;
147 changes: 147 additions & 0 deletions lib/api3/storage/mongoCachedCollection/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
'use strict';

const _ = require('lodash')

/**
* Storage implementation which wraps mongo baseStorage with caching
* @param {Object} ctx
* @param {Object} env
* @param {string} colName - name of the collection in mongo database
* @param {Object} baseStorage - wrapped mongo storage implementation
*/
function MongoCachedCollection (ctx, env, colName, baseStorage) {

const self = this;

self.colName = colName;

self.identifyingFilter = baseStorage.identifyingFilter;

self.findOne = (...args) => baseStorage.findOne(...args);

self.findOneFilter = (...args) => baseStorage.findOneFilter(...args);

self.findMany = (...args) => baseStorage.findMany(...args);


self.insertOne = async (doc) => {
const result = await baseStorage.insertOne(doc, { normalize: false });

if (cacheSupported()) {
updateInCache([doc]);
}

if (doc._id) {
delete doc._id;
}
return result;
}


self.replaceOne = async (identifier, doc) => {
const result = await baseStorage.replaceOne(identifier, doc);

if (cacheSupported()) {
const rawDocs = await baseStorage.findOne(identifier, null, { normalize: false })
updateInCache([rawDocs[0]])
}

return result;
}


self.updateOne = async (identifier, setFields) => {
const result = await baseStorage.updateOne(identifier, setFields);

if (cacheSupported()) {
const rawDocs = await baseStorage.findOne(identifier, null, { normalize: false })

if (rawDocs[0].isValid === false) {
deleteInCache(rawDocs)
}
else {
updateInCache([rawDocs[0]])
}
}

return result;
}

self.deleteOne = async (identifier) => {
let invalidateDocs
if (cacheSupported()) {
invalidateDocs = await baseStorage.findOne(identifier, { _id: 1 }, { normalize: false })
}

const result = await baseStorage.deleteOne(identifier);

if (cacheSupported()) {
deleteInCache(invalidateDocs)
}

return result;
}

self.deleteManyOr = async (filter) => {
let invalidateDocs
if (cacheSupported()) {
invalidateDocs = await baseStorage.findMany({ filter,
limit: 1000,
skip: 0,
projection: { _id: 1 },
options: { normalize: false } });
}

const result = await baseStorage.deleteManyOr(filter);

if (cacheSupported()) {
deleteInCache(invalidateDocs)
}

return result;
}

self.version = (...args) => baseStorage.version(...args);

self.getLastModified = (...args) => baseStorage.getLastModified(...args);

function cacheSupported () {
return ctx.cache
&& ctx.cache[colName]
&& _.isArray(ctx.cache[colName]);
}

function updateInCache (doc) {
if (doc && doc.isValid === false) {
deleteInCache([doc._id])
}
else {
ctx.bus.emit('data-update', {
type: colName
, op: 'update'
, changes: doc
});
}
}

function deleteInCache (docs) {
let changes
if (_.isArray(docs)) {
if (docs.length === 0) {
return
}
else if (docs.length === 1 && docs[0]._id) {
const _id = docs[0]._id.toString()
changes = [ _id ]
}
}

ctx.bus.emit('data-update', {
type: colName
, op: 'remove'
, changes
});
}
}

module.exports = MongoCachedCollection;
34 changes: 21 additions & 13 deletions lib/api3/storage/mongoCollection/find.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ const utils = require('./utils')
* @param {Object} col
* @param {string} identifier
* @param {Object} projection
* @param {Object} options
*/
function findOne (col, identifier, projection) {
function findOne (col, identifier, projection, options) {

return new Promise(function (resolve, reject) {

Expand All @@ -25,7 +26,9 @@ function findOne (col, identifier, projection) {
if (err) {
reject(err);
} else {
_.each(result, utils.normalizeDoc);
if (!options || options.normalize !== false) {
_.each(result, utils.normalizeDoc);
}
resolve(result);
}
});
Expand All @@ -38,8 +41,9 @@ function findOne (col, identifier, projection) {
* @param {Object} col
* @param {Object} filter specific filter
* @param {Object} projection
* @param {Object} options
*/
function findOneFilter (col, filter, projection) {
function findOneFilter (col, filter, projection, options) {

return new Promise(function (resolve, reject) {

Expand All @@ -51,7 +55,9 @@ function findOneFilter (col, filter, projection) {
if (err) {
reject(err);
} else {
_.each(result, utils.normalizeDoc);
if (!options || options.normalize !== false) {
_.each(result, utils.normalizeDoc);
}
resolve(result);
}
});
Expand All @@ -62,23 +68,25 @@ function findOneFilter (col, filter, projection) {
/**
* Find many documents matching the filtering criteria
*/
function findMany (col, filterDef, sort, limit, skip, projection, onlyValid, logicalOperator = 'and') {

function findMany (col, args) {
const logicalOperator = args.logicalOperator || 'and';
return new Promise(function (resolve, reject) {

const filter = utils.parseFilter(filterDef, logicalOperator, onlyValid);
const filter = utils.parseFilter(args.filter, logicalOperator, args.onlyValid);

col.find(filter)
.sort(sort)
.limit(limit)
.skip(skip)
.project(projection)
.sort(args.sort)
.limit(args.limit)
.skip(args.skip)
.project(args.projection)
.toArray(function mongoDone (err, result) {

if (err) {
reject(err);
} else {
_.each(result, utils.normalizeDoc);
if (!args.options || args.options.normalize !== false) {
_.each(result, utils.normalizeDoc);
}
resolve(result);
}
});
Expand All @@ -90,4 +98,4 @@ module.exports = {
findOne,
findOneFilter,
findMany
};
};
8 changes: 4 additions & 4 deletions lib/api3/storage/mongoCollection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function MongoCollection (ctx, env, colName) {

self.col = ctx.store.collection(colName);

ctx.store.ensureIndexes(self.col, [ 'identifier',
ctx.store.ensureIndexes(self.col, [ 'identifier',
'srvModified',
'isValid'
]);
Expand Down Expand Up @@ -64,10 +64,10 @@ function MongoCollection (ctx, env, colName) {


/**
* Get timestamp (e.g. srvModified) of the last modified document
* Get timestamp (e.g. srvModified) of the last modified document
*/
self.getLastModified = function getLastModified (fieldName) {

return new Promise(function (resolve, reject) {

self.col.find()
Expand All @@ -87,4 +87,4 @@ function MongoCollection (ctx, env, colName) {
}
}

module.exports = MongoCollection;
module.exports = MongoCollection;
Loading

0 comments on commit 1911c7e

Please sign in to comment.