Skip to content

JAVA-3168 Copy node info for contact points on initial node refresh only from first match by endpoint#2007

Merged
tolbertam merged 1 commit intoapache:4.xfrom
jahstreet:java-3168-dont-create-pools-for-same-nodes
Jan 31, 2025
Merged

JAVA-3168 Copy node info for contact points on initial node refresh only from first match by endpoint#2007
tolbertam merged 1 commit intoapache:4.xfrom
jahstreet:java-3168-dont-create-pools-for-same-nodes

Conversation

@jahstreet
Copy link
Contributor

@jahstreet jahstreet commented Jan 23, 2025

https://datastax-oss.atlassian.net/browse/JAVA-3168

DataStax Java Cassandra driver supports FixedHostNameAddressTranslator since v4.15.0 (Sep 19, 2022). This address translator plugin allows Cassandra clients to connect to a Cassandra database running in a different (private) network, eg. Kubernetes, via load balancer.

On the initial (control) connection to Cassandra, the driver queries the DB cluster for its topology and fetches its nodes IDs together with their IP addresses (Pod IPs in a private K8s network). These addresses are "translated" by the address translator to the configured URL pointing to the load balancer. So the client "thinks" it has connected to all Cassandra nodes, however the node addresses are the same.

Connecting to Cassandra behind load balancer with FixedHostNameAddressTranslatorAfter, the driver opens a connection pool to every discovered Cassandra node. And given all the node addresses now point to the load balancer, all the connections are getting opened to it. We can calculate the total number of connections as:

client_app_count * cassandra_node_count * connections_per_node_count

By default, the driver sets connections_per_node_count to 1 (advanced.connection.pool), however for data intensive applications it is configured to a higher value. For example, Apache Spark Cassandra Connector overrides it to the number of available JVM CPUs (spark.cassandra.connection.localConnectionsPerExecutor). So it can be a usual scenario to have a Spark job with 64 executors (each running a separate instance of a Cassandra driver) with 16 CPU cores connecting to a 100 node Cassandra cluster, which generates 64 * 100 * 16 = 102'400 connections. And that is a single application. So we must be careful when configuring the load balancer to not exceed limits on number of connections, number of open files, memory, etc.

Driver, when fetched the cluster topology and translated node addresses, does "optimization": it creates an instance of Node class for every contact point address used on initial (control) connection and reuses them for all the translated node addresses if they match any of the contact points. Then, in the scenario when contact point is the load balancer address and address translator also translates node addresses to the load balancer address, the driver metadata will contain node IDs mapping to the same Node instance, eg.: node_id_1 -> LB_Node, node_id_2 -> LB_Node, node_id_3 -> LB_Node, …

Then the connection pool is initialized for each node (or better say multiple times for the same Node), but with a "bug": after a pool is created, it is put to a map with Node as a key, and when Node is the same instance for all pools (LB_Node), we get a map with a single pool in it (each time we put a new value for the same key, the value is being overwritten). So, in result we have created pools for every discovered Cassandra node, but all except one of them are leaked and will be running in a background keeping the connections alive, but staying unused.

With that change, the driver reuses contact point Nodes and fills them with the missing node info on initial node refresh only from first match with the node info by endpoint. That ensures we always create separate Node instances for Nodes having the same EndPoints, which down the line protects us from leaking the connection (channel) pools in PoolManager.

patch by Alex Sasnouskikh; reviewed by Andy Tolbert and Alexandre Dura for JAVA-3168

@jahstreet jahstreet changed the title JAVA-3168 Don't create pools for same nodes JAVA-3168 Don't create pools for the same nodes Jan 27, 2025
@tolbertam tolbertam self-requested a review January 29, 2025 14:54
@jahstreet jahstreet changed the title JAVA-3168 Don't create pools for the same nodes JAVA-3168 Replace contact point nodes on initial node refresh Jan 30, 2025
@jahstreet jahstreet changed the title JAVA-3168 Replace contact point nodes on initial node refresh JAVA-3168 Copy node info for contact points on initial node refresh only from first match by endpoint Jan 30, 2025
Copy link
Contributor

@tolbertam tolbertam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Excellent work!

@jahstreet jahstreet force-pushed the java-3168-dont-create-pools-for-same-nodes branch from d77f414 to 0506c81 Compare January 30, 2025 20:11
Copy link
Contributor

@tolbertam tolbertam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

…nly from first match by endpoint

patch by Alex Sasnouskikh; reviewed by Andy Tolbert and Alexandre Dura for JAVA-3168
@jahstreet jahstreet force-pushed the java-3168-dont-create-pools-for-same-nodes branch from 0506c81 to 7b732d7 Compare January 30, 2025 22:37
@jahstreet
Copy link
Contributor Author

Squashed commits, PTAL.
Ready to go 🚀

@tolbertam
Copy link
Contributor

This is great, thank you @jahstreet !

@tolbertam tolbertam merged commit 04d34a8 into apache:4.x Jan 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants