1
1
package com .lyft .data .gateway .ha .router ;
2
2
3
+ import com .google .common .base .Strings ;
3
4
import com .lyft .data .gateway .ha .config .ProxyBackendConfiguration ;
4
5
import java .util .Collection ;
5
6
import java .util .Collections ;
14
15
import java .util .TreeMap ;
15
16
import java .util .concurrent .ConcurrentHashMap ;
16
17
import java .util .stream .Collectors ;
18
+
17
19
import lombok .extern .slf4j .Slf4j ;
18
20
19
21
/**
@@ -35,6 +37,8 @@ public class PrestoQueueLengthRoutingTable extends HaRoutingManager {
35
37
private ConcurrentHashMap <String , Integer > routingGroupWeightSum ;
36
38
private ConcurrentHashMap <String , ConcurrentHashMap <String , Integer >> clusterQueueLengthMap ;
37
39
40
+ private ConcurrentHashMap <String , ConcurrentHashMap <String , Integer >> userClusterQueueLengthMap ;
41
+
38
42
private Map <String , TreeMap <Integer , String >> weightedDistributionRouting ;
39
43
40
44
/**
@@ -47,6 +51,7 @@ public PrestoQueueLengthRoutingTable(GatewayBackendManager gatewayBackendManager
47
51
routingGroupWeightSum = new ConcurrentHashMap <String , Integer >();
48
52
clusterQueueLengthMap = new ConcurrentHashMap <String , ConcurrentHashMap <String , Integer >>();
49
53
weightedDistributionRouting = new HashMap <String , TreeMap <Integer , String >>();
54
+ userClusterQueueLengthMap = new ConcurrentHashMap <>();
50
55
}
51
56
52
57
/**
@@ -182,7 +187,7 @@ private void computeWeightsBasedOnQueueLength(ConcurrentHashMap<String,
182
187
/**
183
188
* Update the Routing Table only if a previously known backend has been deactivated.
184
189
* Newly added backends are handled through
185
- * {@link PrestoQueueLengthRoutingTable#updateRoutingTable(Map, Map)}
190
+ * {@link PrestoQueueLengthRoutingTable#updateRoutingTable(Map, Map, Map )}
186
191
* updateRoutingTable}
187
192
*/
188
193
public void updateRoutingTable (String routingGroup , Set <String > backends ) {
@@ -212,11 +217,21 @@ public void updateRoutingTable(String routingGroup, Set<String> backends) {
212
217
* Update routing Table with new Queue Lengths.
213
218
*/
214
219
public void updateRoutingTable (Map <String , Map <String , Integer >> updatedQueueLengthMap ,
215
- Map <String , Map <String , Integer >> updatedRunningLengthMap ) {
220
+ Map <String , Map <String , Integer >> updatedRunningLengthMap ,
221
+ Map <String , Map <String , Integer >> updatedUserQueueLengthMap ) {
216
222
synchronized (lockObject ) {
217
223
log .debug ("Update Routing table with new cluster queue lengths : [{}]" ,
218
224
updatedQueueLengthMap .toString ());
219
225
clusterQueueLengthMap .clear ();
226
+ userClusterQueueLengthMap .clear ();
227
+
228
+ if (updatedUserQueueLengthMap != null ) {
229
+ for (String user : updatedUserQueueLengthMap .keySet ()) {
230
+ ConcurrentHashMap <String , Integer > clusterQueueMap =
231
+ new ConcurrentHashMap <>(updatedUserQueueLengthMap .get (user ));
232
+ userClusterQueueLengthMap .put (user , clusterQueueMap );
233
+ }
234
+ }
220
235
221
236
for (String grp : updatedQueueLengthMap .keySet ()) {
222
237
if (grp == null ) {
@@ -227,7 +242,8 @@ public void updateRoutingTable(Map<String, Map<String, Integer>> updatedQueueLen
227
242
int maxQueueLen = Collections .max (updatedQueueLengthMap .get (grp ).values ());
228
243
int minQueueLen = Collections .min (updatedQueueLengthMap .get (grp ).values ());
229
244
230
- if (minQueueLen == maxQueueLen && updatedQueueLengthMap .get (grp ).size () > 1 ) {
245
+ if (minQueueLen == maxQueueLen && updatedQueueLengthMap .get (grp ).size () > 1
246
+ && updatedRunningLengthMap .containsKey (grp )) {
231
247
log .info ("Queue lengths equal: {} for all clusters in the group {}."
232
248
+ " Falling back to Running Counts : {}" , maxQueueLen , grp ,
233
249
updatedRunningLengthMap .get (grp ));
@@ -268,9 +284,40 @@ public Map<String, Integer> getInternalClusterQueueLength(String routingGroup) {
268
284
}
269
285
270
286
/**
271
- * Looks up the closest weight to random number generated for a given routing group .
287
+ * Find the cluster with least user queue else fall back to overall cluster weight based routing .
272
288
*/
273
- public String getEligibleBackEnd (String routingGroup ) {
289
+ public String getEligibleBackEnd (String routingGroup , String user ) {
290
+
291
+ // Route to the least queued backend for the user out of all backends for that group
292
+ if (!Strings .isNullOrEmpty (user )) {
293
+ Map <String , Integer > clusterQueueCountForUser = userClusterQueueLengthMap .get (user );
294
+
295
+ if (clusterQueueCountForUser != null && !clusterQueueCountForUser .isEmpty ()) {
296
+ Set <String > backends = clusterQueueLengthMap .get (routingGroup ).keySet ();
297
+ String leastQueuedCluster = null ;
298
+ Integer minQueueCount = Integer .MAX_VALUE ;
299
+ Integer maxQueueCount = Integer .MIN_VALUE ;
300
+ for (String b : backends ) {
301
+ // If missing, we assume no queued queries for the user on that cluster.
302
+ Integer queueCount = clusterQueueCountForUser .getOrDefault (b , 0 );
303
+
304
+ if (queueCount < minQueueCount ) {
305
+ leastQueuedCluster = b ;
306
+ minQueueCount = queueCount ;
307
+ }
308
+ if (queueCount > maxQueueCount ) {
309
+ maxQueueCount = queueCount ;
310
+ }
311
+ }
312
+ // If all clusters have the same queue count, then fallback to the older weighted logic.
313
+ if (!Strings .isNullOrEmpty (leastQueuedCluster ) && minQueueCount != maxQueueCount ) {
314
+ log .debug ("{} routing to:{}. userQueueCount:{}" , user , leastQueuedCluster , minQueueCount );
315
+
316
+ return leastQueuedCluster ;
317
+ }
318
+ }
319
+ }
320
+ // Looks up the closest weight to random number generated for a given routing group.
274
321
if (routingGroupWeightSum .containsKey (routingGroup )
275
322
&& weightedDistributionRouting .containsKey (routingGroup )) {
276
323
int rnd = RANDOM .nextInt (routingGroupWeightSum .get (routingGroup ));
@@ -285,20 +332,20 @@ public String getEligibleBackEnd(String routingGroup) {
285
332
* backend is found.
286
333
*/
287
334
@ Override
288
- public String provideBackendForRoutingGroup (String routingGroup ) {
335
+ public String provideBackendForRoutingGroup (String routingGroup , String user ) {
289
336
List <ProxyBackendConfiguration > backends =
290
337
getGatewayBackendManager ().getActiveBackends (routingGroup );
291
338
292
339
if (backends .isEmpty ()) {
293
- return provideAdhocBackend ();
340
+ return provideAdhocBackend (user );
294
341
}
295
342
Map <String , String > proxyMap = new HashMap <>();
296
343
for (ProxyBackendConfiguration backend : backends ) {
297
344
proxyMap .put (backend .getName (), backend .getProxyTo ());
298
345
}
299
346
300
347
updateRoutingTable (routingGroup , proxyMap .keySet ());
301
- String clusterId = getEligibleBackEnd (routingGroup );
348
+ String clusterId = getEligibleBackEnd (routingGroup , user );
302
349
log .debug ("Routing to eligible backend : [{}] for routing group: [{}]" ,
303
350
clusterId , routingGroup );
304
351
@@ -318,7 +365,7 @@ public String provideBackendForRoutingGroup(String routingGroup) {
318
365
* <p>d.
319
366
*/
320
367
@ Override
321
- public String provideAdhocBackend () {
368
+ public String provideAdhocBackend (String user ) {
322
369
Map <String , String > proxyMap = new HashMap <>();
323
370
List <ProxyBackendConfiguration > backends = getGatewayBackendManager ().getActiveAdhocBackends ();
324
371
if (backends .size () == 0 ) {
@@ -331,7 +378,7 @@ public String provideAdhocBackend() {
331
378
332
379
updateRoutingTable ("adhoc" , proxyMap .keySet ());
333
380
334
- String clusterId = getEligibleBackEnd ("adhoc" );
381
+ String clusterId = getEligibleBackEnd ("adhoc" , user );
335
382
log .debug ("Routing to eligible backend : " + clusterId + " for routing group: adhoc" );
336
383
if (clusterId != null ) {
337
384
return proxyMap .get (clusterId );
0 commit comments