/* * Copyright 2015 Kakao Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.kakao.hbase.manager.command; import com.google.common.annotations.VisibleForTesting; import com.kakao.hbase.common.Args; import com.kakao.hbase.common.Constant; import com.kakao.hbase.common.util.Util; import com.kakao.hbase.specific.CommandAdapter; import com.kakao.hbase.specific.RegionLoadAdapter; import com.kakao.hbase.specific.RegionLoadDelegator; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.NotServingRegionException; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.util.StringUtils; import java.io.IOException; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; public class MC implements Command { private final HBaseAdmin admin; private final Args args; private final AtomicInteger mcCounter = new AtomicInteger(); private final Map<byte[], String> regionTableMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); private final Map<byte[], Integer> regionSizeMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); private final Map<byte[], Float> regionLocalityMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); private final Map<byte[], String> regionRSMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); private Map<String, NavigableMap<HRegionInfo, ServerName>> regionLocations = new HashMap<>(); // regions or tables private Set<byte[]> targets = null; private boolean tableLevel = false; public MC(HBaseAdmin admin, Args args) { if (args.getOptionSet().nonOptionArguments().size() != 2) { throw new IllegalArgumentException(Args.INVALID_ARGUMENTS); } this.admin = admin; this.args = args; } @SuppressWarnings("unused") public static String usage() { return "Run major compaction on tables.\n" + "usage: " + MC.class.getSimpleName().toLowerCase() + " <zookeeper quorum> <table regex> [options]\n" + " options:\n" + " --" + Args.OPTION_WAIT_UNTIL_FINISH + ": Wait until all of MCs are finished.\n" + " --" + Args.OPTION_INTERACTIVE + ": Ask whether to proceed MC for each region or table.\n" + " --" + Args.OPTION_REGION_SERVER + "=<RS regex>: Compact the regions on these RSs.\n" + " --" + Args.OPTION_CF + "=<CF>: Compact the regions of this CF.\n" + " --" + Args.OPTION_LOCALITY_THRESHOLD + "=<threshold%>: Compact only if the data locality of the region is lower than this threshold.\n" + Args.commonUsage(); } @VisibleForTesting int getMcCounter() { return mcCounter.get(); } @VisibleForTesting Set<byte[]> getTargets() { return targets; } @VisibleForTesting boolean isTableLevel() { return tableLevel; } @Override public void run() throws Exception { targets = Collections.newSetFromMap(new TreeMap<byte[], Boolean>(Bytes.BYTES_COMPARATOR)); tableLevel = false; // or region level Set<String> tables = Args.tables(args, admin); assert tables != null; for (String table : tables) { if (args.has(Args.OPTION_REGION_SERVER) || args.has(Args.OPTION_LOCALITY_THRESHOLD)) { // MC at region level tableLevel = false; if (args.has(Args.OPTION_REGION_SERVER)) { filterWithRsAndLocality(targets, table); } else { if (args.has(Args.OPTION_LOCALITY_THRESHOLD)) { filterWithLocalityOnly(targets, table); } } } else { // MC at table level tableLevel = true; targets.add(table.getBytes()); } } // todo check compaction queue before running if (tableLevel) { System.out.println(targets.size() + " tables will be compacted."); } else { System.out.println(targets.size() + " regions will be compacted."); } if (targets.size() == 0) return; if (!args.isForceProceed() && !Util.askProceed()) return; mc(tableLevel, targets); if (mcCounter.get() > 0) waitUntilFinish(tables); } private void mc(boolean tableLevel, Set<byte[]> targets) throws InterruptedException, IOException { int i = 1; for (byte[] tableOrRegion : targets) { if (args.has(Args.OPTION_CF)) { String cf = (String) args.valueOf(Args.OPTION_CF); try { System.out.print(i++ + "/" + targets.size() + " - Major compaction on " + cf + " CF of " + (tableLevel ? "table " : "region ") + Bytes.toStringBinary(tableOrRegion) + (tableLevel ? "" : " - " + getRegionInfo(tableOrRegion))); if (!Util.askProceedInteractively(args, true)) continue; admin.majorCompact(tableOrRegion, cf.getBytes()); mcCounter.getAndIncrement(); } catch (IOException e) { String message = "column family " + cf + " does not exist"; if (e.getMessage().contains(message)) { System.out.println("WARNING - " + message + " on " + Bytes.toStringBinary(tableOrRegion)); } else { throw e; } } } else { System.out.print(i++ + "/" + targets.size() + " - Major compaction on " + (tableLevel ? "table " : "region ") + Bytes.toStringBinary(tableOrRegion) + (tableLevel ? "" : " - " + getRegionInfo(tableOrRegion))); if (!Util.askProceedInteractively(args, true)) continue; try { admin.majorCompact(tableOrRegion); } catch (NotServingRegionException ignore) { } mcCounter.getAndIncrement(); } } } private String getRegionInfo(byte[] regionName) { return "Table: " + regionTableMap.get(regionName) + ", RS: " + regionRSMap.get(regionName) + ", Locality: " + (regionLocalityMap.get(regionName) == null ? "null" : StringUtils.formatPercent(regionLocalityMap.get(regionName), 2)) + ", SizeMB: " + regionSizeMap.get(regionName); } private void waitUntilFinish(Set<String> tables) throws IOException, InterruptedException { if (args.has(Args.OPTION_WAIT_UNTIL_FINISH)) { long sleepDuration = args.has(Args.OPTION_TEST) ? Constant.SMALL_WAIT_INTERVAL_MS : Constant.LARGE_WAIT_INTERVAL_MS; long timestamp = System.currentTimeMillis(); System.out.print("Running "); while (true) { // sleep first for (int j = 0; j < 6; j++) { System.out.print("."); Thread.sleep(sleepDuration / 6); } int i = 0; for (String table : tables) { if (!CommandAdapter.isMajorCompacting(args, admin, table)) { i++; } } if (i == tables.size()) break; } System.out.println(); System.out.println("All of MCs are finished."); System.out.println("Duration: " + (System.currentTimeMillis() - timestamp) / 1000 + " secs"); } } private void filterWithLocalityOnly(Set<byte[]> targets, String table) throws IOException { long startTimestamp = System.currentTimeMillis(); Util.printVerboseMessage(args, Util.getMethodName() + " - start"); Map<byte[], HRegionInfo> regionMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); for (Map.Entry<HRegionInfo, ServerName> entry : getRegionLocations(table).entrySet()) { byte[] regionName = entry.getKey().getRegionName(); String serverName = entry.getValue().getHostname(); regionMap.put(entry.getKey().getRegionName(), entry.getKey()); regionTableMap.put(regionName, table); regionRSMap.put(regionName, serverName); } filterWithDataLocality(targets, regionMap); Util.printVerboseMessage(args, Util.getMethodName() + " - end", startTimestamp); } private void filterWithRsAndLocality(Set<byte[]> targets, String table) throws IOException { long startTimestamp = System.currentTimeMillis(); Util.printVerboseMessage(args, Util.getMethodName() + " - start"); Map<byte[], HRegionInfo> regionMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); String regex = (String) args.valueOf(Args.OPTION_REGION_SERVER); for (Map.Entry<HRegionInfo, ServerName> entry : getRegionLocations(table).entrySet()) { String serverName = entry.getValue().getHostname() + "," + entry.getValue().getPort(); if (serverName.matches(regex)) { regionMap.put(entry.getKey().getRegionName(), entry.getKey()); byte[] regionName = entry.getKey().getRegionName(); targets.add(regionName); regionTableMap.put(regionName, table); regionRSMap.put(regionName, serverName); } } filterWithDataLocality(targets, regionMap); Util.printVerboseMessage(args, Util.getMethodName() + " - end", startTimestamp); } private NavigableMap<HRegionInfo, ServerName> getRegionLocations(String table) throws IOException { long startTimestamp = System.currentTimeMillis(); Util.printVerboseMessage(args, Util.getMethodName() + " - start"); NavigableMap<HRegionInfo, ServerName> result = regionLocations.get(table); if (result == null) { try (HTable htable = new HTable(admin.getConfiguration(), table)) { result = htable.getRegionLocations(); regionLocations.put(table, result); } } Util.printVerboseMessage(args, Util.getMethodName() + " - end", startTimestamp); return result; } private void filterWithDataLocality(Set<byte[]> targets, Map<byte[], HRegionInfo> regionMap) throws IOException { long startTimestamp = System.currentTimeMillis(); Util.printVerboseMessage(args, Util.getMethodName() + " - start"); final Double dataLocalityThreshold; if (args.has(Args.OPTION_LOCALITY_THRESHOLD)) { dataLocalityThreshold = (Double) args.valueOf(Args.OPTION_LOCALITY_THRESHOLD); if (dataLocalityThreshold < 1 || dataLocalityThreshold > 100) throw new IllegalArgumentException("Invalid data locality"); } else { dataLocalityThreshold = null; } RegionLoadAdapter regionLoadAdapter = new RegionLoadAdapter(admin, regionMap, args); for (HRegionInfo regionInfo : regionMap.values()) { RegionLoadDelegator regionLoad = regionLoadAdapter.get(regionInfo); if (regionLoad == null) continue; try { byte[] regionName = regionInfo.getRegionName(); regionSizeMap.put(regionName, regionLoad.getStorefileSizeMB()); if (dataLocalityThreshold == null) { targets.add(regionName); } else { float dataLocality = regionLoad.getDataLocality(); regionLocalityMap.put(regionName, dataLocality); if (dataLocality * 100 < dataLocalityThreshold) targets.add(regionName); } } catch (IllegalStateException e) { if (e.getMessage().contains("not implemented")) { throw new IllegalStateException("Option " + Args.OPTION_LOCALITY_THRESHOLD + " is not supported in this HBase version."); } else { throw e; } } } Util.printVerboseMessage(args, Util.getMethodName() + " - end", startTimestamp); } }