Skip to content

Commit

Permalink
move data to db or cache based on the change of state of enableRedisS…
Browse files Browse the repository at this point in the history
…ampleStore flag
  • Loading branch information
shriramshankar committed Apr 10, 2017
1 parent e477723 commit ab8bb31
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 59 deletions.
1 change: 1 addition & 0 deletions cache/sampleStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const constants = {
persistInProgressKey: PFX + SEP + 'persistInProgress',
prefix: PFX,
separator: SEP,
previousStatusKey: PFX + SEP + 'previousSampleStoreStatus',
};

/**
Expand Down
118 changes: 78 additions & 40 deletions cache/sampleStoreInit.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,27 @@ const Subject = require('../db').Subject;
const featureToggles = require('feature-toggles');
const redisClient = require('./redisCache').client.sampleStore;
const samsto = require('./sampleStore');
const samstoPersist = require('./sampleStorePersist');
const constants = samsto.constants;

/**
* Deletes the key that does the previous value of the "enableRedisSampleStore"
* flag.
* @returns {Promise} - which resolves to 0 when the key is deleted.
*/
function deletePreviousStatus() {
return redisClient.delAsync(constants.previousStatusKey);
} // deletePreviousStatus

/**
* Gets the value of "previousStatusKey"
*
* @returns {Promise} which resolves to the value of the previoiusStatusKey
*/
function getPreviousStatus() {
return redisClient.getAsync(constants.previousStatusKey);
} // persistInProgress

/**
* Clear all the "sampleStore" keys (for subjects, aspects, samples) from
* redis.
Expand All @@ -38,7 +57,8 @@ function eradicate() {
console.error(err); // eslint-disable-line no-console
Promise.resolve(true);
}));
return Promise.all(promises);
return deletePreviousStatus()
.then(() => Promise.all(promises));
} // eradicate

/**
Expand Down Expand Up @@ -153,10 +173,6 @@ function populateSamples() {
* false if the feature is not enabled.
*/
function populate() {
if (!featureToggles.isFeatureEnabled(constants.featureName)) {
return Promise.resolve(false);
}

const msg = 'Populating redis sample store from db';
console.log(msg); // eslint-disable-line no-console

Expand All @@ -165,51 +181,73 @@ function populate() {
} // populate

/**
* Checks whether the sample store exists by counting the members of the three
* index keys (samples, subjects, aspects). If any has more than one member,
* consider the sample store as existing.
*
* Note: I tried using the redis "exists" command but our travis build kept
* failing with a message that I was passing the wrong number of args to the
* "exists" command. I gave up and went with counting members using "scard"
* instead.
* Compare the current status of the "enableRedisSampleStore" flag with its
* previous status. If both current status and previous status are the same, do
* nothing. When the current status and the previous status do not match, take
* some action depending on the current status.
*
* @returns {Promise} which resolves to true if the sample store already
* exists.
*/
function sampleStoreExists() {
const cmds = [
['scard', constants.indexKey.aspect],
['scard', constants.indexKey.sample],
['scard', constants.indexKey.subject],
];
return redisClient.batch(cmds).execAsync()
.then((batchResponse) =>
batchResponse[0] > 0 || // eslint-disable-line no-magic-numbers
batchResponse[1] > 0 || // eslint-disable-line no-magic-numbers
batchResponse[2] > 0); // eslint-disable-line no-magic-numbers
} // sampleStoreExists
function storeSampleToCacheOrDb() {
const currentStatus = featureToggles.isFeatureEnabled(constants.featureName)
|| false;
return getPreviousStatus()
.then((prevState) => {
const previousStatus = (prevState == 'true') || false;

// set the previousStatus to the currentStatus
redisClient.setAsync(constants.previousStatusKey, currentStatus);

/*
* when the current status and the previous status do not match, actions
* needs to be taken based on the current status
*/
if (previousStatus !== currentStatus) {
/*
* call "popluate" when "enableRedisSampleStore" flag has been changed
* from false to true. Call "eradicate" and "storeSampleToDb" when
* "enableRedisSampleStore" flag has been changed from true to false
*/
if (currentStatus) {
return populate();
}

/*
* If samples are being persisted to the db by some other process, STOP
* and resolve to false.
*/
return samstoPersist.persistInProgress()
.then((inProgress) => {
if (inProgress) {
return Promise.resolve(false);
}

return samstoPersist.storeSampleToDb();
}).then(() => eradicate()); // eradicate the cache
}

// when the current status and previous status are same, resolve to false
return Promise.resolve(false);
});
} // storeSampleToCacheOrDb

/**
* Initializes the redis sample store from the db if the feature is enabled and
* the sample store index keys do not already exist.
* Calls "storeSampleToCacheOrDb" to either populate the cache from db or db
* from cache, depending on the change of state of the "enableRedisSampleStore"
* flag
*
* @returns {Promise} which resolves to true if sample store is enabled and
* has completed initialization; resolves to false if feature is not
* enabled.
* @returns {Promise} which resolves to false if no action was taken or resolves
* to a list of promises if some action was taken.
*/
function init() {
if (!featureToggles.isFeatureEnabled(constants.featureName)) {
return Promise.resolve(false);
}

return sampleStoreExists()
.then((exists) => {
if (exists) {
return Promise.resolve(true);
}

return populate();
return storeSampleToCacheOrDb()
.then((ret) => Promise.resolve(ret))
.catch((err) => {
// NO-OP
console.error(err); // eslint-disable-line no-console
Promise.resolve(false);
});
} // init

Expand Down
45 changes: 28 additions & 17 deletions cache/sampleStorePersist.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,33 @@ function persistInProgress() {
} // persistInProgress

/**
* Truncate the sample table in the db and persist all the samples from redis
* Truncate the sample table in the DB and persist all the samples from redis
* into the empty table.
*
* @returns {Promise} - which resolves to true once the sample is persisted to
* the database
*/
function storeSampleToDb() {
return redisClient.setAsync(constants.persistInProgressKey, 'true')
.then(() => Sample.destroy({ truncate: true, force: true }))
.then(() => redisClient.smembersAsync(constants.indexKey.sample))
.then((keys) => keys.map((key) => ['hgetall', key]))
.then((cmds) => redisClient.batch(cmds).execAsync())
.then((res) => {
const samplesToCreate = res.map((sample) => {
sample.relatedLinks = JSON.parse(sample.relatedLinks);
return sample;
});
return Sample.bulkCreate(samplesToCreate);
})
.then(() => redisClient.delAsync(constants.persistInProgressKey))
.then(() => console.log('persisted redis sample store to db'))
.then(() => true);
} // storeSampleToDb

/**
* Calls the storeSampleToDb function to store sample data back to DB.
*
* @returns {Promise} which resolves to true upon complete if redis sample
* store feature is enabled, or false on error or if feature is disabled or if
* there is already a persist in progress.
Expand All @@ -45,25 +69,11 @@ function persist() {
return persistInProgress()
.then((alreadyInProgress) => {
if (alreadyInProgress) {
Promise.resolve(false);
return Promise.resolve(false);
}

return redisClient.setAsync(constants.persistInProgressKey, 'true');
return storeSampleToDb();
})
.then(() => Sample.destroy({ truncate: true, force: true }))
.then(() => redisClient.smembersAsync(constants.indexKey.sample))
.then((keys) => keys.map((key) => ['hgetall', key]))
.then((cmds) => redisClient.batch(cmds).execAsync())
.then((res) => {
const samplesToCreate = res.map((sample) => {
sample.relatedLinks = JSON.parse(sample.relatedLinks);
return sample;
});
return Sample.bulkCreate(samplesToCreate);
})
.then(() => redisClient.delAsync(constants.persistInProgressKey))
.then(() => console.log('persisted redis sample store to db'))
.then(() => true)
.catch((err) => {
// NO-OP
console.error(err); // eslint-disable-line no-console
Expand All @@ -74,4 +84,5 @@ function persist() {
module.exports = {
persist,
persistInProgress,
storeSampleToDb,
};
6 changes: 5 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,11 @@ function start() { // eslint-disable-line max-statements
});
}

// Make sure sample store is initialized if feature is enabled.
/*
* Based on the change of state of the "enableSampleStore" feature flag
* populate the data into the cache or dump the data from the cache into the
* db
*/
sampleStore.init();

/*
Expand Down
2 changes: 1 addition & 1 deletion tests/cache/sampleStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ describe('sampleStore (feature on):', () => {
.to.be.true;
})
.then(() => samstoinit.init())
.then((res) => expect(res).to.be.true)
.then((res) => expect(res).to.not.be.false)
.then(() => done())
.catch(done);
});
Expand Down

0 comments on commit ab8bb31

Please sign in to comment.