-
Notifications
You must be signed in to change notification settings - Fork 625
HDDS-11411. Snapshot garbage collection should not run when the keys are moved from a deleted snapshot to the next snapshot in the chain #7193
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 4 commits
b5f21a0
7c556b4
bad7fe6
7e66143
83491aa
05787df
bf40940
7b570d5
89ca0f3
2a707fa
792db8e
36411a6
723c52b
3f568db
7cb49b7
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 |
|---|---|---|
|
|
@@ -1381,6 +1381,8 @@ message PurgeKeysRequest { | |
| // if set, will purge keys in a snapshot DB instead of active DB | ||
| optional string snapshotTableKey = 2; | ||
| repeated SnapshotMoveKeyInfos keysToUpdate = 3; | ||
| // previous snapshotID can also be null & this field would be absent in older requests. | ||
| optional NullableUUID expectedPreviousSnapshotID = 4; | ||
|
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 do you deal with it if this field is null ?
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. protobuf fields cannot be null.
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. I want to pass expectedPreviousSnapshotID = null for the case there are no snapshots in the chain. But older requests might not have a expectedPreviousSnapshotID in the request, so this validation could incorrectly run for the older requests leading to inconsistencies in the OM db on replays.
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. Since there is no direct explicit way to differentiate b/w older requests & null values. I had to create a wrapper which means I can set NullableUUID which doesn't have anything inside. Since uuid field inside NullableUUID type is optional, we can signify this as a newer request and having nothing inside the field would signify a null value. |
||
| } | ||
|
|
||
| message PurgeKeysResponse { | ||
|
|
@@ -1403,6 +1405,12 @@ message PurgePathsResponse { | |
| message PurgeDirectoriesRequest { | ||
| repeated PurgePathRequest deletedPath = 1; | ||
| optional string snapshotTableKey = 2; | ||
| // previous snapshotID can also be null & this field would be absent in older requests. | ||
| optional NullableUUID expectedPreviousSnapshotID = 3; | ||
|
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. same comment as above regarding this field being null. |
||
| } | ||
|
|
||
| message NullableUUID { | ||
|
hemantk-12 marked this conversation as resolved.
|
||
| optional hadoop.hdds.UUID uuid = 1; | ||
| } | ||
|
|
||
| message PurgeDirectoriesResponse { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -143,6 +143,8 @@ | |
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_OPEN_KEY_CLEANUP_SERVICE_INTERVAL_DEFAULT; | ||
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_OPEN_KEY_CLEANUP_SERVICE_TIMEOUT; | ||
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_OPEN_KEY_CLEANUP_SERVICE_TIMEOUT_DEFAULT; | ||
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_DEEP_CLEANING_ENABLED; | ||
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_DEEP_CLEANING_ENABLED_DEFAULT; | ||
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_DIRECTORY_SERVICE_INTERVAL; | ||
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_DIRECTORY_SERVICE_INTERVAL_DEFAULT; | ||
| import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_DIRECTORY_SERVICE_TIMEOUT; | ||
|
|
@@ -230,6 +232,8 @@ public KeyManagerImpl(OzoneManager om, ScmClient scmClient, | |
|
|
||
| @Override | ||
| public void start(OzoneConfiguration configuration) { | ||
| boolean isSnapshotDeepCleaningEnabled = configuration.getBoolean(OZONE_SNAPSHOT_DEEP_CLEANING_ENABLED, | ||
| OZONE_SNAPSHOT_DEEP_CLEANING_ENABLED_DEFAULT); | ||
| if (keyDeletingService == null) { | ||
| long blockDeleteInterval = configuration.getTimeDuration( | ||
| OZONE_BLOCK_DELETING_SERVICE_INTERVAL, | ||
|
|
@@ -241,7 +245,7 @@ public void start(OzoneConfiguration configuration) { | |
| TimeUnit.MILLISECONDS); | ||
| keyDeletingService = new KeyDeletingService(ozoneManager, | ||
| scmClient.getBlockClient(), this, blockDeleteInterval, | ||
| serviceTimeout, configuration); | ||
| serviceTimeout, configuration, isSnapshotDeepCleaningEnabled); | ||
| keyDeletingService.start(); | ||
| } | ||
|
|
||
|
|
@@ -314,7 +318,7 @@ public void start(OzoneConfiguration configuration) { | |
| } | ||
| } | ||
|
|
||
| if (snapshotDirectoryCleaningService == null && | ||
| if (isSnapshotDeepCleaningEnabled && snapshotDirectoryCleaningService == null && | ||
|
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. && or
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. It has to be && . Configuration to isSnapshotDeepCleaningEnabled should be enabled & also snapshotDirectoryCleaningService should not have been initialized before. |
||
| ozoneManager.isFilesystemSnapshotEnabled()) { | ||
| long dirDeleteInterval = configuration.getTimeDuration( | ||
| OZONE_SNAPSHOT_DIRECTORY_SERVICE_INTERVAL, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,13 +23,21 @@ | |
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.Set; | ||
| import java.util.UUID; | ||
|
|
||
| import com.google.common.collect.Maps; | ||
| import org.apache.commons.compress.utils.Lists; | ||
| import org.apache.commons.lang3.tuple.Pair; | ||
| import org.apache.hadoop.hdds.utils.TransactionInfo; | ||
| import org.apache.hadoop.hdds.utils.db.cache.CacheKey; | ||
| import org.apache.hadoop.hdds.utils.db.cache.CacheValue; | ||
| import org.apache.hadoop.ozone.OzoneConsts; | ||
| import org.apache.hadoop.ozone.om.OMMetrics; | ||
| import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; | ||
| import org.apache.hadoop.ozone.om.SnapshotChainManager; | ||
| import org.apache.hadoop.ozone.om.exceptions.OMException; | ||
|
swamirishi marked this conversation as resolved.
Outdated
|
||
| import org.apache.hadoop.ozone.om.snapshot.SnapshotUtils; | ||
| import org.apache.ratis.server.protocol.TermIndex; | ||
| import org.apache.hadoop.ozone.om.OMMetadataManager; | ||
|
|
@@ -45,8 +53,10 @@ | |
| import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; | ||
| import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; | ||
|
|
||
| import static org.apache.hadoop.hdds.HddsUtils.fromProtobuf; | ||
| import static org.apache.hadoop.ozone.OzoneConsts.DELETED_HSYNC_KEY; | ||
| import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.BUCKET_LOCK; | ||
| import static org.apache.hadoop.ozone.om.snapshot.SnapshotUtils.validatePreviousSnapshotId; | ||
|
|
||
| /** | ||
| * Handles purging of keys from OM DB. | ||
|
|
@@ -66,19 +76,34 @@ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, TermIn | |
|
|
||
| List<OzoneManagerProtocolProtos.PurgePathRequest> purgeRequests = | ||
| purgeDirsRequest.getDeletedPathList(); | ||
|
|
||
| SnapshotInfo fromSnapshotInfo = null; | ||
| Set<Pair<String, String>> lockSet = new HashSet<>(); | ||
| Map<Pair<String, String>, OmBucketInfo> volBucketInfoMap = new HashMap<>(); | ||
| OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager(); | ||
| OmMetadataManagerImpl omMetadataManager = (OmMetadataManagerImpl) ozoneManager.getMetadataManager(); | ||
| Map<String, OmKeyInfo> openKeyInfoMap = new HashMap<>(); | ||
|
|
||
| OMMetrics omMetrics = ozoneManager.getMetrics(); | ||
| OMResponse.Builder omResponse = OmResponseUtil.getOMResponseBuilder( | ||
| getOmRequest()); | ||
| final SnapshotInfo fromSnapshotInfo; | ||
| try { | ||
| if (fromSnapshot != null) { | ||
| fromSnapshotInfo = SnapshotUtils.getSnapshotInfo(ozoneManager, fromSnapshot); | ||
| fromSnapshotInfo = fromSnapshot != null ? SnapshotUtils.getSnapshotInfo(ozoneManager, | ||
| fromSnapshot) : null; | ||
| // Checking if this request is an old request or new one. | ||
|
hemantk-12 marked this conversation as resolved.
|
||
| if (purgeDirsRequest.hasExpectedPreviousSnapshotID()) { | ||
| // Validating previous snapshot since while purging deletes, a snapshot create request could make this purge | ||
|
swamirishi marked this conversation as resolved.
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. IMO, this is redundant.
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. the comment here is different |
||
| // directory request invalid on AOS since the deletedDirectory would be in the newly created snapshot. Adding | ||
| // subdirectories could lead to not being able to reclaim sub-files and subdirectories since the | ||
| // file/directory would be present in the newly created snapshot. | ||
| // Validating previous snapshot can ensure the chain hasn't changed. | ||
| UUID expectedPreviousSnapshotId = purgeDirsRequest.getExpectedPreviousSnapshotID().hasUuid() | ||
| ? fromProtobuf(purgeDirsRequest.getExpectedPreviousSnapshotID().getUuid()) : null; | ||
| validatePreviousSnapshotId(fromSnapshotInfo, omMetadataManager.getSnapshotChainManager(), | ||
| expectedPreviousSnapshotId); | ||
| } | ||
|
|
||
| } catch (IOException e) { | ||
| LOG.error("Error occured while performing OMDirectoriesPurge. ", e); | ||
|
swamirishi marked this conversation as resolved.
Outdated
|
||
| return new OMDirectoriesPurgeResponseWithFSO(createErrorOMResponse(omResponse, e)); | ||
| } | ||
| try { | ||
| for (OzoneManagerProtocolProtos.PurgePathRequest path : purgeRequests) { | ||
| for (OzoneManagerProtocolProtos.KeyInfo key : | ||
| path.getMarkDeletedSubDirsList()) { | ||
|
|
@@ -170,12 +195,8 @@ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, TermIn | |
| } | ||
| } | ||
|
|
||
| OMResponse.Builder omResponse = OmResponseUtil.getOMResponseBuilder( | ||
| getOmRequest()); | ||
| OMClientResponse omClientResponse = new OMDirectoriesPurgeResponseWithFSO( | ||
| return new OMDirectoriesPurgeResponseWithFSO( | ||
| omResponse.build(), purgeRequests, ozoneManager.isRatisEnabled(), | ||
| getBucketLayout(), volBucketInfoMap, fromSnapshotInfo, openKeyInfoMap); | ||
|
|
||
| return omClientResponse; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ | |
| import org.apache.hadoop.hdds.utils.db.cache.CacheKey; | ||
| import org.apache.hadoop.hdds.utils.db.cache.CacheValue; | ||
| import org.apache.hadoop.ozone.om.OmMetadataManagerImpl; | ||
| import org.apache.hadoop.ozone.om.exceptions.OMException; | ||
| import org.apache.hadoop.ozone.om.snapshot.SnapshotUtils; | ||
| import org.apache.ratis.server.protocol.TermIndex; | ||
| import org.apache.hadoop.ozone.om.OzoneManager; | ||
|
|
@@ -42,6 +43,10 @@ | |
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.util.List; | ||
| import java.util.UUID; | ||
|
|
||
| import static org.apache.hadoop.hdds.HddsUtils.fromProtobuf; | ||
| import static org.apache.hadoop.ozone.om.snapshot.SnapshotUtils.validatePreviousSnapshotId; | ||
|
|
||
| /** | ||
| * Handles purging of keys from OM DB. | ||
|
|
@@ -58,30 +63,46 @@ public OMKeyPurgeRequest(OMRequest omRequest) { | |
| @Override | ||
| public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, TermIndex termIndex) { | ||
| PurgeKeysRequest purgeKeysRequest = getOmRequest().getPurgeKeysRequest(); | ||
| List<DeletedKeys> bucketDeletedKeysList = purgeKeysRequest | ||
| .getDeletedKeysList(); | ||
| List<SnapshotMoveKeyInfos> keysToUpdateList = purgeKeysRequest | ||
| .getKeysToUpdateList(); | ||
| String fromSnapshot = purgeKeysRequest.hasSnapshotTableKey() ? | ||
| purgeKeysRequest.getSnapshotTableKey() : null; | ||
| List<String> keysToBePurgedList = new ArrayList<>(); | ||
| List<DeletedKeys> bucketDeletedKeysList = purgeKeysRequest.getDeletedKeysList(); | ||
| List<SnapshotMoveKeyInfos> keysToUpdateList = purgeKeysRequest.getKeysToUpdateList(); | ||
| String fromSnapshot = purgeKeysRequest.hasSnapshotTableKey() ? purgeKeysRequest.getSnapshotTableKey() : null; | ||
| OmMetadataManagerImpl omMetadataManager = (OmMetadataManagerImpl) ozoneManager.getMetadataManager(); | ||
|
|
||
| OMResponse.Builder omResponse = OmResponseUtil.getOMResponseBuilder( | ||
| getOmRequest()); | ||
| OMClientResponse omClientResponse = null; | ||
|
|
||
| for (DeletedKeys bucketWithDeleteKeys : bucketDeletedKeysList) { | ||
| for (String deletedKey : bucketWithDeleteKeys.getKeysList()) { | ||
| keysToBePurgedList.add(deletedKey); | ||
|
|
||
| final SnapshotInfo fromSnapshotInfo; | ||
| try { | ||
| fromSnapshotInfo = fromSnapshot != null ? SnapshotUtils.getSnapshotInfo(ozoneManager, | ||
| fromSnapshot) : null; | ||
| // Checking if this request is an old request or new one. | ||
| if (purgeKeysRequest.hasExpectedPreviousSnapshotID()) { | ||
| // Validating previous snapshot since while purging deletes, a snapshot create request could make this purge | ||
| // directory request invalid on AOS since the deletedDirectory would be in the newly created snapshot. Adding | ||
| // subdirectories could lead to not being able to reclaim sub-files and subdirectories since the | ||
| // file/directory would be present in the newly created snapshot. | ||
| // Validating previous snapshot can ensure the chain hasn't changed. | ||
|
swamirishi marked this conversation as resolved.
Outdated
|
||
| UUID expectedPreviousSnapshotId = purgeKeysRequest.getExpectedPreviousSnapshotID().hasUuid() | ||
| ? fromProtobuf(purgeKeysRequest.getExpectedPreviousSnapshotID().getUuid()) : null; | ||
| validatePreviousSnapshotId(fromSnapshotInfo, omMetadataManager.getSnapshotChainManager(), | ||
| expectedPreviousSnapshotId); | ||
| } | ||
| } catch (IOException e) { | ||
| LOG.error("Error occured while performing OMDirectoriesPurge. ", e); | ||
|
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. if validatePreviousSnapshotID throws exception, it would be caught here. the relevant error message should be here instead of line 104.
swamirishi marked this conversation as resolved.
Outdated
|
||
| return new OMKeyPurgeResponse(createErrorOMResponse(omResponse, e)); | ||
| } | ||
| final SnapshotInfo fromSnapshotInfo; | ||
|
|
||
| try { | ||
| fromSnapshotInfo = fromSnapshot == null ? null : SnapshotUtils.getSnapshotInfo(ozoneManager, fromSnapshot); | ||
| } catch (IOException ex) { | ||
| return new OMKeyPurgeResponse(createErrorOMResponse(omResponse, ex)); | ||
| List<String> keysToBePurgedList = new ArrayList<>(); | ||
|
|
||
| for (DeletedKeys bucketWithDeleteKeys : bucketDeletedKeysList) { | ||
| keysToBePurgedList.addAll(bucketWithDeleteKeys.getKeysList()); | ||
| } | ||
|
|
||
| if (keysToBePurgedList.isEmpty()) { | ||
| return new OMKeyPurgeResponse(createErrorOMResponse(omResponse, | ||
|
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. is this message right for this condition ?
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. yeah I forgot to move this block above. I had expectedPreviousSnapshotId inside each bucketDeleteKeysList to make it more optimized. But realized it is not worth the effort to making the request bulky to do this validation for all the buckets. It is better to have it atomic than do partial operations. |
||
| new OMException("None of the keys can be purged be purged since a new snapshot was created for all the " + | ||
| "buckets, making this request invalid", OMException.ResultCodes.KEY_DELETION_ERROR))); | ||
| } | ||
|
|
||
| // Setting transaction info for snapshot, this is to prevent duplicate purge requests to OM from background | ||
|
|
@@ -95,10 +116,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, TermIn | |
| } catch (IOException e) { | ||
| return new OMKeyPurgeResponse(createErrorOMResponse(omResponse, e)); | ||
| } | ||
| omClientResponse = new OMKeyPurgeResponse(omResponse.build(), keysToBePurgedList, fromSnapshotInfo, | ||
| keysToUpdateList); | ||
|
|
||
| return omClientResponse; | ||
| return new OMKeyPurgeResponse(omResponse.build(), | ||
| keysToBePurgedList, fromSnapshotInfo, keysToUpdateList); | ||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.