socketry / async-redis

MIT License
83 stars 18 forks source link

How can I SELECT database? #15

Closed ritxi closed 5 years ago

ritxi commented 5 years ago

I was digging the code but I cannot see an easy way to choose a redis database.

I was thinking to make a call('SELECT', 2) before I call other commands, but I don't know if it will apply to all connections on the pool.

redis = Async::Redis::Client.new
redis.call('SELECT', 2)
redis.get('foo')

It would be nice to just do:

redis = Async::Redis::Client.new(db: 2)
redis.get('foo')

Any ideas?

Thanks

ioquatix commented 5 years ago

That makes sense.

It depends on how complex you think the commands need to be.

Is the main one going to be SELECT or are there others?

huba commented 5 years ago

At the moment your best bet is to use client.nested, like so:

client = Async::Redis::Client.new
context = client.nested
context.call 'SELECT', 2
context.get 'foo'
context.close

Mind you, this is a bit of a hack. client.nested was made for accommodating things like the PUB/SUB mechanism. One big caveat is that this will return a connection to the pool that now has database 2 selected and there is no way to quickly know this when the pool gives you a connection.

Since the redis server tracks a bunch of state based on the connection I'm now seriously beginning to wonder if the connection pool we have is more trouble than it's worth.

ioquatix commented 5 years ago

The connection pool is required for transparent scalability. Using the nested approach can probably work but is not very scalable since you are dealing with a discrete connection which is stateful.

I actually don't know how much state is affected by the requested method SELECT, but there shouldn't be anything particularly wrong with just manually running that commend (e.g. using a wrapper/nested context). For example, how does SELECT interact with other things like pub/sub. Can you select one database, do some stuff, then select a different one, and do something else?

Unless Redis makes this a client connection level thing (e.g. different port, different credentials, etc), I don't see why we should do the same.

huba commented 5 years ago

The purpose of client.nested is to let the caller hold on to the same connection instance between calls. For example if you call SUBSCRIBE on a connection that connection will not be able to do anything until UNSUBSCRIBE is called on it.

huba commented 5 years ago

SELECT will change the selected database number for that connection only until SELECT is called again or the connection is closed.

huba commented 5 years ago

Since RESP is a stateful protocol maybe we should implement some sort of state tracking as well.

ioquatix commented 5 years ago

Is SELECT deprecated?

olleolleolle commented 5 years ago

When using Redis Cluster, the SELECT command cannot be used, since Redis Cluster only supports database zero.

Source

ioquatix commented 5 years ago

@olleolleolle when I tried to read about the use case for SELECT, the general impression I got was it was a bad feature and shouldn't be used. @ritxi, what's the use case for it? The advice I saw was to use multiple instances of redis. Maybe that's a stupid solution, but the other solution is what @huba suggested which is to use a nested context.

ioquatix commented 5 years ago

The general advice is that select is an application level feature:

In practical terms, Redis databases should mainly used in order to, if needed, separate different keys belonging to the same application, and not in order to use a single Redis instance for multiple unrelated applications.

And in cluster mode it is a no-op.

At least as far as introducing additional logic into this gem, I'm not in favour of introducing select right now.

troex commented 3 years ago

@ioquatix I kind of agree that DB concept in Redis is far from perfect however there are cases where it's very useful, for example in my case I have an app which has 4 different sidekiqs, in production each has it's own redis instance but locally/testing/ci it's way easier to run single redis and just switch use different databases, redis gem and even redis-cli support database selection in connection string so you don't have to manage it on each request/call. Also calling SELECT on every call to redis would be an overkill.

I think it should be fairly easy to implement after_connect method, so once new connection is created in the pool it calls this method where you can select or do other stuff, pretty much the same as many database clients do to set timezone and etc parameters (ref: https://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html#label-General+connection+options)

ioquatix commented 3 years ago

The best way to achieve this is to create your own protocol subclass:

https://github.com/socketry/async-redis/blob/master/examples/auth/protocol.rb

This prepares the connection using whatever you want - select, auth, etc.

akostadinov commented 5 months ago

For any lurkers here, this example for a wrapper protocol with SSL and logical databases support works like a charm

https://github.com/socketry/async-redis/blob/main/examples/auth/wrapper.rb

Thanks, @troex , for taking the time to write it. I wonder if same is expected to work with Async::Redis::SentinelsClient too.

troex commented 5 months ago

@akostadinov never worked with Sentinels so I have no idea how it would work, but taking into account this gem is pretty transparent, it would just pass though what you pass. But as said I have no idea what is needed for Sentinels to work

ioquatix commented 1 week ago

FYI, we now have Async::Redis::Endpoint which will select the database index if you specify it.