Skip to content

Commit f5c5d35

Browse files
HDFS-17529. RBF: Improve router state store cache entry deletion (#6833)
1 parent d168d3f commit f5c5d35

File tree

5 files changed

+138
-27
lines changed

5 files changed

+138
-27
lines changed

hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/store/CachedRecordStore.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.ArrayList;
2222
import java.util.LinkedList;
2323
import java.util.List;
24+
import java.util.Map;
2425
import java.util.concurrent.locks.Lock;
2526
import java.util.concurrent.locks.ReadWriteLock;
2627
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -172,7 +173,7 @@ private boolean isUpdateTime() {
172173
*/
173174
public void overrideExpiredRecords(QueryResult<R> query) throws IOException {
174175
List<R> commitRecords = new ArrayList<>();
175-
List<R> deleteRecords = new ArrayList<>();
176+
List<R> toDeleteRecords = new ArrayList<>();
176177
List<R> newRecords = query.getRecords();
177178
long currentDriverTime = query.getTimestamp();
178179
if (newRecords == null || currentDriverTime <= 0) {
@@ -182,13 +183,8 @@ public void overrideExpiredRecords(QueryResult<R> query) throws IOException {
182183
for (R record : newRecords) {
183184
if (record.shouldBeDeleted(currentDriverTime)) {
184185
String recordName = StateStoreUtils.getRecordName(record.getClass());
185-
if (getDriver().remove(record)) {
186-
deleteRecords.add(record);
187-
LOG.info("Deleted State Store record {}: {}", recordName, record);
188-
} else {
189-
LOG.warn("Couldn't delete State Store record {}: {}", recordName,
190-
record);
191-
}
186+
LOG.info("State Store record to delete {}: {}", recordName, record);
187+
toDeleteRecords.add(record);
192188
} else if (!record.isExpired() && record.checkExpired(currentDriverTime)) {
193189
String recordName = StateStoreUtils.getRecordName(record.getClass());
194190
LOG.info("Override State Store record {}: {}", recordName, record);
@@ -198,8 +194,12 @@ public void overrideExpiredRecords(QueryResult<R> query) throws IOException {
198194
if (commitRecords.size() > 0) {
199195
getDriver().putAll(commitRecords, true, false);
200196
}
201-
if (deleteRecords.size() > 0) {
202-
newRecords.removeAll(deleteRecords);
197+
if (!toDeleteRecords.isEmpty()) {
198+
for (Map.Entry<R, Boolean> entry : getDriver().removeMultiple(toDeleteRecords).entrySet()) {
199+
if (entry.getValue()) {
200+
newRecords.remove(entry.getKey());
201+
}
202+
}
203203
}
204204
}
205205

hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/store/driver/StateStoreRecordOperations.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.IOException;
2121
import java.util.List;
22+
import java.util.Map;
2223

2324
import org.apache.hadoop.classification.InterfaceAudience;
2425
import org.apache.hadoop.classification.InterfaceStability;
@@ -127,6 +128,17 @@ <T extends BaseRecord> StateStoreOperationResult putAll(
127128
@AtMostOnce
128129
<T extends BaseRecord> boolean remove(T record) throws IOException;
129130

131+
/**
132+
* Remove multiple records.
133+
*
134+
* @param <T> Record class of the records.
135+
* @param records Records to be removed.
136+
* @return Map of record to a boolean indicating if the record has being removed successfully.
137+
* @throws IOException Throws exception if unable to query the data store.
138+
*/
139+
@AtMostOnce
140+
<T extends BaseRecord> Map<T, Boolean> removeMultiple(List<T> records) throws IOException;
141+
130142
/**
131143
* Remove all records of this class from the store.
132144
*
@@ -152,4 +164,17 @@ <T extends BaseRecord> StateStoreOperationResult putAll(
152164
<T extends BaseRecord> int remove(Class<T> clazz, Query<T> query)
153165
throws IOException;
154166

167+
/**
168+
* Remove all records of a specific class that match any query in a list of queries.
169+
* Requires the getAll implementation to fetch fresh records on each call.
170+
*
171+
* @param clazz The class to match the records with.
172+
* @param queries Queries (logical OR) to filter what to remove.
173+
* @param <T> Record class of the records.
174+
* @return Map of query to number of records removed by that query.
175+
* @throws IOException Throws exception if unable to query the data store.
176+
*/
177+
@AtMostOnce
178+
<T extends BaseRecord> Map<Query<T>, Integer> remove(Class<T> clazz, List<Query<T>> queries)
179+
throws IOException;
155180
}

hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/store/driver/impl/StateStoreBaseImpl.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121

2222
import java.io.IOException;
2323
import java.util.ArrayList;
24+
import java.util.HashMap;
2425
import java.util.List;
26+
import java.util.Map;
27+
import java.util.stream.Collectors;
2528

2629
import org.apache.hadoop.classification.InterfaceAudience;
2730
import org.apache.hadoop.classification.InterfaceStability;
@@ -86,4 +89,37 @@ public <T extends BaseRecord> boolean remove(T record) throws IOException {
8689
Class<T> recordClass = (Class<T>)StateStoreUtils.getRecordClass(clazz);
8790
return remove(recordClass, query) == 1;
8891
}
92+
93+
@Override
94+
public <T extends BaseRecord> Map<T, Boolean> removeMultiple(List<T> records) throws IOException {
95+
assert !records.isEmpty();
96+
// Fall back to iterative remove() calls if all records don't share 1 class
97+
Class<? extends BaseRecord> expectedClazz = records.get(0).getClass();
98+
if (!records.stream().allMatch(x -> x.getClass() == expectedClazz)) {
99+
Map<T, Boolean> result = new HashMap<>();
100+
for (T record : records) {
101+
result.put(record, remove(record));
102+
}
103+
return result;
104+
}
105+
106+
final List<Query<T>> queries = new ArrayList<>();
107+
for (T record : records) {
108+
queries.add(new Query<>(record));
109+
}
110+
@SuppressWarnings("unchecked")
111+
Class<T> recordClass = (Class<T>) StateStoreUtils.getRecordClass(expectedClazz);
112+
Map<Query<T>, Integer> result = remove(recordClass, queries);
113+
return result.entrySet().stream()
114+
.collect(Collectors.toMap(e -> e.getKey().getPartial(), e -> e.getValue() == 1));
115+
}
116+
117+
public <T extends BaseRecord> Map<Query<T>, Integer> remove(Class<T> clazz,
118+
List<Query<T>> queries) throws IOException {
119+
Map<Query<T>, Integer> result = new HashMap<>();
120+
for (Query<T> query : queries) {
121+
result.put(query, remove(clazz, query));
122+
}
123+
return result;
124+
}
89125
}

hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/store/driver/impl/StateStoreZooKeeperImpl.java

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
import java.io.IOException;
2626
import java.util.ArrayList;
2727
import java.util.Collections;
28+
import java.util.HashMap;
29+
import java.util.HashSet;
2830
import java.util.List;
31+
import java.util.Map;
32+
import java.util.Set;
2933
import java.util.concurrent.Callable;
3034
import java.util.concurrent.Future;
3135
import java.util.concurrent.LinkedBlockingQueue;
@@ -284,51 +288,86 @@ public <T extends BaseRecord> StateStoreOperationResult putAll(
284288
}
285289

286290
@Override
287-
public <T extends BaseRecord> int remove(
288-
Class<T> clazz, Query<T> query) throws IOException {
291+
public <T extends BaseRecord> Map<Query<T>, Integer> remove(Class<T> clazz,
292+
List<Query<T>> queries) throws IOException {
289293
verifyDriverReady();
290-
if (query == null) {
291-
return 0;
294+
// Track how many entries are deleted by each query
295+
Map<Query<T>, Integer> ret = new HashMap<>();
296+
final List<T> trueRemoved = Collections.synchronizedList(new ArrayList<>());
297+
if (queries.isEmpty()) {
298+
return ret;
292299
}
293300

294301
// Read the current data
295302
long start = monotonicNow();
296-
List<T> records = null;
303+
List<T> records;
297304
try {
298305
QueryResult<T> result = get(clazz);
299306
records = result.getRecords();
300307
} catch (IOException ex) {
301308
LOG.error("Cannot get existing records", ex);
302309
getMetrics().addFailure(monotonicNow() - start);
303-
return 0;
310+
return ret;
304311
}
305312

306313
// Check the records to remove
307314
String znode = getZNodeForClass(clazz);
308-
List<T> recordsToRemove = filterMultiple(query, records);
315+
Set<T> recordsToRemove = new HashSet<>();
316+
Map<Query<T>, List<T>> queryToRecords = new HashMap<>();
317+
for (Query<T> query : queries) {
318+
List<T> filtered = filterMultiple(query, records);
319+
queryToRecords.put(query, filtered);
320+
recordsToRemove.addAll(filtered);
321+
}
309322

310323
// Remove the records
311-
int removed = 0;
312-
for (T existingRecord : recordsToRemove) {
324+
List<Callable<Void>> callables = new ArrayList<>();
325+
recordsToRemove.forEach(existingRecord -> callables.add(() -> {
313326
LOG.info("Removing \"{}\"", existingRecord);
314327
try {
315328
String primaryKey = getPrimaryKey(existingRecord);
316329
String path = getNodePath(znode, primaryKey);
317330
if (zkManager.delete(path)) {
318-
removed++;
331+
trueRemoved.add(existingRecord);
319332
} else {
320333
LOG.error("Did not remove \"{}\"", existingRecord);
321334
}
322335
} catch (Exception e) {
323336
LOG.error("Cannot remove \"{}\"", existingRecord, e);
324337
getMetrics().addFailure(monotonicNow() - start);
325338
}
339+
return null;
340+
}));
341+
try {
342+
if (enableConcurrent) {
343+
executorService.invokeAll(callables);
344+
} else {
345+
for (Callable<Void> callable : callables) {
346+
callable.call();
347+
}
348+
}
349+
} catch (Exception e) {
350+
LOG.error("Record removal failed : {}", e.getMessage(), e);
326351
}
327352
long end = monotonicNow();
328-
if (removed > 0) {
353+
if (!trueRemoved.isEmpty()) {
329354
getMetrics().addRemove(end - start);
330355
}
331-
return removed;
356+
// Generate return map
357+
for (Map.Entry<Query<T>, List<T>> entry : queryToRecords.entrySet()) {
358+
for (T record : entry.getValue()) {
359+
if (trueRemoved.contains(record)) {
360+
ret.compute(entry.getKey(), (k, v) -> (v == null) ? 1 : v + 1);
361+
}
362+
}
363+
}
364+
return ret;
365+
}
366+
367+
@Override
368+
public <T extends BaseRecord> int remove(Class<T> clazz, Query<T> query)
369+
throws IOException {
370+
return remove(clazz, Collections.singletonList(query)).get(query);
332371
}
333372

334373
@Override

hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/store/driver/TestStateStoreZK.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,17 +140,28 @@ public void testAsyncPerformance() throws Exception {
140140
insertList.add(newRecord);
141141
}
142142
// Insert Multiple on sync mode
143-
long startSync = Time.now();
143+
long startSyncPut = Time.now();
144144
stateStoreDriver.putAll(insertList, true, false);
145-
long endSync = Time.now();
145+
long endSyncPut = Time.now();
146+
// Removing 1000 records synchronously is painfully slow so test with only 5 records
147+
// Then remove the rest with removeAll()
148+
long startSyncRemove = Time.now();
149+
for (MountTable entry : insertList.subList(0, 5)) {
150+
stateStoreDriver.remove(entry);
151+
}
152+
long endSyncRemove = Time.now();
146153
stateStoreDriver.removeAll(MembershipState.class);
147154

148155
stateStoreDriver.setEnableConcurrent(true);
149156
// Insert Multiple on async mode
150-
long startAsync = Time.now();
157+
long startAsyncPut = Time.now();
151158
stateStoreDriver.putAll(insertList, true, false);
152-
long endAsync = Time.now();
153-
assertTrue((endSync - startSync) > (endAsync - startAsync));
159+
long endAsyncPut = Time.now();
160+
long startAsyncRemove = Time.now();
161+
stateStoreDriver.removeMultiple(insertList.subList(0, 5));
162+
long endAsyncRemove = Time.now();
163+
assertTrue((endSyncPut - startSyncPut) > (endAsyncPut - startAsyncPut));
164+
assertTrue((endSyncRemove - startSyncRemove) > (endAsyncRemove - startAsyncRemove));
154165
}
155166

156167
@Test

0 commit comments

Comments
 (0)