-
Notifications
You must be signed in to change notification settings - Fork 615
HDDS-10156. Optimize Snapshot Cache get and eviction #6024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
f7b4354
7140017
82f06f7
0045d82
63c392d
fa5f165
8dfe92d
8254931
7df0ac9
e41e6d8
108d0ff
a7cdcad
115204d
ab8727e
a72cefb
0965244
15246fe
36742cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -485,7 +485,8 @@ private enum State { | |
| private OmMetadataReader omMetadataReader; | ||
| // Wrap active DB metadata reader in ReferenceCounted once to avoid | ||
| // instance creation every single time. | ||
| private ReferenceCounted<IOmMetadataReader, SnapshotCache> rcOmMetadataReader; | ||
| private ReferenceCounted<IOmMetadataReader, SnapshotCache> | ||
| rcOmMetadataReader; | ||
| private OmSnapshotManager omSnapshotManager; | ||
|
|
||
| @SuppressWarnings("methodlength") | ||
|
|
@@ -2580,8 +2581,8 @@ public boolean getAllowListAllVolumes() { | |
| return allowListAllVolumes; | ||
| } | ||
|
|
||
| public ReferenceCounted< | ||
| IOmMetadataReader, SnapshotCache> getOmMetadataReader() { | ||
| public ReferenceCounted<IOmMetadataReader, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: it could be in a single line since 120 chars are allowed per line now. |
||
| SnapshotCache> getOmMetadataReader() { | ||
| return rcOmMetadataReader; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.apache.hadoop.ozone.om.snapshot; | ||
|
|
||
| /** | ||
| * Callback interface for ReferenceCounted. | ||
| */ | ||
| public interface ReferenceCountedCallback { | ||
| void callback(ReferenceCounted referenceCounted); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |
|
|
||
| import com.google.common.annotations.VisibleForTesting; | ||
| import com.google.common.cache.CacheLoader; | ||
| import org.apache.hadoop.hdds.utils.Scheduler; | ||
| import org.apache.hadoop.ozone.om.IOmMetadataReader; | ||
| import org.apache.hadoop.ozone.om.OmSnapshot; | ||
| import org.apache.hadoop.ozone.om.OmSnapshotManager; | ||
|
|
@@ -29,15 +30,17 @@ | |
| import java.io.IOException; | ||
| import java.util.Iterator; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.FILE_NOT_FOUND; | ||
| import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE; | ||
|
|
||
| /** | ||
| * Thread-safe custom unbounded LRU cache to manage open snapshot DB instances. | ||
| */ | ||
| public class SnapshotCache { | ||
| public class SnapshotCache implements ReferenceCountedCallback, AutoCloseable { | ||
|
|
||
| static final Logger LOG = LoggerFactory.getLogger(SnapshotCache.class); | ||
|
|
||
|
|
@@ -53,14 +56,29 @@ public class SnapshotCache { | |
| // opened on the OM. | ||
| private final int cacheSizeLimit; | ||
|
|
||
| private final Set<String> pendingEvictionQueue; | ||
| private final Scheduler scheduler; | ||
| private static final String SNAPSHOT_CACHE_CLEANUP_SERVICE = | ||
| "SnapshotCacheCleanupService"; | ||
|
|
||
|
|
||
| public SnapshotCache( | ||
| OmSnapshotManager omSnapshotManager, | ||
| CacheLoader<String, OmSnapshot> cacheLoader, | ||
| int cacheSizeLimit) { | ||
| int cacheSizeLimit, long cleanupInterval) { | ||
| this.dbMap = new ConcurrentHashMap<>(); | ||
| this.omSnapshotManager = omSnapshotManager; | ||
| this.cacheLoader = cacheLoader; | ||
| this.cacheSizeLimit = cacheSizeLimit; | ||
| this.pendingEvictionQueue = ConcurrentHashMap.newKeySet(); | ||
| if (cleanupInterval > 0) { | ||
| this.scheduler = new Scheduler(SNAPSHOT_CACHE_CLEANUP_SERVICE, | ||
| true, 1); | ||
| this.scheduler.scheduleWithFixedDelay(this::cleanup, cleanupInterval, | ||
| cleanupInterval, TimeUnit.MILLISECONDS); | ||
| } else { | ||
| this.scheduler = null; | ||
| } | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
|
|
@@ -114,6 +132,13 @@ public void invalidateAll() { | |
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| if (this.scheduler != null) { | ||
| this.scheduler.close(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * State the reason the current thread is getting the OmSnapshot instance. | ||
| * Unused for now. | ||
|
|
@@ -178,18 +203,12 @@ public ReferenceCounted<IOmMetadataReader, SnapshotCache> get(String key, boolea | |
| // when called from SDT (and some) if the snapshot is not active anymore. | ||
| if (!skipActiveCheck && !omSnapshotManager.isSnapshotStatus(key, SNAPSHOT_ACTIVE)) { | ||
| // Ref count was incremented. Need to decrement on exception here. | ||
| rcOmSnapshot.decrementRefCount(); | ||
| rcOmSnapshot.close(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Had a offline discussion, |
||
| throw new OMException("Unable to load snapshot. " + | ||
| "Snapshot with table key '" + key + "' is no longer active", | ||
| FILE_NOT_FOUND); | ||
| } | ||
|
|
||
| // Check if any entries can be cleaned up. | ||
| // At this point, cache size might temporarily exceed cacheSizeLimit | ||
| // even if there are entries that can be evicted, which is fine since it | ||
| // is a soft limit. | ||
| cleanup(); | ||
|
|
||
| return rcOmSnapshot; | ||
| } | ||
|
|
||
|
|
@@ -198,18 +217,12 @@ public ReferenceCounted<IOmMetadataReader, SnapshotCache> get(String key, boolea | |
| * @param key snapshot table key | ||
| */ | ||
| public void release(String key) { | ||
| dbMap.compute(key, (k, v) -> { | ||
| if (v == null) { | ||
| throw new IllegalArgumentException("Key '" + key + "' does not exist in cache."); | ||
| } else { | ||
| v.decrementRefCount(); | ||
| } | ||
| return v; | ||
| }); | ||
|
|
||
| // The cache size might have already exceeded the soft limit | ||
| // Thus triggering cleanup() to check and evict if applicable | ||
| cleanup(); | ||
| ReferenceCounted<IOmMetadataReader, SnapshotCache> val = dbMap.get(key); | ||
| if (val == null) { | ||
| throw new IllegalArgumentException("Key '" + key + "' does not " + | ||
| "exist in cache."); | ||
| } | ||
| val.decrementRefCount(); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -221,11 +234,25 @@ public void release(OmSnapshot omSnapshot) { | |
| release(snapshotTableKey); | ||
| } | ||
|
|
||
| /** | ||
| * Callback method used to enqueue or dequeue ReferenceCounted from | ||
| * pendingEvictionList. | ||
| * @param referenceCounted ReferenceCounted object | ||
| */ | ||
| @Override | ||
| public void callback(ReferenceCounted referenceCounted) { | ||
| if (referenceCounted.getTotalRefCount() == 0L) { | ||
| // Reference count reaches zero, add to pendingEvictionList | ||
| pendingEvictionQueue.add(((OmSnapshot) referenceCounted.get()) | ||
| .getSnapshotTableKey()); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Wrapper for cleanupInternal() that is synchronized to prevent multiple | ||
| * threads from interleaving into the cleanup method. | ||
| */ | ||
| private synchronized void cleanup() { | ||
| private void cleanup() { | ||
| if (dbMap.size() > cacheSizeLimit) { | ||
| cleanupInternal(); | ||
| } | ||
|
|
@@ -237,8 +264,9 @@ private synchronized void cleanup() { | |
| * TODO: [SNAPSHOT] Add new ozone debug CLI command to trigger this directly. | ||
| */ | ||
| private void cleanupInternal() { | ||
| for (Map.Entry<String, ReferenceCounted<IOmMetadataReader, SnapshotCache>> entry : dbMap.entrySet()) { | ||
| dbMap.compute(entry.getKey(), (k, v) -> { | ||
| for (String evictionKey : pendingEvictionQueue) { | ||
| dbMap.compute(evictionKey, (k, v) -> { | ||
| pendingEvictionQueue.remove(k); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about this. Iterating and removing elements, while
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be fine, since this is a concurrent hashset.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add test for it? Or may be use iterator. |
||
| if (v == null) { | ||
| throw new IllegalStateException("Key '" + k + "' does not exist in cache. The RocksDB " + | ||
| "instance of the Snapshot may not be closed properly."); | ||
|
|
@@ -261,4 +289,6 @@ private void cleanupInternal() { | |
| }); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,7 +82,7 @@ static void beforeAll() throws Exception { | |
| void setUp() { | ||
| // Reset cache for each test case | ||
| snapshotCache = new SnapshotCache( | ||
| omSnapshotManager, cacheLoader, CACHE_SIZE_LIMIT); | ||
| omSnapshotManager, cacheLoader, CACHE_SIZE_LIMIT, 0); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How setting the clean up interval to 0 is working for the test? I don't think it is correct to set clean-up to 0 or tests are more correct.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't want the cleanup service to run here, since everything is mocked here. |
||
| } | ||
|
|
||
| @AfterEach | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it was indented better before.