jpodwys / cache-service

A tiered caching solution for JavaScript.
MIT License
12 stars 6 forks source link

How to use nameSpace? #25

Closed damienleroux closed 6 years ago

damienleroux commented 6 years ago

Hello.

The document says nameSpace can be set on cache service creation.

Thank you

jpodwys commented 6 years ago

Hi there!

I originally intended for each of the cache modules available for use with cache-service to have a nameSpace property. When using those caches individually, you would be able set namespaces on them. When using cache-service, you would be able to assign a namespace that would be set to all child caches. Each child cache would then prefix cache keys and logs with the cache namespace. (Currently only cache-service-redis prefixes keys with the provided nameSpace.)

However, it appears that, at this point, namespace is mostly just left over and unimplemented. Additionally, in retrospect, it doesn't make much sense for cache-service's namespace to overwrite all child cache namespaces.

If I were to update my nameSpace implementation, I would want child cache nameSpace properties to trump the cache-service nameSpace property. In fact, I would probably just remove nameSpace from cache-service and just have users manually set nameSpace on all child caches.

This would require updates to:

I would most likely not update cache-service-node-cache. I would like to deprecate this repo in favor of cache-service-cache-module.

What are your thoughts?

damienleroux commented 6 years ago

Hello,

Thank you for you answer that enlightened me.

I agree that configuring nameSpace should be handle by cache-service as it makes more sense that each cache should handle it own nameSpace.

I may not know yet all the ins and outs of cache-service, but I believe it useful to retrieve or to update data from multiple caches, without having to know the cache that keeps the targeted key and corrsponding response.

I would have believed that nameSpace was used to target, when required, a specific cache.

Example of what I would have had in mind for cache-service:

var nodeCache = new nodeCacheModule({nameSpace: 'nodeNameSpace'});
var redisCache = new redisModule({nameSpace: 'redisNameSpace'});
var cacheModules = [nodeCache, redisCache];
var cacheService = new cs({}, cacheModules);

//target a key without a specific namespace
cacheService.get(key, myCallback); 

//target a key with a specific namespace
cacheService.get(key, myCallback, 'nodeNameSpace'); 

Another use case that would match my needs: I need to create dynamically caches with their own namespace. This is what I would have wanted:

var cacheService = new cs({}, []);

//create cache for damien
var nodeCache = new nodeCacheModule({nameSpace: 'damien'});
cacheService.addCache(nodeCache); 

//create cache for jpodwys
var nodeCache = new nodeCacheModule({nameSpace: 'jpodwys'});
cacheService.addCache(nodeCache); 

//add keys for damien
cacheService.set('a', 'aa', undefined, undefined , undefined, 'damien'); 
cacheService.set('b', 'bb', undefined, undefined , undefined, 'damien'); 

//add keys for jpodwys
cacheService.set('a', 'AA', undefined, undefined , undefined, 'jpodwys'); 
cacheService.set('c', 'CC', undefined, undefined , undefined, 'jpodwys'); 

//target a key for damien
cacheService.get('a', myCallback, 'damien');  // found value : 'aa'

//target a key for all : returns several values for each found namespace
cacheService.get('a', myCallback); // found values : { damien: 'aa', jpodwys: 'AA'}

//delete damien's cache
cacheService.deleteCache('damien'); 

As you can see with this example, I need a solution to handle several caches with their own namespace and I think that cache-service should provide it.

From what I understand, the original nameSpace that you thought is serving a different purpose. I don't think that nameSpace should be applied to cache keys but should be used in a collection to separate caches.

Let me know if I miss something or if I misunderstood the intended cache-service behavior.

Thank you

jpodwys commented 6 years ago

I apologize, this is a big response.

Simple Solution

If I understand correctly, you want the ability to call public methods on a single cache module within cache-service based on the nameSpace you gave that individual cache. Does that sounds right?

Let me start off by saying I'm not interested in adding arguments to public methods at this time. I would rather you just fetch the cache you want and call one of its public methods directly.

In fact, in a manner of speaking, there's already a way to do just that. Because you provided the caches to cache-service, you know the index of the cache you want. As such, you can call cacheService.caches[index].set(key, value);.

If that's insufficient for your needs, a small utility function should do the trick. Perhaps something like this:

function getCacheByNameSpace(caches, nameSpace){
  for(var i = 0; i < caches.length; i++){
    if(caches[i].nameSpace === nameSpace){
      return caches[i];
    }
  }
  return null;
}

You could use the above function like this:

var individualCache = getCacheByNameSpace(cacheService.caches, nameSpace);
if(individualCache){
  individualCache.set(key, value);
}

I might consider baking this sort of behavior into a new public method so you could call cacheService.getCacheByNameSpace('damien').set(key, value);.

The Problem

The only action you mentioned above that my recommendations here would not satisfy is this one:

cacheService.get('a', myCallback); // found values : { damien: 'aa', jpodwys: 'AA'}

cache-service is not intended to fetch a single key from multiple caches. Rather, it finds the first matching key in a list of caches. (More on its original intent at the bottom of this comment.)

When you say you want to be able to write to and query from multiple caches based on more than one property (namely key and nameSpace), it starts to sound like you need a database rather than a key/value cache implementation.

For example, the pseudo-code examples you provided above basically boil down to these generic database queries:

I may be mistaken about what you need, but if this sounds like the kind of functionality you're hoping for, MySQL, Mongo, or even Redis can support these use cases far better than cache-service.

If I've misunderstood, please correct me.

cache-service's Original Intent

I originally intended cache-service to be a tiered caching solution. By that, I mean that each cache serves as a level of redundancy for the prior layer.

Here's an example:

Let's say your cache lives on your web server. Awesome! Caching on your web server makes responses as fast as they can be. However, you're going to encounter some issues:

  1. You lose all your data every time you deploy or your server restarts
  2. When you spin up a second instance of your server, it has no data in its cache

As a result, you decide to move your cache off of your server and into redis. Now your data persists between server restarts and all instances of your server benefit from it. However, your cache lookups are now slightly slower than they used to be.

cache-service gives you the speed of storing your cache locally with the redundancy of storing your cache remotely. It works by allowing you to have a primary in-memory cache module backed by a secondary remote cache module. Any time you set a cache key, it gets set to all caches passed to cache-service. Additionally, any time you read from cache-service, if the key isn't in the first cache but is in the second cache, cache-service writes that key and value to the first cache so that they will be available in-memory the next time you need them.

Additionally, if you have memory capacity concerns, you can decrease your primary cache's expiration so keys stored in memory are short-lived. Doing this allows you to keep only your most used keys in memory with all the others in your remote cache.

damienleroux commented 6 years ago

Hello,

Thank you for your time. You have perfectly well resumed my intents. I actually end up with a simple solution that looks like your example except that I didn't use cache-service at all but create a simple collection that contained all caches:

import CsNodeCache from 'cache-service-node-cache';

const caches = {};

export function getCache(namespaces) {
    if (!caches[namespaces]) {
        //see https://github.com/jpodwys/cache-service-node-cache#basic-usage
        caches[namespaces] = new CsNodeCache();
    }

    return caches[namespaces];
}

export function getCacheInfo(namespaces, req) {
    //remove req.headers to keep only "accept" and "accept-language"
    const headersToRemove = Object.keys(req.headers);
    const headersToKeep = ["accept", "accept-language"];
    headersToKeep.map(headerToKeep => {
        const i = headersToRemove.indexOf(headerToKeep);
        if (i > -1) {
            headersToRemove.splice(i, 1);
        }
    });

    return {
        cache: getCache(namespaces),
        config: {
            pruneHeader: headersToRemove
        }
    };
}

export function deleteCache(namespaces) {
    if (caches[namespaces]) {
        //see https://github.com/jpodwys/cache-service-node-cache#flushcb
        caches[namespaces].flush();
        delete (caches[namespaces]);
    }
}

And I agree with you about that searching multiple results through multiple caches sounds like database queries ^^. The idea crossed my mind too.

I misunderstood the primary goal of cache-service that's why your last explanation has been very enlightening. For now, I'm making only tests locally to server instances but when I'll put the cache into Redis, I think cache-service will be very useful.

I close the issue. Thank you