flipper icon indicating copy to clipboard operation
flipper copied to clipboard

ConnectionPool adapter

Open bkeepers opened this issue 1 year ago • 7 comments
trafficstars

This is an attempt at creating a generic ConnectionPool adapter that can be composed with other adapters as discussed in https://github.com/flippercloud/flipper/pull/826#pullrequestreview-1832444441.

It just wraps each adapter method call in a pool.with { } call, but I think this mostly makes sense since the adapter API already represents atomic actions. It's not a lot of code and can be used with any adapter that requires a connection pool (Redis, RedisCache, and probably others).

ConnectionPool doesn't expose a mechanism for extending an existing pool instance, caching objects that depend on connection, or obsevering lifecycle events, so this adapter has to keep a cache of adapter instances for each connection. I'm not 100% sure this is kosher.

Usage as primary storage adapter:

pool = ConnectionPool.new(size: 5, timeout: 5) { Redis.new }
adapter = Flipper::Adapters::ConnectionPool.new(pool) do |client|
  Flipper::Adapters::Redis.new(client)
end

# Read about the `config.adapter` problem below
Flipper.configure do |config|
  config.adapter { adapter }
end

Usage with cache adapter:

pool = ConnectionPool.new(size: 5, timeout: 5) { Redis.new }
storage = Flipper::Adapters::ActiveRecord.new
adapter = Flipper::Adapters::ConnectionPool.new(pool) do |client|
  Flipper::Adapters::RedisCache.new(storage, client)
end

# Read about the `config.adapter` problem below
Flipper.configure do |config|
  config.adapter { adapter }
end

The config.adapter problem

Currently, Flipper returns a new adapter instance (it calls the config.adapter block) for each thread. This is to guard against some adapters not being thread safe. Initializing this adapter inside config.adapter block would probably work fine, but it would result in a separate adapter cache for each thread (n threads * x pool size).

I'm thinking we should add threadsafe? to the adapter API and change Flipper to reuse adapter instances of threadsafe adapters.

cc @cymen

bkeepers avatar Feb 03 '24 13:02 bkeepers

@bkeepers Sounds good! I'm taking some time off work so I won't be able to keep my PR going forward but this sounds like a good way to go (and makes my PR not necessary).

cymen avatar Feb 03 '24 15:02 cymen

Thanks for working on this, @bkeepers! I started down this same path this morning and then found this PR. I'm looking forward to using it with the Redis adapter (and ideally an existing Redis pool). If it doesn't get merged for whatever reason, maybe I can contribute a less generic version for Redis.

maleksiuk avatar Feb 10 '24 19:02 maleksiuk

@jnunemaker is this worth merging?

gstokkink avatar Sep 11 '24 11:09 gstokkink

I'm thinking we should add threadsafe? to the adapter API and change Flipper to reuse adapter instances of threadsafe adapters.

@bkeepers I like this idea.

jnunemaker avatar Sep 11 '24 11:09 jnunemaker

@bkeepers anything left on this? It seems like you've responded to all the feedback. Do we just need someone to guinea pig in production somewhere?

jnunemaker avatar Sep 11 '24 12:09 jnunemaker

@jnunemaker slightly off-topic, but do you know if there are any plans to also support the new redis-client (https://github.com/redis-rb/redis-client) gem alongside the slower and more bloated redis-rb (https://github.com/redis/redis-rb) gem? Note that the redis-rb gem also uses redis-client internally, so might be more efficient to be able to cut out the middleman.

gstokkink avatar Sep 12 '24 09:09 gstokkink

@gstokkink sure. I'd just make a new adapter for it and specs. You can start by cloning the current redis one or taking a peek at the redis cache one. I'd just call it RedisClient.

jnunemaker avatar Sep 12 '24 14:09 jnunemaker