Skip to content

HostChooser as a plugin enhancement in pgjdbc driver#1

Open
kneeraj wants to merge 21 commits intomasterfrom
gh-3367
Open

HostChooser as a plugin enhancement in pgjdbc driver#1
kneeraj wants to merge 21 commits intomasterfrom
gh-3367

Conversation

@kneeraj
Copy link
Copy Markdown
Owner

@kneeraj kneeraj commented Sep 16, 2024

All Submissions:

    • Have you followed the guidelines in our Contributing document?
    • Have you checked to ensure there aren't other open Pull Requests for the same update/change?

New Feature Submissions:

    • Does your submission pass tests?
    • Does ./gradlew styleCheck pass ?
    • Have you added your new test classes to an existing test suite in alphabetical order?

Changes to Existing Features:

    • Does this break existing behaviour? If so please explain.
    • Have you added an explanation of what your changes do and why you'd like us to include them?
      Github Issue with details.
    • Have you written new tests for your core changes, as applicable?
    • Have you successfully run tests with your changes locally?

@kneeraj kneeraj changed the title HostChooser plugin first cut HostChooser as a plugin enhancement in pgjdbc driver Oct 3, 2024
*
* @return true or false.
*/
boolean isHostDrainingConnections(String host);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

minor: maybe this can just be called isValid(String host) to make it easy to understand what it maps to.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I was also thinking the same. Let me change it.

(HostChooser) ((QueryExecutorImpl) this.queryExecutor).getHostChooser();
if (hc != null) {
String host = queryExecutor.getHostSpec().getHost();
return !hc.isHostDrainingConnections(host);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Given some conn pools like Hikari don't call isValid because this implementation of executing an empty prepared statement can involve a server round trip (as I understand it), should we allow the hostchooser isValid to also control whether this prepared statement execution is done in line 1553 above. For ex, if the hostchooser can return an is_valid, is_valid_recheck, is_invalid response and only if the response is is_valid_recheck, the empty prepared statement is executed. This can allow a user to set the Hikari setting com.zaxxer.hikari.aliveBypassWindowMs to 0 with no perf penalty and the host chooser can use its own information to decide if a host is still valid, perhaps by running periodic checks in the background.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Nice point. Let me try to add this approach.

public class CustomHostChooserManager {
private final Map<HostChooserUrlProperty, HostChooser> hostChooserMap =
new WeakHashMap<>();
private static CustomHostChooserManager instance = getInstance();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

does instance need to be initialized up front? Can it be initialized in a lazy way?

Also does instance need to be marked volatile?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

If we change it to
private static final
then volatile is not required. I think it is not required even otherwise as this is the first time initialization and it is a singleton instance.

On demand creation can be done but this is just one manager object. It should be ok I think.


public HostChooser getHostChooser(HostChooserUrlProperty customImplClassName) {
if (hostChooserMap.containsKey(customImplClassName)) {
return hostChooserMap.get(customImplClassName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is it ok to use this hash map without synchronization? Given that a different thread may be currently modifying this, the result of this lookup may be invalid or inaccurate.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

It is called in the getOrCreateHostChooser method. There we are synchronizing it in case it is null. We can avoid costly synchronization when it is not required most of the times.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can there be two host choosers in the same process? It seems so because the property can be changed on each conn?

If one thread is modifying the map to insert a new host chooser and another is trying to read the map, this part of the code which operates without synchronization can see unexpected results.

If you want to keep an optimized path for the 99% case where there is just one host chooser, I would not use a map and instead do something like

cachedHostChooser = { className, obj }  ;
concurrenthashmap<....>

where the first plugin is written to the cachedHostChooser and the get path looks up the cachedHostChooser without synchronization. If it is not found there, it can lookup the concurrent hash map in a safe way.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

A few reasons for why multiple HostChoosers has value

  • It’s not uncommon to connect to ybdb and a postgresql instance both from the same app. We got this explicit ask from a customer recently albeit for ruby driver.
  • Pgjdbc maintainers initially didn't like us using the postgresql url protocol jdbc:postgresql prefixed url for yugabytedb. They said that we are forcing the user to use our flavour of the driver to connect to a postgresql db also in case the app wants to connect to both postgresql and yugabytedb, which made a lot of sense.
  • I personally liked this flexibility because a yb user can use some implementation of our HostChooser for one universe or may want to modify it a bit for a different universe.
  • If the user has primary secondary pg setup itself for high availability then they may want to handle unavailability of servers as per their own logic and so on…

Also it doesn't look like that the sync is necessary. A 'null' value when lookup is done will enforce the creation inside a synch block. And a value which is modified inside the synch block is written back to the main memory and it invalidates all the cache values. So to me it looks like we can avoid the synchronization and the current code looks ok.

Comment thread pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java Outdated
GlobalHostStatusTracker.reportHostStatus(hostSpec, hostStatus);
knownStates.put(hostSpec, hostStatus);
if (!candidateHost.targetServerType.allowConnectingTo(hostStatus)) {
// Log target server type did not match
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this a todo or the comment suggests to log a message from inside registerFailure()?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Shall remove it. Got left behind accidentally. Thanks.

*/
boolean isHostDrainingConnections(String host);

boolean isInbuilt();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Javadoc for this? Why is it needed?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I just thought having it for future use would be good where in certain things the driver might not want to do if the plugin is inbuilt. BTW currently also it is being used here:
if (!hostChooser.isInbuilt()) { key.setHostChooser(hostChooser); }

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.

4 participants