HBASE-22037 Re-enable TestAvoidCellReferencesIntoShippedBlocks
[hbase.git] / hbase-client / src / main / java / org / apache / hadoop / hbase / client / MetaCache.java
blobfde2838acc266bad59a1d6671db4b3beb8df845f
1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package org.apache.hadoop.hbase.client;
21 import static org.apache.hadoop.hbase.util.ConcurrentMapUtils.computeIfAbsent;
23 import java.util.Map;
24 import java.util.Map.Entry;
25 import java.util.Set;
26 import java.util.concurrent.ConcurrentMap;
27 import java.util.concurrent.ConcurrentNavigableMap;
28 import java.util.concurrent.CopyOnWriteArraySet;
30 import org.apache.hadoop.hbase.HConstants;
31 import org.apache.hadoop.hbase.HRegionLocation;
32 import org.apache.hadoop.hbase.RegionLocations;
33 import org.apache.hadoop.hbase.ServerName;
34 import org.apache.hadoop.hbase.TableName;
35 import org.apache.hadoop.hbase.types.CopyOnWriteArrayMap;
36 import org.apache.hadoop.hbase.util.Bytes;
37 import org.apache.yetus.audience.InterfaceAudience;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 /**
42 * A cache implementation for region locations from meta.
44 @InterfaceAudience.Private
45 public class MetaCache {
47 private static final Logger LOG = LoggerFactory.getLogger(MetaCache.class);
49 /**
50 * Map of table to table {@link HRegionLocation}s.
52 private final ConcurrentMap<TableName, ConcurrentNavigableMap<byte[], RegionLocations>>
53 cachedRegionLocations = new CopyOnWriteArrayMap<>();
55 // The presence of a server in the map implies it's likely that there is an
56 // entry in cachedRegionLocations that map to this server; but the absence
57 // of a server in this map guarantees that there is no entry in cache that
58 // maps to the absent server.
59 // The access to this attribute must be protected by a lock on cachedRegionLocations
60 private final Set<ServerName> cachedServers = new CopyOnWriteArraySet<>();
62 private final MetricsConnection metrics;
64 public MetaCache(MetricsConnection metrics) {
65 this.metrics = metrics;
68 /**
69 * Search the cache for a location that fits our table and row key.
70 * Return null if no suitable region is located.
72 * @return Null or region location found in cache.
74 public RegionLocations getCachedLocation(final TableName tableName, final byte [] row) {
75 ConcurrentNavigableMap<byte[], RegionLocations> tableLocations =
76 getTableLocations(tableName);
78 Entry<byte[], RegionLocations> e = tableLocations.floorEntry(row);
79 if (e == null) {
80 if (metrics != null) metrics.incrMetaCacheMiss();
81 return null;
83 RegionLocations possibleRegion = e.getValue();
85 // make sure that the end key is greater than the row we're looking
86 // for, otherwise the row actually belongs in the next region, not
87 // this one. the exception case is when the endkey is
88 // HConstants.EMPTY_END_ROW, signifying that the region we're
89 // checking is actually the last region in the table.
90 byte[] endKey = possibleRegion.getRegionLocation().getRegion().getEndKey();
91 // Here we do direct Bytes.compareTo and not doing CellComparator/MetaCellComparator path.
92 // MetaCellComparator is for comparing against data in META table which need special handling.
93 // Not doing that is ok for this case because
94 // 1. We are getting the Region location for the given row in non META tables only. The compare
95 // checks the given row is within the end key of the found region. So META regions are not
96 // coming in here.
97 // 2. Even if META region comes in, its end key will be empty byte[] and so Bytes.equals(endKey,
98 // HConstants.EMPTY_END_ROW) check itself will pass.
99 if (Bytes.equals(endKey, HConstants.EMPTY_END_ROW) ||
100 Bytes.compareTo(endKey, 0, endKey.length, row, 0, row.length) > 0) {
101 if (metrics != null) metrics.incrMetaCacheHit();
102 return possibleRegion;
105 // Passed all the way through, so we got nothing - complete cache miss
106 if (metrics != null) metrics.incrMetaCacheMiss();
107 return null;
111 * Put a newly discovered HRegionLocation into the cache.
112 * @param tableName The table name.
113 * @param source the source of the new location
114 * @param location the new location
116 public void cacheLocation(final TableName tableName, final ServerName source,
117 final HRegionLocation location) {
118 assert source != null;
119 byte [] startKey = location.getRegion().getStartKey();
120 ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
121 RegionLocations locations = new RegionLocations(new HRegionLocation[] {location}) ;
122 RegionLocations oldLocations = tableLocations.putIfAbsent(startKey, locations);
123 boolean isNewCacheEntry = (oldLocations == null);
124 if (isNewCacheEntry) {
125 if (LOG.isTraceEnabled()) {
126 LOG.trace("Cached location: " + location);
128 addToCachedServers(locations);
129 return;
132 // If the server in cache sends us a redirect, assume it's always valid.
133 HRegionLocation oldLocation = oldLocations.getRegionLocation(
134 location.getRegion().getReplicaId());
135 boolean force = oldLocation != null && oldLocation.getServerName() != null
136 && oldLocation.getServerName().equals(source);
138 // For redirect if the number is equal to previous
139 // record, the most common case is that first the region was closed with seqNum, and then
140 // opened with the same seqNum; hence we will ignore the redirect.
141 // There are so many corner cases with various combinations of opens and closes that
142 // an additional counter on top of seqNum would be necessary to handle them all.
143 RegionLocations updatedLocations = oldLocations.updateLocation(location, false, force);
144 if (oldLocations != updatedLocations) {
145 boolean replaced = tableLocations.replace(startKey, oldLocations, updatedLocations);
146 if (replaced && LOG.isTraceEnabled()) {
147 LOG.trace("Changed cached location to: " + location);
149 addToCachedServers(updatedLocations);
154 * Put a newly discovered HRegionLocation into the cache.
155 * @param tableName The table name.
156 * @param locations the new locations
158 public void cacheLocation(final TableName tableName, final RegionLocations locations) {
159 byte [] startKey = locations.getRegionLocation().getRegion().getStartKey();
160 ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
161 RegionLocations oldLocation = tableLocations.putIfAbsent(startKey, locations);
162 boolean isNewCacheEntry = (oldLocation == null);
163 if (isNewCacheEntry) {
164 if (LOG.isTraceEnabled()) {
165 LOG.trace("Cached location: " + locations);
167 addToCachedServers(locations);
168 return;
171 // merge old and new locations and add it to the cache
172 // Meta record might be stale - some (probably the same) server has closed the region
173 // with later seqNum and told us about the new location.
174 RegionLocations mergedLocation = oldLocation.mergeLocations(locations);
175 boolean replaced = tableLocations.replace(startKey, oldLocation, mergedLocation);
176 if (replaced && LOG.isTraceEnabled()) {
177 LOG.trace("Merged cached locations: " + mergedLocation);
179 addToCachedServers(locations);
182 private void addToCachedServers(RegionLocations locations) {
183 for (HRegionLocation loc : locations.getRegionLocations()) {
184 if (loc != null) {
185 cachedServers.add(loc.getServerName());
191 * @param tableName
192 * @return Map of cached locations for passed <code>tableName</code>
194 private ConcurrentNavigableMap<byte[], RegionLocations> getTableLocations(
195 final TableName tableName) {
196 // find the map of cached locations for this table
197 return computeIfAbsent(cachedRegionLocations, tableName,
198 () -> new CopyOnWriteArrayMap<>(Bytes.BYTES_COMPARATOR));
202 * Check the region cache to see whether a region is cached yet or not.
203 * @param tableName tableName
204 * @param row row
205 * @return Region cached or not.
207 public boolean isRegionCached(TableName tableName, final byte[] row) {
208 RegionLocations location = getCachedLocation(tableName, row);
209 return location != null;
213 * Return the number of cached region for a table. It will only be called
214 * from a unit test.
216 public int getNumberOfCachedRegionLocations(final TableName tableName) {
217 Map<byte[], RegionLocations> tableLocs = this.cachedRegionLocations.get(tableName);
218 if (tableLocs == null) {
219 return 0;
221 int numRegions = 0;
222 for (RegionLocations tableLoc : tableLocs.values()) {
223 numRegions += tableLoc.numNonNullElements();
225 return numRegions;
229 * Delete all cached entries.
231 public void clearCache() {
232 this.cachedRegionLocations.clear();
233 this.cachedServers.clear();
237 * Delete all cached entries of a server.
239 public void clearCache(final ServerName serverName) {
240 if (!this.cachedServers.contains(serverName)) {
241 return;
244 boolean deletedSomething = false;
245 synchronized (this.cachedServers) {
246 // We block here, because if there is an error on a server, it's likely that multiple
247 // threads will get the error simultaneously. If there are hundreds of thousand of
248 // region location to check, it's better to do this only once. A better pattern would
249 // be to check if the server is dead when we get the region location.
250 if (!this.cachedServers.contains(serverName)) {
251 return;
253 for (ConcurrentMap<byte[], RegionLocations> tableLocations : cachedRegionLocations.values()){
254 for (Entry<byte[], RegionLocations> e : tableLocations.entrySet()) {
255 RegionLocations regionLocations = e.getValue();
256 if (regionLocations != null) {
257 RegionLocations updatedLocations = regionLocations.removeByServer(serverName);
258 if (updatedLocations != regionLocations) {
259 if (updatedLocations.isEmpty()) {
260 deletedSomething |= tableLocations.remove(e.getKey(), regionLocations);
261 } else {
262 deletedSomething |= tableLocations.replace(e.getKey(), regionLocations,
263 updatedLocations);
269 this.cachedServers.remove(serverName);
271 if (deletedSomething) {
272 if (metrics != null) {
273 metrics.incrMetaCacheNumClearServer();
275 if (LOG.isTraceEnabled()) {
276 LOG.trace("Removed all cached region locations that map to " + serverName);
282 * Delete all cached entries of a table.
284 public void clearCache(final TableName tableName) {
285 if (LOG.isTraceEnabled()) {
286 LOG.trace("Removed all cached region locations for table " + tableName);
288 this.cachedRegionLocations.remove(tableName);
292 * Delete a cached location, no matter what it is. Called when we were told to not use cache.
293 * @param tableName tableName
294 * @param row
296 public void clearCache(final TableName tableName, final byte [] row) {
297 ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
299 RegionLocations regionLocations = getCachedLocation(tableName, row);
300 if (regionLocations != null) {
301 byte[] startKey = regionLocations.getRegionLocation().getRegion().getStartKey();
302 boolean removed = tableLocations.remove(startKey, regionLocations);
303 if (removed) {
304 if (metrics != null) {
305 metrics.incrMetaCacheNumClearRegion();
307 if (LOG.isTraceEnabled()) {
308 LOG.trace("Removed " + regionLocations + " from cache");
315 * Delete a cached location with specific replicaId.
316 * @param tableName tableName
317 * @param row row key
318 * @param replicaId region replica id
320 public void clearCache(final TableName tableName, final byte [] row, int replicaId) {
321 ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
323 RegionLocations regionLocations = getCachedLocation(tableName, row);
324 if (regionLocations != null) {
325 HRegionLocation toBeRemoved = regionLocations.getRegionLocation(replicaId);
326 if (toBeRemoved != null) {
327 RegionLocations updatedLocations = regionLocations.remove(replicaId);
328 byte[] startKey = regionLocations.getRegionLocation().getRegion().getStartKey();
329 boolean removed;
330 if (updatedLocations.isEmpty()) {
331 removed = tableLocations.remove(startKey, regionLocations);
332 } else {
333 removed = tableLocations.replace(startKey, regionLocations, updatedLocations);
336 if (removed) {
337 if (metrics != null) {
338 metrics.incrMetaCacheNumClearRegion();
340 if (LOG.isTraceEnabled()) {
341 LOG.trace("Removed " + toBeRemoved + " from cache");
349 * Delete a cached location for a table, row and server
351 public void clearCache(final TableName tableName, final byte [] row, ServerName serverName) {
352 ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
354 RegionLocations regionLocations = getCachedLocation(tableName, row);
355 if (regionLocations != null) {
356 RegionLocations updatedLocations = regionLocations.removeByServer(serverName);
357 if (updatedLocations != regionLocations) {
358 byte[] startKey = regionLocations.getRegionLocation().getRegion().getStartKey();
359 boolean removed = false;
360 if (updatedLocations.isEmpty()) {
361 removed = tableLocations.remove(startKey, regionLocations);
362 } else {
363 removed = tableLocations.replace(startKey, regionLocations, updatedLocations);
365 if (removed) {
366 if (metrics != null) {
367 metrics.incrMetaCacheNumClearRegion();
369 if (LOG.isTraceEnabled()) {
370 LOG.trace("Removed locations of table: " + tableName + " ,row: " + Bytes.toString(row)
371 + " mapping to server: " + serverName + " from cache");
379 * Deletes the cached location of the region if necessary, based on some error from source.
380 * @param hri The region in question.
382 public void clearCache(RegionInfo hri) {
383 ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(hri.getTable());
384 RegionLocations regionLocations = tableLocations.get(hri.getStartKey());
385 if (regionLocations != null) {
386 HRegionLocation oldLocation = regionLocations.getRegionLocation(hri.getReplicaId());
387 if (oldLocation == null) return;
388 RegionLocations updatedLocations = regionLocations.remove(oldLocation);
389 boolean removed;
390 if (updatedLocations != regionLocations) {
391 if (updatedLocations.isEmpty()) {
392 removed = tableLocations.remove(hri.getStartKey(), regionLocations);
393 } else {
394 removed = tableLocations.replace(hri.getStartKey(), regionLocations, updatedLocations);
396 if (removed) {
397 if (metrics != null) {
398 metrics.incrMetaCacheNumClearRegion();
400 if (LOG.isTraceEnabled()) {
401 LOG.trace("Removed " + oldLocation + " from cache");
408 public void clearCache(final HRegionLocation location) {
409 if (location == null) {
410 return;
412 TableName tableName = location.getRegion().getTable();
413 ConcurrentMap<byte[], RegionLocations> tableLocations = getTableLocations(tableName);
414 RegionLocations regionLocations = tableLocations.get(location.getRegion().getStartKey());
415 if (regionLocations != null) {
416 RegionLocations updatedLocations = regionLocations.remove(location);
417 boolean removed;
418 if (updatedLocations != regionLocations) {
419 if (updatedLocations.isEmpty()) {
420 removed = tableLocations.remove(location.getRegion().getStartKey(), regionLocations);
421 } else {
422 removed = tableLocations.replace(location.getRegion().getStartKey(), regionLocations,
423 updatedLocations);
425 if (removed) {
426 if (metrics != null) {
427 metrics.incrMetaCacheNumClearRegion();
429 if (LOG.isTraceEnabled()) {
430 LOG.trace("Removed " + location + " from cache");