/* * Copyright 2013 NGDATA nv * * 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 org.lilyproject.tools.generatesplitkeys; import com.google.common.base.Strings; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.hadoop.hbase.util.Bytes; import org.lilyproject.cli.BaseCliTool; import org.lilyproject.cli.OptionUtil; import org.lilyproject.util.Version; import java.util.Arrays; import java.util.List; /** * A tool to generate split keys for use in conf/general/tables.xml. */ public class GenerateSplitKeys extends BaseCliTool { private Option uuidSplitsOption; private Option uuidSplitsLengthOption; private Option userIdSplitsOption; private Option userIdSplitsLengthOption; private Option noPrefixOption; public static void main(String[] args) { new GenerateSplitKeys().start(args); } @Override protected String getCmdName() { return "lily-generate-split-keys"; } @Override protected String getVersion() { return Version.readVersion("org.lilyproject", "lily-generate-split-keys"); } @Override @SuppressWarnings("static-access") public List<Option> getOptions() { List<Option> options = super.getOptions(); uuidSplitsOption = OptionBuilder .withArgName("count") .hasArg() .withDescription("Generate this amount of UUID splits") .withLongOpt("uuid-splits") .create("us"); options.add(uuidSplitsOption); uuidSplitsLengthOption = OptionBuilder .withArgName("length") .hasArg() .withDescription("Length of UUID split key in bytes") .withLongOpt("uuid-splits-length") .create("usl"); options.add(uuidSplitsLengthOption); userIdSplitsOption = OptionBuilder .withArgName("count") .hasArg() .withDescription("Generate this amount of USER ID splits") .withLongOpt("userid-splits") .create("is"); options.add(userIdSplitsOption); userIdSplitsLengthOption = OptionBuilder .withArgName("length") .hasArg() .withDescription("Length of USER split key in bytes") .withLongOpt("userid-splits-length") .create("isl"); options.add(userIdSplitsLengthOption); noPrefixOption = OptionBuilder .withDescription("Do not include the type-prefix byte") .withLongOpt("no-prefix") .create("np"); options.add(noPrefixOption); return options; } @Override public int run(CommandLine cmd) throws Exception { int result = super.run(cmd); if (result != 0) { return result; } int uuidSplits = OptionUtil.getIntOption(cmd, uuidSplitsOption, -1); int uuidSplitsLength = OptionUtil.getIntOption(cmd, uuidSplitsLengthOption, 3); int userIdSplits = OptionUtil.getIntOption(cmd, userIdSplitsOption, -1); int userIdSplitsLength = OptionUtil.getIntOption(cmd, userIdSplitsLengthOption, 3); boolean noPrefix = cmd.hasOption(noPrefixOption.getOpt()); if (uuidSplits != -1 && userIdSplits != -1) { String splitKeys = generateUserHexadecimalSplits(userIdSplits, userIdSplitsLength, noPrefix) + "," + Bytes.toStringBinary(new byte[] { 1 }) + "," + generateUuidSplits(uuidSplits, uuidSplitsLength, noPrefix); System.out.println(splitKeys); } else if (uuidSplits != -1) { System.out.println(generateUuidSplits(uuidSplits, uuidSplitsLength, noPrefix)); } else if (userIdSplits != -1) { System.out.println(generateUserHexadecimalSplits(userIdSplits, userIdSplitsLength, noPrefix)); } else { System.out.println("Nothing to do, use -h to get help."); } return 0; } public String generateUuidSplits(int regionCount, int splitKeyLength, boolean noPrefix) { byte[] startBytes = new byte[] {}; byte[] endBytes = new byte[splitKeyLength]; for (int i = 0; i < endBytes.length; i++) { endBytes[i] = (byte)0xFF; } // number of splits = number of regions - 1 byte[][] splitKeys = Bytes.split(startBytes, endBytes, regionCount - 1); // Stripping the first key to avoid a region [null,0[ which will always be empty // And the last key to avoid [xffxffxff....,null[ to contain only few values if variants are created // for a record with record id xffxffxff..... splitKeys = Arrays.copyOfRange(splitKeys, 1, splitKeys.length - 1); StringBuilder builder = new StringBuilder(); for (byte[] splitKey : splitKeys) { if (builder.length() > 0) builder.append(","); byte[] fullSplitKey; if (noPrefix) { fullSplitKey = splitKey; } else { fullSplitKey = new byte[splitKey.length + 1]; fullSplitKey[0] = 1; // UUID record id's start with a 1 byte System.arraycopy(splitKey, 0, fullSplitKey, 1, splitKey.length); } builder.append(Bytes.toStringBinary(fullSplitKey)); } return builder.toString(); } /** * Calculates split keys for USER ID's assuming they contain random hexadecimal data, * typical use-case or keys prefixed with a hash (in hexadecimal notation). */ public String generateUserHexadecimalSplits(int regionCount, int splitKeyLength, boolean noPrefix) { // Since it's hexadecimal, every character can take 16 values, and if we have e.g. 3 of them we // have 16*16*16 = 16^3 possible values double space = Math.pow(16, splitKeyLength); // Partition these values over the number of regions requested double part = space / (double)regionCount; StringBuilder builder = new StringBuilder(); double current = 0; for (int i = 0; i < regionCount - 1; i++) { if (builder.length() > 0) builder.append(","); current += part; if (!noPrefix) { builder.append(Bytes.toStringBinary(new byte[] { 0 })); } builder.append(toFixedLengthHex(Math.round(current), splitKeyLength)); } return builder.toString(); } private String toFixedLengthHex(long value, int length) { String hex = Long.toHexString(value); if (hex.length() > length) { throw new RuntimeException("Unexpected: hex representation is longer than it should be: " + hex + ", expected only " + length + " characters"); } return Strings.repeat("0", length - hex.length()) + hex; } }