Skip to content

Commit 47536f4

Browse files
authored
HBASE-28900: Avoid resetting the bucket cache during recovery from persistence. (#6360) (#6365)
Signed-off-by: Wellington Chevreuil <wchevreuil@apache.org>
1 parent 6b5cb90 commit 47536f4

File tree

3 files changed

+77
-8
lines changed

3 files changed

+77
-8
lines changed

hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/bucket/BucketAllocator.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public long allocate() {
126126
return offset;
127127
}
128128

129-
public void addAllocation(long offset) throws BucketAllocatorException {
129+
public boolean addAllocation(long offset) throws BucketAllocatorException {
130130
offset -= baseOffset;
131131
if (offset < 0 || offset % itemAllocationSize != 0)
132132
throw new BucketAllocatorException("Attempt to add allocation for bad offset: " + offset
@@ -137,10 +137,14 @@ public void addAllocation(long offset) throws BucketAllocatorException {
137137
if (matchFound) freeList[i - 1] = freeList[i];
138138
else if (freeList[i] == idx) matchFound = true;
139139
}
140-
if (!matchFound) throw new BucketAllocatorException(
141-
"Couldn't find match for index " + idx + " in free list");
140+
if (!matchFound) {
141+
LOG.warn("We found more entries for bucket starting at offset {} for blocks of {} size. "
142+
+ "Skipping entry at cache offset {}", baseOffset, itemAllocationSize, offset);
143+
return false;
144+
}
142145
++usedCount;
143146
--freeCount;
147+
return true;
144148
}
145149

146150
private void free(long offset) {
@@ -402,10 +406,11 @@ public BucketSizeInfo roundUpToBucketSizeInfo(int blockSize) {
402406
bsi.instantiateBucket(b);
403407
reconfigured[bucketNo] = true;
404408
}
405-
realCacheSize.add(foundLen);
406-
buckets[bucketNo].addAllocation(foundOffset);
407-
usedSize += buckets[bucketNo].getItemAllocationSize();
408-
bucketSizeInfos[bucketSizeIndex].blockAllocated(b);
409+
if (buckets[bucketNo].addAllocation(foundOffset)) {
410+
realCacheSize.add(foundLen);
411+
usedSize += buckets[bucketNo].getItemAllocationSize();
412+
bucketSizeInfos[bucketSizeIndex].blockAllocated(b);
413+
}
409414
}
410415

411416
if (sizeNotMatchedCount > 0) {

hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/bucket/BucketCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -842,7 +842,7 @@ private boolean doEvictBlock(BlockCacheKey cacheKey, BucketEntry bucketEntry,
842842
* it is {@link ByteBuffAllocator#putbackBuffer}.
843843
* </pre>
844844
*/
845-
private Recycler createRecycler(final BucketEntry bucketEntry) {
845+
public Recycler createRecycler(final BucketEntry bucketEntry) {
846846
return () -> {
847847
freeBucketEntry(bucketEntry);
848848
return;

hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/bucket/TestRecoveryPersistentBucketCache.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
package org.apache.hadoop.hbase.io.hfile.bucket;
1919

2020
import static org.apache.hadoop.hbase.io.hfile.CacheConfig.BUCKETCACHE_PERSIST_INTERVAL_KEY;
21+
import static org.apache.hadoop.hbase.io.hfile.bucket.BucketCache.ACCEPT_FACTOR_CONFIG_NAME;
2122
import static org.apache.hadoop.hbase.io.hfile.bucket.BucketCache.DEFAULT_ERROR_TOLERATION_DURATION;
23+
import static org.apache.hadoop.hbase.io.hfile.bucket.BucketCache.EXTRA_FREE_FACTOR_CONFIG_NAME;
24+
import static org.apache.hadoop.hbase.io.hfile.bucket.BucketCache.MIN_FACTOR_CONFIG_NAME;
2225
import static org.junit.Assert.assertEquals;
2326
import static org.junit.Assert.assertNull;
2427
import static org.junit.Assert.assertTrue;
@@ -199,6 +202,67 @@ public void testValidateCacheInitialization() throws Exception {
199202
TEST_UTIL.cleanupTestDir();
200203
}
201204

205+
@Test
206+
public void testBucketCacheRecoveryWithAllocationInconsistencies() throws Exception {
207+
HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
208+
Path testDir = TEST_UTIL.getDataTestDir();
209+
TEST_UTIL.getTestFileSystem().mkdirs(testDir);
210+
Configuration conf = HBaseConfiguration.create();
211+
// Disables the persister thread by setting its interval to MAX_VALUE
212+
conf.setLong(BUCKETCACHE_PERSIST_INTERVAL_KEY, Long.MAX_VALUE);
213+
conf.setDouble(MIN_FACTOR_CONFIG_NAME, 0.99);
214+
conf.setDouble(ACCEPT_FACTOR_CONFIG_NAME, 1);
215+
conf.setDouble(EXTRA_FREE_FACTOR_CONFIG_NAME, 0.01);
216+
int[] bucketSizes = new int[] { 8 * 1024 + 1024 };
217+
BucketCache bucketCache = new BucketCache("file:" + testDir + "/bucket.cache", 36 * 1024, 8192,
218+
bucketSizes, writeThreads, writerQLen, testDir + "/bucket.persistence",
219+
DEFAULT_ERROR_TOLERATION_DURATION, conf);
220+
assertTrue(bucketCache.waitForCacheInitialization(1000));
221+
assertTrue(
222+
bucketCache.isCacheInitialized("testBucketCacheRecovery") && bucketCache.isCacheEnabled());
223+
224+
CacheTestUtils.HFileBlockPair[] blocks = CacheTestUtils.generateHFileBlocks(8192, 5);
225+
226+
// Add four blocks
227+
cacheAndWaitUntilFlushedToBucket(bucketCache, blocks[0].getBlockName(), blocks[0].getBlock());
228+
cacheAndWaitUntilFlushedToBucket(bucketCache, blocks[1].getBlockName(), blocks[1].getBlock());
229+
cacheAndWaitUntilFlushedToBucket(bucketCache, blocks[2].getBlockName(), blocks[2].getBlock());
230+
cacheAndWaitUntilFlushedToBucket(bucketCache, blocks[3].getBlockName(), blocks[3].getBlock());
231+
232+
// creates a entry for a 5th block with the same cache offset of the 1st block. Just add it
233+
// straight to the backingMap, bypassing caching, in order to fabricate an inconsistency
234+
BucketEntry bucketEntry =
235+
new BucketEntry(bucketCache.backingMap.get(blocks[0].getBlockName()).offset(),
236+
blocks[4].getBlock().getSerializedLength(), blocks[4].getBlock().getOnDiskSizeWithHeader(),
237+
0, false, bucketCache::createRecycler, blocks[4].getBlock().getByteBuffAllocator());
238+
bucketEntry.setDeserializerReference(blocks[4].getBlock().getDeserializer());
239+
bucketCache.getBackingMap().put(blocks[4].getBlockName(), bucketEntry);
240+
241+
// saves the current state of the cache: 5 blocks in the map, but we only have cached 4. The
242+
// 5th block has same cache offset as the first
243+
bucketCache.persistToFile();
244+
245+
BucketCache newBucketCache = new BucketCache("file:" + testDir + "/bucket.cache", 36 * 1024,
246+
8192, bucketSizes, writeThreads, writerQLen, testDir + "/bucket.persistence",
247+
DEFAULT_ERROR_TOLERATION_DURATION, conf);
248+
while (!newBucketCache.getBackingMapValidated().get()) {
249+
Thread.sleep(10);
250+
}
251+
252+
assertNull(newBucketCache.getBlock(blocks[4].getBlockName(), false, false, false));
253+
// The backing map entry with key blocks[0].getBlockName() for the may point to a valid entry
254+
// or null based on different ordering of the keys in the backing map.
255+
// Hence, skipping the check for that key.
256+
assertEquals(blocks[1].getBlock(),
257+
newBucketCache.getBlock(blocks[1].getBlockName(), false, false, false));
258+
assertEquals(blocks[2].getBlock(),
259+
newBucketCache.getBlock(blocks[2].getBlockName(), false, false, false));
260+
assertEquals(blocks[3].getBlock(),
261+
newBucketCache.getBlock(blocks[3].getBlockName(), false, false, false));
262+
assertEquals(4, newBucketCache.backingMap.size());
263+
TEST_UTIL.cleanupTestDir();
264+
}
265+
202266
private void waitUntilFlushedToBucket(BucketCache cache, BlockCacheKey cacheKey)
203267
throws InterruptedException {
204268
Waiter.waitFor(HBaseConfiguration.create(), 12000,

0 commit comments

Comments
 (0)