HBASE-9976 Don't create duplicated TableName objects

git-svn-id: https://svn.apache.org/repos/asf/hbase/trunk@1545177 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
nkeywal 2013-11-25 08:53:29 +00:00
parent 73845ae7ac
commit 81f21ca409
5 changed files with 247 additions and 78 deletions

View File

@ -418,6 +418,7 @@ public class HRegionInfo implements Comparable<HRegionInfo> {
return buff; return buff;
} }
/** /**
* Gets the table name from the specified region name. * Gets the table name from the specified region name.
* Like {@link #getTableName(byte[])} only returns a {@link TableName} rather than a byte array. * Like {@link #getTableName(byte[])} only returns a {@link TableName} rather than a byte array.

View File

@ -2446,8 +2446,8 @@ public final class ProtobufUtil {
} }
public static TableName toTableName(HBaseProtos.TableName tableNamePB) { public static TableName toTableName(HBaseProtos.TableName tableNamePB) {
return TableName.valueOf(tableNamePB.getNamespace().toByteArray(), return TableName.valueOf(tableNamePB.getNamespace().asReadOnlyByteBuffer(),
tableNamePB.getQualifier().toByteArray()); tableNamePB.getQualifier().asReadOnlyByteBuffer());
} }
public static HBaseProtos.TableName toProtoTableName(TableName tableName) { public static HBaseProtos.TableName toProtoTableName(TableName tableName) {

View File

@ -23,6 +23,11 @@ import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.hbase.KeyValue.KVComparator; import org.apache.hadoop.hbase.KeyValue.KVComparator;
import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Bytes;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/** /**
* Immutable POJO class for representing a table name. * Immutable POJO class for representing a table name.
* Which is of the form: * Which is of the form:
@ -40,11 +45,20 @@ import org.apache.hadoop.hbase.util.Bytes;
* b) bar, means namespace=default and qualifier=bar * b) bar, means namespace=default and qualifier=bar
* c) default:bar, means namespace=default and qualifier=bar * c) default:bar, means namespace=default and qualifier=bar
* *
* <p>
* Internally, in this class, we cache the instances to limit the number of objects and
* make the "equals" faster. We try to minimize the number of objects created of
* the number of array copy to check if we already have an instance of this TableName. The code
* is not optimize for a new instance creation but is optimized to check for existence.
* </p>
*/ */
@InterfaceAudience.Public @InterfaceAudience.Public
@InterfaceStability.Evolving @InterfaceStability.Evolving
public final class TableName implements Comparable<TableName> { public final class TableName implements Comparable<TableName> {
/** See {@link #createTableNameIfNecessary(ByteBuffer, ByteBuffer)} */
private static final Set<TableName> tableCache = new CopyOnWriteArraySet<TableName>();
/** Namespace delimiter */ /** Namespace delimiter */
//this should always be only 1 byte long //this should always be only 1 byte long
public final static char NAMESPACE_DELIM = ':'; public final static char NAMESPACE_DELIM = ':';
@ -75,6 +89,8 @@ public final class TableName implements Comparable<TableName> {
public static final String OLD_META_STR = ".META."; public static final String OLD_META_STR = ".META.";
public static final String OLD_ROOT_STR = "-ROOT-"; public static final String OLD_ROOT_STR = "-ROOT-";
/** /**
* TableName for old -ROOT- table. It is used to read/process old WALs which have * TableName for old -ROOT- table. It is used to read/process old WALs which have
* ROOT edits. * ROOT edits.
@ -85,15 +101,14 @@ public final class TableName implements Comparable<TableName> {
*/ */
public static final TableName OLD_META_TABLE_NAME = getADummyTableName(OLD_META_STR); public static final TableName OLD_META_TABLE_NAME = getADummyTableName(OLD_META_STR);
private byte[] name; private final byte[] name;
private String nameAsString; private final String nameAsString;
private byte[] namespace; private final byte[] namespace;
private String namespaceAsString; private final String namespaceAsString;
private byte[] qualifier; private final byte[] qualifier;
private String qualifierAsString; private final String qualifierAsString;
private boolean systemTable; private final boolean systemTable;
private final int hashCode;
private TableName() {}
/** /**
* Check passed byte array, "tableName", is legal user-space table name. * Check passed byte array, "tableName", is legal user-space table name.
@ -118,6 +133,7 @@ public final class TableName implements Comparable<TableName> {
if (tableName == null || tableName.length <= 0) { if (tableName == null || tableName.length <= 0) {
throw new IllegalArgumentException("Name is null or empty"); throw new IllegalArgumentException("Name is null or empty");
} }
int namespaceDelimIndex = com.google.common.primitives.Bytes.lastIndexOf(tableName, int namespaceDelimIndex = com.google.common.primitives.Bytes.lastIndexOf(tableName,
(byte) NAMESPACE_DELIM); (byte) NAMESPACE_DELIM);
if (namespaceDelimIndex == 0 || namespaceDelimIndex == -1){ if (namespaceDelimIndex == 0 || namespaceDelimIndex == -1){
@ -149,6 +165,7 @@ public final class TableName implements Comparable<TableName> {
if(end - start < 1) { if(end - start < 1) {
throw new IllegalArgumentException("Table qualifier must not be empty"); throw new IllegalArgumentException("Table qualifier must not be empty");
} }
if (qualifierName[start] == '.' || qualifierName[start] == '-') { if (qualifierName[start] == '.' || qualifierName[start] == '-') {
throw new IllegalArgumentException("Illegal first character <" + qualifierName[0] + throw new IllegalArgumentException("Illegal first character <" + qualifierName[0] +
"> at 0. Namespaces can only start with alphanumeric " + "> at 0. Namespaces can only start with alphanumeric " +
@ -161,8 +178,9 @@ public final class TableName implements Comparable<TableName> {
qualifierName[i] == '.') { qualifierName[i] == '.') {
continue; continue;
} }
throw new IllegalArgumentException("Illegal character <" + qualifierName[i] + throw new IllegalArgumentException("Illegal character code:" + qualifierName[i] +
"> at " + i + ". User-space table qualifiers can only contain " + ", <" + (char) qualifierName[i] + "> at " + i +
". User-space table qualifiers can only contain " +
"'alphanumeric characters': i.e. [a-zA-Z_0-9-.]: " + "'alphanumeric characters': i.e. [a-zA-Z_0-9-.]: " +
Bytes.toString(qualifierName, start, end)); Bytes.toString(qualifierName, start, end));
} }
@ -174,9 +192,6 @@ public final class TableName implements Comparable<TableName> {
/** /**
* Valid namespace characters are [a-zA-Z_0-9] * Valid namespace characters are [a-zA-Z_0-9]
* @param namespaceName
* @param offset
* @param length
*/ */
public static void isLegalNamespaceName(byte[] namespaceName, int offset, int length) { public static void isLegalNamespaceName(byte[] namespaceName, int offset, int length) {
for (int i = offset; i < length; i++) { for (int i = offset; i < length; i++) {
@ -227,85 +242,208 @@ public final class TableName implements Comparable<TableName> {
return nameAsString; return nameAsString;
} }
public static TableName valueOf(byte[] namespace, byte[] qualifier) { /**
TableName ret = new TableName(); *
if(namespace == null || namespace.length < 1) { * @throws IllegalArgumentException See {@link #valueOf(byte[])}
namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME; */
private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgumentException {
this.qualifier = new byte[qualifier.remaining()];
qualifier.get(this.qualifier);
this.qualifierAsString = Bytes.toString(this.qualifier);
if (qualifierAsString.equals(OLD_ROOT_STR)) {
throw new IllegalArgumentException(OLD_ROOT_STR + " has been deprecated.");
}
if (qualifierAsString.equals(OLD_META_STR)) {
throw new IllegalArgumentException(OLD_META_STR + " no longer exists. The table has been " +
"renamed to " + META_TABLE_NAME);
} }
ret.namespace = namespace;
ret.namespaceAsString = Bytes.toString(namespace);
ret.qualifier = qualifier;
ret.qualifierAsString = Bytes.toString(qualifier);
finishValueOf(ret); if (Bytes.equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME, namespace)) {
// Using the same objects: this will make the comparison faster later
this.namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
this.namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
this.systemTable = false;
return ret; // The name does not include the namespace when it's the default one.
this.nameAsString = qualifierAsString;
this.name = this.qualifier;
} else {
if (Bytes.equals(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME, namespace)) {
this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
this.systemTable = true;
} else {
this.namespace = new byte[namespace.remaining()];
namespace.get(this.namespace);
this.namespaceAsString = Bytes.toString(this.namespace);
this.systemTable = false;
}
this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
this.name = Bytes.toBytes(nameAsString);
}
this.hashCode = nameAsString.hashCode();
isLegalNamespaceName(this.namespace);
isLegalTableQualifierName(this.qualifier);
} }
/**
* This is only for the old and meta tables.
*/
private TableName(String qualifier) {
this.qualifier = Bytes.toBytes(qualifier);
this.qualifierAsString = qualifier;
this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
this.systemTable = true;
// WARNING: nameAsString is different than name for old meta & root!
// This is by design.
this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
this.name = this.qualifier;
this.hashCode = nameAsString.hashCode();
}
/**
* Check that the object does not exist already. There are two reasons for creating the objects
* only once:
* 1) With 100K regions, the table names take ~20MB.
* 2) Equals becomes much faster as it's resolved with a reference and an int comparison.
*/
private static TableName createTableNameIfNecessary(ByteBuffer bns, ByteBuffer qns) {
for (TableName tn : tableCache) {
if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
return tn;
}
}
TableName newTable = new TableName(bns, qns);
if (tableCache.add(newTable)) { // Adds the specified element if it is not already present
return newTable;
} else {
// Someone else added it. Let's find it.
for (TableName tn : tableCache) {
if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
return tn;
}
}
}
throw new IllegalStateException(newTable + " was supposed to be in the cache");
}
/** /**
* It is used to create table names for old META, and ROOT table. * It is used to create table names for old META, and ROOT table.
* These tables are not really legal tables. They are not added into the cache.
* @return a dummy TableName instance (with no validation) for the passed qualifier * @return a dummy TableName instance (with no validation) for the passed qualifier
*/ */
private static TableName getADummyTableName(String qualifier) { private static TableName getADummyTableName(String qualifier) {
TableName ret = new TableName(); return new TableName(qualifier);
ret.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
ret.qualifierAsString = qualifier;
ret.nameAsString = createFullyQualified(ret.namespaceAsString, ret.qualifierAsString);
ret.name = Bytes.toBytes(qualifier);
return ret;
} }
public static TableName valueOf(String namespaceAsString, String qualifierAsString) { public static TableName valueOf(String namespaceAsString, String qualifierAsString) {
TableName ret = new TableName(); if (namespaceAsString == null || namespaceAsString.length() < 1) {
if(namespaceAsString == null || namespaceAsString.length() < 1) {
namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR; namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
} }
ret.namespaceAsString = namespaceAsString;
ret.namespace = Bytes.toBytes(namespaceAsString);
ret.qualifier = Bytes.toBytes(qualifierAsString);
ret.qualifierAsString = qualifierAsString;
finishValueOf(ret); for (TableName tn : tableCache) {
if (qualifierAsString.equals(tn.getQualifierAsString()) &&
namespaceAsString.equals(tn.getNameAsString())) {
return tn;
}
}
return ret; return createTableNameIfNecessary(
ByteBuffer.wrap(Bytes.toBytes(namespaceAsString)),
ByteBuffer.wrap(Bytes.toBytes(qualifierAsString)));
} }
private static void finishValueOf(TableName tableName) {
isLegalNamespaceName(tableName.namespace);
isLegalTableQualifierName(tableName.qualifier);
tableName.nameAsString = /**
createFullyQualified(tableName.namespaceAsString, tableName.qualifierAsString); * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
tableName.name = Bytes.toBytes(tableName.nameAsString); * depends on this. The test is buried in the table creation to save on array comparison
tableName.systemTable = Bytes.equals( * when we're creating a standard table object that will be in the cache.
tableName.namespace, NamespaceDescriptor.SYSTEM_NAMESPACE_NAME); */
public static TableName valueOf(byte[] fullName) throws IllegalArgumentException{
for (TableName tn : tableCache) {
if (Arrays.equals(tn.getName(), fullName)) {
return tn;
}
}
int namespaceDelimIndex = com.google.common.primitives.Bytes.lastIndexOf(fullName,
(byte) NAMESPACE_DELIM);
if (namespaceDelimIndex < 0) {
return createTableNameIfNecessary(
ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
ByteBuffer.wrap(fullName));
} else {
return createTableNameIfNecessary(
ByteBuffer.wrap(fullName, 0, namespaceDelimIndex),
ByteBuffer.wrap(fullName, namespaceDelimIndex + 1,
fullName.length - (namespaceDelimIndex + 1)));
}
} }
public static TableName valueOf(byte[] name) {
return valueOf(Bytes.toString(name));
}
/**
* @throws IllegalArgumentException if fullName equals old root or old meta. Some code
* depends on this.
*/
public static TableName valueOf(String name) { public static TableName valueOf(String name) {
if(name.equals(OLD_ROOT_STR)) { for (TableName tn : tableCache) {
throw new IllegalArgumentException(OLD_ROOT_STR + " has been deprecated."); if (name.equals(tn.getNameAsString())) {
} return tn;
if(name.equals(OLD_META_STR)) { }
throw new IllegalArgumentException(OLD_META_STR + " no longer exists. The table has been " +
"renamed to "+META_TABLE_NAME);
} }
isLegalFullyQualifiedTableName(Bytes.toBytes(name)); int namespaceDelimIndex = name.indexOf(NAMESPACE_DELIM);
int index = name.indexOf(NAMESPACE_DELIM); byte[] nameB = Bytes.toBytes(name);
if (index != -1) {
return TableName.valueOf(name.substring(0, index), name.substring(index + 1)); if (namespaceDelimIndex < 0) {
return createTableNameIfNecessary(
ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
ByteBuffer.wrap(nameB));
} else {
return createTableNameIfNecessary(
ByteBuffer.wrap(nameB, 0, namespaceDelimIndex),
ByteBuffer.wrap(nameB, namespaceDelimIndex + 1,
nameB.length - (namespaceDelimIndex + 1)));
} }
return TableName.valueOf(NamespaceDescriptor.DEFAULT_NAMESPACE.getName(), name);
} }
private static String createFullyQualified(String namespace, String tableQualifier) {
if (namespace.equals(NamespaceDescriptor.DEFAULT_NAMESPACE.getName())) { public static TableName valueOf(byte[] namespace, byte[] qualifier) {
return tableQualifier; if (namespace == null || namespace.length < 1) {
namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
} }
return namespace+NAMESPACE_DELIM+tableQualifier;
for (TableName tn : tableCache) {
if (Arrays.equals(tn.getQualifier(), namespace) &&
Arrays.equals(tn.getNamespace(), namespace)) {
return tn;
}
}
return createTableNameIfNecessary(
ByteBuffer.wrap(namespace), ByteBuffer.wrap(qualifier));
}
public static TableName valueOf(ByteBuffer namespace, ByteBuffer qualifier) {
if (namespace == null || namespace.remaining() < 1) {
return createTableNameIfNecessary(
ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), qualifier);
}
return createTableNameIfNecessary(namespace, qualifier);
} }
@Override @Override
@ -315,20 +453,26 @@ public final class TableName implements Comparable<TableName> {
TableName tableName = (TableName) o; TableName tableName = (TableName) o;
if (!nameAsString.equals(tableName.nameAsString)) return false; return o.hashCode() == hashCode && nameAsString.equals(tableName.nameAsString);
return true;
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = nameAsString.hashCode(); return hashCode;
return result;
} }
/**
* For performance reasons, the ordering is not lexicographic.
*/
@Override @Override
public int compareTo(TableName tableName) { public int compareTo(TableName tableName) {
if (this == tableName) return 0; if (this == tableName) return 0;
if (this.hashCode < tableName.hashCode()) {
return -1;
}
if (this.hashCode > tableName.hashCode()) {
return 1;
}
return this.nameAsString.compareTo(tableName.getNameAsString()); return this.nameAsString.compareTo(tableName.getNameAsString());
} }

View File

@ -1276,6 +1276,28 @@ public class Bytes {
} }
/**
* @param a left operand
* @param b right operand
* @return True if equal
*/
public static boolean equals(byte[] a, ByteBuffer b) {
if (a == null) return b == null;
if (b == null) return false;
if (a.length != b.remaining()) return false;
b.mark();
for (byte anA : a) {
if (anA != b.get()) {
b.reset();
return false;
}
}
b.reset();
return true;
}
/** /**
* Return true if the byte array on the right is a prefix of the byte * Return true if the byte array on the right is a prefix of the byte
* array on the left. * array on the left.

View File

@ -18,6 +18,7 @@
package org.apache.hadoop.hbase.master.balancer; package org.apache.hadoop.hbase.master.balancer;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.util.ArrayList; import java.util.ArrayList;
@ -33,6 +34,7 @@ import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.SmallTests; import org.apache.hadoop.hbase.SmallTests;
import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.master.RackManager; import org.apache.hadoop.hbase.master.RackManager;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Triple; import org.apache.hadoop.hbase.util.Triple;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -104,7 +106,7 @@ public class TestFavoredNodeAssignmentHelper {
List<ServerName> servers = getServersFromRack(rackToServerCount); List<ServerName> servers = getServersFromRack(rackToServerCount);
FavoredNodeAssignmentHelper helper = new FavoredNodeAssignmentHelper(servers, FavoredNodeAssignmentHelper helper = new FavoredNodeAssignmentHelper(servers,
new Configuration()); new Configuration());
assertTrue(helper.canPlaceFavoredNodes() == false); assertFalse(helper.canPlaceFavoredNodes());
} }
@Test @Test
@ -263,16 +265,15 @@ public class TestFavoredNodeAssignmentHelper {
int regionCount, Map<String, Integer> rackToServerCount) { int regionCount, Map<String, Integer> rackToServerCount) {
Map<HRegionInfo, ServerName> primaryRSMap = new HashMap<HRegionInfo, ServerName>(); Map<HRegionInfo, ServerName> primaryRSMap = new HashMap<HRegionInfo, ServerName>();
List<ServerName> servers = getServersFromRack(rackToServerCount); List<ServerName> servers = getServersFromRack(rackToServerCount);
FavoredNodeAssignmentHelper helper = new FavoredNodeAssignmentHelper(servers, FavoredNodeAssignmentHelper helper = new FavoredNodeAssignmentHelper(servers, rackManager);
new Configuration());
helper = new FavoredNodeAssignmentHelper(servers, rackManager);
Map<ServerName, List<HRegionInfo>> assignmentMap = Map<ServerName, List<HRegionInfo>> assignmentMap =
new HashMap<ServerName, List<HRegionInfo>>(); new HashMap<ServerName, List<HRegionInfo>>();
helper.initialize(); helper.initialize();
// create regions // create regions
List<HRegionInfo> regions = new ArrayList<HRegionInfo>(regionCount); List<HRegionInfo> regions = new ArrayList<HRegionInfo>(regionCount);
for (int i = 0; i < regionCount; i++) { for (int i = 0; i < regionCount; i++) {
HRegionInfo region = new HRegionInfo(TableName.valueOf("foobar" + i)); HRegionInfo region = new HRegionInfo(TableName.valueOf("foobar"),
Bytes.toBytes(i), Bytes.toBytes(i + 1));
regions.add(region); regions.add(region);
} }
// place the regions // place the regions
@ -300,7 +301,8 @@ public class TestFavoredNodeAssignmentHelper {
// create some regions // create some regions
List<HRegionInfo> regions = new ArrayList<HRegionInfo>(regionCount); List<HRegionInfo> regions = new ArrayList<HRegionInfo>(regionCount);
for (int i = 0; i < regionCount; i++) { for (int i = 0; i < regionCount; i++) {
HRegionInfo region = new HRegionInfo(TableName.valueOf("foobar" + i)); HRegionInfo region = new HRegionInfo(TableName.valueOf("foobar"),
Bytes.toBytes(i), Bytes.toBytes(i + 1));
regions.add(region); regions.add(region);
} }
// place those regions in primary RSs // place those regions in primary RSs
@ -353,7 +355,7 @@ public class TestFavoredNodeAssignmentHelper {
private String printProportions(int firstRackSize, int secondRackSize, private String printProportions(int firstRackSize, int secondRackSize,
int thirdRackSize, int regionsOnRack1, int regionsOnRack2, int regionsOnRack3) { int thirdRackSize, int regionsOnRack1, int regionsOnRack2, int regionsOnRack3) {
return "The rack sizes" + firstRackSize + " " + secondRackSize return "The rack sizes " + firstRackSize + " " + secondRackSize
+ " " + thirdRackSize + " " + regionsOnRack1 + " " + regionsOnRack2 + + " " + thirdRackSize + " " + regionsOnRack1 + " " + regionsOnRack2 +
" " + regionsOnRack3; " " + regionsOnRack3;
} }