Skip to content
Open
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,25 @@ public static void scanMetaForTableRegions(Connection connection, Visitor visito
scanMetaForTableRegions(connection, visitor, tableName, CatalogReplicaMode.NONE);
}

/**
* Scan meta for regions of {@code tableName}, starting at the meta row derived from
* {@code startRow} and returning at most {@code rowLimit} rows. {@code startRow} must be a region
* start-key boundary (e.g. the end key of the previously visited region), or {@code null}/empty
* to start at the first region. The combination of {@code rowLimit} and the existing
* {@code setLimit + PREAD} machinery in {@link #getMetaScan(Configuration, int)} causes the
* underlying scan to complete in a single RPC.
*/
public static void scanMetaForTableRegions(Connection connection, Visitor visitor,
TableName tableName, byte[] startRow, int rowLimit, CatalogReplicaMode metaReplicaMode)
throws IOException {
byte[] metaStart = (startRow == null || startRow.length == 0)
? getTableStartRowForMeta(tableName, QueryType.REGION)
: RegionInfo.createRegionName(tableName, startRow, HConstants.ZEROES, false);
byte[] metaStop = getTableStopRowForMeta(tableName, QueryType.REGION);
scanMeta(connection, metaStart, metaStop, QueryType.REGION, null, rowLimit, visitor,
metaReplicaMode);
}

private static void scanMeta(Connection connection, TableName table, QueryType type, int maxRows,
final Visitor visitor, CatalogReplicaMode metaReplicaMode) throws IOException {
scanMeta(connection, getTableStartRowForMeta(table, type), getTableStopRowForMeta(table, type),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1146,11 +1146,7 @@ rpcControllerFactory, getMetaLookupPool(), connectionConfig.getMetaReadRpcTimeou
}
} finally {
if (lockedUserRegion) {
userRegionLock.unlock();
// update duration of the lock being held
if (metrics != null) {
metrics.updateUserRegionLockHeld(EnvironmentEdgeManager.currentTime() - lockStartTime);
}
releaseUserRegionLock(lockStartTime);
}
}
try {
Expand Down Expand Up @@ -1185,6 +1181,19 @@ void takeUserRegionLock() throws IOException {
}
}

/**
* Release {@link #userRegionLock} previously acquired via {@link #takeUserRegionLock()} and
* record the held duration in metrics.
* @param lockStartTimeMs value of {@link EnvironmentEdgeManager#currentTime()} captured
* immediately after {@link #takeUserRegionLock()} returned
*/
void releaseUserRegionLock(long lockStartTimeMs) {
userRegionLock.unlock();
if (metrics != null) {
metrics.updateUserRegionLockHeld(EnvironmentEdgeManager.currentTime() - lockStartTimeMs);
}
}

/**
* Put a newly discovered HRegionLocation into the cache.
* @param tableName The table name.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.trace.TableSpanBuilder;
import org.apache.hadoop.hbase.trace.TraceUtil;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.yetus.audience.InterfaceAudience;

import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils;
Expand Down Expand Up @@ -111,6 +112,61 @@ public List<HRegionLocation> getAllRegionLocations() throws IOException {
}, HRegionLocator::getRegionNames, supplier);
}

@Override
public List<HRegionLocation> getRegionLocations(byte[] startKey, int limit) throws IOException {
if (TableName.isMetaTableName(tableName)) {
throw new IOException("getRegionLocations(startKey, limit) is not supported for hbase:meta;"
+ " use getRegionLocation(EMPTY_START_ROW) instead.");
}
final int effectiveLimit = limit > 0
? limit
: connection.getConfiguration().getInt(HConstants.HBASE_META_SCANNER_CACHING,
HConstants.DEFAULT_HBASE_META_SCANNER_CACHING);
final byte[] effectiveStart = startKey == null ? HConstants.EMPTY_START_ROW : startKey;
final CatalogReplicaMode metaReplicaMode = CatalogReplicaMode.fromString(connection
.getConfiguration().get(LOCATOR_META_REPLICAS_MODE, CatalogReplicaMode.NONE.toString()));

final Supplier<Span> supplier = new TableSpanBuilder(connection)
.setName("HRegionLocator.getRegionLocations").setTableName(tableName);
return tracedLocationFuture(() -> {
final List<HRegionLocation> out = new ArrayList<>(effectiveLimit);
MetaTableAccessor.Visitor visitor = new MetaTableAccessor.TableVisitorBase(tableName) {
@Override
public boolean visitInternal(Result result) throws IOException {
RegionLocations locs = MetaTableAccessor.getRegionLocations(result);
if (locs == null) {
return true;
}
for (HRegionLocation loc : locs.getRegionLocations()) {
if (loc != null) {
out.add(loc);
}
}
RegionLocations cleaned = locs.removeElementsWithNullLocation();
if (cleaned != null) {
connection.cacheLocation(tableName, cleaned);
}
return true;
}
};

boolean locked = false;
long lockStart = 0;
try {
connection.takeUserRegionLock();
lockStart = EnvironmentEdgeManager.currentTime();
locked = true;
MetaTableAccessor.scanMetaForTableRegions(connection, visitor, tableName, effectiveStart,
effectiveLimit, metaReplicaMode);
} finally {
if (locked) {
connection.releaseUserRegionLock(lockStart);
}
}
return out;
}, HRegionLocator::getRegionNames, supplier);
}

private static List<String> getRegionNames(List<HRegionLocation> locations) {
if (CollectionUtils.isEmpty(locations)) {
return Collections.emptyList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.util.Pair;
Expand Down Expand Up @@ -130,6 +131,44 @@ default List<HRegionLocation> getRegionLocations(byte[] row) throws IOException
*/
List<HRegionLocation> getAllRegionLocations() throws IOException;

/**
* Bulk lookup of region locations from {@code hbase:meta} in a single RPC, starting at
* {@code startKey} (region start-key boundary, inclusive) and returning at most {@code limit}
* regions in start-key order.
* <p/>
* The returned list includes all replicas of each region (matching
* {@link #getAllRegionLocations()}), and the result is also written to the connection's region
* location cache.
* <p/>
* Ordering: regions are returned in ascending region start-key order (the natural order of
* {@code hbase:meta} rows for a single table). Within each region, replicas are returned in
* ascending replica-id order (replica 0, then 1, then 2, ...). Split parents and offline regions
* are filtered out, which may cause a page to contain fewer than {@code limit} regions but never
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this filtering happening? I didn't see any test coverage either. The existing methods don't do any such filtering correct, so is this even needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filtering is already implemented and happens in inside MetaTableAccessor.DefaultVisitorBase#visit(). Call chain: HRegionLocator#getRegionLocations() -> MetaTableAccessor.TableVisitorBase#visit() -> MetaTableAccessor.DefaultVisitorBase#visit().

Copy link
Copy Markdown
Contributor Author

@sanjeet006py sanjeet006py May 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am reusing the filtering logic so, no test coverage needed.

* disturbs ordering of the survivors.
* <p/>
* To page through all regions of a table, call repeatedly passing
* {@code last.getRegion().getEndKey()} as the next {@code startKey}, where {@code last} is the
* final element of the previous response. All replicas of a region share the same
* {@link RegionInfo}, so the last entry's end key is the correct cursor regardless of which
* replica it is. Pass {@code null} for the first call. Stop paging when the returned list is
* empty or when the last region's end key is {@link HConstants#EMPTY_END_ROW} (zero-length) -
* that signals the end of the table; passing it back in would re-scan from the beginning since by
* convention an empty start key means "from the first region".
* <p/>
* Unlike {@link #getAllRegionLocations()}, this method performs at most one RPC against
* {@code hbase:meta} per invocation, so its latency is bounded by {@code limit} rather than table
* size. Suitable for callers that wrap meta lookups in a lock with a fixed timeout, e.g. for bulk
* region-cache warmup.
* @param startKey region start-key to begin scanning from (inclusive); {@code null} or empty
* starts from the first region
* @param limit maximum number of regions to return; if &lt;= 0, falls back to
* {@code hbase.meta.scanner.caching}
* @return up to {@code limit} {@link HRegionLocation}s in start-key order, possibly empty when no
* more regions exist
* @throws IOException if a remote or network exception occurs
*/
List<HRegionLocation> getRegionLocations(byte[] startKey, int limit) throws IOException;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is changing a public interface on a stable branch, this will break external code implementing the interface, why not have a default implementation perhaps throw unsupported or return a blank list?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this is overloading an existing methods that takes a data row to find its locations, which is a totally different semantic and so reusing that name for this can be very confusing. Perhaps just call this getLocations or may be you can come up with a better name.


/**
* Gets the starting row key for every region in the currently open table.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ public List<HRegionLocation> getAllRegionLocations() throws IOException {
return rawLocations;
}

@Override
public List<HRegionLocation> getRegionLocations(byte[] startKey, int limit) throws IOException {
// No need to page as region locations are already in-memory.
return getAllRegionLocations();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually breaking the contract established by the javadoc by always returning all regions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was also wondering same. Thanks for bringing this up. Instead of implementing paging here, shall I throw an exception saying use getAllRegionLocations as I don't see advantage of paging in here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that works too, as long as you call out the fact that all implementation may not support it and that they should fallback to getAllRegionLocations.


@Override
public TableName getName() {
return tableName;
Expand Down
Loading
Loading