redis / ioredis

🚀 A robust, performance-focused, and full-featured Redis client for Node.js.
MIT License
14.26k stars 1.19k forks source link

Is there way to use multiple prefixes with a single connection? #983

Closed celalo closed 4 years ago

celalo commented 4 years ago

Maybe something like this?

var myRedis = new Redis();
fooRedis = myRedis.keyPrefix("foo:"); //just an example
barRedis = myRedis.clone().keyPrefix = "bar:"; //another example

//so that, fooRedis and barRedis are essentially two pointers to the same connection.
fooRedis.set("a", "1"); //sets foo:a -> 1
barRedis.set("b", "2"); //sets bar:b -> 2
tuananh commented 4 years ago

I don't think it is possible right now. What's the problem with 2 instance of redis? I never had to worry about no. of connection to redis.

celalo commented 4 years ago

You are right connection to redis is very very lightweight but it adds up if you need to open 10 connections with different prefixes in a lambda function that runs in sub 100ms. Also, the issue multiplies when you have a lot of concurrent lambda functions without being able to control container's life-cycle. Redis server ends up with hundreds of connections. It becomes a bit worrying.

tuananh commented 4 years ago

I see. connection pooling is still problem in Lambda last i tried.

khaledosman commented 4 years ago

Right, another problem is if you have a cap on the number of connections limit. I'm using a free redis instance from redislabs.com which has 30 as a max number of connections. I connect to it via an AWS lambda through an apollo graphql server. So I'm facing a similar issue where I hit the 30 connections limit even though no one is using the endpoint besides 2/3 developers. https://github.com/apollographql/apollo-server/issues/3434

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 7 days if no further activity occurs, but feel free to re-open a closed issue if needed.

titanism commented 1 year ago

We looked into this, and it looks like this is exactly what refix provided. Albeit refix is old and unmaintained (and also written in CoffeeScript) → a new approach would be to use @ioredis/commands. We ultimately didn't go this approach, but here's how you would do it – this has been tested ✅ :

const commands = require('@ioredis/commands');
const Redis = require('ioredis-mock'); // or `require('ioredis')`

// create your redis instance
const client = new Redis();

// modern approach inspired by `refix` package
// <https://github.com/luin/ioredis/issues/983#issuecomment-1448839874>
function refix(client, prefix) {
  const proxy = {};
  Object.setPrototypeOf(proxy, client);
  for (const command of commands.list) {
    proxy[command] = function (...args) {
      const keyIndexes = commands.getKeyIndexes(command, args);
      for (const index of keyIndexes) {
        args[index] = prefix + args[index];
      }

      return client[command](...args);
    };
  }

  return proxy;
}

// create a new instance with refix
const prefixedInstance = refix(client, 'foo:'); // will prepend all keys with "foo:" prefix

prefixedInstance.set('hello', 'world'); // sets "foo:hello" to "world"

This was research done for :tangerine: Tangerine for @forwardemail.

titanism commented 1 year ago

Previous comment updated with working and tested example ✅

titanism commented 1 year ago

Slight improvement to support mset and mget via Map or Object:

// modern approach inspired by `refix` package
// <https://github.com/luin/ioredis/issues/983#issuecomment-1448839874>
// <https://github.com/luin/ioredis/issues/983#issuecomment-1536728696>
function refix(client, prefix) {
  const proxy = {};
  Object.setPrototypeOf(proxy, client);
  for (const command of commands.list) {
    proxy[command] = function (...args) {
      const keyIndexes = commands.getKeyIndexes(command, args);
      for (const index of keyIndexes) {
        if (args[index] instanceof Map) {
          const map = new Map();
          for (const [key, value] of args[index]) {
            map.set(prefix + key, value);
          }

          args[index] = map;
        } else if (
          typeof args[index] === 'object' &&
          !Array.isArray(args[index])
        ) {
          const obj = {};
          for (const key of Object.keys(args[index])) {
            obj[prefix + key] = args[index][key];
          }

          args[index] = obj;
        } else {
          args[index] = prefix + args[index];
        }
      }

      return client[command](...args);
    };
  }

  return proxy;
}
xanhz commented 6 months ago

You can use native Proxy by Javascript instead

function refix(client, prefix) {
    return new Proxy(client, {
        get(target, prop, receiver) {
            if (prop === 'options') {
                return {
                    ...target.options,
                    keyPrefix: prefix,
                };
            }
            return Reflect.get(target, prop, receiver);
        },
    });
}