Skip to content

Commit 15e4705

Browse files
jhungundwchevreuil
authored andcommitted
HBASE-28900: Avoid resetting the bucket cache during recovery from persistence. (#6360) (#6365)
Signed-off-by: Wellington Chevreuil <[email protected]>
1 parent d3fcf73 commit 15e4705

File tree

3 files changed

+74
-8
lines changed

3 files changed

+74
-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
@@ -772,7 +772,7 @@ private boolean doEvictBlock(BlockCacheKey cacheKey, BucketEntry bucketEntry,
772772
* it is {@link ByteBuffAllocator#putbackBuffer}.
773773
* </pre>
774774
*/
775-
private Recycler createRecycler(final BucketEntry bucketEntry) {
775+
public Recycler createRecycler(final BucketEntry bucketEntry) {
776776
return () -> {
777777
freeBucketEntry(bucketEntry);
778778
return;

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

Lines changed: 61 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

@@ -141,6 +144,64 @@ public void testBucketCacheEvictByHFileAfterRecovery() throws Exception {
141144
TEST_UTIL.cleanupTestDir();
142145
}
143146

147+
@Test
148+
public void testBucketCacheRecoveryWithAllocationInconsistencies() throws Exception {
149+
HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
150+
Path testDir = TEST_UTIL.getDataTestDir();
151+
TEST_UTIL.getTestFileSystem().mkdirs(testDir);
152+
Configuration conf = HBaseConfiguration.create();
153+
// Disables the persister thread by setting its interval to MAX_VALUE
154+
conf.setLong(BUCKETCACHE_PERSIST_INTERVAL_KEY, Long.MAX_VALUE);
155+
conf.setDouble(MIN_FACTOR_CONFIG_NAME, 0.99);
156+
conf.setDouble(ACCEPT_FACTOR_CONFIG_NAME, 1);
157+
conf.setDouble(EXTRA_FREE_FACTOR_CONFIG_NAME, 0.01);
158+
int[] bucketSizes = new int[] { 8 * 1024 + 1024 };
159+
BucketCache bucketCache = new BucketCache("file:" + testDir + "/bucket.cache", 36 * 1024, 8192,
160+
bucketSizes, writeThreads, writerQLen, testDir + "/bucket.persistence",
161+
DEFAULT_ERROR_TOLERATION_DURATION, conf);
162+
163+
CacheTestUtils.HFileBlockPair[] blocks = CacheTestUtils.generateHFileBlocks(8192, 5);
164+
165+
// Add four blocks
166+
cacheAndWaitUntilFlushedToBucket(bucketCache, blocks[0].getBlockName(), blocks[0].getBlock());
167+
cacheAndWaitUntilFlushedToBucket(bucketCache, blocks[1].getBlockName(), blocks[1].getBlock());
168+
cacheAndWaitUntilFlushedToBucket(bucketCache, blocks[2].getBlockName(), blocks[2].getBlock());
169+
cacheAndWaitUntilFlushedToBucket(bucketCache, blocks[3].getBlockName(), blocks[3].getBlock());
170+
171+
// creates a entry for a 5th block with the same cache offset of the 1st block. Just add it
172+
// straight to the backingMap, bypassing caching, in order to fabricate an inconsistency
173+
BucketEntry bucketEntry =
174+
new BucketEntry(bucketCache.backingMap.get(blocks[0].getBlockName()).offset(),
175+
blocks[4].getBlock().getSerializedLength(), blocks[4].getBlock().getOnDiskSizeWithHeader(),
176+
0, false, bucketCache::createRecycler, blocks[4].getBlock().getByteBuffAllocator());
177+
bucketEntry.setDeserializerReference(blocks[4].getBlock().getDeserializer());
178+
bucketCache.getBackingMap().put(blocks[4].getBlockName(), bucketEntry);
179+
180+
// saves the current state of the cache: 5 blocks in the map, but we only have cached 4. The
181+
// 5th block has same cache offset as the first
182+
bucketCache.persistToFile();
183+
184+
BucketCache newBucketCache = new BucketCache("file:" + testDir + "/bucket.cache", 36 * 1024,
185+
8192, bucketSizes, writeThreads, writerQLen, testDir + "/bucket.persistence",
186+
DEFAULT_ERROR_TOLERATION_DURATION, conf);
187+
while (!newBucketCache.getBackingMapValidated().get()) {
188+
Thread.sleep(10);
189+
}
190+
191+
assertNull(newBucketCache.getBlock(blocks[4].getBlockName(), false, false, false));
192+
// The backing map entry with key blocks[0].getBlockName() for the may point to a valid entry
193+
// or null based on different ordering of the keys in the backing map.
194+
// Hence, skipping the check for that key.
195+
assertEquals(blocks[1].getBlock(),
196+
newBucketCache.getBlock(blocks[1].getBlockName(), false, false, false));
197+
assertEquals(blocks[2].getBlock(),
198+
newBucketCache.getBlock(blocks[2].getBlockName(), false, false, false));
199+
assertEquals(blocks[3].getBlock(),
200+
newBucketCache.getBlock(blocks[3].getBlockName(), false, false, false));
201+
assertEquals(4, newBucketCache.backingMap.size());
202+
TEST_UTIL.cleanupTestDir();
203+
}
204+
144205
private void waitUntilFlushedToBucket(BucketCache cache, BlockCacheKey cacheKey)
145206
throws InterruptedException {
146207
while (!cache.backingMap.containsKey(cacheKey) || cache.ramCache.containsKey(cacheKey)) {

0 commit comments

Comments
 (0)