-
Notifications
You must be signed in to change notification settings - Fork 109
Refactor RoutingManager #741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
* request object. Default implementation comes here. | ||
*/ | ||
public abstract class RoutingManager | ||
public interface RoutingManager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have java doc for this and method as this becomes our primary interface for all RoutingManager implementations. ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good idea to have an interface to enforce the rules.
CacheBuilder.newBuilder() | ||
.maximumSize(10000) | ||
.expireAfterAccess(30, TimeUnit.MINUTES) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you can extract this into separate builder and reuse 3 time while building cache.
for example
private final CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterAccess(30, TimeUnit.MINUTES);
queryIdBackendCache = builder.build(
new CacheLoader<>()
{
@Override
public String load(String queryId)
{
return findBackendForUnknownQueryId(queryId);
}
});
queryIdRoutingGroupCache = builder.build(
new CacheLoader<>()
{
@Override
public String load(String queryId)
{
return findRoutingGroupForUnknownQueryId(queryId);
}
});
queryIdExternalUrlCache = builder.build(
new CacheLoader<>()
{
@Override
public String load(String queryId)
{
return findExternalUrlForUnknownQueryId(queryId);
}
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
TrinoStatus status = backendToStatus.get(backendId); | ||
if (status == null) { | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TrinoStatus status = backendToStatus.get(backendId); | |
if (status == null) { | |
return true; | |
} | |
TrinoStatus status = backendToStatus.getOrDefault(backendId, TrinoStatus.UNKNOWN); | |
return status != TrinoStatus.HEALTHY; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
public ProxyBackendConfiguration provideDefaultBackendConfiguration(String user) | ||
{ | ||
List<ProxyBackendConfiguration> backends = gatewayBackendManager.getActiveDefaultBackends(); | ||
backends.removeIf(backend -> isBackendNotHealthy(backend.getName())); | ||
return selectBackend(backends, user).orElseThrow(() -> new IllegalStateException("Number of active backends found zero")); | ||
} | ||
|
||
/** | ||
* Performs routing to a given cluster group. This falls back to a default backend, if no scheduled | ||
* backend is found. | ||
*/ | ||
@Override | ||
public ProxyBackendConfiguration provideBackendConfiguration(String routingGroup, String user) | ||
{ | ||
List<ProxyBackendConfiguration> backends = gatewayBackendManager.getActiveBackends(routingGroup); | ||
backends.removeIf(backend -> isBackendNotHealthy(backend.getName())); | ||
return selectBackend(backends, user).orElseGet(() -> provideDefaultBackendConfiguration(user)); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are removing the unhealthy backends from candidate list. You can use lambda to filter out unwanted entries. It will simplify the predicate function that identifies healthy backends.
For example:
public ProxyBackendConfiguration provideDefaultBackendConfiguration(String user) | |
{ | |
List<ProxyBackendConfiguration> backends = gatewayBackendManager.getActiveDefaultBackends(); | |
backends.removeIf(backend -> isBackendNotHealthy(backend.getName())); | |
return selectBackend(backends, user).orElseThrow(() -> new IllegalStateException("Number of active backends found zero")); | |
} | |
/** | |
* Performs routing to a given cluster group. This falls back to a default backend, if no scheduled | |
* backend is found. | |
*/ | |
@Override | |
public ProxyBackendConfiguration provideBackendConfiguration(String routingGroup, String user) | |
{ | |
List<ProxyBackendConfiguration> backends = gatewayBackendManager.getActiveBackends(routingGroup); | |
backends.removeIf(backend -> isBackendNotHealthy(backend.getName())); | |
return selectBackend(backends, user).orElseGet(() -> provideDefaultBackendConfiguration(user)); | |
} | |
public ProxyBackendConfiguration provideDefaultBackendConfiguration(String user) { | |
var backends = gatewayBackendManager.getActiveDefaultBackends() | |
.stream() | |
.filter(backend -> isBackendHealthy(backend.getName())) | |
.toList(); | |
return selectBackend(backends, user).orElseThrow(() -> new IllegalStateException("Number of active backends found zero")); | |
} | |
@Override | |
public ProxyBackendConfiguration provideBackendConfiguration(String routingGroup, String user) { | |
var backends = gatewayBackendManager.getActiveBackends(routingGroup) | |
.stream() | |
.filter(backend -> isBackendHealthy(backend.getName())) | |
.toList(); | |
return selectBackend(backends, user).orElseGet(() -> provideDefaultBackendConfiguration(user)); | |
} | |
private boolean isBackendHealthy(String backendId) { | |
TrinoStatus status = backendToStatus.getOrDefault(backendId, TrinoStatus.UNKNOWN); | |
return status == TrinoStatus.HEALTHY; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* This class performs health check, stats counts for each backend and provides a backend given | ||
* request object. Default implementation comes here. | ||
*/ | ||
public abstract class BaseRoutingManager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion IMO, for ease of reading you may want to group methods by visibility and order them.
1/ constructor
2/ abstract methods
3/ public method
4/ protected - package
5/ private
I see the abstract methods as an interface for subclasses so it is easier to discover them by future maintainers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
return routingGroup; | ||
} | ||
|
||
protected void updateBackEndHealth(List<ClusterStats> stats) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we delete this method ?
It get confusing as we have 3 methods that update the state in cache.
You move this method logic into the interface method public void updateClusterStats
. This would simplify and we avoid method overloading of interface method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if (entry.getValue().isDone()) { | ||
int responseCode = entry.getValue().get(); | ||
if (responseCode == 200) { | ||
log.info("Found query [%s] on backend [%s]", queryId, entry.getKey()); | ||
setBackendForQueryId(queryId, entry.getKey()); | ||
return entry.getKey(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: Curious, Why did you add condition check for future.isDone()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of these code are existing - https://github.com/trinodb/trino-gateway/blob/main/gateway-ha/src/main/java/io/trino/gateway/ha/router/RoutingManager.java
I changed the file name from RoutingManager to BaseRoutingManager and created interface file with name RoutingManager. So git instead of marking it as file name change, assumed everything in the file as new changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we separate out this PR into 2 different ones.
(1) Just fix the bug and add the minimal interface needed, and the fixed tests.
(2) Rest of the refactoring
8853b9b
to
7cdbcbf
Compare
Tried splitting it into 2 commits but feels bit complicated as the refactoring kind of took care of the bug. |
I'm concerned that having a default implementation in the A better approach would be to move the default logic into its own class, say On a related note, could you detail what new runtime tests are being added to catch these kinds of integration failures? |
composition might makes things very complicated. Interface with an abstract base implementation is a common pattern which should be ok. For instance in Trino there is a TrinoCatalog interface with AbtsractTrinoCatalog implementation that has common methods implemented. The primary problem I see is the interface is bloated and can further be made lean. There should only be 4 methods -
Maintaining 3 separate cache and separately setting every cache makes no sense as they all point to same data. One cache with all data together should be enough and exposing just one method to get and set the backEnd configuration. This should make things less confusing to be overridden and implemented. If this new lean interface sounds good I can make the changes. |
Description
This commit addresses a number of items
Additional context and related issues
Release notes
( ) This is not user-visible or is docs only, and no release notes are required.
( ) Release notes are required, with the following suggested text:
* Fix some things.