001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master;
019
020import java.text.DecimalFormat;
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027import org.apache.hadoop.hbase.ServerName;
028import org.apache.hadoop.hbase.TableName;
029import org.apache.hadoop.hbase.client.RegionInfo;
030import org.apache.hadoop.hbase.favored.FavoredNodeAssignmentHelper;
031import org.apache.hadoop.hbase.favored.FavoredNodesPlan;
032import org.apache.yetus.audience.InterfaceAudience;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036/**
037 * Helper class that is used by RegionPlacementMaintainer to print information for favored nodes
038 */
039@InterfaceAudience.Private
040public class AssignmentVerificationReport {
041  private static final Logger LOG =
042    LoggerFactory.getLogger(AssignmentVerificationReport.class.getName());
043
044  private TableName tableName = null;
045  private boolean enforceLocality = false;
046  private boolean isFilledUp = false;
047
048  private int totalRegions = 0;
049  private int totalRegionServers = 0;
050  // for unassigned regions
051  private List<RegionInfo> unAssignedRegionsList = new ArrayList<>();
052
053  // For regions without valid favored nodes
054  private List<RegionInfo> regionsWithoutValidFavoredNodes = new ArrayList<>();
055
056  // For regions not running on the favored nodes
057  private List<RegionInfo> nonFavoredAssignedRegionList = new ArrayList<>();
058
059  // For regions running on the favored nodes
060  private int totalFavoredAssignments = 0;
061  private int[] favoredNodes = new int[FavoredNodeAssignmentHelper.FAVORED_NODES_NUM];
062  private float[] favoredNodesLocalitySummary =
063    new float[FavoredNodeAssignmentHelper.FAVORED_NODES_NUM];
064  private float actualLocalitySummary = 0;
065
066  // For region balancing information
067  private float avgRegionsOnRS = 0;
068  private int maxRegionsOnRS = 0;
069  private int minRegionsOnRS = Integer.MAX_VALUE;
070  private Set<ServerName> mostLoadedRSSet = new HashSet<>();
071  private Set<ServerName> leastLoadedRSSet = new HashSet<>();
072
073  private float avgDispersionScore = 0;
074  private float maxDispersionScore = 0;
075  private Set<ServerName> maxDispersionScoreServerSet = new HashSet<>();
076  private float minDispersionScore = Float.MAX_VALUE;
077  private Set<ServerName> minDispersionScoreServerSet = new HashSet<>();
078
079  private float avgDispersionNum = 0;
080  private float maxDispersionNum = 0;
081  private Set<ServerName> maxDispersionNumServerSet = new HashSet<>();
082  private float minDispersionNum = Float.MAX_VALUE;
083  private Set<ServerName> minDispersionNumServerSet = new HashSet<>();
084
085  public void fillUp(TableName tableName, SnapshotOfRegionAssignmentFromMeta snapshot,
086    Map<String, Map<String, Float>> regionLocalityMap) {
087    // Set the table name
088    this.tableName = tableName;
089
090    // Get all the regions for this table
091    List<RegionInfo> regionInfoList = snapshot.getTableToRegionMap().get(tableName);
092    // Get the total region num for the current table
093    this.totalRegions = regionInfoList.size();
094
095    // Get the existing assignment plan
096    FavoredNodesPlan favoredNodesAssignment = snapshot.getExistingAssignmentPlan();
097    // Get the region to region server mapping
098    Map<RegionInfo, ServerName> currentAssignment = snapshot.getRegionToRegionServerMap();
099    // Initialize the server to its hosing region counter map
100    Map<ServerName, Integer> serverToHostingRegionCounterMap = new HashMap<>();
101
102    Map<ServerName, Integer> primaryRSToRegionCounterMap = new HashMap<>();
103    Map<ServerName, Set<ServerName>> primaryToSecTerRSMap = new HashMap<>();
104
105    // Check the favored nodes and its locality information
106    // Also keep tracker of the most loaded and least loaded region servers
107    for (RegionInfo region : regionInfoList) {
108      try {
109        ServerName currentRS = currentAssignment.get(region);
110        // Handle unassigned regions
111        if (currentRS == null) {
112          unAssignedRegionsList.add(region);
113          continue;
114        }
115
116        // Keep updating the server to is hosting region counter map
117        Integer hostRegionCounter = serverToHostingRegionCounterMap.get(currentRS);
118        if (hostRegionCounter == null) {
119          hostRegionCounter = Integer.valueOf(0);
120        }
121        hostRegionCounter = hostRegionCounter.intValue() + 1;
122        serverToHostingRegionCounterMap.put(currentRS, hostRegionCounter);
123
124        // Get the favored nodes from the assignment plan and verify it.
125        List<ServerName> favoredNodes = favoredNodesAssignment.getFavoredNodes(region);
126        if (
127          favoredNodes == null
128            || favoredNodes.size() != FavoredNodeAssignmentHelper.FAVORED_NODES_NUM
129        ) {
130          regionsWithoutValidFavoredNodes.add(region);
131          continue;
132        }
133        // Get the primary, secondary and tertiary region server
134        ServerName primaryRS = favoredNodes.get(FavoredNodesPlan.Position.PRIMARY.ordinal());
135        ServerName secondaryRS = favoredNodes.get(FavoredNodesPlan.Position.SECONDARY.ordinal());
136        ServerName tertiaryRS = favoredNodes.get(FavoredNodesPlan.Position.TERTIARY.ordinal());
137
138        // Update the primary rs to its region set map
139        Integer regionCounter = primaryRSToRegionCounterMap.get(primaryRS);
140        if (regionCounter == null) {
141          regionCounter = Integer.valueOf(0);
142        }
143        regionCounter = regionCounter.intValue() + 1;
144        primaryRSToRegionCounterMap.put(primaryRS, regionCounter);
145
146        // Update the primary rs to secondary and tertiary rs map
147        Set<ServerName> secAndTerSet = primaryToSecTerRSMap.get(primaryRS);
148        if (secAndTerSet == null) {
149          secAndTerSet = new HashSet<>();
150        }
151        secAndTerSet.add(secondaryRS);
152        secAndTerSet.add(tertiaryRS);
153        primaryToSecTerRSMap.put(primaryRS, secAndTerSet);
154
155        // Get the position of the current region server in the favored nodes list
156        FavoredNodesPlan.Position favoredNodePosition =
157          FavoredNodesPlan.getFavoredServerPosition(favoredNodes, currentRS);
158
159        // Handle the non favored assignment.
160        if (favoredNodePosition == null) {
161          nonFavoredAssignedRegionList.add(region);
162          continue;
163        }
164        // Increase the favored nodes assignment.
165        this.favoredNodes[favoredNodePosition.ordinal()]++;
166        totalFavoredAssignments++;
167
168        // Summary the locality information for each favored nodes
169        if (regionLocalityMap != null) {
170          // Set the enforce locality as true;
171          this.enforceLocality = true;
172
173          // Get the region degree locality map
174          Map<String, Float> regionDegreeLocalityMap =
175            regionLocalityMap.get(region.getEncodedName());
176          if (regionDegreeLocalityMap == null) {
177            continue; // ignore the region which doesn't have any store files.
178          }
179
180          // Get the locality summary for each favored nodes
181          for (FavoredNodesPlan.Position p : FavoredNodesPlan.Position.values()) {
182            ServerName favoredNode = favoredNodes.get(p.ordinal());
183            // Get the locality for the current favored nodes
184            Float locality = regionDegreeLocalityMap.get(favoredNode.getHostname());
185            if (locality != null) {
186              this.favoredNodesLocalitySummary[p.ordinal()] += locality;
187            }
188          }
189
190          // Get the locality summary for the current region server
191          Float actualLocality = regionDegreeLocalityMap.get(currentRS.getHostname());
192          if (actualLocality != null) {
193            this.actualLocalitySummary += actualLocality;
194          }
195        }
196      } catch (Exception e) {
197        LOG.error("Cannot verify the region assignment for region "
198          + ((region == null) ? " null " : region.getRegionNameAsString()) + "because of " + e);
199      }
200    }
201
202    float dispersionScoreSummary = 0;
203    float dispersionNumSummary = 0;
204    // Calculate the secondary score for each primary region server
205    for (Map.Entry<ServerName, Integer> entry : primaryRSToRegionCounterMap.entrySet()) {
206      ServerName primaryRS = entry.getKey();
207      Integer regionsOnPrimary = entry.getValue();
208
209      // Process the dispersion number and score
210      float dispersionScore = 0;
211      int dispersionNum = 0;
212      if (primaryToSecTerRSMap.get(primaryRS) != null && regionsOnPrimary.intValue() != 0) {
213        dispersionNum = primaryToSecTerRSMap.get(primaryRS).size();
214        dispersionScore = dispersionNum / ((float) regionsOnPrimary.intValue() * 2);
215      }
216      // Update the max dispersion score
217      if (dispersionScore > this.maxDispersionScore) {
218        this.maxDispersionScoreServerSet.clear();
219        this.maxDispersionScoreServerSet.add(primaryRS);
220        this.maxDispersionScore = dispersionScore;
221      } else if (dispersionScore == this.maxDispersionScore) {
222        this.maxDispersionScoreServerSet.add(primaryRS);
223      }
224
225      // Update the max dispersion num
226      if (dispersionNum > this.maxDispersionNum) {
227        this.maxDispersionNumServerSet.clear();
228        this.maxDispersionNumServerSet.add(primaryRS);
229        this.maxDispersionNum = dispersionNum;
230      } else if (dispersionNum == this.maxDispersionNum) {
231        this.maxDispersionNumServerSet.add(primaryRS);
232      }
233
234      // Update the min dispersion score
235      if (dispersionScore < this.minDispersionScore) {
236        this.minDispersionScoreServerSet.clear();
237        this.minDispersionScoreServerSet.add(primaryRS);
238        this.minDispersionScore = dispersionScore;
239      } else if (dispersionScore == this.minDispersionScore) {
240        this.minDispersionScoreServerSet.add(primaryRS);
241      }
242
243      // Update the min dispersion num
244      if (dispersionNum < this.minDispersionNum) {
245        this.minDispersionNumServerSet.clear();
246        this.minDispersionNumServerSet.add(primaryRS);
247        this.minDispersionNum = dispersionNum;
248      } else if (dispersionNum == this.minDispersionNum) {
249        this.minDispersionNumServerSet.add(primaryRS);
250      }
251
252      dispersionScoreSummary += dispersionScore;
253      dispersionNumSummary += dispersionNum;
254    }
255
256    // Update the avg dispersion score
257    if (primaryRSToRegionCounterMap.keySet().size() != 0) {
258      this.avgDispersionScore =
259        dispersionScoreSummary / (float) primaryRSToRegionCounterMap.keySet().size();
260      this.avgDispersionNum =
261        dispersionNumSummary / (float) primaryRSToRegionCounterMap.keySet().size();
262    }
263
264    // Fill up the most loaded and least loaded region server information
265    for (Map.Entry<ServerName, Integer> entry : serverToHostingRegionCounterMap.entrySet()) {
266      ServerName currentRS = entry.getKey();
267      int hostRegionCounter = entry.getValue().intValue();
268
269      // Update the most loaded region server list and maxRegionsOnRS
270      if (hostRegionCounter > this.maxRegionsOnRS) {
271        maxRegionsOnRS = hostRegionCounter;
272        this.mostLoadedRSSet.clear();
273        this.mostLoadedRSSet.add(currentRS);
274      } else if (hostRegionCounter == this.maxRegionsOnRS) {
275        this.mostLoadedRSSet.add(currentRS);
276      }
277
278      // Update the least loaded region server list and minRegionsOnRS
279      if (hostRegionCounter < this.minRegionsOnRS) {
280        this.minRegionsOnRS = hostRegionCounter;
281        this.leastLoadedRSSet.clear();
282        this.leastLoadedRSSet.add(currentRS);
283      } else if (hostRegionCounter == this.minRegionsOnRS) {
284        this.leastLoadedRSSet.add(currentRS);
285      }
286    }
287
288    // and total region servers
289    this.totalRegionServers = serverToHostingRegionCounterMap.keySet().size();
290    this.avgRegionsOnRS =
291      (totalRegionServers == 0) ? 0 : (totalRegions / (float) totalRegionServers);
292    // Set the isFilledUp as true
293    isFilledUp = true;
294  }
295
296  /**
297   * Use this to project the dispersion scores
298   */
299  public void fillUpDispersion(TableName tableName, SnapshotOfRegionAssignmentFromMeta snapshot,
300    FavoredNodesPlan newPlan) {
301    // Set the table name
302    this.tableName = tableName;
303    // Get all the regions for this table
304    List<RegionInfo> regionInfoList = snapshot.getTableToRegionMap().get(tableName);
305    // Get the total region num for the current table
306    this.totalRegions = regionInfoList.size();
307    FavoredNodesPlan plan = null;
308    if (newPlan == null) {
309      plan = snapshot.getExistingAssignmentPlan();
310    } else {
311      plan = newPlan;
312    }
313    // Get the region to region server mapping
314    Map<ServerName, Integer> primaryRSToRegionCounterMap = new HashMap<>();
315    Map<ServerName, Set<ServerName>> primaryToSecTerRSMap = new HashMap<>();
316
317    // Check the favored nodes and its locality information
318    // Also keep tracker of the most loaded and least loaded region servers
319    for (RegionInfo region : regionInfoList) {
320      try {
321        // Get the favored nodes from the assignment plan and verify it.
322        List<ServerName> favoredNodes = plan.getFavoredNodes(region);
323        if (
324          favoredNodes == null
325            || favoredNodes.size() != FavoredNodeAssignmentHelper.FAVORED_NODES_NUM
326        ) {
327          regionsWithoutValidFavoredNodes.add(region);
328          continue;
329        }
330        // Get the primary, secondary and tertiary region server
331        ServerName primaryRS = favoredNodes.get(FavoredNodesPlan.Position.PRIMARY.ordinal());
332        ServerName secondaryRS = favoredNodes.get(FavoredNodesPlan.Position.SECONDARY.ordinal());
333        ServerName tertiaryRS = favoredNodes.get(FavoredNodesPlan.Position.TERTIARY.ordinal());
334
335        // Update the primary rs to its region set map
336        Integer regionCounter = primaryRSToRegionCounterMap.get(primaryRS);
337        if (regionCounter == null) {
338          regionCounter = Integer.valueOf(0);
339        }
340        regionCounter = regionCounter.intValue() + 1;
341        primaryRSToRegionCounterMap.put(primaryRS, regionCounter);
342
343        // Update the primary rs to secondary and tertiary rs map
344        Set<ServerName> secAndTerSet = primaryToSecTerRSMap.get(primaryRS);
345        if (secAndTerSet == null) {
346          secAndTerSet = new HashSet<>();
347        }
348        secAndTerSet.add(secondaryRS);
349        secAndTerSet.add(tertiaryRS);
350        primaryToSecTerRSMap.put(primaryRS, secAndTerSet);
351      } catch (Exception e) {
352        LOG.error("Cannot verify the region assignment for region "
353          + ((region == null) ? " null " : region.getRegionNameAsString()) + "because of " + e);
354      }
355    }
356    float dispersionScoreSummary = 0;
357    float dispersionNumSummary = 0;
358    // Calculate the secondary score for each primary region server
359    for (Map.Entry<ServerName, Integer> entry : primaryRSToRegionCounterMap.entrySet()) {
360      ServerName primaryRS = entry.getKey();
361      Integer regionsOnPrimary = entry.getValue();
362
363      // Process the dispersion number and score
364      float dispersionScore = 0;
365      int dispersionNum = 0;
366      if (primaryToSecTerRSMap.get(primaryRS) != null && regionsOnPrimary.intValue() != 0) {
367        dispersionNum = primaryToSecTerRSMap.get(primaryRS).size();
368        dispersionScore = dispersionNum / ((float) regionsOnPrimary.intValue() * 2);
369      }
370
371      // Update the max dispersion num
372      if (dispersionNum > this.maxDispersionNum) {
373        this.maxDispersionNumServerSet.clear();
374        this.maxDispersionNumServerSet.add(primaryRS);
375        this.maxDispersionNum = dispersionNum;
376      } else if (dispersionNum == this.maxDispersionNum) {
377        this.maxDispersionNumServerSet.add(primaryRS);
378      }
379
380      // Update the min dispersion score
381      if (dispersionScore < this.minDispersionScore) {
382        this.minDispersionScoreServerSet.clear();
383        this.minDispersionScoreServerSet.add(primaryRS);
384        this.minDispersionScore = dispersionScore;
385      } else if (dispersionScore == this.minDispersionScore) {
386        this.minDispersionScoreServerSet.add(primaryRS);
387      }
388
389      // Update the min dispersion num
390      if (dispersionNum < this.minDispersionNum) {
391        this.minDispersionNumServerSet.clear();
392        this.minDispersionNumServerSet.add(primaryRS);
393        this.minDispersionNum = dispersionNum;
394      } else if (dispersionNum == this.minDispersionNum) {
395        this.minDispersionNumServerSet.add(primaryRS);
396      }
397
398      dispersionScoreSummary += dispersionScore;
399      dispersionNumSummary += dispersionNum;
400    }
401
402    // Update the avg dispersion score
403    if (primaryRSToRegionCounterMap.keySet().size() != 0) {
404      this.avgDispersionScore =
405        dispersionScoreSummary / (float) primaryRSToRegionCounterMap.keySet().size();
406      this.avgDispersionNum =
407        dispersionNumSummary / (float) primaryRSToRegionCounterMap.keySet().size();
408    }
409  }
410
411  /**
412   * Return a list which contains 3 elements: average dispersion score, max dispersion score and min
413   * dispersion score as first, second and third elements, respectively.
414   */
415  public List<Float> getDispersionInformation() {
416    List<Float> dispersion = new ArrayList<>();
417    dispersion.add(avgDispersionScore);
418    dispersion.add(maxDispersionScore);
419    dispersion.add(minDispersionScore);
420    return dispersion;
421  }
422
423  public void print(boolean isDetailMode) {
424    if (!isFilledUp) {
425      System.err.println("[Error] Region assignment verification report" + "hasn't been filled up");
426    }
427    DecimalFormat df = new java.text.DecimalFormat("#.##");
428
429    // Print some basic information
430    System.out.println("Region Assignment Verification for Table: " + tableName
431      + "\n\tTotal regions : " + totalRegions);
432
433    // Print the number of regions on each kinds of the favored nodes
434    System.out.println("\tTotal regions on favored nodes " + totalFavoredAssignments);
435    for (FavoredNodesPlan.Position p : FavoredNodesPlan.Position.values()) {
436      System.out.println(
437        "\t\tTotal regions on " + p.toString() + " region servers: " + favoredNodes[p.ordinal()]);
438    }
439    // Print the number of regions in each kinds of invalid assignment
440    System.out.println("\tTotal unassigned regions: " + unAssignedRegionsList.size());
441    if (isDetailMode) {
442      for (RegionInfo region : unAssignedRegionsList) {
443        System.out.println("\t\t" + region.getRegionNameAsString());
444      }
445    }
446
447    System.out
448      .println("\tTotal regions NOT on favored nodes: " + nonFavoredAssignedRegionList.size());
449    if (isDetailMode) {
450      for (RegionInfo region : nonFavoredAssignedRegionList) {
451        System.out.println("\t\t" + region.getRegionNameAsString());
452      }
453    }
454
455    System.out
456      .println("\tTotal regions without favored nodes: " + regionsWithoutValidFavoredNodes.size());
457    if (isDetailMode) {
458      for (RegionInfo region : regionsWithoutValidFavoredNodes) {
459        System.out.println("\t\t" + region.getRegionNameAsString());
460      }
461    }
462
463    // Print the locality information if enabled
464    if (this.enforceLocality && totalRegions != 0) {
465      // Print the actual locality for this table
466      float actualLocality = 100 * this.actualLocalitySummary / (float) totalRegions;
467      System.out.println("\n\tThe actual avg locality is " + df.format(actualLocality) + " %");
468
469      // Print the expected locality if regions are placed on the each kinds of
470      // favored nodes
471      for (FavoredNodesPlan.Position p : FavoredNodesPlan.Position.values()) {
472        float avgLocality = 100 * (favoredNodesLocalitySummary[p.ordinal()] / (float) totalRegions);
473        System.out.println("\t\tThe expected avg locality if all regions" + " on the "
474          + p.toString() + " region servers: " + df.format(avgLocality) + " %");
475      }
476    }
477
478    // Print the region balancing information
479    System.out.println("\n\tTotal hosting region servers: " + totalRegionServers);
480    // Print the region balance information
481    if (totalRegionServers != 0) {
482      System.out.println("\tAvg dispersion num: " + df.format(avgDispersionNum)
483        + " hosts;\tMax dispersion num: " + df.format(maxDispersionNum)
484        + " hosts;\tMin dispersion num: " + df.format(minDispersionNum) + " hosts;");
485
486      System.out.println("\t\tThe number of the region servers with the max" + " dispersion num: "
487        + this.maxDispersionNumServerSet.size());
488      if (isDetailMode) {
489        printHServerAddressSet(maxDispersionNumServerSet);
490      }
491
492      System.out.println("\t\tThe number of the region servers with the min" + " dispersion num: "
493        + this.minDispersionNumServerSet.size());
494      if (isDetailMode) {
495        printHServerAddressSet(maxDispersionNumServerSet);
496      }
497
498      System.out.println("\tAvg dispersion score: " + df.format(avgDispersionScore)
499        + ";\tMax dispersion score: " + df.format(maxDispersionScore) + ";\tMin dispersion score: "
500        + df.format(minDispersionScore) + ";");
501
502      System.out.println("\t\tThe number of the region servers with the max" + " dispersion score: "
503        + this.maxDispersionScoreServerSet.size());
504      if (isDetailMode) {
505        printHServerAddressSet(maxDispersionScoreServerSet);
506      }
507
508      System.out.println("\t\tThe number of the region servers with the min" + " dispersion score: "
509        + this.minDispersionScoreServerSet.size());
510      if (isDetailMode) {
511        printHServerAddressSet(minDispersionScoreServerSet);
512      }
513
514      System.out.println("\tAvg regions/region server: " + df.format(avgRegionsOnRS)
515        + ";\tMax regions/region server: " + maxRegionsOnRS + ";\tMin regions/region server: "
516        + minRegionsOnRS + ";");
517
518      // Print the details about the most loaded region servers
519      System.out
520        .println("\t\tThe number of the most loaded region servers: " + mostLoadedRSSet.size());
521      if (isDetailMode) {
522        printHServerAddressSet(mostLoadedRSSet);
523      }
524
525      // Print the details about the least loaded region servers
526      System.out
527        .println("\t\tThe number of the least loaded region servers: " + leastLoadedRSSet.size());
528      if (isDetailMode) {
529        printHServerAddressSet(leastLoadedRSSet);
530      }
531    }
532    System.out.println("==============================");
533  }
534
535  /**
536   * Return the unassigned regions
537   * @return unassigned regions
538   */
539  List<RegionInfo> getUnassignedRegions() {
540    return unAssignedRegionsList;
541  }
542
543  /**
544   * Return the regions without favored nodes
545   * @return regions without favored nodes
546   */
547  List<RegionInfo> getRegionsWithoutValidFavoredNodes() {
548    return regionsWithoutValidFavoredNodes;
549  }
550
551  /**
552   * Return the regions not assigned to its favored nodes
553   * @return regions not assigned to its favored nodes
554   */
555  List<RegionInfo> getNonFavoredAssignedRegions() {
556    return nonFavoredAssignedRegionList;
557  }
558
559  /**
560   * Return the number of regions assigned to their favored nodes
561   * @return number of regions assigned to their favored nodes
562   */
563  int getTotalFavoredAssignments() {
564    return totalFavoredAssignments;
565  }
566
567  /**
568   * Return the number of regions based on the position (primary/secondary/ tertiary) assigned to
569   * their favored nodes
570   * @return the number of regions
571   */
572  int getNumRegionsOnFavoredNodeByPosition(FavoredNodesPlan.Position position) {
573    return favoredNodes[position.ordinal()];
574  }
575
576  private void printHServerAddressSet(Set<ServerName> serverSet) {
577    if (serverSet == null) {
578      return;
579    }
580    int i = 0;
581    for (ServerName addr : serverSet) {
582      if (i++ % 3 == 0) {
583        System.out.print("\n\t\t\t");
584      }
585      System.out.print(addr.getAddress() + " ; ");
586    }
587    System.out.println("\n");
588  }
589}