Skip to content

Commit 410873a

Browse files
bharatviswa504ahussein
authored andcommitted
HDDS-1986. Fix listkeys API. (apache#1588)
1 parent 6e3694e commit 410873a

File tree

3 files changed

+352
-23
lines changed

3 files changed

+352
-23
lines changed

hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
import java.util.Iterator;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Set;
27+
import java.util.TreeMap;
28+
import java.util.TreeSet;
2629
import java.util.stream.Collectors;
2730

2831
import org.apache.hadoop.hdds.client.BlockID;
@@ -653,7 +656,12 @@ public List<OmBucketInfo> listBuckets(final String volumeName,
653656
@Override
654657
public List<OmKeyInfo> listKeys(String volumeName, String bucketName,
655658
String startKey, String keyPrefix, int maxKeys) throws IOException {
659+
656660
List<OmKeyInfo> result = new ArrayList<>();
661+
if (maxKeys <= 0) {
662+
return result;
663+
}
664+
657665
if (Strings.isNullOrEmpty(volumeName)) {
658666
throw new OMException("Volume name is required.",
659667
ResultCodes.VOLUME_NOT_FOUND);
@@ -688,26 +696,85 @@ public List<OmKeyInfo> listKeys(String volumeName, String bucketName,
688696
seekPrefix = getBucketKey(volumeName, bucketName + OM_KEY_PREFIX);
689697
}
690698
int currentCount = 0;
691-
try (TableIterator<String, ? extends KeyValue<String, OmKeyInfo>> keyIter =
692-
getKeyTable()
693-
.iterator()) {
694-
KeyValue<String, OmKeyInfo> kv = keyIter.seek(seekKey);
695-
while (currentCount < maxKeys && keyIter.hasNext()) {
696-
kv = keyIter.next();
697-
// Skip the Start key if needed.
698-
if (kv != null && skipStartKey && kv.getKey().equals(seekKey)) {
699-
continue;
699+
700+
701+
TreeMap<String, OmKeyInfo> cacheKeyMap = new TreeMap<>();
702+
Set<String> deletedKeySet = new TreeSet<>();
703+
Iterator<Map.Entry<CacheKey<String>, CacheValue<OmKeyInfo>>> iterator =
704+
keyTable.cacheIterator();
705+
706+
//TODO: We can avoid this iteration if table cache has stored entries in
707+
// treemap. Currently HashMap is used in Cache. HashMap get operation is an
708+
// constant time operation, where as for treeMap get is log(n).
709+
// So if we move to treemap, the get operation will be affected. As get
710+
// is frequent operation on table. So, for now in list we iterate cache map
711+
// and construct treeMap which match with keyPrefix and are greater than or
712+
// equal to startKey. Later we can revisit this, if list operation
713+
// is becoming slow.
714+
while (iterator.hasNext()) {
715+
Map.Entry< CacheKey<String>, CacheValue<OmKeyInfo>> entry =
716+
iterator.next();
717+
718+
String key = entry.getKey().getCacheKey();
719+
OmKeyInfo omKeyInfo = entry.getValue().getCacheValue();
720+
// Making sure that entry in cache is not for delete key request.
721+
722+
if (omKeyInfo != null) {
723+
if (key.startsWith(seekPrefix) && key.compareTo(seekKey) >= 0) {
724+
cacheKeyMap.put(key, omKeyInfo);
700725
}
726+
} else {
727+
deletedKeySet.add(key);
728+
}
729+
}
730+
731+
// Get maxKeys from DB if it has.
732+
733+
try (TableIterator<String, ? extends KeyValue<String, OmKeyInfo>>
734+
keyIter = getKeyTable().iterator()) {
735+
KeyValue< String, OmKeyInfo > kv;
736+
keyIter.seek(seekKey);
737+
// we need to iterate maxKeys + 1 here because if skipStartKey is true,
738+
// we should skip that entry and return the result.
739+
while (currentCount < maxKeys + 1 && keyIter.hasNext()) {
740+
kv = keyIter.next();
701741
if (kv != null && kv.getKey().startsWith(seekPrefix)) {
702-
result.add(kv.getValue());
703-
currentCount++;
742+
743+
// Entry should not be marked for delete, consider only those
744+
// entries.
745+
if(!deletedKeySet.contains(kv.getKey())) {
746+
cacheKeyMap.put(kv.getKey(), kv.getValue());
747+
currentCount++;
748+
}
704749
} else {
705750
// The SeekPrefix does not match any more, we can break out of the
706751
// loop.
707752
break;
708753
}
709754
}
710755
}
756+
757+
// Finally DB entries and cache entries are merged, then return the count
758+
// of maxKeys from the sorted map.
759+
currentCount = 0;
760+
761+
for (Map.Entry<String, OmKeyInfo> cacheKey : cacheKeyMap.entrySet()) {
762+
if (cacheKey.getKey().equals(seekKey) && skipStartKey) {
763+
continue;
764+
}
765+
766+
result.add(cacheKey.getValue());
767+
currentCount++;
768+
769+
if (currentCount == maxKeys) {
770+
break;
771+
}
772+
}
773+
774+
// Clear map and set.
775+
cacheKeyMap.clear();
776+
deletedKeySet.clear();
777+
711778
return result;
712779
}
713780

hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmMetadataManager.java

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import com.google.common.base.Optional;
2020
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
2121
import org.apache.hadoop.hdds.protocol.StorageType;
22+
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
2223
import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
2324
import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
2425
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
26+
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
2527
import org.apache.hadoop.ozone.om.request.TestOMRequestUtils;
2628
import org.junit.Assert;
2729
import org.junit.Before;
@@ -188,4 +190,228 @@ private void addBucketsToCache(String volumeName, String bucketName) {
188190
new CacheValue<>(Optional.of(omBucketInfo), 1));
189191
}
190192

193+
@Test
194+
public void testListKeys() throws Exception {
195+
196+
String volumeNameA = "volumeA";
197+
String volumeNameB = "volumeB";
198+
String ozoneBucket = "ozoneBucket";
199+
String hadoopBucket = "hadoopBucket";
200+
201+
202+
// Create volumes and buckets.
203+
TestOMRequestUtils.addVolumeToDB(volumeNameA, omMetadataManager);
204+
TestOMRequestUtils.addVolumeToDB(volumeNameB, omMetadataManager);
205+
addBucketsToCache(volumeNameA, ozoneBucket);
206+
addBucketsToCache(volumeNameB, hadoopBucket);
207+
208+
209+
String prefixKeyA = "key-a";
210+
String prefixKeyB = "key-b";
211+
TreeSet<String> keysASet = new TreeSet<>();
212+
TreeSet<String> keysBSet = new TreeSet<>();
213+
for (int i=1; i<= 100; i++) {
214+
if (i % 2 == 0) {
215+
keysASet.add(
216+
prefixKeyA + i);
217+
addKeysToOM(volumeNameA, ozoneBucket, prefixKeyA + i, i);
218+
} else {
219+
keysBSet.add(
220+
prefixKeyB + i);
221+
addKeysToOM(volumeNameA, hadoopBucket, prefixKeyB + i, i);
222+
}
223+
}
224+
225+
226+
TreeSet<String> keysAVolumeBSet = new TreeSet<>();
227+
TreeSet<String> keysBVolumeBSet = new TreeSet<>();
228+
for (int i=1; i<= 100; i++) {
229+
if (i % 2 == 0) {
230+
keysAVolumeBSet.add(
231+
prefixKeyA + i);
232+
addKeysToOM(volumeNameB, ozoneBucket, prefixKeyA + i, i);
233+
} else {
234+
keysBVolumeBSet.add(
235+
prefixKeyB + i);
236+
addKeysToOM(volumeNameB, hadoopBucket, prefixKeyB + i, i);
237+
}
238+
}
239+
240+
241+
// List all keys which have prefix "key-a"
242+
List<OmKeyInfo> omKeyInfoList =
243+
omMetadataManager.listKeys(volumeNameA, ozoneBucket,
244+
null, prefixKeyA, 100);
245+
246+
Assert.assertEquals(omKeyInfoList.size(), 50);
247+
248+
for (OmKeyInfo omKeyInfo : omKeyInfoList) {
249+
Assert.assertTrue(omKeyInfo.getKeyName().startsWith(
250+
prefixKeyA));
251+
}
252+
253+
254+
String startKey = prefixKeyA + 10;
255+
omKeyInfoList =
256+
omMetadataManager.listKeys(volumeNameA, ozoneBucket,
257+
startKey, prefixKeyA, 100);
258+
259+
Assert.assertEquals(keysASet.tailSet(
260+
startKey).size() - 1, omKeyInfoList.size());
261+
262+
startKey = prefixKeyA + 38;
263+
omKeyInfoList =
264+
omMetadataManager.listKeys(volumeNameA, ozoneBucket,
265+
startKey, prefixKeyA, 100);
266+
267+
Assert.assertEquals(keysASet.tailSet(
268+
startKey).size() - 1, omKeyInfoList.size());
269+
270+
for (OmKeyInfo omKeyInfo : omKeyInfoList) {
271+
Assert.assertTrue(omKeyInfo.getKeyName().startsWith(
272+
prefixKeyA));
273+
Assert.assertFalse(omKeyInfo.getBucketName().equals(
274+
prefixKeyA + 38));
275+
}
276+
277+
278+
279+
omKeyInfoList = omMetadataManager.listKeys(volumeNameB, hadoopBucket,
280+
null, prefixKeyB, 100);
281+
282+
Assert.assertEquals(omKeyInfoList.size(), 50);
283+
284+
for (OmKeyInfo omKeyInfo : omKeyInfoList) {
285+
Assert.assertTrue(omKeyInfo.getKeyName().startsWith(
286+
prefixKeyB));
287+
}
288+
289+
// Try to get keys by count 10, like that get all keys in the
290+
// volumeB/ozoneBucket with "key-a".
291+
startKey = null;
292+
TreeSet<String> expectedKeys = new TreeSet<>();
293+
for (int i=0; i<5; i++) {
294+
295+
omKeyInfoList = omMetadataManager.listKeys(volumeNameB, hadoopBucket,
296+
startKey, prefixKeyB, 10);
297+
298+
Assert.assertEquals(10, omKeyInfoList.size());
299+
300+
for (OmKeyInfo omKeyInfo : omKeyInfoList) {
301+
expectedKeys.add(omKeyInfo.getKeyName());
302+
Assert.assertTrue(omKeyInfo.getKeyName().startsWith(
303+
prefixKeyB));
304+
startKey = omKeyInfo.getKeyName();
305+
}
306+
}
307+
308+
Assert.assertEquals(expectedKeys, keysBVolumeBSet);
309+
310+
311+
// As now we have iterated all 50 buckets, calling next time should
312+
// return empty list.
313+
omKeyInfoList = omMetadataManager.listKeys(volumeNameB, hadoopBucket,
314+
startKey, prefixKeyB, 10);
315+
316+
Assert.assertEquals(omKeyInfoList.size(), 0);
317+
318+
}
319+
320+
@Test
321+
public void testListKeysWithFewDeleteEntriesInCache() throws Exception {
322+
String volumeNameA = "volumeA";
323+
String ozoneBucket = "ozoneBucket";
324+
325+
// Create volumes and bucket.
326+
TestOMRequestUtils.addVolumeToDB(volumeNameA, omMetadataManager);
327+
328+
addBucketsToCache(volumeNameA, ozoneBucket);
329+
330+
String prefixKeyA = "key-a";
331+
TreeSet<String> keysASet = new TreeSet<>();
332+
TreeSet<String> deleteKeySet = new TreeSet<>();
333+
334+
335+
for (int i=1; i<= 100; i++) {
336+
if (i % 2 == 0) {
337+
keysASet.add(
338+
prefixKeyA + i);
339+
addKeysToOM(volumeNameA, ozoneBucket, prefixKeyA + i, i);
340+
} else {
341+
addKeysToOM(volumeNameA, ozoneBucket, prefixKeyA + i, i);
342+
String key = omMetadataManager.getOzoneKey(volumeNameA,
343+
ozoneBucket, prefixKeyA + i);
344+
// Mark as deleted in cache.
345+
omMetadataManager.getKeyTable().addCacheEntry(
346+
new CacheKey<>(key),
347+
new CacheValue<>(Optional.absent(), 100L));
348+
deleteKeySet.add(key);
349+
}
350+
}
351+
352+
// Now list keys which match with prefixKeyA.
353+
List<OmKeyInfo> omKeyInfoList =
354+
omMetadataManager.listKeys(volumeNameA, ozoneBucket,
355+
null, prefixKeyA, 100);
356+
357+
// As in total 100, 50 are marked for delete. It should list only 50 keys.
358+
Assert.assertEquals(50, omKeyInfoList.size());
359+
360+
TreeSet<String> expectedKeys = new TreeSet<>();
361+
362+
for (OmKeyInfo omKeyInfo : omKeyInfoList) {
363+
expectedKeys.add(omKeyInfo.getKeyName());
364+
Assert.assertTrue(omKeyInfo.getKeyName().startsWith(prefixKeyA));
365+
}
366+
367+
Assert.assertEquals(expectedKeys, keysASet);
368+
369+
370+
// Now get key count by 10.
371+
String startKey = null;
372+
expectedKeys = new TreeSet<>();
373+
for (int i=0; i<5; i++) {
374+
375+
omKeyInfoList = omMetadataManager.listKeys(volumeNameA, ozoneBucket,
376+
startKey, prefixKeyA, 10);
377+
378+
System.out.println(i);
379+
Assert.assertEquals(10, omKeyInfoList.size());
380+
381+
for (OmKeyInfo omKeyInfo : omKeyInfoList) {
382+
expectedKeys.add(omKeyInfo.getKeyName());
383+
Assert.assertTrue(omKeyInfo.getKeyName().startsWith(
384+
prefixKeyA));
385+
startKey = omKeyInfo.getKeyName();
386+
}
387+
}
388+
389+
Assert.assertEquals(keysASet, expectedKeys);
390+
391+
392+
// As now we have iterated all 50 buckets, calling next time should
393+
// return empty list.
394+
omKeyInfoList = omMetadataManager.listKeys(volumeNameA, ozoneBucket,
395+
startKey, prefixKeyA, 10);
396+
397+
Assert.assertEquals(omKeyInfoList.size(), 0);
398+
399+
400+
401+
}
402+
403+
private void addKeysToOM(String volumeName, String bucketName,
404+
String keyName, int i) throws Exception {
405+
406+
if (i%2== 0) {
407+
TestOMRequestUtils.addKeyToTable(false, volumeName, bucketName, keyName,
408+
1000L, HddsProtos.ReplicationType.RATIS,
409+
HddsProtos.ReplicationFactor.ONE, omMetadataManager);
410+
} else {
411+
TestOMRequestUtils.addKeyToTableCache(volumeName, bucketName, keyName,
412+
HddsProtos.ReplicationType.RATIS, HddsProtos.ReplicationFactor.ONE,
413+
omMetadataManager);
414+
}
415+
}
416+
191417
}

0 commit comments

Comments
 (0)