org.lilyproject.tools.generatesplitkeys.GenerateSplitKeys.java Source code

Java tutorial

Introduction

Here is the source code for org.lilyproject.tools.generatesplitkeys.GenerateSplitKeys.java

Source

/*
 * 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;
    }
}