Conversation
| * | ||
| * @return true or false. | ||
| */ | ||
| boolean isHostDrainingConnections(String host); |
There was a problem hiding this comment.
minor: maybe this can just be called isValid(String host) to make it easy to understand what it maps to.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
does instance need to be initialized up front? Can it be initialized in a lazy way?
Also does instance need to be marked volatile?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| GlobalHostStatusTracker.reportHostStatus(hostSpec, hostStatus); | ||
| knownStates.put(hostSpec, hostStatus); | ||
| if (!candidateHost.targetServerType.allowConnectingTo(hostStatus)) { | ||
| // Log target server type did not match |
There was a problem hiding this comment.
Is this a todo or the comment suggests to log a message from inside registerFailure()?
There was a problem hiding this comment.
Shall remove it. Got left behind accidentally. Thanks.
| */ | ||
| boolean isHostDrainingConnections(String host); | ||
|
|
||
| boolean isInbuilt(); |
There was a problem hiding this comment.
Javadoc for this? Why is it needed?
There was a problem hiding this comment.
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); }
All Submissions:
New Feature Submissions:
./gradlew styleCheckpass ?Changes to Existing Features:
Github Issue with details.