HBASE-24949 Optimize FSTableDescriptors.get to not always go to fs when cache miss (#2317)

Signed-off-by: Guanghao Zhang <zghao@apache.org>
This commit is contained in:
Duo Zhang 2020-08-29 22:26:36 +08:00
parent 85370f1443
commit 54454f8de6
11 changed files with 161 additions and 181 deletions

View File

@ -31,7 +31,7 @@ public interface TableDescriptors {
/** /**
* @return TableDescriptor for tablename * @return TableDescriptor for tablename
*/ */
TableDescriptor get(final TableName tableName) throws IOException; TableDescriptor get(TableName tableName) throws IOException;
/** /**
* Get Map of all NamespaceDescriptors for a given namespace. * Get Map of all NamespaceDescriptors for a given namespace.
@ -40,32 +40,33 @@ public interface TableDescriptors {
Map<String, TableDescriptor> getByNamespace(String name) throws IOException; Map<String, TableDescriptor> getByNamespace(String name) throws IOException;
/** /**
* Get Map of all TableDescriptors. Populates the descriptor cache as a * Get Map of all TableDescriptors. Populates the descriptor cache as a side effect.
* side effect. * </p>
* Notice: the key of map is the table name which contains namespace. It was generated by * Notice: the key of map is the table name which contains namespace. It was generated by
* {@link TableName#getNameWithNamespaceInclAsString()}. * {@link TableName#getNameWithNamespaceInclAsString()}.
* @return Map of all descriptors. * @return Map of all descriptors.
*/ */
Map<String, TableDescriptor> getAll() throws IOException; Map<String, TableDescriptor> getAll() throws IOException;
/**
* Add or update descriptor. Just call {@link #update(TableDescriptor, boolean)} with
* {@code cacheOnly} as {@code false}.
*/
default void update(TableDescriptor htd) throws IOException {
update(htd, false);
}
/** /**
* Add or update descriptor * Add or update descriptor
* @param htd Descriptor to set into TableDescriptors * @param htd Descriptor to set into TableDescriptors
* @param cacheOnly only add the given {@code htd} to cache, without updating the storage. For
* example, when creating table, we will write the descriptor to fs when creating the fs
* layout, so we do not need to update the fs again.
*/ */
void update(final TableDescriptor htd) throws IOException; void update(TableDescriptor htd, boolean cacheOnly) throws IOException;
/** /**
* @return Instance of table descriptor or null if none found. * @return Instance of table descriptor or null if none found.
*/ */
TableDescriptor remove(final TableName tablename) throws IOException; TableDescriptor remove(TableName tablename) throws IOException;
/**
* Enables the tabledescriptor cache
*/
void setCacheOn() throws IOException;
/**
* Disables the tabledescriptor cache
*/
void setCacheOff() throws IOException;
} }

View File

@ -21,6 +21,7 @@ import static org.apache.hadoop.hbase.HConstants.DEFAULT_HBASE_SPLIT_COORDINATED
import static org.apache.hadoop.hbase.HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS; import static org.apache.hadoop.hbase.HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS;
import static org.apache.hadoop.hbase.HConstants.HBASE_SPLIT_WAL_COORDINATED_BY_ZK; import static org.apache.hadoop.hbase.HConstants.HBASE_SPLIT_WAL_COORDINATED_BY_ZK;
import static org.apache.hadoop.hbase.util.DNS.MASTER_HOSTNAME_KEY; import static org.apache.hadoop.hbase.util.DNS.MASTER_HOSTNAME_KEY;
import com.google.protobuf.Descriptors; import com.google.protobuf.Descriptors;
import com.google.protobuf.Service; import com.google.protobuf.Service;
import java.io.IOException; import java.io.IOException;
@ -213,19 +214,21 @@ import org.apache.hadoop.hbase.zookeeper.ZKClusterId;
import org.apache.hadoop.hbase.zookeeper.ZKUtil; import org.apache.hadoop.hbase.zookeeper.ZKUtil;
import org.apache.hadoop.hbase.zookeeper.ZKWatcher; import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
import org.apache.hadoop.hbase.zookeeper.ZNodePaths; import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceAudience;
import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
import org.apache.hbase.thirdparty.com.google.common.collect.Lists; import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
import org.apache.hbase.thirdparty.com.google.common.collect.Maps; import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils; import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Server; import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Server;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector; import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector;
import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletHolder; import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletHolder;
import org.apache.hbase.thirdparty.org.eclipse.jetty.webapp.WebAppContext; import org.apache.hbase.thirdparty.org.eclipse.jetty.webapp.WebAppContext;
import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoResponse.CompactionState; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoResponse.CompactionState;
import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
@ -748,6 +751,11 @@ public class HMaster extends HRegionServer implements MasterServices {
return true; return true;
} }
@Override
protected boolean cacheTableDescriptor() {
return true;
}
@Override @Override
protected RSRpcServices createRpcServices() throws IOException { protected RSRpcServices createRpcServices() throws IOException {
return new MasterRpcServices(this); return new MasterRpcServices(this);
@ -901,9 +909,6 @@ public class HMaster extends HRegionServer implements MasterServices {
this.fileSystemManager = new MasterFileSystem(conf); this.fileSystemManager = new MasterFileSystem(conf);
this.walManager = new MasterWalManager(this); this.walManager = new MasterWalManager(this);
// enable table descriptors cache
this.tableDescriptors.setCacheOn();
// warm-up HTDs cache on master initialization // warm-up HTDs cache on master initialization
if (preLoadTableDescriptors) { if (preLoadTableDescriptors) {
status.setStatus("Pre-loading table descriptors"); status.setStatus("Pre-loading table descriptors");

View File

@ -141,6 +141,7 @@ public class CloneSnapshotProcedure
break; break;
case CLONE_SNAPSHOT_WRITE_FS_LAYOUT: case CLONE_SNAPSHOT_WRITE_FS_LAYOUT:
newRegions = createFilesystemLayout(env, tableDescriptor, newRegions); newRegions = createFilesystemLayout(env, tableDescriptor, newRegions);
env.getMasterServices().getTableDescriptors().update(tableDescriptor, true);
setNextState(CloneSnapshotState.CLONE_SNAPSHOT_ADD_TO_META); setNextState(CloneSnapshotState.CLONE_SNAPSHOT_ADD_TO_META);
break; break;
case CLONE_SNAPSHOT_ADD_TO_META: case CLONE_SNAPSHOT_ADD_TO_META:
@ -172,8 +173,9 @@ public class CloneSnapshotProcedure
setNextState(CloneSnapshotState.CLONE_SNAPSHOT_UPDATE_DESC_CACHE); setNextState(CloneSnapshotState.CLONE_SNAPSHOT_UPDATE_DESC_CACHE);
break; break;
case CLONE_SNAPSHOT_UPDATE_DESC_CACHE: case CLONE_SNAPSHOT_UPDATE_DESC_CACHE:
// XXX: this stage should be named as set table enabled, as now we will cache the
// descriptor after writing fs layout.
CreateTableProcedure.setEnabledState(env, getTableName()); CreateTableProcedure.setEnabledState(env, getTableName());
CreateTableProcedure.updateTableDescCache(env, getTableName());
setNextState(CloneSnapshotState.CLONE_SNAPHOST_RESTORE_ACL); setNextState(CloneSnapshotState.CLONE_SNAPHOST_RESTORE_ACL);
break; break;
case CLONE_SNAPHOST_RESTORE_ACL: case CLONE_SNAPHOST_RESTORE_ACL:

View File

@ -98,6 +98,7 @@ public class CreateTableProcedure
case CREATE_TABLE_WRITE_FS_LAYOUT: case CREATE_TABLE_WRITE_FS_LAYOUT:
DeleteTableProcedure.deleteFromFs(env, getTableName(), newRegions, true); DeleteTableProcedure.deleteFromFs(env, getTableName(), newRegions, true);
newRegions = createFsLayout(env, tableDescriptor, newRegions); newRegions = createFsLayout(env, tableDescriptor, newRegions);
env.getMasterServices().getTableDescriptors().update(tableDescriptor, true);
setNextState(CreateTableState.CREATE_TABLE_ADD_TO_META); setNextState(CreateTableState.CREATE_TABLE_ADD_TO_META);
break; break;
case CREATE_TABLE_ADD_TO_META: case CREATE_TABLE_ADD_TO_META:
@ -111,8 +112,9 @@ public class CreateTableProcedure
setNextState(CreateTableState.CREATE_TABLE_UPDATE_DESC_CACHE); setNextState(CreateTableState.CREATE_TABLE_UPDATE_DESC_CACHE);
break; break;
case CREATE_TABLE_UPDATE_DESC_CACHE: case CREATE_TABLE_UPDATE_DESC_CACHE:
// XXX: this stage should be named as set table enabled, as now we will cache the
// descriptor after writing fs layout.
setEnabledState(env, getTableName()); setEnabledState(env, getTableName());
updateTableDescCache(env, getTableName());
setNextState(CreateTableState.CREATE_TABLE_POST_OPERATION); setNextState(CreateTableState.CREATE_TABLE_POST_OPERATION);
break; break;
case CREATE_TABLE_POST_OPERATION: case CREATE_TABLE_POST_OPERATION:
@ -386,11 +388,6 @@ public class CreateTableProcedure
regionInfos, tableDescriptor.getRegionReplication()); regionInfos, tableDescriptor.getRegionReplication());
} }
protected static void updateTableDescCache(final MasterProcedureEnv env,
final TableName tableName) throws IOException {
env.getMasterServices().getTableDescriptors().get(tableName);
}
@Override @Override
protected boolean shouldWaitClientAck(MasterProcedureEnv env) { protected boolean shouldWaitClientAck(MasterProcedureEnv env) {
// system tables are created on bootstrap internally by the system // system tables are created on bootstrap internally by the system

View File

@ -67,7 +67,7 @@ public class InitMetaProcedure extends AbstractStateMachineTableProcedure<InitMe
return TableOperationType.CREATE; return TableOperationType.CREATE;
} }
private static void writeFsLayout(Path rootDir, Configuration conf) throws IOException { private static TableDescriptor writeFsLayout(Path rootDir, Configuration conf) throws IOException {
LOG.info("BOOTSTRAP: creating hbase:meta region"); LOG.info("BOOTSTRAP: creating hbase:meta region");
FileSystem fs = rootDir.getFileSystem(conf); FileSystem fs = rootDir.getFileSystem(conf);
Path tableDir = CommonFSUtils.getTableDir(rootDir, TableName.META_TABLE_NAME); Path tableDir = CommonFSUtils.getTableDir(rootDir, TableName.META_TABLE_NAME);
@ -78,13 +78,13 @@ public class InitMetaProcedure extends AbstractStateMachineTableProcedure<InitMe
// created here in bootstrap and it'll need to be cleaned up. Better to // created here in bootstrap and it'll need to be cleaned up. Better to
// not make it in first place. Turn off block caching for bootstrap. // not make it in first place. Turn off block caching for bootstrap.
// Enable after. // Enable after.
FSTableDescriptors.tryUpdateMetaTableDescriptor(conf, fs, rootDir, TableDescriptor metaDescriptor = FSTableDescriptors.tryUpdateAndGetMetaTableDescriptor(conf, fs,
builder -> builder.setRegionReplication( rootDir, builder -> builder.setRegionReplication(
conf.getInt(HConstants.META_REPLICAS_NUM, HConstants.DEFAULT_META_REPLICA_NUM))); conf.getInt(HConstants.META_REPLICAS_NUM, HConstants.DEFAULT_META_REPLICA_NUM)));
TableDescriptor metaDescriptor = new FSTableDescriptors(conf).get(TableName.META_TABLE_NAME);
HRegion HRegion
.createHRegion(RegionInfoBuilder.FIRST_META_REGIONINFO, rootDir, conf, metaDescriptor, null) .createHRegion(RegionInfoBuilder.FIRST_META_REGIONINFO, rootDir, conf, metaDescriptor, null)
.close(); .close();
return metaDescriptor;
} }
@Override @Override
@ -96,7 +96,8 @@ public class InitMetaProcedure extends AbstractStateMachineTableProcedure<InitMe
case INIT_META_WRITE_FS_LAYOUT: case INIT_META_WRITE_FS_LAYOUT:
Configuration conf = env.getMasterConfiguration(); Configuration conf = env.getMasterConfiguration();
Path rootDir = CommonFSUtils.getRootDir(conf); Path rootDir = CommonFSUtils.getRootDir(conf);
writeFsLayout(rootDir, conf); TableDescriptor td = writeFsLayout(rootDir, conf);
env.getMasterServices().getTableDescriptors().update(td, true);
setNextState(InitMetaState.INIT_META_ASSIGN_META); setNextState(InitMetaState.INIT_META_ASSIGN_META);
return Flow.HAS_MORE_STATE; return Flow.HAS_MORE_STATE;
case INIT_META_ASSIGN_META: case INIT_META_ASSIGN_META:

View File

@ -130,7 +130,7 @@ public class TruncateTableProcedure
case TRUNCATE_TABLE_CREATE_FS_LAYOUT: case TRUNCATE_TABLE_CREATE_FS_LAYOUT:
DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true); DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true);
regions = CreateTableProcedure.createFsLayout(env, tableDescriptor, regions); regions = CreateTableProcedure.createFsLayout(env, tableDescriptor, regions);
CreateTableProcedure.updateTableDescCache(env, getTableName()); env.getMasterServices().getTableDescriptors().update(tableDescriptor, true);
setNextState(TruncateTableState.TRUNCATE_TABLE_ADD_TO_META); setNextState(TruncateTableState.TRUNCATE_TABLE_ADD_TO_META);
break; break;
case TRUNCATE_TABLE_ADD_TO_META: case TRUNCATE_TABLE_ADD_TO_META:

View File

@ -736,8 +736,8 @@ public class HRegionServer extends Thread implements
CommonFSUtils.setFsDefault(this.conf, CommonFSUtils.getRootDir(this.conf)); CommonFSUtils.setFsDefault(this.conf, CommonFSUtils.getRootDir(this.conf));
this.dataFs = new HFileSystem(this.conf, useHBaseChecksum); this.dataFs = new HFileSystem(this.conf, useHBaseChecksum);
this.dataRootDir = CommonFSUtils.getRootDir(this.conf); this.dataRootDir = CommonFSUtils.getRootDir(this.conf);
this.tableDescriptors = this.tableDescriptors = new FSTableDescriptors(this.dataFs, this.dataRootDir,
new FSTableDescriptors(this.dataFs, this.dataRootDir, !canUpdateTableDescriptor(), false); !canUpdateTableDescriptor(), cacheTableDescriptor());
} }
protected void login(UserProvider user, String host) throws IOException { protected void login(UserProvider user, String host) throws IOException {
@ -763,6 +763,10 @@ public class HRegionServer extends Thread implements
return false; return false;
} }
protected boolean cacheTableDescriptor() {
return false;
}
protected RSRpcServices createRpcServices() throws IOException { protected RSRpcServices createRpcServices() throws IOException {
return new RSRpcServices(this); return new RSRpcServices(this);
} }

View File

@ -18,7 +18,6 @@
package org.apache.hadoop.hbase.util; package org.apache.hadoop.hbase.util;
import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@ -79,7 +78,7 @@ public class FSTableDescriptors implements TableDescriptors {
private final FileSystem fs; private final FileSystem fs;
private final Path rootdir; private final Path rootdir;
private final boolean fsreadonly; private final boolean fsreadonly;
private volatile boolean usecache; private final boolean usecache;
private volatile boolean fsvisited; private volatile boolean fsvisited;
@VisibleForTesting @VisibleForTesting
@ -121,15 +120,16 @@ public class FSTableDescriptors implements TableDescriptors {
@VisibleForTesting @VisibleForTesting
public static void tryUpdateMetaTableDescriptor(Configuration conf) throws IOException { public static void tryUpdateMetaTableDescriptor(Configuration conf) throws IOException {
tryUpdateMetaTableDescriptor(conf, CommonFSUtils.getCurrentFileSystem(conf), tryUpdateAndGetMetaTableDescriptor(conf, CommonFSUtils.getCurrentFileSystem(conf),
CommonFSUtils.getRootDir(conf), null); CommonFSUtils.getRootDir(conf), null);
} }
public static void tryUpdateMetaTableDescriptor(Configuration conf, FileSystem fs, Path rootdir, public static TableDescriptor tryUpdateAndGetMetaTableDescriptor(Configuration conf,
Function<TableDescriptorBuilder, TableDescriptorBuilder> metaObserver) throws IOException { FileSystem fs, Path rootdir,
Function<TableDescriptorBuilder, TableDescriptorBuilder> metaObserver) throws IOException {
// see if we already have meta descriptor on fs. Write one if not. // see if we already have meta descriptor on fs. Write one if not.
try { try {
getTableDescriptorFromFs(fs, rootdir, TableName.META_TABLE_NAME); return getTableDescriptorFromFs(fs, rootdir, TableName.META_TABLE_NAME);
} catch (TableInfoMissingException e) { } catch (TableInfoMissingException e) {
TableDescriptorBuilder builder = createMetaTableDescriptorBuilder(conf); TableDescriptorBuilder builder = createMetaTableDescriptorBuilder(conf);
if (metaObserver != null) { if (metaObserver != null) {
@ -144,6 +144,7 @@ public class FSTableDescriptors implements TableDescriptors {
throw new IOException("Failed update hbase:meta table descriptor"); throw new IOException("Failed update hbase:meta table descriptor");
} }
LOG.info("Updated hbase:meta table descriptor to {}", p); LOG.info("Updated hbase:meta table descriptor to {}", p);
return td;
} }
} }
@ -187,57 +188,45 @@ public class FSTableDescriptors implements TableDescriptors {
.build()); .build());
} }
@Override
public void setCacheOn() throws IOException {
this.cache.clear();
this.usecache = true;
}
@Override
public void setCacheOff() throws IOException {
this.usecache = false;
this.cache.clear();
}
@VisibleForTesting @VisibleForTesting
public boolean isUsecache() { protected boolean isUsecache() {
return this.usecache; return this.usecache;
} }
/** /**
* Get the current table descriptor for the given table, or null if none exists. * Get the current table descriptor for the given table, or null if none exists.
* * <p/>
* Uses a local cache of the descriptor but still checks the filesystem on each call * Uses a local cache of the descriptor but still checks the filesystem on each call if
* to see if a newer file has been created since the cached one was read. * {@link #fsvisited} is not {@code true}, i.e, we haven't done a full scan yet, to see if a newer
* file has been created since the cached one was read.
*/ */
@Override @Override
@Nullable @Nullable
public TableDescriptor get(final TableName tablename) public TableDescriptor get(TableName tableName) {
throws IOException {
invocations++; invocations++;
if (usecache) { if (usecache) {
// Look in cache of descriptors. // Look in cache of descriptors.
TableDescriptor cachedtdm = this.cache.get(tablename); TableDescriptor cachedtdm = this.cache.get(tableName);
if (cachedtdm != null) { if (cachedtdm != null) {
cachehits++; cachehits++;
return cachedtdm; return cachedtdm;
} }
// we do not need to go to fs any more
if (fsvisited) {
return null;
}
} }
TableDescriptor tdmt = null; TableDescriptor tdmt = null;
try { try {
tdmt = getTableDescriptorFromFs(fs, rootdir, tablename); tdmt = getTableDescriptorFromFs(fs, rootdir, tableName);
} catch (NullPointerException e) {
LOG.debug("Exception during readTableDecriptor. Current table name = "
+ tablename, e);
} catch (TableInfoMissingException e) { } catch (TableInfoMissingException e) {
// ignore. This is regular operation // ignore. This is regular operation
} catch (IOException ioe) { } catch (NullPointerException | IOException ioe) {
LOG.debug("Exception during readTableDecriptor. Current table name = " LOG.debug("Exception during readTableDecriptor. Current table name = " + tableName, ioe);
+ tablename, ioe);
} }
// last HTD written wins // last HTD written wins
if (usecache && tdmt != null) { if (usecache && tdmt != null) {
this.cache.put(tablename, tdmt); this.cache.put(tableName, tdmt);
} }
return tdmt; return tdmt;
@ -249,29 +238,22 @@ public class FSTableDescriptors implements TableDescriptors {
@Override @Override
public Map<String, TableDescriptor> getAll() throws IOException { public Map<String, TableDescriptor> getAll() throws IOException {
Map<String, TableDescriptor> tds = new TreeMap<>(); Map<String, TableDescriptor> tds = new TreeMap<>();
if (fsvisited && usecache) { if (fsvisited) {
for (Map.Entry<TableName, TableDescriptor> entry: this.cache.entrySet()) { for (Map.Entry<TableName, TableDescriptor> entry: this.cache.entrySet()) {
tds.put(entry.getKey().getNameWithNamespaceInclAsString(), entry.getValue()); tds.put(entry.getKey().getNameWithNamespaceInclAsString(), entry.getValue());
} }
} else { } else {
LOG.trace("Fetching table descriptors from the filesystem."); LOG.trace("Fetching table descriptors from the filesystem.");
boolean allvisited = true; boolean allvisited = usecache;
for (Path d : FSUtils.getTableDirs(fs, rootdir)) { for (Path d : FSUtils.getTableDirs(fs, rootdir)) {
TableDescriptor htd = null; TableDescriptor htd = get(CommonFSUtils.getTableName(d));
try {
htd = get(CommonFSUtils.getTableName(d));
} catch (FileNotFoundException fnfe) {
// inability of retrieving one HTD shouldn't stop getting the remaining
LOG.warn("Trouble retrieving htd", fnfe);
}
if (htd == null) { if (htd == null) {
allvisited = false; allvisited = false;
continue;
} else { } else {
tds.put(htd.getTableName().getNameWithNamespaceInclAsString(), htd); tds.put(htd.getTableName().getNameWithNamespaceInclAsString(), htd);
} }
fsvisited = allvisited;
} }
fsvisited = allvisited;
} }
return tds; return tds;
} }
@ -281,35 +263,48 @@ public class FSTableDescriptors implements TableDescriptors {
* @see #get(org.apache.hadoop.hbase.TableName) * @see #get(org.apache.hadoop.hbase.TableName)
*/ */
@Override @Override
public Map<String, TableDescriptor> getByNamespace(String name) public Map<String, TableDescriptor> getByNamespace(String name) throws IOException {
throws IOException {
Map<String, TableDescriptor> htds = new TreeMap<>(); Map<String, TableDescriptor> htds = new TreeMap<>();
List<Path> tableDirs = List<Path> tableDirs =
FSUtils.getLocalTableDirs(fs, CommonFSUtils.getNamespaceDir(rootdir, name)); FSUtils.getLocalTableDirs(fs, CommonFSUtils.getNamespaceDir(rootdir, name));
for (Path d: tableDirs) { for (Path d : tableDirs) {
TableDescriptor htd = null; TableDescriptor htd = get(CommonFSUtils.getTableName(d));
try { if (htd == null) {
htd = get(CommonFSUtils.getTableName(d)); continue;
} catch (FileNotFoundException fnfe) {
// inability of retrieving one HTD shouldn't stop getting the remaining
LOG.warn("Trouble retrieving htd", fnfe);
} }
if (htd == null) continue;
htds.put(CommonFSUtils.getTableName(d).getNameAsString(), htd); htds.put(CommonFSUtils.getTableName(d).getNameAsString(), htd);
} }
return htds; return htds;
} }
/**
* Adds (or updates) the table descriptor to the FileSystem
* and updates the local cache with it.
*/
@Override @Override
public void update(TableDescriptor htd) throws IOException { public void update(TableDescriptor td, boolean cacheOnly) throws IOException {
// TODO: in fact this method will only be called at master side, so fsreadonly and usecache will
// always be true. In general, we'd better have a ReadOnlyFSTableDesciptors for HRegionServer
// but now, HMaster extends HRegionServer, so unless making use of generic, we can not have
// different implementations for HMaster and HRegionServer. Revisit this when we make HMaster
// not extend HRegionServer in the future.
if (fsreadonly) { if (fsreadonly) {
throw new NotImplementedException("Cannot add a table descriptor - in read only mode"); throw new UnsupportedOperationException("Cannot add a table descriptor - in read only mode");
} }
updateTableDescriptor(htd); if (!cacheOnly) {
updateTableDescriptor(td);
}
if (usecache) {
this.cache.put(td.getTableName(), td);
}
}
@VisibleForTesting
Path updateTableDescriptor(TableDescriptor td) throws IOException {
TableName tableName = td.getTableName();
Path tableDir = getTableDir(tableName);
Path p = writeTableDescriptor(fs, td, tableDir, getTableInfoPath(tableDir));
if (p == null) {
throw new IOException("Failed update");
}
LOG.info("Updated tableinfo=" + p);
return p;
} }
/** /**
@ -371,22 +366,21 @@ public class FSTableDescriptors implements TableDescriptors {
/** /**
* Find the most current table info file in the given directory * Find the most current table info file in the given directory
* * <p/>
* Looks within the given directory for any table info files * Looks within the given directory for any table info files and takes the 'current' one - meaning
* and takes the 'current' one - meaning the one with the highest sequence number if present * the one with the highest sequence number if present or no sequence number at all if none exist
* or no sequence number at all if none exist (for backward compatibility from before there * (for backward compatibility from before there were sequence numbers).
* were sequence numbers). * <p/>
* If there are multiple possible files found * If there are multiple possible files found and the we're not in read only mode it also deletes
* and the we're not in read only mode it also deletes the older files. * the older files.
*
* @return The file status of the current table info file or null if it does not exist * @return The file status of the current table info file or null if it does not exist
* @throws IOException
*/ */
// only visible for FSTableDescriptorMigrationToSubdir, can be removed with that private static FileStatus getCurrentTableInfoStatus(FileSystem fs, Path dir,
static FileStatus getCurrentTableInfoStatus(FileSystem fs, Path dir, boolean removeOldFiles) boolean removeOldFiles) throws IOException {
throws IOException {
FileStatus[] status = CommonFSUtils.listStatus(fs, dir, TABLEINFO_PATHFILTER); FileStatus[] status = CommonFSUtils.listStatus(fs, dir, TABLEINFO_PATHFILTER);
if (status == null || status.length < 1) return null; if (status == null || status.length < 1) {
return null;
}
FileStatus mostCurrent = null; FileStatus mostCurrent = null;
for (FileStatus file : status) { for (FileStatus file : status) {
if (mostCurrent == null || TABLEINFO_FILESTATUS_COMPARATOR.compare(file, mostCurrent) < 0) { if (mostCurrent == null || TABLEINFO_FILESTATUS_COMPARATOR.compare(file, mostCurrent) < 0) {
@ -410,16 +404,16 @@ public class FSTableDescriptors implements TableDescriptors {
} }
/** /**
* Compare {@link FileStatus} instances by {@link Path#getName()}. Returns in * Compare {@link FileStatus} instances by {@link Path#getName()}. Returns in reverse order.
* reverse order.
*/ */
@VisibleForTesting @VisibleForTesting
static final Comparator<FileStatus> TABLEINFO_FILESTATUS_COMPARATOR = static final Comparator<FileStatus> TABLEINFO_FILESTATUS_COMPARATOR =
new Comparator<FileStatus>() { new Comparator<FileStatus>() {
@Override @Override
public int compare(FileStatus left, FileStatus right) { public int compare(FileStatus left, FileStatus right) {
return right.compareTo(left); return right.compareTo(left);
}}; }
};
/** /**
* Return the table directory in HDFS * Return the table directory in HDFS
@ -439,12 +433,13 @@ public class FSTableDescriptors implements TableDescriptors {
/** /**
* Width of the sequenceid that is a suffix on a tableinfo file. * Width of the sequenceid that is a suffix on a tableinfo file.
*/ */
@VisibleForTesting static final int WIDTH_OF_SEQUENCE_ID = 10; @VisibleForTesting
static final int WIDTH_OF_SEQUENCE_ID = 10;
/* /**
* @param number Number to use as suffix. * @param number Number to use as suffix.
* @return Returns zero-prefixed decimal version of passed * @return Returns zero-prefixed decimal version of passed number (Does absolute in case number is
* number (Does absolute in case number is negative). * negative).
*/ */
private static String formatTableInfoSequenceId(final int number) { private static String formatTableInfoSequenceId(final int number) {
byte [] b = new byte[WIDTH_OF_SEQUENCE_ID]; byte [] b = new byte[WIDTH_OF_SEQUENCE_ID];
@ -468,12 +463,19 @@ public class FSTableDescriptors implements TableDescriptors {
* @param p Path to a <code>.tableinfo</code> file. * @param p Path to a <code>.tableinfo</code> file.
* @return The current editid or 0 if none found. * @return The current editid or 0 if none found.
*/ */
@VisibleForTesting static int getTableInfoSequenceId(final Path p) { @VisibleForTesting
if (p == null) return 0; static int getTableInfoSequenceId(final Path p) {
if (p == null) {
return 0;
}
Matcher m = TABLEINFO_FILE_REGEX.matcher(p.getName()); Matcher m = TABLEINFO_FILE_REGEX.matcher(p.getName());
if (!m.matches()) throw new IllegalArgumentException(p.toString()); if (!m.matches()) {
throw new IllegalArgumentException(p.toString());
}
String suffix = m.group(2); String suffix = m.group(2);
if (suffix == null || suffix.length() <= 0) return 0; if (suffix == null || suffix.length() <= 0) {
return 0;
}
return Integer.parseInt(m.group(2)); return Integer.parseInt(m.group(2));
} }
@ -481,7 +483,8 @@ public class FSTableDescriptors implements TableDescriptors {
* @param sequenceid * @param sequenceid
* @return Name of tableinfo file. * @return Name of tableinfo file.
*/ */
@VisibleForTesting static String getTableInfoFileName(final int sequenceid) { @VisibleForTesting
static String getTableInfoFileName(final int sequenceid) {
return TABLEINFO_FILE_PREFIX + "." + formatTableInfoSequenceId(sequenceid); return TABLEINFO_FILE_PREFIX + "." + formatTableInfoSequenceId(sequenceid);
} }
@ -502,7 +505,7 @@ public class FSTableDescriptors implements TableDescriptors {
* @throws TableInfoMissingException if there is no descriptor * @throws TableInfoMissingException if there is no descriptor
*/ */
public static TableDescriptor getTableDescriptorFromFs(FileSystem fs, Path tableDir) public static TableDescriptor getTableDescriptorFromFs(FileSystem fs, Path tableDir)
throws IOException { throws IOException {
FileStatus status = getTableInfoPath(fs, tableDir, false); FileStatus status = getTableInfoPath(fs, tableDir, false);
if (status == null) { if (status == null) {
throw new TableInfoMissingException("No table descriptor file under " + tableDir); throw new TableInfoMissingException("No table descriptor file under " + tableDir);
@ -529,29 +532,6 @@ public class FSTableDescriptors implements TableDescriptors {
return htd; return htd;
} }
/**
* Update table descriptor on the file system
* @throws IOException Thrown if failed update.
* @throws NotImplementedException if in read only mode
*/
@VisibleForTesting
Path updateTableDescriptor(TableDescriptor td) throws IOException {
if (fsreadonly) {
throw new NotImplementedException("Cannot update a table descriptor - in read only mode");
}
TableName tableName = td.getTableName();
Path tableDir = getTableDir(tableName);
Path p = writeTableDescriptor(fs, td, tableDir, getTableInfoPath(tableDir));
if (p == null) {
throw new IOException("Failed update");
}
LOG.info("Updated tableinfo=" + p);
if (usecache) {
this.cache.put(td.getTableName(), td);
}
return p;
}
/** /**
* Deletes files matching the table info file pattern within the given directory * Deletes files matching the table info file pattern within the given directory
* whose sequenceId is at most the given max sequenceId. * whose sequenceId is at most the given max sequenceId.
@ -574,18 +554,15 @@ public class FSTableDescriptors implements TableDescriptors {
} }
/** /**
* Attempts to write a new table descriptor to the given table's directory. * Attempts to write a new table descriptor to the given table's directory. It first writes it to
* It first writes it to the .tmp dir then uses an atomic rename to move it into place. * the .tmp dir then uses an atomic rename to move it into place. It begins at the
* It begins at the currentSequenceId + 1 and tries 10 times to find a new sequence number * currentSequenceId + 1 and tries 10 times to find a new sequence number not already in use.
* not already in use. * <p/>
* Removes the current descriptor file if passed in. * Removes the current descriptor file if passed in.
*
* @return Descriptor file or null if we failed write. * @return Descriptor file or null if we failed write.
*/ */
private static Path writeTableDescriptor(final FileSystem fs, private static Path writeTableDescriptor(final FileSystem fs, final TableDescriptor htd,
final TableDescriptor htd, final Path tableDir, final Path tableDir, final FileStatus currentDescriptorFile) throws IOException {
final FileStatus currentDescriptorFile)
throws IOException {
// Get temporary dir into which we'll first write a file to avoid half-written file phenomenon. // Get temporary dir into which we'll first write a file to avoid half-written file phenomenon.
// This directory is never removed to avoid removing it out from under a concurrent writer. // This directory is never removed to avoid removing it out from under a concurrent writer.
Path tmpTableDir = new Path(tableDir, TMP_DIR); Path tmpTableDir = new Path(tableDir, TMP_DIR);

View File

@ -339,17 +339,9 @@ public class MockMasterServices extends MockNoopMasterServices {
} }
@Override @Override
public void update(TableDescriptor htd) throws IOException { public void update(TableDescriptor htd, boolean cacheOnly) throws IOException {
// noop // noop
} }
@Override
public void setCacheOn() throws IOException {
}
@Override
public void setCacheOff() throws IOException {
}
}; };
} }

View File

@ -71,7 +71,7 @@ public class TestReadAndWriteRegionInfoFile {
RegionInfo ri = RegionInfoBuilder.FIRST_META_REGIONINFO; RegionInfo ri = RegionInfoBuilder.FIRST_META_REGIONINFO;
// Create a region. That'll write the .regioninfo file. // Create a region. That'll write the .regioninfo file.
FSTableDescriptors fsTableDescriptors = new FSTableDescriptors(FS, ROOT_DIR); FSTableDescriptors fsTableDescriptors = new FSTableDescriptors(FS, ROOT_DIR);
FSTableDescriptors.tryUpdateMetaTableDescriptor(CONF, FS, ROOT_DIR, null); FSTableDescriptors.tryUpdateAndGetMetaTableDescriptor(CONF, FS, ROOT_DIR, null);
HRegion r = HBaseTestingUtility.createRegionAndWAL(ri, ROOT_DIR, CONF, HRegion r = HBaseTestingUtility.createRegionAndWAL(ri, ROOT_DIR, CONF,
fsTableDescriptors.get(TableName.META_TABLE_NAME)); fsTableDescriptors.get(TableName.META_TABLE_NAME));
// Get modtime on the file. // Get modtime on the file.

View File

@ -24,7 +24,6 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
@ -38,7 +37,6 @@ import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.TableDescriptors; import org.apache.hadoop.hbase.TableDescriptors;
import org.apache.hadoop.hbase.TableExistsException;
import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.TableDescriptor; import org.apache.hadoop.hbase.client.TableDescriptor;
@ -93,7 +91,7 @@ public class TestFSTableDescriptors {
FileStatus[] statuses = fs.listStatus(testdir); FileStatus[] statuses = fs.listStatus(testdir);
assertTrue("statuses.length=" + statuses.length, statuses.length == 1); assertTrue("statuses.length=" + statuses.length, statuses.length == 1);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
fstd.updateTableDescriptor(htd); fstd.update(htd);
} }
statuses = fs.listStatus(testdir); statuses = fs.listStatus(testdir);
assertTrue(statuses.length == 1); assertTrue(statuses.length == 1);
@ -218,8 +216,7 @@ public class TestFSTableDescriptors {
Path rootdir = new Path(UTIL.getDataTestDir(), name); Path rootdir = new Path(UTIL.getDataTestDir(), name);
FSTableDescriptors htds = new FSTableDescriptors(fs, rootdir) { FSTableDescriptors htds = new FSTableDescriptors(fs, rootdir) {
@Override @Override
public TableDescriptor get(TableName tablename) public TableDescriptor get(TableName tablename) {
throws TableExistsException, FileNotFoundException, IOException {
LOG.info(tablename + ", cachehits=" + this.cachehits); LOG.info(tablename + ", cachehits=" + this.cachehits);
return super.get(tablename); return super.get(tablename);
} }
@ -240,7 +237,7 @@ public class TestFSTableDescriptors {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(TableName.valueOf(name + i)); TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(TableName.valueOf(name + i));
builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of("" + i)); builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of("" + i));
htds.updateTableDescriptor(builder.build()); htds.update(builder.build());
} }
// Wait a while so mod time we write is for sure different. // Wait a while so mod time we write is for sure different.
Thread.sleep(100); Thread.sleep(100);
@ -276,7 +273,7 @@ public class TestFSTableDescriptors {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(TableName.valueOf(name + i)); TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(TableName.valueOf(name + i));
builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of("" + i)); builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of("" + i));
htds.updateTableDescriptor(builder.build()); htds.update(builder.build());
} }
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
assertNotNull("Expected HTD, got null instead", htds.get(TableName.valueOf(name + i))); assertNotNull("Expected HTD, got null instead", htds.get(TableName.valueOf(name + i)));
@ -371,13 +368,18 @@ public class TestFSTableDescriptors {
// random will only increase the cachehit by 1 // random will only increase the cachehit by 1
assertEquals(nonchtds.getAll().size(), chtds.getAll().size() + 1); assertEquals(nonchtds.getAll().size(), chtds.getAll().size() + 1);
for (Map.Entry<String, TableDescriptor> entry: nonchtds.getAll().entrySet()) { for (Map.Entry<String, TableDescriptor> entry : chtds.getAll().entrySet()) {
String t = (String) entry.getKey(); String t = (String) entry.getKey();
TableDescriptor nchtd = entry.getValue(); TableDescriptor nchtd = entry.getValue();
assertTrue("expected " + htd.toString() + assertTrue(
" got: " + chtds.get(TableName.valueOf(t)).toString(), "expected " + htd.toString() + " got: " + chtds.get(TableName.valueOf(t)).toString(),
(nchtd.equals(chtds.get(TableName.valueOf(t))))); (nchtd.equals(chtds.get(TableName.valueOf(t)))));
} }
// this is by design, for FSTableDescriptor with cache enabled, once we have done a full scan
// and load all the table descriptors to cache, we will not go to file system again, as the only
// way to update table descriptor is to through us so we can cache it when updating.
assertNotNull(nonchtds.get(random));
assertNull(chtds.get(random));
} }
@Test @Test
@ -474,8 +476,7 @@ public class TestFSTableDescriptors {
} }
@Override @Override
public TableDescriptor get(TableName tablename) public TableDescriptor get(TableName tablename) {
throws TableExistsException, FileNotFoundException, IOException {
LOG.info((super.isUsecache() ? "Cached" : "Non-Cached") + LOG.info((super.isUsecache() ? "Cached" : "Non-Cached") +
" TableDescriptor.get() on " + tablename + ", cachehits=" + this.cachehits); " TableDescriptor.get() on " + tablename + ", cachehits=" + this.cachehits);
return super.get(tablename); return super.get(tablename);