Skip to content

HBASE-25510 Optimize TableName.valueOf from O(n) to O(1) #2885

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 40 additions & 99 deletions hbase-common/src/main/java/org/apache/hadoop/hbase/TableName.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.hadoop.hbase.util.Bytes;
Expand Down Expand Up @@ -57,8 +57,9 @@
@InterfaceAudience.Public
public final class TableName implements Comparable<TableName> {

/** See {@link #createTableNameIfNecessary(ByteBuffer, ByteBuffer)} */
private static final Set<TableName> tableCache = new CopyOnWriteArraySet<>();
/** See {@link #createTableNameIfNecessary(String, String)} */
// nameAsString -> TableName
private static final ConcurrentHashMap<String, TableName> tableCache = new ConcurrentHashMap<>();

/** Namespace delimiter */
//this should always be only 1 byte long
Expand Down Expand Up @@ -313,10 +314,9 @@ public String toString() {
*
* @throws IllegalArgumentException See {@link #valueOf(byte[])}
*/
private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgumentException {
this.qualifier = new byte[qualifier.remaining()];
qualifier.duplicate().get(this.qualifier);
this.qualifierAsString = Bytes.toString(this.qualifier);
private TableName(String namespaceStr, String qualifierStr) throws IllegalArgumentException {
this.qualifier = Bytes.toBytes(qualifierStr);
this.qualifierAsString = qualifierStr;

if (qualifierAsString.equals(OLD_ROOT_STR)) {
throw new IllegalArgumentException(OLD_ROOT_STR + " has been deprecated.");
Expand All @@ -326,7 +326,7 @@ private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgu
"renamed to " + META_TABLE_NAME);
}

if (Bytes.equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME, namespace)) {
if (NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR.equals(namespaceStr)) {
// Using the same objects: this will make the comparison faster later
this.namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
this.namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
Expand All @@ -336,14 +336,13 @@ private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgu
this.nameAsString = qualifierAsString;
this.name = this.qualifier;
} else {
if (Bytes.equals(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME, namespace)) {
if (NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR.equals(namespaceStr)) {
this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
this.systemTable = true;
} else {
this.namespace = new byte[namespace.remaining()];
namespace.duplicate().get(this.namespace);
this.namespaceAsString = Bytes.toString(this.namespace);
this.namespace = Bytes.toBytes(namespaceStr);
this.namespaceAsString = namespaceStr;
this.systemTable = false;
}
this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
Expand Down Expand Up @@ -382,29 +381,21 @@ private TableName(String qualifier) {
* 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;
private static TableName createTableNameIfNecessary(String namespace, String qualifier) {
// cache key
String nameAsStr = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR.equals(namespace)
? qualifier : namespace + NAMESPACE_DELIM + qualifier;

TableName oldTable = tableCache.get(nameAsStr);
if(oldTable != null) {
return oldTable;
}

// 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;
}
}
// this should never happen.
throw new IllegalStateException(newTable + " was supposed to be in the cache");
TableName newTable = new TableName(namespace, qualifier);
oldTable = tableCache.putIfAbsent(nameAsStr, newTable);
return oldTable == null ? newTable : oldTable;
}


/**
* 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.
Expand All @@ -420,16 +411,7 @@ public static TableName valueOf(String namespaceAsString, String qualifierAsStri
namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
}

for (TableName tn : tableCache) {
if (qualifierAsString.equals(tn.getQualifierAsString()) &&
namespaceAsString.equals(tn.getNamespaceAsString())) {
return tn;
}
}

return createTableNameIfNecessary(
ByteBuffer.wrap(Bytes.toBytes(namespaceAsString)),
ByteBuffer.wrap(Bytes.toBytes(qualifierAsString)));
return createTableNameIfNecessary(namespaceAsString, qualifierAsString);
}


Expand All @@ -456,103 +438,62 @@ public static TableName valueOf(byte[] fullName, int offset, int length)
offset, fullName.length);
Preconditions.checkArgument(length <= fullName.length,
"length (%s) must be <= array length (%s)", length, fullName.length);
for (TableName tn : tableCache) {
final byte[] tnName = tn.getName();
if (Bytes.equals(tnName, 0, tnName.length, fullName, offset, length)) {
return tn;
}
}

int namespaceDelimIndex = ArrayUtils.lastIndexOf(fullName, (byte) NAMESPACE_DELIM,
offset + length - 1);

if (namespaceDelimIndex < offset) {
return createTableNameIfNecessary(
ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
ByteBuffer.wrap(fullName, offset, length));
} else {
return createTableNameIfNecessary(
ByteBuffer.wrap(fullName, offset, namespaceDelimIndex),
ByteBuffer.wrap(fullName, namespaceDelimIndex + 1, length - (namespaceDelimIndex + 1)));
}
return valueOf(Bytes.toString(fullName, offset, length));
}

/**
* @param fullname of a table, possibly with a leading namespace and ':' as delimiter.
* @throws IllegalArgumentException if fullName equals old root or old meta.
*/
public static TableName valueOf(ByteBuffer fullname) {
fullname = fullname.duplicate();
fullname.mark();
boolean miss = true;
while (fullname.hasRemaining() && miss) {
miss = ((byte) NAMESPACE_DELIM) != fullname.get();
}
if (miss) {
fullname.reset();
return valueOf(null, fullname);
} else {
ByteBuffer qualifier = fullname.slice();
int delimiterIndex = fullname.position() - 1;
fullname.reset();
// changing variable name for clarity
ByteBuffer namespace = fullname.duplicate();
namespace.limit(delimiterIndex);
return valueOf(namespace, qualifier);
}
String fullNameStr = Bytes.toString(Bytes.getBytes(fullname));
return valueOf(fullNameStr);
}

/**
* @throws IllegalArgumentException if fullName equals old root or old meta. Some code
* depends on this.
*/
public static TableName valueOf(String name) {
for (TableName tn : tableCache) {
if (name.equals(tn.getNameAsString())) {
return tn;
}
TableName tn = tableCache.get(name);
if(tn != null) {
return tn;
}

final int namespaceDelimIndex = name.indexOf(NAMESPACE_DELIM);

if (namespaceDelimIndex < 0) {
return createTableNameIfNecessary(
ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
ByteBuffer.wrap(Bytes.toBytes(name)));
NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR,
name);
} else {
// indexOf is by character, not byte (consider multi-byte characters)
String ns = name.substring(0, namespaceDelimIndex);
String qualifier = name.substring(namespaceDelimIndex + 1);
return createTableNameIfNecessary(
ByteBuffer.wrap(Bytes.toBytes(ns)),
ByteBuffer.wrap(Bytes.toBytes(qualifier)));
name.substring(0, namespaceDelimIndex),
name.substring(namespaceDelimIndex + 1));
}
}


public static TableName valueOf(byte[] namespace, byte[] qualifier) {
if (namespace == null || namespace.length < 1) {
namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
}

for (TableName tn : tableCache) {
if (Arrays.equals(tn.getQualifier(), qualifier) &&
Arrays.equals(tn.getNamespace(), namespace)) {
return tn;
}
return createTableNameIfNecessary(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR,
Bytes.toString(qualifier));
}

return createTableNameIfNecessary(
ByteBuffer.wrap(namespace), ByteBuffer.wrap(qualifier));
Bytes.toString(namespace), Bytes.toString(qualifier));
}

public static TableName valueOf(ByteBuffer namespace, ByteBuffer qualifier) {
String qualifierStr = Bytes.toString(Bytes.getBytes(qualifier));
if (namespace == null || namespace.remaining() < 1) {
return createTableNameIfNecessary(
ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), qualifier);
NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR, qualifierStr);
}

return createTableNameIfNecessary(namespace, qualifier);
String namespaceStr = Bytes.toString(Bytes.getBytes(namespace));
return createTableNameIfNecessary(namespaceStr, qualifierStr);
}

@Override
Expand Down