Code Monkey home page Code Monkey logo

distribucache's Introduction

Distribucache Build Status NPM version

Datastore-independent automatically-repopulating cache. This cache does everything in its power to shield the caller from the delays of the downstream services. It has a unique feature, where the cache will populate itself on a certain interval, and will stop doing so when the values that were being refreshed have not been used.

There are multiple available datastores, including:

The cache can be used in various ways, ranging from the simplest get / set, to complex scenarios with watermarks for staleness and final expiration.

Usage

Basic

There are many different ways to use the cache. Features are added to the cache, based on the configuration that you use. Below is an example of the simplest cache:

var distribucache = require('distribucache'),
  // create a Redis store (to keep track of the Redis connections)
  // generally performed once in the lifetime of the app
  redisStore = require('distribucache-redis-store'),
  cacheClient = distribucache.createClient(redisStore({
    host: 'localhost',
    port: 6379
  })),
  // create a new cache
  // performed every time a new cache configuration is needed
  cache = cacheClient.create('nsp');

cache.get('k1', function (err, value) {
  if (err) throw err;
  console.log('got value:', value);
});

cache.set('k1', 'v1', function (err) {
  if (err) throw err;
  console.log('set value');
});

cache.del('k1', function (err) {
  if (err) throw err;
  console.log('deleted k1');
});

Promises: if a callback is not provided as the last argument, a Promise will be returned from the get, set and del methods.

Note: the value from the get will be null if the value is not in the cache.

Configuration

The cache can be configured in two places: (a) when creating a cache-client, and (b) when creating a cache. As you expect, the configuration in the cache overrides the configuration of the cache-client:

var cacheClient = distribucache.createClient(store, {
  expiresIn: '2 sec'   // setting globally
});

// overrides the globally set `expiresIn`
var articleCache = cacheClient.create('articles', {
  expiresIn: '1 min'
});

// uses the global `expiresIn`
var pageCache = cacheClient.create('pages');

Populating

A common pattern is to call the get first, and if the item is not in the cache, call set. For this common pattern, you can provide a populate function when creating the cache. On a get, if the cache is empty your populate function will be called to populate the cache, and then the flow will continue to the get callback. This ensures that the get always returns a value, either from the cache or from the downstream service.

var cache = cacheClient.create('nsp', {
  populate: function (key, cb) {
    setTimeout(function () {
      cb(null, 42);
    }, 100);
  }
});

cache.get('k1', function (err, value) {
  console.log(value); // 42
});

Promises: the populate function may return a Promise if you choose.

Expiration / Staleness

When an expiresIn is set, a get request will return null after the time expires. After this, the value will be dropped from the datastore. When the populate function is set, instead of returning null the populate method will be called.

The expiresIn may be set in milliseconds or in the human-readable format.

var cache = cacheClient.create('nsp', {
  expiresIn: 2000  // 2 seconds
});

A staleIn can also be set. It acts as a low-water-mark. When a value is stale it is still returned as is to the caller. Two additional things happen: (a) the stale event is called (with key as the argument) and (b) the populate is called in the background if it is provided; allowing the next get call to get a fresh value, without incurring the delay of accessing a downstream service.

var cache = cacheClient.create('nsp', {
  staleIn: 1000  // 1 second
});

Timer-based Background Population

The more complex, yet most powerful feature of the cache is its ability to update its keys on a specific interval. To do this set the populateIn config. You must also set a pausePopulateIn to make sure the cache is not re-populated forever needlessly.

The cache will use the pausePopulateIn to check whether the key has been used during that interval. The cache does this by tracking the access time of keys. For example, if you want the cache to stop populating when the key hasn't been used for a minute, set pausePopulateIn to 1000 * 60 ms.

var cache = cacheClient.create('nsp', {
  populateIn: 1000  // 1 second
  pausePopulateIn: 1000 * 60  // 1 minute
});

Note: this feature will work even with disruptions to the service, as the burden of determining which keys need to be re-populated is on the store (e.g., in the Redis store this is done using a combination of keyspace events and expiring keys).

API

Distribucache

  • createClient(store, config)

Possible config values below.

{String} [config.namespace]
{Boolean} [config.optimizeForSmallValues] defaults to false
{Boolean} [config.optimizeForBuffers] defaults to false
{String} [config.expiresIn] in ms
{String} [config.staleIn] in ms
{Function} [config.populate]
{Number} [config.populateIn] in ms, defaults to 30sec
{Number} [config.populateInAttempts] defaults to 5
{Number} [config.pausePopulateIn] in ms, defaults to 30sec
{Number} [config.timeoutPopulateIn] in ms
{Number} [config.leaseExpiresIn] in ms
{Number} [config.accessedAtThrottle] in ms, defaults to 1000

Notes:

  • The values above are allowed for the config and are also available to the CacheClient#create
  • See the Optimizations docs for values that begin with optimizeFor

CacheClient

  • create(namespace, config)
    • namespace is a String that will identify the particular cache. It is good practice to add a version to the namespace in order to make sure that when you change the interface, you will not get older cached objects (with a possibly different signature). For example: create('articles:v1').
    • config is an Object. See the global config above for all of the possible values.

More

License

MIT

distribucache's People

Contributors

adamrights avatar jamesmanning avatar nemtsov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

distribucache's Issues

Add support for populate() with their own ETag

As a client using the populate(),
I want the method to accept a new ETag, provide the ETag of the previous value,
and I want to be able to tell populate that the previous value has not changed,
So that I would not have to waste bandwidth when the data-provider is capable of working with ETags.

Add ability to return raw values from the datastore (sans marshalling)

As a client configuring a new store,
I want to force distribucache to not marshall values,
In order to optimize for memory and cpu.

This is useful when you need to store a buffer or string and not an object (e.g., caching the html of the page). It will avoid the Buffer (Redis) -> String (Redis) -> Object (Distribucache) -> String (Express) -> Buffer (Http) conversion that currently happens with the Redis store when storing the page html in cache.

Possible configuration:

cache = cacheClient.create('page:v1', {
  useRawValues: true
});

unhandled error event log message

distribucache error from an unhandled error event:
PopulateError: failed to populate key "centralbanks"; cause: timed out after 30000ms
at null._onTimeout (/home/servo/node_modules/distribucache/lib/util.js:37:8)
at Timer.listOnTimeout as ontimeout

Why is this reporting an error is unhandled?

populateIn does not restart after pausePopulateIn triggers

Repro:

var distribucache = require('distribucache'),
  memoryStore = require('distribucache-memory-store'),
  store = memoryStore(),
  logEvents = require('distribucache-console-logger'),
  cacheClient = distribucache.createClient(store),
  cache;

cache = cacheClient.create('randomness', {
  populateIn: '5 sec',
  pausePopulateIn: '10 sec',
  populate: function (key, cb) {
    setTimeout(function () {
      var value = Math.round(Math.random() * 1000);
      console.log('[client] populating with:', value);
      cb(null, value);
    }, 250);
  }
});

logEvents(cache);

function doIt() {
  var t = Date.now();
  cache.get('k8', function (err, value) {
    if (err) return console.error('[client] ', err);
    console.log('[client] got `%j` (type: %s) in %dms',
      value, typeof value, Date.now() - t);
  });
}

console.log('--------------- GET ------------------')
doIt();

setTimeout(function () { 
  console.log('------------- POPULATE -------------')
}, 5000);

setTimeout(function () { 
  console.log('---------- PAUSING NOW ----------')
}, 10000);

setTimeout(function () {
  doIt();
  setInterval(doIt, 5000);
}, 16000);

Result:

--------------- GET ------------------
2016-02-09T19:45:32.991Z get:before - k8
2016-02-09T19:45:32.993Z get:after - k8,2
2016-02-09T19:45:32.994Z get:miss - k8
2016-02-09T19:45:32.994Z populate:before - k8
[client] populating with: 561
2016-02-09T19:45:33.246Z set:before - k8,561
2016-02-09T19:45:33.248Z set:after - k8,561,2
2016-02-09T19:45:33.248Z populate:after - k8,254
[client] got `561` (type: number) in 258ms
---------- POPULATE ----------
2016-02-09T19:45:38.254Z populateIn:before - k8
2016-02-09T19:45:38.256Z populate:before - k8
[client] populating with: 327
2016-02-09T19:45:38.510Z set:before - k8,327
2016-02-09T19:45:38.511Z set:after - k8,327,1
2016-02-09T19:45:38.511Z populate:after - k8,255
2016-02-09T19:45:38.511Z populateIn:after - k8,257
---------- PAUSING NOW ----------
2016-02-09T19:45:43.515Z populateIn:before - k8
2016-02-09T19:45:43.515Z populateIn:pause - k8
2016-02-09T19:45:43.516Z populateIn:after - k8,1
2016-02-09T19:45:48.995Z get:before - k8
2016-02-09T19:45:48.996Z get:after - k8,1
2016-02-09T19:45:48.996Z get:hit - k8
[client] got `327` (type: number) in 1ms
2016-02-09T19:45:53.997Z get:before - k8
2016-02-09T19:45:53.997Z get:after - k8,0
2016-02-09T19:45:53.997Z get:hit - k8
[client] got `327` (type: number) in 0ms
2016-02-09T19:45:58.998Z get:before - k8
2016-02-09T19:45:58.998Z get:after - k8,0
2016-02-09T19:45:58.998Z get:hit - k8
[client] got `327` (type: number) in 0ms
2016-02-09T19:46:04.000Z get:before - k8
2016-02-09T19:46:04.000Z get:after - k8,0
2016-02-09T19:46:04.001Z get:hit - k8
[client] got `327` (type: number) in 1ms
2016-02-09T19:46:09.007Z get:before - k8
2016-02-09T19:46:09.007Z get:after - k8,0
2016-02-09T19:46:09.007Z get:hit - k8
[client] got `327` (type: number) in 0ms

Issue:
The [client] got 327 does not change after pausePopulateIn.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.