Skip to content
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

AbfsoutputStream refactor #118

Merged
merged 88 commits into from
Jun 25, 2024
Merged

Conversation

anmolanmol1234
Copy link
Collaborator

@anmolanmol1234 anmolanmol1234 commented Jun 13, 2024

Description of PR :

This Pr is in correlation to the series of work done under Parent Jira: [HADOOP-19179]

(https://issues.apache.org/jira/browse/HADOOP-19179)
Jira for this Patch: https://issues.apache.org/jira/browse/HADOOP-19187

Scope of this task is to refactor the AbfsOutputStream class to handle the ingress for DFS and Blob endpoint effectively.

Production code changes :

The AbfsOutputStream class is crucial for handling the data being written to Azure Storage. Its primary responsibilities include:

  • Buffering: Temporarily holding data in memory before it is uploaded.
  • Streaming: Efficiently streaming data to Azure Storage.
  • Committing: Ensuring that buffered data is correctly uploaded and committed to Azure Storage.

New Additions

The new additions introduce a more modular and flexible approach to managing data ingress (data being written to storage), catering to both Azure Data Lake Storage (ADLS) and Azure Blob Storage.

AzureIngressHandler

The AzureIngressHandler is a new parent class designed to encapsulate common logic for data ingress operations. It simplifies the process of writing data to Azure Storage by providing a unified interface. This class has two specialized child classes:

  1. AzureDfsIngressHandler:

    • Manages data ingress specifically for Azure Data Lake Storage (DFS).
    • Handles operations like creating, appending, and flushing data blocks for DFS.
  2. AzureBlobIngressHandler:

    • Manages data ingress specifically for Azure Blob Storage (BLOB).
    • Handles operations like creating, appending, and flushing data blocks for Blob Storage, while ensuring that each block has a unique blockId.

AbfsBlock and AbfsBlobBlock

Data is managed in discrete blocks to improve efficiency and manageability.

  1. AbfsBlock:

    • A basic structure for buffering data.
    • Used as a common block type for both DFS and Blob Storage.
  2. AbfsBlobBlock:

    • A subclass of AbfsBlock tailored for Blob Storage.
    • Requires a unique blockId for each block, which is necessary for the Blob Storage API.

Block Managers

To manage these data blocks, new manager classes have been introduced. These classes handle the lifecycle of blocks, including creation, appending, and flushing.

  1. AzureBlockManager:

    • A parent class for managing the lifecycle of data blocks.
    • Provides common functionality for block management.
  2. AzureDFSBlockManager:

    • Manages the lifecycle of AbfsBlock instances for DFS.
    • Handles the specifics of appending and flushing blocks in DFS.
  3. AzureBlobBlockManager:

    • Manages the lifecycle of AbfsBlobBlock instances for Blob Storage.
    • Ensures each block has a unique blockId.
    • Handles the specifics of appending and flushing blocks in Blob Storage.

Integration with AbfsOutputStream

The AbfsOutputStream class has been updated to incorporate the new ingress flow logic, enhancing its ability to handle data writes to both DFS and Blob Storage. Here’s how it integrates:

  1. Configuration Selection:

    • The AbfsOutputStream reads the configuration parameter fs.azure.ingress.service.type to determine whether the user has configured the system to use BLOB or DFS for data ingress.
  2. Handler Initialization:

    • Based on the configuration, AbfsOutputStream initializes the appropriate handler (AzureBlobIngressHandler or AzureDfsIngressHandler).
  3. Buffering Data:

    • As data is written to AbfsOutputStream, it is buffered into blocks (AbfsBlock for DFS or AbfsBlobBlock for Blob Storage).
  4. Managing Blocks:

    • The corresponding block manager (AzureDFSBlockManager or AzureBlobBlockManager) manages the lifecycle of these blocks, ensuring that data is correctly created, appended, and flushed.
  5. Block Id Management (Blob Specific):

    • For Blob Storage, AzureBlobBlockManager ensures that each block has a unique blockId, adhering to the requirements of the Blob Storage API.

Detailed Flow

  1. Creating Data Blocks:

    • When data is written to AbfsOutputStream, it is divided into blocks (AbfsBlock for DFS or AbfsBlobBlock for Blob Storage).
  2. Appending Data:

    • These blocks are appended to the Azure storage system via the appropriate handler (AzureBlobIngressHandler or AzureDfsIngressHandler).
  3. Flushing Data:

    • Once all data has been buffered, the blocks are flushed to ensure all buffered data is committed to the storage system.
  4. Lifecycle Management:

    • The block managers (AzureDFSBlockManager and AzureBlobBlockManager) oversee the lifecycle of blocks, handling retries, errors, and ensuring data integrity.

image

Test Code Changes:

  1. Existing tests are modified to work with new abstracted design.
  2. Test Suite was run on DFS Endpoint to make sure the original driver works seamlessly and undisturbed.
  3. Adding some new tests around the new code

Copy link
Collaborator

@anujmodi2021 anujmodi2021 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments.

Copy link
Collaborator

@saxenapranav saxenapranav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking the suggestions. +1

@anmolanmol1234 anmolanmol1234 changed the base branch from azureBlobClient to wasbDepCodeReview June 19, 2024 10:53
Copy link
Collaborator

@anujmodi2021 anujmodi2021 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM...
Thanks

Copy link
Collaborator

@saxenapranav saxenapranav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great code! minor comments. One suggestion related to the outputStream verification in #prepareListToCommit.

Comment on lines 153 to 154
APR_10_2021("2021-04-10"),
AUG_06_2021("2021-08-06"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aug06 looks unused, lets remove it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taken

@@ -109,6 +109,7 @@ public abstract class AbfsClient implements Closeable {

private final URL baseUrl;
private final SharedKeyCredentials sharedKeyCredentials;
// TODO: Abstract for blob and dfs for CPK in OSS PR
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO comment.

Copy link
Collaborator Author

@anmolanmol1234 anmolanmol1234 Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will take care of it while raising OSS PR. Currently we are planning to do scale tests with the latest version, so added a TODO as a reminder

.contains(BLOB_OPERATION_NOT_SUPPORTED);
return ex.getStatusCode() == HTTP_CONFLICT && (ex.getErrorCode()
.getErrorCode().equals(AzureServiceErrorCode.BLOB_OPERATION_NOT_SUPPORTED.getErrorCode()) ||
ex.getErrorMessage().contains(INVALID_APPEND_OPERATION));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we take dependency on error code and not on the message (message might change in future, or if that is not the case)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have written a mail to XFE folks to confirm that can we take dependency on this error code because InvalidAppend is a very generic one. We want to confirm if there are any other scenarios where they return this error code. Once they confirm will update the code as needed

Comment on lines 65 to 67
if (blockManager instanceof AzureBlobBlockManager) {
this.blobBlockManager = (AzureBlobBlockManager) blockManager;
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this is redundant check. reason being, this class can be used only if the outputStream is a newly created class. From the code of switchHadler, looks like this class cant come in switching.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taken

Comment on lines 69 to 71
if (blockManager instanceof AzureDFSBlockManager) {
this.dfsBlockManager = (AzureDFSBlockManager) blockManager;
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks a redundant check, as for the two cases when this class would inited:

  1. outputStream init: blockManager would be null.
  2. in switch, the blockManager sent would be blobBlockManager.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taken

if (switchCompleted && ingressHandler != null) {
return ingressHandler; // Return the existing ingress handler
}
this.client = clientHandler.getClient(serviceType);
if (serviceType == AbfsServiceType.BLOB) {
if (isDFSToBlobFallbackEnabled && serviceTypeAtInit == AbfsServiceType.BLOB) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a constraint that there can not be switch from dfsHandler to blobHandler if the config is not given. As in if the dfs appends error for inter-op, and the config is not given. Job should throw error.

Copy link
Collaborator Author

@anmolanmol1234 anmolanmol1234 Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens already because we don't have switchHandler handling in case of DFS appends as they don't throw the InvalidIngressServiceException, and we switch only if that exception is caught. So DFSIngressHandler to BlobIngress is not posssible via code

Comment on lines 881 to 885
} catch (InvalidIngressServiceException ex) {
// If an invalid ingress service is encountered, switch handler and retry
switchHandler();
op = remoteFlush(offset, retainUncommittedData, isClose, leaseId,
tracingContext);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont think that switch can happen at this this state. Or are you referring that the outputStream is closed without writing anything? If yes, then probably we can completely leave flushing if no write has happened? What you say?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So DFS today calls flush even if there are no appends, in case of Blob handling we have taken care of this by not doing any PutBlockList calls if map is empty. Since we are planning not to change the DFS behaviour, this handling is needed.


int mapEntry = 0;
// If any of the entry in the map doesn't have the status of SUCCESS, fail the flush.
for (Map.Entry<String, AbfsBlockStatus> entry : getBlockStatusMap().entrySet()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that this block is to verify if the AbfsOutputStream has correctly added blockIds in correct sequence. Is there any more reason?

If its to verify, I would be more inclined towards removing this block -> this would save computation at flush + computation to set blockStatusMap. And as far as the verification is concerned, I would say our integ tests should give that confidence (plus if this is breaking, this will break at large scale).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we remove this block, I would suggest further to remove blockStatusMap totally. orderedBlockList is the important data-structure for us.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate the suggestion here but would like to keep this code block as it is. Computation cost is not much here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that this block is to verify if the AbfsOutputStream has correctly added blockIds in correct sequence. Is there any more reason? -> this is not just for sequence, it validates that all appends are successful before flushing

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code would be firing at flushWrittenBytesToServiceInternal which has two callers:

  1. flushWrittenBytesToServiceAsync
  2. flushWrittenBytesToService
    In both the cases, flushWrittenBytesToServiceInternal can be only called when the parallel threads are completely done sucessfully.

So, technically this flush api on server would be getting called only once the writing is successful on all the threads. As per code, the data-consistency for write / flush is maintained.

@anmolanmol1234 anmolanmol1234 merged commit 8937ae5 into wasbDepCodeReview Jun 25, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants