krojew / cdrs-tokio

High-level async Cassandra client written in 100% Rust.
Apache License 2.0
137 stars 24 forks source link

How to build generic sessions based on runtime parameters, and use them equivalently. #183

Closed stevenj closed 3 months ago

stevenj commented 3 months ago

We have a need to have different sessions set up for different circumstances. We are using a typical approach of defining a set of env vars to choose how to configure the connection.

When we implement something like this:

let session = match cfg.lb_strategy {
        LoadBalancingStrategy::Random => TcpSessionBuilder::new(RandomLoadBalancingStrategy::new(), node_config)
                .with_compression(cdrs_tokio::compression::Compression::Lz4)
                .with_keyspace(keyspace)
                .build()
                .await?,
        LoadBalancingStrategy::RoundRobin => TcpSessionBuilder::new(RoundRobinLoadBalancingStrategy::new(), node_config)
                .with_compression(cdrs_tokio::compression::Compression::Snappy)
                .with_keyspace(keyspace)
                .build()
                .await?,
    };

The compiler rejects it because the type returned by TcpSessionBuilder::new() changes depending on its parameters. How is one supposed to create a generic session? Or to put it another way, what type do we declare session to be so that it can take anything that TcpSessionBuilder::new(...) can return?

The only approach we can find so far is to use an enum to wrap all the possibilities and shim it, which seems very wrong, and largely unmaintainable.

The only examples I can find have a single session type concretely defined, and it's baked in to the source code which seems to preclude runtime configurability. Is there an example of creating a session based on runtime configurable parameters, that can then just be used without caring about what types that were used to configure it?

krojew commented 3 months ago

Load balancing is special in a way that it's a part of the type at the moment. For now, you can make your own and implement the LoadBalancingStrategy trait. You might also take a look at https://github.com/krojew/cdrs-tokio/blob/master/cdrs-tokio/examples/generic_connection.rs if that makes things easier.

stevenj commented 3 months ago

I don't want to implement my own load balancer, I wanted our operations team to be able to say "For this service, use random load balancing" and for some other service, "use topology aware load balancing with these parameters". Or whatever they choose.

As an implementer of the system I don't know how production will want to deploy a particular service (or even what flavor of Cassandra they will target), so it's not possible to know what's the best configuration for the session before it's deployed. This is also true of TLS, not just the Load balancer and a number of other options related to the session/connection.

I did look at the generic_connection.rs but it seems to be a different issue to what I am having. For example, it defines:

type CurrentSession = Session<
    TransportTcp,
    VirtualConnectionManager,
    RoundRobinLoadBalancingStrategy<TransportTcp, VirtualConnectionManager>,
>;

Which is a single-preconfigured concrete session type.

What I am trying to achieve is a generic session type, because the code which just wants to query and insert data doesn't care how the session was configured under the hood.

krojew commented 3 months ago

I'm sorry - I think I was misunderstood. I'm not suggesting implementing a whole load balancer - only a wrapper which will delegate to one of the built-in balancers at runtime. You only need to store an Arc<dyn LoadBalancingStrategy> which will hold the desired built-in LB. Then simply delegate fn query_plan() to this internal one.

stevenj commented 3 months ago

Oh, I see, I will try it. Thankyou.

stevenj commented 3 months ago

I tried that, but LoadBalancingStrategy itself had traits that would need to be re-implemented in this way, and so on. For example TCP or RustTLS are two different types, that I would have needed to replicate a builder for to make a dynamic version of CdrsTransport. It's too difficult to runtime configure, so I can't proceed. We can close this issue.

krojew commented 3 months ago

That's why I suggested looking at the generic session example - it uses a virtual connection manager, which can abstract away different connection types.