Skip to content

Commit f5e0ba6

Browse files
committed
HBASE-28504 Implement eviction logic for scanners in Rest APIs to prevent scanner leakage (#5802)
Signed-off-by: Peter Somogyi <[email protected]>
1 parent fcae9c7 commit f5e0ba6

File tree

3 files changed

+40
-8
lines changed

3 files changed

+40
-8
lines changed

hbase-rest/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@
140140
<groupId>org.apache.commons</groupId>
141141
<artifactId>commons-lang3</artifactId>
142142
</dependency>
143+
<dependency>
144+
<groupId>com.github.ben-manes.caffeine</groupId>
145+
<artifactId>caffeine</artifactId>
146+
</dependency>
143147
<dependency>
144148
<groupId>org.slf4j</groupId>
145149
<artifactId>slf4j-api</artifactId>

hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/Constants.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ public interface Constants {
6969
String REST_DNS_NAMESERVER = "hbase.rest.dns.nameserver";
7070
String REST_DNS_INTERFACE = "hbase.rest.dns.interface";
7171

72+
String REST_SCANNERCACHE_SIZE = "hbase.rest.scannercache.size";
73+
final int DEFAULT_REST_SCANNERCACHE_SIZE = 10000;
74+
75+
String REST_SCANNERCACHE_EXPIRE_TIME = "hbase.rest.scannercache.expire.time";
76+
final long DEFAULT_REST_SCANNERCACHE_EXPIRE_TIME_MS = 60 * 60 * 1000;
77+
7278
String FILTER_CLASSES = "hbase.rest.filter.classes";
7379
String SCAN_START_ROW = "startrow";
7480
String SCAN_END_ROW = "endrow";

hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/ScannerResource.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919

2020
import com.fasterxml.jackson.core.JsonParseException;
2121
import com.fasterxml.jackson.databind.JsonMappingException;
22+
import com.github.benmanes.caffeine.cache.Cache;
23+
import com.github.benmanes.caffeine.cache.Caffeine;
24+
import com.github.benmanes.caffeine.cache.RemovalCause;
2225
import java.io.IOException;
2326
import java.net.URI;
24-
import java.util.Collections;
25-
import java.util.HashMap;
26-
import java.util.Map;
27+
import java.util.concurrent.TimeUnit;
28+
import org.apache.hadoop.conf.Configuration;
29+
import org.apache.hadoop.hbase.HBaseConfiguration;
2730
import org.apache.hadoop.hbase.TableNotFoundException;
2831
import org.apache.hadoop.hbase.filter.Filter;
2932
import org.apache.hadoop.hbase.rest.model.ScannerModel;
@@ -46,9 +49,7 @@ public class ScannerResource extends ResourceBase {
4649

4750
private static final Logger LOG = LoggerFactory.getLogger(ScannerResource.class);
4851

49-
static final Map<String, ScannerInstanceResource> scanners =
50-
Collections.synchronizedMap(new HashMap<String, ScannerInstanceResource>());
51-
52+
private static final Cache<String, ScannerInstanceResource> scanners = setupScanners();
5253
TableResource tableResource;
5354

5455
/**
@@ -59,8 +60,23 @@ public ScannerResource(TableResource tableResource) throws IOException {
5960
this.tableResource = tableResource;
6061
}
6162

63+
private static Cache<String, ScannerInstanceResource> setupScanners() {
64+
final Configuration conf = HBaseConfiguration.create();
65+
66+
int size = conf.getInt(REST_SCANNERCACHE_SIZE, DEFAULT_REST_SCANNERCACHE_SIZE);
67+
long evictTimeoutMs = conf.getTimeDuration(REST_SCANNERCACHE_EXPIRE_TIME,
68+
DEFAULT_REST_SCANNERCACHE_EXPIRE_TIME_MS, TimeUnit.MILLISECONDS);
69+
70+
Cache<String, ScannerInstanceResource> cache =
71+
Caffeine.newBuilder().removalListener(ScannerResource::removalListener).maximumSize(size)
72+
.expireAfterAccess(evictTimeoutMs, TimeUnit.MILLISECONDS)
73+
.<String, ScannerInstanceResource> build();
74+
75+
return cache;
76+
}
77+
6278
static boolean delete(final String id) {
63-
ScannerInstanceResource instance = scanners.remove(id);
79+
ScannerInstanceResource instance = scanners.asMap().remove(id);
6480
if (instance != null) {
6581
instance.generator.close();
6682
return true;
@@ -69,6 +85,12 @@ static boolean delete(final String id) {
6985
}
7086
}
7187

88+
static void removalListener(String key, ScannerInstanceResource value, RemovalCause cause) {
89+
if (cause.wasEvicted()) {
90+
delete(key);
91+
}
92+
}
93+
7294
Response update(final ScannerModel model, final boolean replace, final UriInfo uriInfo) {
7395
servlet.getMetrics().incrementRequests(1);
7496
if (servlet.isReadOnly()) {
@@ -140,7 +162,7 @@ public Response post(final ScannerModel model, final @Context UriInfo uriInfo) {
140162
@Path("{scanner: .+}")
141163
public ScannerInstanceResource getScannerInstanceResource(final @PathParam("scanner") String id)
142164
throws IOException {
143-
ScannerInstanceResource instance = scanners.get(id);
165+
ScannerInstanceResource instance = scanners.getIfPresent(id);
144166
if (instance == null) {
145167
servlet.getMetrics().incrementFailedGetRequests(1);
146168
return new ScannerInstanceResource();

0 commit comments

Comments
 (0)