Skip to content

Commit 3727330

Browse files
committed
Add ROW_CACHE_EVICT_ON_CLOSE configuration option
1 parent cceda48 commit 3727330

File tree

6 files changed

+176
-3
lines changed

6 files changed

+176
-3
lines changed

hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,12 @@ public enum OperationStatusCode {
10231023
public static final String ROW_CACHE_SIZE_KEY = "row.cache.size";
10241024
public static final float ROW_CACHE_SIZE_DEFAULT = 0.0f;
10251025

1026+
/**
1027+
* Configuration key for the evict the row cache on close
1028+
*/
1029+
public static final String ROW_CACHE_EVICT_ON_CLOSE_KEY = "row.cache.evictOnClose";
1030+
public static final boolean ROW_CACHE_EVICT_ON_CLOSE_DEFAULT = false;
1031+
10261032
/**
10271033
* Configuration key for the memory size of the block cache
10281034
*/

hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
package org.apache.hadoop.hbase.regionserver;
1919

2020
import static org.apache.hadoop.hbase.HConstants.REPLICATION_SCOPE_LOCAL;
21+
import static org.apache.hadoop.hbase.HConstants.ROW_CACHE_EVICT_ON_CLOSE_DEFAULT;
22+
import static org.apache.hadoop.hbase.HConstants.ROW_CACHE_EVICT_ON_CLOSE_KEY;
2123
import static org.apache.hadoop.hbase.regionserver.HStoreFile.MAJOR_COMPACTION_KEY;
2224
import static org.apache.hadoop.hbase.trace.HBaseSemanticAttributes.REGION_NAMES_KEY;
2325
import static org.apache.hadoop.hbase.trace.HBaseSemanticAttributes.ROW_LOCK_READ_LOCK_KEY;
@@ -1946,6 +1948,8 @@ public Pair<byte[], Collection<HStoreFile>> call() throws IOException {
19461948
}
19471949
}
19481950

1951+
evictRowCache();
1952+
19491953
status.setStatus("Writing region close event to WAL");
19501954
// Always write close marker to wal even for read only table. This is not a big problem as we
19511955
// do not write any data into the region; it is just a meta edit in the WAL file.
@@ -1986,6 +1990,22 @@ public Pair<byte[], Collection<HStoreFile>> call() throws IOException {
19861990
}
19871991
}
19881992

1993+
private void evictRowCache() {
1994+
boolean evictOnClose = getReadOnlyConfiguration().getBoolean(ROW_CACHE_EVICT_ON_CLOSE_KEY,
1995+
ROW_CACHE_EVICT_ON_CLOSE_DEFAULT);
1996+
1997+
if (!evictOnClose) {
1998+
return;
1999+
}
2000+
2001+
if (!(rsServices instanceof HRegionServer regionServer)) {
2002+
return;
2003+
}
2004+
2005+
RowCacheService rowCacheService = regionServer.getRSRpcServices().getRowCacheService();
2006+
rowCacheService.evictRowsByRegion(this);
2007+
}
2008+
19892009
/** Wait for all current flushes and compactions of the region to complete */
19902010
// TODO HBASE-18906. Check the usage (if any) in Phoenix and expose this or give alternate way for
19912011
// Phoenix needs.

hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RowCache.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ void evictRow(RowCacheKey key) {
7575
cache.asMap().remove(key);
7676
}
7777

78+
/**
79+
* Evict all rows belonging to the specified region. This is heavy operation as it iterates the
80+
* entire RowCache key set.
81+
* @param region the region whose rows should be evicted
82+
*/
83+
void evictRowsByRegion(HRegion region) {
84+
cache.asMap().keySet().removeIf(key -> key.isSameRegion(region));
85+
}
86+
7887
public long getHitCount() {
7988
return cache.stats().hitCount();
8089
}

hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RowCacheKey.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public class RowCacheKey implements HeapSize {
3030
private final String encodedRegionName;
3131
private final byte[] rowKey;
3232

33-
// Row cache keys should not be evicted on close, since the cache may contain many entries and
34-
// eviction would be slow. Instead, the region’s rowCacheSeqNum is used to generate new keys that
35-
// ignore the existing cache when the region is reopened or bulk-loaded.
33+
// When a region is reopened or bulk-loaded, its rowCacheSeqNum is used to generate new keys that
34+
// bypass the existing cache. This mechanism is effective when ROW_CACHE_EVICT_ON_CLOSE is set to
35+
// false.
3636
private final long rowCacheSeqNum;
3737

3838
public RowCacheKey(HRegion region, byte[] rowKey) {
@@ -64,4 +64,8 @@ public String toString() {
6464
public long heapSize() {
6565
return FIXED_OVERHEAD + ClassSize.align(rowKey.length);
6666
}
67+
68+
boolean isSameRegion(HRegion region) {
69+
return this.encodedRegionName.equals(region.getRegionInfo().getEncodedName());
70+
}
6771
}

hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RowCacheService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,8 @@ AtomicInteger getRegionLevelBarrier(HRegion region) {
353353
public RowCache getRowCache() {
354354
return rowCache;
355355
}
356+
357+
void evictRowsByRegion(HRegion region) {
358+
rowCache.evictRowsByRegion(region);
359+
}
356360
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
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.
17+
*/
18+
package org.apache.hadoop.hbase.regionserver;
19+
20+
import static org.apache.hadoop.hbase.HConstants.HFILE_BLOCK_CACHE_SIZE_KEY;
21+
import static org.apache.hadoop.hbase.HConstants.ROW_CACHE_EVICT_ON_CLOSE_KEY;
22+
import static org.apache.hadoop.hbase.HConstants.ROW_CACHE_SIZE_KEY;
23+
import static org.junit.Assert.assertArrayEquals;
24+
import static org.junit.Assert.assertEquals;
25+
26+
import java.util.Arrays;
27+
import java.util.List;
28+
import org.apache.hadoop.conf.Configuration;
29+
import org.apache.hadoop.hbase.HBaseClassTestRule;
30+
import org.apache.hadoop.hbase.HBaseTestingUtil;
31+
import org.apache.hadoop.hbase.SingleProcessHBaseCluster;
32+
import org.apache.hadoop.hbase.TableName;
33+
import org.apache.hadoop.hbase.client.*;
34+
import org.apache.hadoop.hbase.testclassification.MediumTests;
35+
import org.apache.hadoop.hbase.testclassification.RegionServerTests;
36+
import org.apache.hadoop.hbase.util.Bytes;
37+
import org.junit.ClassRule;
38+
import org.junit.Rule;
39+
import org.junit.Test;
40+
import org.junit.experimental.categories.Category;
41+
import org.junit.rules.TestName;
42+
import org.junit.runner.RunWith;
43+
import org.junit.runners.Parameterized;
44+
45+
@Category({ RegionServerTests.class, MediumTests.class })
46+
@RunWith(Parameterized.class)
47+
public class TestRowCacheEvictOnClose {
48+
@ClassRule
49+
public static final HBaseClassTestRule CLASS_RULE =
50+
HBaseClassTestRule.forClass(TestRowCacheEvictOnClose.class);
51+
52+
private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
53+
private static final byte[] CF1 = Bytes.toBytes("cf1");
54+
private static final byte[] Q1 = Bytes.toBytes("q1");
55+
private static final byte[] Q2 = Bytes.toBytes("q2");
56+
57+
@Rule
58+
public TestName testName = new TestName();
59+
60+
@Parameterized.Parameter
61+
public boolean evictOnClose;
62+
63+
@Parameterized.Parameters
64+
public static List<Object[]> params() {
65+
return Arrays.asList(new Object[][] { { true }, { false } });
66+
}
67+
68+
@Test
69+
public void testEvictOnClose() throws Exception {
70+
Configuration conf = TEST_UTIL.getConfiguration();
71+
72+
// Enable row cache
73+
conf.setFloat(ROW_CACHE_SIZE_KEY, 0.01f);
74+
conf.setFloat(HFILE_BLOCK_CACHE_SIZE_KEY, 0.39f);
75+
76+
// Set ROW_CACHE_EVICT_ON_CLOSE to true
77+
conf.setBoolean(ROW_CACHE_EVICT_ON_CLOSE_KEY, evictOnClose);
78+
79+
// Start cluster
80+
SingleProcessHBaseCluster cluster = TEST_UTIL.startMiniCluster();
81+
cluster.waitForActiveAndReadyMaster();
82+
Admin admin = TEST_UTIL.getAdmin();
83+
84+
RowCache rowCache = TEST_UTIL.getHBaseCluster().getRegionServer(0).getRSRpcServices()
85+
.getRowCacheService().getRowCache();
86+
87+
// Create table with row cache enabled
88+
ColumnFamilyDescriptor cf1 = ColumnFamilyDescriptorBuilder.newBuilder(CF1).build();
89+
TableName tableName = TableName.valueOf(testName.getMethodName().replaceAll("[\\[\\]]", "_"));
90+
TableDescriptor td = TableDescriptorBuilder.newBuilder(tableName).setRowCacheEnabled(true)
91+
.setColumnFamily(cf1).build();
92+
admin.createTable(td);
93+
Table table = admin.getConnection().getTable(tableName);
94+
95+
int numRows = 10;
96+
97+
// Put rows
98+
for (int i = 0; i < numRows; i++) {
99+
byte[] rowKey = ("row" + i).getBytes();
100+
Put put = new Put(rowKey);
101+
put.addColumn(CF1, Q1, Bytes.toBytes(0L));
102+
put.addColumn(CF1, Q2, "12".getBytes());
103+
table.put(put);
104+
}
105+
// Need to flush because the row cache is not populated when reading only from the memstore.
106+
admin.flush(tableName);
107+
108+
// Populate row caches
109+
for (int i = 0; i < numRows; i++) {
110+
byte[] rowKey = ("row" + i).getBytes();
111+
Get get = new Get(rowKey);
112+
Result result = table.get(get);
113+
assertArrayEquals(rowKey, result.getRow());
114+
assertArrayEquals(Bytes.toBytes(0L), result.getValue(CF1, Q1));
115+
assertArrayEquals("12".getBytes(), result.getValue(CF1, Q2));
116+
}
117+
118+
// Verify row cache has some entries
119+
assertEquals(numRows, rowCache.getCount());
120+
121+
// Disable table
122+
admin.disableTable(tableName);
123+
124+
// Verify row cache is cleared on table close
125+
assertEquals(evictOnClose ? 0 : numRows, rowCache.getCount());
126+
127+
admin.deleteTable(tableName);
128+
TEST_UTIL.shutdownMiniCluster();
129+
}
130+
}

0 commit comments

Comments
 (0)