GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API - #39904

Merged
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move
Feb 10, 2024
Merged

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API#39904
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move

Conversation

@felipecrv

@felipecrvfelipecrv commented Feb 2, 2024

Copy link
Copy Markdown
Contributor

Rationale for this change

We need to move directories and files via the arrow::FileSystem interface.

What changes are included in this PR?

  • A few filesystem error reporting improvements
  • A helper class to deal with Azure Storage leases 1
  • The Move() implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
  • Lots of tests

Are these changes tested?

Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.

Are there any user-facing changes?

No breaking changes, only new functionality.

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I took a look at all changes but I don't fully understand yet. I'll review again later.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
// "subdir0/file-at-subdir" exists

// src is a directory and dest does not exists
CreateDirectory(adlfs_client, "subdir0");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that this is needless.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's necessary because I'm testing the scenario where the src exists. The next line moves the subdir0 to subdir1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Right. Right. Removing it.

Comment on lines +1275 to +1276
CreateDirectory(adlfs_client, "subdir1");
CreateDirectory(adlfs_client, "subdir2");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that they are needless.

latest < break_or_expires_at_ &&
!latest_known_expiry_time_.compare_exchange_weak(latest, break_or_expires_at_)) {
}
DCHECK_GE(latest_known_expiry_time_.load(), break_or_expires_at_);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it safe?
I think that latest_known_expiry_time_ may be changed between latest_known_expiry_time_.compare_exchange_weak() and latest_known_expiry_time_.load().

@felipecrvfelipecrvFeb 2, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's safe because latest_known_expiry_time_ monotonically increases (it never goes down). So even if it's changed, the [G]reater than or [E]qual will always work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see!

/// doesn't exist, otherwise a PathNotFound(location) error is produced right away
/// \return A BlobLeaseClient is wrapped as a unique_ptr so it's moveable and
/// optional (nullptr denotes blob not found)
Result<std::unique_ptr<Blobs::BlobLeaseClient>> AcquireBlobLease(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that most codes are duplicated with AcquireContainerLease(). Can we unify them?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

They use different SDK classes and the error handling is subtly different between each. Unifying these would require a lot of templating that would obfuscate the code more than clarify it.

@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting review Awaiting review labels Feb 2, 2024
@github-actionsgithub-actionsBot added awaiting change review Awaiting change review awaiting changes Awaiting changes and removed awaiting changes Awaiting changes awaiting change review Awaiting change review labels Feb 2, 2024
static constexpr std::chrono::seconds kMaxLeaseDuration{60};

public:
LeaseGuard(std::unique_ptr<Blobs::BlobLeaseClient> lease_client,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does it really make sense for this to be controlled by a consumer vs being controlled more internally? Is this a common pattern with Azure outside of our usage?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients. Without concurrent mutators, they are very cheap (lead to no delays at all) and with concurrent mutators, they lead to outcomes we and users can reason about. Note that I often use the lease acquisition as an existence check I would have to do anyways.

[1] https://martinfowler.com/articles/patterns-of-distributed-systems/lease.html

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

I'm referring to anyone using arrow::filesystem::AzureFileSystem directly, whether inside the arrow library (datasets) or not.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients

Yup, I know. I'm just referring to where the control of the lease is managed. But I also just realized that this entire leaseguard class isn't publicly exposed haha. Making my entire question here moot. So we're all good.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A-ha. Yes, the class is totally private. It will get more use-cases but they will all be within this file.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I might move it to a separate internal.h/cc but I would prefer doing it later to reduce noise in these PR as a lot would be moving.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 5, 2024
//
// NOTE: The initial constant values were chosen conservatively. If we learn,
// from experience, that they are causing issues, we can increase them. And if
// broadly applicable values aren't possible, we can make them configurable.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@zeroshade There isn't much to these numbers, but what I can say is that they work well for a client running in Brazil talking to a storage account in a US east coast zone replicated across the US.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It might make sense to make them configurable right off the bat?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think that would be premature. My plan if these constants are not good enough:

Raise lease time to 30s.
Raise operation times to 15s.

Network slow downs are unbounded, but failing without data loss risk and allowing a retry would be the way to go here IMO.

(I hardcoded GetUrl on the SDK class to debug my changes :)
@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Feb 7, 2024
kou
kou approved these changes Feb 8, 2024

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment on lines +2070 to +2072
ARROW_ASSIGN_OR_RAISE(auto src_lease_client,
AcquireContainerLease(src, kLeaseDuration));
LeaseGuard src_lease_guard{std::move(src_lease_client), kLeaseDuration};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We must specify the same lease duration to AcquireContainer() and LeaseGuard::LeaseGuard(), right?
It may be misused.
Can we return std::unique_ptr<LeaseGuard> by AcquireContainerLease() to avoid creating a LeaseGuard manually?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I considered this, but the problem is that the LeaseGuard should often live in an outer scope relative to the AcquireContainerLease call, so I considered this misuse trap less bad than writing code that declares the guard far from where it's used -- I do that now sometime with optionals and I think that communicates the intent more clearly.

try {
auto src_list_response = src_container_client.ListBlobs(list_blobs_options);
if (!src_list_response.Blobs.empty()) {
return Status::IOError("Unable to replace empty container: '", dest.all, "'");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return Status::IOError("Unable to replace empty container: '", dest.all, "'");
return Status::IOError("Unable to replace by non empty container: '", src.all, "'");

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added a comment explaining why dest is the correct here.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
}
try {
src_lease_guard.BreakBeforeDeletion(kTimeNeededForContainerDeletion);
src_container_client.DeleteIfExists(options);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to use DeleteIfExists() here? Can we use Delete() here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, Delete is enough. I'm changing.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated

// These functions are marked ARROW_NOINLINE because they are called from
// multiple locations, but are not performance-critical.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This comment is about the 3 functions below, not only the immediately next one.

Comment on lines +1013 to +1014
// TODO(felipecrv): investigate why this can't be false
select.allow_not_found = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you want to solve this in this PR?
If you want to defer this to a follow-up task, could you create an issue for it?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment on lines +1205 to +1206
GTEST_SKIP()
<< "The rest of TestMovePaths is not implemented for non-HNS scenarios";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just return here?
Should we use GTEST_SKIP() here? We have some tests for non-HNS case.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I would prefer to more loudly communicate that MOST of the tests are not in fact running. The return is too subtle.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 9, 2024
@conbench-apache-arrow

Copy link
Copy Markdown

After merging your PR, Conbench analyzed the 6 benchmarking runs that have been run so far on merge-commit 0ce54b6.

There were no benchmark performance regressions. 🎉

The full Conbench report has more details. It also includes information about 4 possible false positives for unstable benchmarks that are known to sometimes produce them.

dgreiss pushed a commit to dgreiss/arrow that referenced this pull request Feb 19, 2024
…aLake Storage Gen 2 API (apache#39904)
### Rationale for this change
We need to move directories and files via the `arrow::FileSystem` interface.
### What changes are included in this PR?
- A few filesystem error reporting improvements
- A helper class to deal with Azure Storage leases [1]
- The `Move()` implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
- Lots of tests
[1]: https://learn.microsoft.com/en-us/rest/api/storageservices/lease-blob
### Are these changes tested?
Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.
### Are there any user-facing changes?
No breaking changes, only new functionality.
* Closes: apache#38704
Authored-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Signed-off-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Sign up for freeto 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.

[C++][FS][Azure] Implement Move()

3 participants

@felipecrv@kou@zeroshade
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API - #39904

Merged
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move
Feb 10, 2024
Merged

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API#39904
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move

Conversation

@felipecrv

@felipecrvfelipecrv commented Feb 2, 2024

Copy link
Copy Markdown
Contributor

Rationale for this change

We need to move directories and files via the arrow::FileSystem interface.

What changes are included in this PR?

  • A few filesystem error reporting improvements
  • A helper class to deal with Azure Storage leases 1
  • The Move() implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
  • Lots of tests

Are these changes tested?

Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.

Are there any user-facing changes?

No breaking changes, only new functionality.

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I took a look at all changes but I don't fully understand yet. I'll review again later.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
// "subdir0/file-at-subdir" exists

// src is a directory and dest does not exists
CreateDirectory(adlfs_client, "subdir0");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that this is needless.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's necessary because I'm testing the scenario where the src exists. The next line moves the subdir0 to subdir1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Right. Right. Removing it.

Comment on lines +1275 to +1276
CreateDirectory(adlfs_client, "subdir1");
CreateDirectory(adlfs_client, "subdir2");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that they are needless.

latest < break_or_expires_at_ &&
!latest_known_expiry_time_.compare_exchange_weak(latest, break_or_expires_at_)) {
}
DCHECK_GE(latest_known_expiry_time_.load(), break_or_expires_at_);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it safe?
I think that latest_known_expiry_time_ may be changed between latest_known_expiry_time_.compare_exchange_weak() and latest_known_expiry_time_.load().

@felipecrvfelipecrvFeb 2, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's safe because latest_known_expiry_time_ monotonically increases (it never goes down). So even if it's changed, the [G]reater than or [E]qual will always work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see!

/// doesn't exist, otherwise a PathNotFound(location) error is produced right away
/// \return A BlobLeaseClient is wrapped as a unique_ptr so it's moveable and
/// optional (nullptr denotes blob not found)
Result<std::unique_ptr<Blobs::BlobLeaseClient>> AcquireBlobLease(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that most codes are duplicated with AcquireContainerLease(). Can we unify them?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

They use different SDK classes and the error handling is subtly different between each. Unifying these would require a lot of templating that would obfuscate the code more than clarify it.

@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting review Awaiting review labels Feb 2, 2024
@github-actionsgithub-actionsBot added awaiting change review Awaiting change review awaiting changes Awaiting changes and removed awaiting changes Awaiting changes awaiting change review Awaiting change review labels Feb 2, 2024
static constexpr std::chrono::seconds kMaxLeaseDuration{60};

public:
LeaseGuard(std::unique_ptr<Blobs::BlobLeaseClient> lease_client,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does it really make sense for this to be controlled by a consumer vs being controlled more internally? Is this a common pattern with Azure outside of our usage?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients. Without concurrent mutators, they are very cheap (lead to no delays at all) and with concurrent mutators, they lead to outcomes we and users can reason about. Note that I often use the lease acquisition as an existence check I would have to do anyways.

[1] https://martinfowler.com/articles/patterns-of-distributed-systems/lease.html

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

I'm referring to anyone using arrow::filesystem::AzureFileSystem directly, whether inside the arrow library (datasets) or not.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients

Yup, I know. I'm just referring to where the control of the lease is managed. But I also just realized that this entire leaseguard class isn't publicly exposed haha. Making my entire question here moot. So we're all good.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A-ha. Yes, the class is totally private. It will get more use-cases but they will all be within this file.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I might move it to a separate internal.h/cc but I would prefer doing it later to reduce noise in these PR as a lot would be moving.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 5, 2024
//
// NOTE: The initial constant values were chosen conservatively. If we learn,
// from experience, that they are causing issues, we can increase them. And if
// broadly applicable values aren't possible, we can make them configurable.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@zeroshade There isn't much to these numbers, but what I can say is that they work well for a client running in Brazil talking to a storage account in a US east coast zone replicated across the US.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It might make sense to make them configurable right off the bat?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think that would be premature. My plan if these constants are not good enough:

Raise lease time to 30s.
Raise operation times to 15s.

Network slow downs are unbounded, but failing without data loss risk and allowing a retry would be the way to go here IMO.

(I hardcoded GetUrl on the SDK class to debug my changes :)
@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Feb 7, 2024
kou
kou approved these changes Feb 8, 2024

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment on lines +2070 to +2072
ARROW_ASSIGN_OR_RAISE(auto src_lease_client,
AcquireContainerLease(src, kLeaseDuration));
LeaseGuard src_lease_guard{std::move(src_lease_client), kLeaseDuration};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We must specify the same lease duration to AcquireContainer() and LeaseGuard::LeaseGuard(), right?
It may be misused.
Can we return std::unique_ptr<LeaseGuard> by AcquireContainerLease() to avoid creating a LeaseGuard manually?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I considered this, but the problem is that the LeaseGuard should often live in an outer scope relative to the AcquireContainerLease call, so I considered this misuse trap less bad than writing code that declares the guard far from where it's used -- I do that now sometime with optionals and I think that communicates the intent more clearly.

try {
auto src_list_response = src_container_client.ListBlobs(list_blobs_options);
if (!src_list_response.Blobs.empty()) {
return Status::IOError("Unable to replace empty container: '", dest.all, "'");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return Status::IOError("Unable to replace empty container: '", dest.all, "'");
return Status::IOError("Unable to replace by non empty container: '", src.all, "'");

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added a comment explaining why dest is the correct here.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
}
try {
src_lease_guard.BreakBeforeDeletion(kTimeNeededForContainerDeletion);
src_container_client.DeleteIfExists(options);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to use DeleteIfExists() here? Can we use Delete() here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, Delete is enough. I'm changing.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated

// These functions are marked ARROW_NOINLINE because they are called from
// multiple locations, but are not performance-critical.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This comment is about the 3 functions below, not only the immediately next one.

Comment on lines +1013 to +1014
// TODO(felipecrv): investigate why this can't be false
select.allow_not_found = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you want to solve this in this PR?
If you want to defer this to a follow-up task, could you create an issue for it?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment on lines +1205 to +1206
GTEST_SKIP()
<< "The rest of TestMovePaths is not implemented for non-HNS scenarios";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just return here?
Should we use GTEST_SKIP() here? We have some tests for non-HNS case.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I would prefer to more loudly communicate that MOST of the tests are not in fact running. The return is too subtle.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 9, 2024
@conbench-apache-arrow

Copy link
Copy Markdown

After merging your PR, Conbench analyzed the 6 benchmarking runs that have been run so far on merge-commit 0ce54b6.

There were no benchmark performance regressions. 🎉

The full Conbench report has more details. It also includes information about 4 possible false positives for unstable benchmarks that are known to sometimes produce them.

dgreiss pushed a commit to dgreiss/arrow that referenced this pull request Feb 19, 2024
…aLake Storage Gen 2 API (apache#39904)
### Rationale for this change
We need to move directories and files via the `arrow::FileSystem` interface.
### What changes are included in this PR?
- A few filesystem error reporting improvements
- A helper class to deal with Azure Storage leases [1]
- The `Move()` implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
- Lots of tests
[1]: https://learn.microsoft.com/en-us/rest/api/storageservices/lease-blob
### Are these changes tested?
Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.
### Are there any user-facing changes?
No breaking changes, only new functionality.
* Closes: apache#38704
Authored-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Signed-off-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Sign up for freeto 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.

[C++][FS][Azure] Implement Move()

3 participants

@felipecrv@kou@zeroshade
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API - #39904

Merged
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move
Feb 10, 2024
Merged

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API#39904
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move

Conversation

@felipecrv

@felipecrvfelipecrv commented Feb 2, 2024

Copy link
Copy Markdown
Contributor

Rationale for this change

We need to move directories and files via the arrow::FileSystem interface.

What changes are included in this PR?

  • A few filesystem error reporting improvements
  • A helper class to deal with Azure Storage leases 1
  • The Move() implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
  • Lots of tests

Are these changes tested?

Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.

Are there any user-facing changes?

No breaking changes, only new functionality.

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I took a look at all changes but I don't fully understand yet. I'll review again later.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
// "subdir0/file-at-subdir" exists

// src is a directory and dest does not exists
CreateDirectory(adlfs_client, "subdir0");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that this is needless.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's necessary because I'm testing the scenario where the src exists. The next line moves the subdir0 to subdir1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Right. Right. Removing it.

Comment on lines +1275 to +1276
CreateDirectory(adlfs_client, "subdir1");
CreateDirectory(adlfs_client, "subdir2");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that they are needless.

latest < break_or_expires_at_ &&
!latest_known_expiry_time_.compare_exchange_weak(latest, break_or_expires_at_)) {
}
DCHECK_GE(latest_known_expiry_time_.load(), break_or_expires_at_);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it safe?
I think that latest_known_expiry_time_ may be changed between latest_known_expiry_time_.compare_exchange_weak() and latest_known_expiry_time_.load().

@felipecrvfelipecrvFeb 2, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's safe because latest_known_expiry_time_ monotonically increases (it never goes down). So even if it's changed, the [G]reater than or [E]qual will always work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see!

/// doesn't exist, otherwise a PathNotFound(location) error is produced right away
/// \return A BlobLeaseClient is wrapped as a unique_ptr so it's moveable and
/// optional (nullptr denotes blob not found)
Result<std::unique_ptr<Blobs::BlobLeaseClient>> AcquireBlobLease(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that most codes are duplicated with AcquireContainerLease(). Can we unify them?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

They use different SDK classes and the error handling is subtly different between each. Unifying these would require a lot of templating that would obfuscate the code more than clarify it.

@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting review Awaiting review labels Feb 2, 2024
@github-actionsgithub-actionsBot added awaiting change review Awaiting change review awaiting changes Awaiting changes and removed awaiting changes Awaiting changes awaiting change review Awaiting change review labels Feb 2, 2024
static constexpr std::chrono::seconds kMaxLeaseDuration{60};

public:
LeaseGuard(std::unique_ptr<Blobs::BlobLeaseClient> lease_client,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does it really make sense for this to be controlled by a consumer vs being controlled more internally? Is this a common pattern with Azure outside of our usage?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients. Without concurrent mutators, they are very cheap (lead to no delays at all) and with concurrent mutators, they lead to outcomes we and users can reason about. Note that I often use the lease acquisition as an existence check I would have to do anyways.

[1] https://martinfowler.com/articles/patterns-of-distributed-systems/lease.html

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

I'm referring to anyone using arrow::filesystem::AzureFileSystem directly, whether inside the arrow library (datasets) or not.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients

Yup, I know. I'm just referring to where the control of the lease is managed. But I also just realized that this entire leaseguard class isn't publicly exposed haha. Making my entire question here moot. So we're all good.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A-ha. Yes, the class is totally private. It will get more use-cases but they will all be within this file.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I might move it to a separate internal.h/cc but I would prefer doing it later to reduce noise in these PR as a lot would be moving.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 5, 2024
//
// NOTE: The initial constant values were chosen conservatively. If we learn,
// from experience, that they are causing issues, we can increase them. And if
// broadly applicable values aren't possible, we can make them configurable.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@zeroshade There isn't much to these numbers, but what I can say is that they work well for a client running in Brazil talking to a storage account in a US east coast zone replicated across the US.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It might make sense to make them configurable right off the bat?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think that would be premature. My plan if these constants are not good enough:

Raise lease time to 30s.
Raise operation times to 15s.

Network slow downs are unbounded, but failing without data loss risk and allowing a retry would be the way to go here IMO.

(I hardcoded GetUrl on the SDK class to debug my changes :)
@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Feb 7, 2024
kou
kou approved these changes Feb 8, 2024

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment on lines +2070 to +2072
ARROW_ASSIGN_OR_RAISE(auto src_lease_client,
AcquireContainerLease(src, kLeaseDuration));
LeaseGuard src_lease_guard{std::move(src_lease_client), kLeaseDuration};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We must specify the same lease duration to AcquireContainer() and LeaseGuard::LeaseGuard(), right?
It may be misused.
Can we return std::unique_ptr<LeaseGuard> by AcquireContainerLease() to avoid creating a LeaseGuard manually?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I considered this, but the problem is that the LeaseGuard should often live in an outer scope relative to the AcquireContainerLease call, so I considered this misuse trap less bad than writing code that declares the guard far from where it's used -- I do that now sometime with optionals and I think that communicates the intent more clearly.

try {
auto src_list_response = src_container_client.ListBlobs(list_blobs_options);
if (!src_list_response.Blobs.empty()) {
return Status::IOError("Unable to replace empty container: '", dest.all, "'");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return Status::IOError("Unable to replace empty container: '", dest.all, "'");
return Status::IOError("Unable to replace by non empty container: '", src.all, "'");

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added a comment explaining why dest is the correct here.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
}
try {
src_lease_guard.BreakBeforeDeletion(kTimeNeededForContainerDeletion);
src_container_client.DeleteIfExists(options);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to use DeleteIfExists() here? Can we use Delete() here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, Delete is enough. I'm changing.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated

// These functions are marked ARROW_NOINLINE because they are called from
// multiple locations, but are not performance-critical.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This comment is about the 3 functions below, not only the immediately next one.

Comment on lines +1013 to +1014
// TODO(felipecrv): investigate why this can't be false
select.allow_not_found = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you want to solve this in this PR?
If you want to defer this to a follow-up task, could you create an issue for it?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment on lines +1205 to +1206
GTEST_SKIP()
<< "The rest of TestMovePaths is not implemented for non-HNS scenarios";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just return here?
Should we use GTEST_SKIP() here? We have some tests for non-HNS case.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I would prefer to more loudly communicate that MOST of the tests are not in fact running. The return is too subtle.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 9, 2024
@conbench-apache-arrow

Copy link
Copy Markdown

After merging your PR, Conbench analyzed the 6 benchmarking runs that have been run so far on merge-commit 0ce54b6.

There were no benchmark performance regressions. 🎉

The full Conbench report has more details. It also includes information about 4 possible false positives for unstable benchmarks that are known to sometimes produce them.

dgreiss pushed a commit to dgreiss/arrow that referenced this pull request Feb 19, 2024
…aLake Storage Gen 2 API (apache#39904)
### Rationale for this change
We need to move directories and files via the `arrow::FileSystem` interface.
### What changes are included in this PR?
- A few filesystem error reporting improvements
- A helper class to deal with Azure Storage leases [1]
- The `Move()` implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
- Lots of tests
[1]: https://learn.microsoft.com/en-us/rest/api/storageservices/lease-blob
### Are these changes tested?
Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.
### Are there any user-facing changes?
No breaking changes, only new functionality.
* Closes: apache#38704
Authored-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Signed-off-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Sign up for freeto 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.

[C++][FS][Azure] Implement Move()

3 participants

@felipecrv@kou@zeroshade
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API - #39904

Merged
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move
Feb 10, 2024
Merged

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API#39904
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move

Conversation

@felipecrv

@felipecrvfelipecrv commented Feb 2, 2024

Copy link
Copy Markdown
Contributor

Rationale for this change

We need to move directories and files via the arrow::FileSystem interface.

What changes are included in this PR?

  • A few filesystem error reporting improvements
  • A helper class to deal with Azure Storage leases 1
  • The Move() implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
  • Lots of tests

Are these changes tested?

Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.

Are there any user-facing changes?

No breaking changes, only new functionality.

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I took a look at all changes but I don't fully understand yet. I'll review again later.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
// "subdir0/file-at-subdir" exists

// src is a directory and dest does not exists
CreateDirectory(adlfs_client, "subdir0");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that this is needless.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's necessary because I'm testing the scenario where the src exists. The next line moves the subdir0 to subdir1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Right. Right. Removing it.

Comment on lines +1275 to +1276
CreateDirectory(adlfs_client, "subdir1");
CreateDirectory(adlfs_client, "subdir2");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that they are needless.

latest < break_or_expires_at_ &&
!latest_known_expiry_time_.compare_exchange_weak(latest, break_or_expires_at_)) {
}
DCHECK_GE(latest_known_expiry_time_.load(), break_or_expires_at_);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it safe?
I think that latest_known_expiry_time_ may be changed between latest_known_expiry_time_.compare_exchange_weak() and latest_known_expiry_time_.load().

@felipecrvfelipecrvFeb 2, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's safe because latest_known_expiry_time_ monotonically increases (it never goes down). So even if it's changed, the [G]reater than or [E]qual will always work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see!

/// doesn't exist, otherwise a PathNotFound(location) error is produced right away
/// \return A BlobLeaseClient is wrapped as a unique_ptr so it's moveable and
/// optional (nullptr denotes blob not found)
Result<std::unique_ptr<Blobs::BlobLeaseClient>> AcquireBlobLease(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that most codes are duplicated with AcquireContainerLease(). Can we unify them?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

They use different SDK classes and the error handling is subtly different between each. Unifying these would require a lot of templating that would obfuscate the code more than clarify it.

@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting review Awaiting review labels Feb 2, 2024
@github-actionsgithub-actionsBot added awaiting change review Awaiting change review awaiting changes Awaiting changes and removed awaiting changes Awaiting changes awaiting change review Awaiting change review labels Feb 2, 2024
static constexpr std::chrono::seconds kMaxLeaseDuration{60};

public:
LeaseGuard(std::unique_ptr<Blobs::BlobLeaseClient> lease_client,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does it really make sense for this to be controlled by a consumer vs being controlled more internally? Is this a common pattern with Azure outside of our usage?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients. Without concurrent mutators, they are very cheap (lead to no delays at all) and with concurrent mutators, they lead to outcomes we and users can reason about. Note that I often use the lease acquisition as an existence check I would have to do anyways.

[1] https://martinfowler.com/articles/patterns-of-distributed-systems/lease.html

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

I'm referring to anyone using arrow::filesystem::AzureFileSystem directly, whether inside the arrow library (datasets) or not.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients

Yup, I know. I'm just referring to where the control of the lease is managed. But I also just realized that this entire leaseguard class isn't publicly exposed haha. Making my entire question here moot. So we're all good.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A-ha. Yes, the class is totally private. It will get more use-cases but they will all be within this file.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I might move it to a separate internal.h/cc but I would prefer doing it later to reduce noise in these PR as a lot would be moving.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 5, 2024
//
// NOTE: The initial constant values were chosen conservatively. If we learn,
// from experience, that they are causing issues, we can increase them. And if
// broadly applicable values aren't possible, we can make them configurable.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@zeroshade There isn't much to these numbers, but what I can say is that they work well for a client running in Brazil talking to a storage account in a US east coast zone replicated across the US.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It might make sense to make them configurable right off the bat?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think that would be premature. My plan if these constants are not good enough:

Raise lease time to 30s.
Raise operation times to 15s.

Network slow downs are unbounded, but failing without data loss risk and allowing a retry would be the way to go here IMO.

(I hardcoded GetUrl on the SDK class to debug my changes :)
@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Feb 7, 2024
kou
kou approved these changes Feb 8, 2024

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment on lines +2070 to +2072
ARROW_ASSIGN_OR_RAISE(auto src_lease_client,
AcquireContainerLease(src, kLeaseDuration));
LeaseGuard src_lease_guard{std::move(src_lease_client), kLeaseDuration};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We must specify the same lease duration to AcquireContainer() and LeaseGuard::LeaseGuard(), right?
It may be misused.
Can we return std::unique_ptr<LeaseGuard> by AcquireContainerLease() to avoid creating a LeaseGuard manually?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I considered this, but the problem is that the LeaseGuard should often live in an outer scope relative to the AcquireContainerLease call, so I considered this misuse trap less bad than writing code that declares the guard far from where it's used -- I do that now sometime with optionals and I think that communicates the intent more clearly.

try {
auto src_list_response = src_container_client.ListBlobs(list_blobs_options);
if (!src_list_response.Blobs.empty()) {
return Status::IOError("Unable to replace empty container: '", dest.all, "'");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return Status::IOError("Unable to replace empty container: '", dest.all, "'");
return Status::IOError("Unable to replace by non empty container: '", src.all, "'");

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added a comment explaining why dest is the correct here.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
}
try {
src_lease_guard.BreakBeforeDeletion(kTimeNeededForContainerDeletion);
src_container_client.DeleteIfExists(options);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to use DeleteIfExists() here? Can we use Delete() here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, Delete is enough. I'm changing.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated

// These functions are marked ARROW_NOINLINE because they are called from
// multiple locations, but are not performance-critical.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This comment is about the 3 functions below, not only the immediately next one.

Comment on lines +1013 to +1014
// TODO(felipecrv): investigate why this can't be false
select.allow_not_found = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you want to solve this in this PR?
If you want to defer this to a follow-up task, could you create an issue for it?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment on lines +1205 to +1206
GTEST_SKIP()
<< "The rest of TestMovePaths is not implemented for non-HNS scenarios";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just return here?
Should we use GTEST_SKIP() here? We have some tests for non-HNS case.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I would prefer to more loudly communicate that MOST of the tests are not in fact running. The return is too subtle.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 9, 2024
@conbench-apache-arrow

Copy link
Copy Markdown

After merging your PR, Conbench analyzed the 6 benchmarking runs that have been run so far on merge-commit 0ce54b6.

There were no benchmark performance regressions. 🎉

The full Conbench report has more details. It also includes information about 4 possible false positives for unstable benchmarks that are known to sometimes produce them.

dgreiss pushed a commit to dgreiss/arrow that referenced this pull request Feb 19, 2024
…aLake Storage Gen 2 API (apache#39904)
### Rationale for this change
We need to move directories and files via the `arrow::FileSystem` interface.
### What changes are included in this PR?
- A few filesystem error reporting improvements
- A helper class to deal with Azure Storage leases [1]
- The `Move()` implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
- Lots of tests
[1]: https://learn.microsoft.com/en-us/rest/api/storageservices/lease-blob
### Are these changes tested?
Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.
### Are there any user-facing changes?
No breaking changes, only new functionality.
* Closes: apache#38704
Authored-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Signed-off-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Sign up for freeto 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.

[C++][FS][Azure] Implement Move()

3 participants

@felipecrv@kou@zeroshade
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API - #39904

Merged
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move
Feb 10, 2024
Merged

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API#39904
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move

Conversation

@felipecrv

@felipecrvfelipecrv commented Feb 2, 2024

Copy link
Copy Markdown
Contributor

Rationale for this change

We need to move directories and files via the arrow::FileSystem interface.

What changes are included in this PR?

  • A few filesystem error reporting improvements
  • A helper class to deal with Azure Storage leases 1
  • The Move() implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
  • Lots of tests

Are these changes tested?

Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.

Are there any user-facing changes?

No breaking changes, only new functionality.

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I took a look at all changes but I don't fully understand yet. I'll review again later.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
// "subdir0/file-at-subdir" exists

// src is a directory and dest does not exists
CreateDirectory(adlfs_client, "subdir0");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that this is needless.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's necessary because I'm testing the scenario where the src exists. The next line moves the subdir0 to subdir1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Right. Right. Removing it.

Comment on lines +1275 to +1276
CreateDirectory(adlfs_client, "subdir1");
CreateDirectory(adlfs_client, "subdir2");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that they are needless.

latest < break_or_expires_at_ &&
!latest_known_expiry_time_.compare_exchange_weak(latest, break_or_expires_at_)) {
}
DCHECK_GE(latest_known_expiry_time_.load(), break_or_expires_at_);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it safe?
I think that latest_known_expiry_time_ may be changed between latest_known_expiry_time_.compare_exchange_weak() and latest_known_expiry_time_.load().

@felipecrvfelipecrvFeb 2, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's safe because latest_known_expiry_time_ monotonically increases (it never goes down). So even if it's changed, the [G]reater than or [E]qual will always work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see!

/// doesn't exist, otherwise a PathNotFound(location) error is produced right away
/// \return A BlobLeaseClient is wrapped as a unique_ptr so it's moveable and
/// optional (nullptr denotes blob not found)
Result<std::unique_ptr<Blobs::BlobLeaseClient>> AcquireBlobLease(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that most codes are duplicated with AcquireContainerLease(). Can we unify them?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

They use different SDK classes and the error handling is subtly different between each. Unifying these would require a lot of templating that would obfuscate the code more than clarify it.

@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting review Awaiting review labels Feb 2, 2024
@github-actionsgithub-actionsBot added awaiting change review Awaiting change review awaiting changes Awaiting changes and removed awaiting changes Awaiting changes awaiting change review Awaiting change review labels Feb 2, 2024
static constexpr std::chrono::seconds kMaxLeaseDuration{60};

public:
LeaseGuard(std::unique_ptr<Blobs::BlobLeaseClient> lease_client,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does it really make sense for this to be controlled by a consumer vs being controlled more internally? Is this a common pattern with Azure outside of our usage?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients. Without concurrent mutators, they are very cheap (lead to no delays at all) and with concurrent mutators, they lead to outcomes we and users can reason about. Note that I often use the lease acquisition as an existence check I would have to do anyways.

[1] https://martinfowler.com/articles/patterns-of-distributed-systems/lease.html

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

I'm referring to anyone using arrow::filesystem::AzureFileSystem directly, whether inside the arrow library (datasets) or not.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients

Yup, I know. I'm just referring to where the control of the lease is managed. But I also just realized that this entire leaseguard class isn't publicly exposed haha. Making my entire question here moot. So we're all good.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A-ha. Yes, the class is totally private. It will get more use-cases but they will all be within this file.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I might move it to a separate internal.h/cc but I would prefer doing it later to reduce noise in these PR as a lot would be moving.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 5, 2024
//
// NOTE: The initial constant values were chosen conservatively. If we learn,
// from experience, that they are causing issues, we can increase them. And if
// broadly applicable values aren't possible, we can make them configurable.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@zeroshade There isn't much to these numbers, but what I can say is that they work well for a client running in Brazil talking to a storage account in a US east coast zone replicated across the US.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It might make sense to make them configurable right off the bat?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think that would be premature. My plan if these constants are not good enough:

Raise lease time to 30s.
Raise operation times to 15s.

Network slow downs are unbounded, but failing without data loss risk and allowing a retry would be the way to go here IMO.

(I hardcoded GetUrl on the SDK class to debug my changes :)
@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Feb 7, 2024
kou
kou approved these changes Feb 8, 2024

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment on lines +2070 to +2072
ARROW_ASSIGN_OR_RAISE(auto src_lease_client,
AcquireContainerLease(src, kLeaseDuration));
LeaseGuard src_lease_guard{std::move(src_lease_client), kLeaseDuration};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We must specify the same lease duration to AcquireContainer() and LeaseGuard::LeaseGuard(), right?
It may be misused.
Can we return std::unique_ptr<LeaseGuard> by AcquireContainerLease() to avoid creating a LeaseGuard manually?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I considered this, but the problem is that the LeaseGuard should often live in an outer scope relative to the AcquireContainerLease call, so I considered this misuse trap less bad than writing code that declares the guard far from where it's used -- I do that now sometime with optionals and I think that communicates the intent more clearly.

try {
auto src_list_response = src_container_client.ListBlobs(list_blobs_options);
if (!src_list_response.Blobs.empty()) {
return Status::IOError("Unable to replace empty container: '", dest.all, "'");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return Status::IOError("Unable to replace empty container: '", dest.all, "'");
return Status::IOError("Unable to replace by non empty container: '", src.all, "'");

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added a comment explaining why dest is the correct here.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
}
try {
src_lease_guard.BreakBeforeDeletion(kTimeNeededForContainerDeletion);
src_container_client.DeleteIfExists(options);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to use DeleteIfExists() here? Can we use Delete() here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, Delete is enough. I'm changing.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated

// These functions are marked ARROW_NOINLINE because they are called from
// multiple locations, but are not performance-critical.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This comment is about the 3 functions below, not only the immediately next one.

Comment on lines +1013 to +1014
// TODO(felipecrv): investigate why this can't be false
select.allow_not_found = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you want to solve this in this PR?
If you want to defer this to a follow-up task, could you create an issue for it?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment on lines +1205 to +1206
GTEST_SKIP()
<< "The rest of TestMovePaths is not implemented for non-HNS scenarios";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just return here?
Should we use GTEST_SKIP() here? We have some tests for non-HNS case.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I would prefer to more loudly communicate that MOST of the tests are not in fact running. The return is too subtle.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 9, 2024
@conbench-apache-arrow

Copy link
Copy Markdown

After merging your PR, Conbench analyzed the 6 benchmarking runs that have been run so far on merge-commit 0ce54b6.

There were no benchmark performance regressions. 🎉

The full Conbench report has more details. It also includes information about 4 possible false positives for unstable benchmarks that are known to sometimes produce them.

dgreiss pushed a commit to dgreiss/arrow that referenced this pull request Feb 19, 2024
…aLake Storage Gen 2 API (apache#39904)
### Rationale for this change
We need to move directories and files via the `arrow::FileSystem` interface.
### What changes are included in this PR?
- A few filesystem error reporting improvements
- A helper class to deal with Azure Storage leases [1]
- The `Move()` implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
- Lots of tests
[1]: https://learn.microsoft.com/en-us/rest/api/storageservices/lease-blob
### Are these changes tested?
Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.
### Are there any user-facing changes?
No breaking changes, only new functionality.
* Closes: apache#38704
Authored-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Signed-off-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Sign up for freeto 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.

[C++][FS][Azure] Implement Move()

3 participants

@felipecrv@kou@zeroshade
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API - #39904

Merged
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move
Feb 10, 2024
Merged

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API#39904
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move

Conversation

@felipecrv

@felipecrvfelipecrv commented Feb 2, 2024

Copy link
Copy Markdown
Contributor

Rationale for this change

We need to move directories and files via the arrow::FileSystem interface.

What changes are included in this PR?

  • A few filesystem error reporting improvements
  • A helper class to deal with Azure Storage leases 1
  • The Move() implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
  • Lots of tests

Are these changes tested?

Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.

Are there any user-facing changes?

No breaking changes, only new functionality.

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I took a look at all changes but I don't fully understand yet. I'll review again later.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
// "subdir0/file-at-subdir" exists

// src is a directory and dest does not exists
CreateDirectory(adlfs_client, "subdir0");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that this is needless.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's necessary because I'm testing the scenario where the src exists. The next line moves the subdir0 to subdir1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Right. Right. Removing it.

Comment on lines +1275 to +1276
CreateDirectory(adlfs_client, "subdir1");
CreateDirectory(adlfs_client, "subdir2");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that they are needless.

latest < break_or_expires_at_ &&
!latest_known_expiry_time_.compare_exchange_weak(latest, break_or_expires_at_)) {
}
DCHECK_GE(latest_known_expiry_time_.load(), break_or_expires_at_);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it safe?
I think that latest_known_expiry_time_ may be changed between latest_known_expiry_time_.compare_exchange_weak() and latest_known_expiry_time_.load().

@felipecrvfelipecrvFeb 2, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's safe because latest_known_expiry_time_ monotonically increases (it never goes down). So even if it's changed, the [G]reater than or [E]qual will always work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see!

/// doesn't exist, otherwise a PathNotFound(location) error is produced right away
/// \return A BlobLeaseClient is wrapped as a unique_ptr so it's moveable and
/// optional (nullptr denotes blob not found)
Result<std::unique_ptr<Blobs::BlobLeaseClient>> AcquireBlobLease(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that most codes are duplicated with AcquireContainerLease(). Can we unify them?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

They use different SDK classes and the error handling is subtly different between each. Unifying these would require a lot of templating that would obfuscate the code more than clarify it.

@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting review Awaiting review labels Feb 2, 2024
@github-actionsgithub-actionsBot added awaiting change review Awaiting change review awaiting changes Awaiting changes and removed awaiting changes Awaiting changes awaiting change review Awaiting change review labels Feb 2, 2024
static constexpr std::chrono::seconds kMaxLeaseDuration{60};

public:
LeaseGuard(std::unique_ptr<Blobs::BlobLeaseClient> lease_client,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does it really make sense for this to be controlled by a consumer vs being controlled more internally? Is this a common pattern with Azure outside of our usage?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients. Without concurrent mutators, they are very cheap (lead to no delays at all) and with concurrent mutators, they lead to outcomes we and users can reason about. Note that I often use the lease acquisition as an existence check I would have to do anyways.

[1] https://martinfowler.com/articles/patterns-of-distributed-systems/lease.html

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

I'm referring to anyone using arrow::filesystem::AzureFileSystem directly, whether inside the arrow library (datasets) or not.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients

Yup, I know. I'm just referring to where the control of the lease is managed. But I also just realized that this entire leaseguard class isn't publicly exposed haha. Making my entire question here moot. So we're all good.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A-ha. Yes, the class is totally private. It will get more use-cases but they will all be within this file.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I might move it to a separate internal.h/cc but I would prefer doing it later to reduce noise in these PR as a lot would be moving.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 5, 2024
//
// NOTE: The initial constant values were chosen conservatively. If we learn,
// from experience, that they are causing issues, we can increase them. And if
// broadly applicable values aren't possible, we can make them configurable.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@zeroshade There isn't much to these numbers, but what I can say is that they work well for a client running in Brazil talking to a storage account in a US east coast zone replicated across the US.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It might make sense to make them configurable right off the bat?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think that would be premature. My plan if these constants are not good enough:

Raise lease time to 30s.
Raise operation times to 15s.

Network slow downs are unbounded, but failing without data loss risk and allowing a retry would be the way to go here IMO.

(I hardcoded GetUrl on the SDK class to debug my changes :)
@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Feb 7, 2024
kou
kou approved these changes Feb 8, 2024

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment on lines +2070 to +2072
ARROW_ASSIGN_OR_RAISE(auto src_lease_client,
AcquireContainerLease(src, kLeaseDuration));
LeaseGuard src_lease_guard{std::move(src_lease_client), kLeaseDuration};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We must specify the same lease duration to AcquireContainer() and LeaseGuard::LeaseGuard(), right?
It may be misused.
Can we return std::unique_ptr<LeaseGuard> by AcquireContainerLease() to avoid creating a LeaseGuard manually?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I considered this, but the problem is that the LeaseGuard should often live in an outer scope relative to the AcquireContainerLease call, so I considered this misuse trap less bad than writing code that declares the guard far from where it's used -- I do that now sometime with optionals and I think that communicates the intent more clearly.

try {
auto src_list_response = src_container_client.ListBlobs(list_blobs_options);
if (!src_list_response.Blobs.empty()) {
return Status::IOError("Unable to replace empty container: '", dest.all, "'");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return Status::IOError("Unable to replace empty container: '", dest.all, "'");
return Status::IOError("Unable to replace by non empty container: '", src.all, "'");

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added a comment explaining why dest is the correct here.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
}
try {
src_lease_guard.BreakBeforeDeletion(kTimeNeededForContainerDeletion);
src_container_client.DeleteIfExists(options);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to use DeleteIfExists() here? Can we use Delete() here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, Delete is enough. I'm changing.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated

// These functions are marked ARROW_NOINLINE because they are called from
// multiple locations, but are not performance-critical.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This comment is about the 3 functions below, not only the immediately next one.

Comment on lines +1013 to +1014
// TODO(felipecrv): investigate why this can't be false
select.allow_not_found = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you want to solve this in this PR?
If you want to defer this to a follow-up task, could you create an issue for it?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment on lines +1205 to +1206
GTEST_SKIP()
<< "The rest of TestMovePaths is not implemented for non-HNS scenarios";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just return here?
Should we use GTEST_SKIP() here? We have some tests for non-HNS case.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I would prefer to more loudly communicate that MOST of the tests are not in fact running. The return is too subtle.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 9, 2024
@conbench-apache-arrow

Copy link
Copy Markdown

After merging your PR, Conbench analyzed the 6 benchmarking runs that have been run so far on merge-commit 0ce54b6.

There were no benchmark performance regressions. 🎉

The full Conbench report has more details. It also includes information about 4 possible false positives for unstable benchmarks that are known to sometimes produce them.

dgreiss pushed a commit to dgreiss/arrow that referenced this pull request Feb 19, 2024
…aLake Storage Gen 2 API (apache#39904)
### Rationale for this change
We need to move directories and files via the `arrow::FileSystem` interface.
### What changes are included in this PR?
- A few filesystem error reporting improvements
- A helper class to deal with Azure Storage leases [1]
- The `Move()` implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
- Lots of tests
[1]: https://learn.microsoft.com/en-us/rest/api/storageservices/lease-blob
### Are these changes tested?
Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.
### Are there any user-facing changes?
No breaking changes, only new functionality.
* Closes: apache#38704
Authored-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Signed-off-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Sign up for freeto 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.

[C++][FS][Azure] Implement Move()

3 participants

@felipecrv@kou@zeroshade
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API - #39904

Merged
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move
Feb 10, 2024
Merged

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API#39904
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move

Conversation

@felipecrv

@felipecrvfelipecrv commented Feb 2, 2024

Copy link
Copy Markdown
Contributor

Rationale for this change

We need to move directories and files via the arrow::FileSystem interface.

What changes are included in this PR?

  • A few filesystem error reporting improvements
  • A helper class to deal with Azure Storage leases 1
  • The Move() implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
  • Lots of tests

Are these changes tested?

Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.

Are there any user-facing changes?

No breaking changes, only new functionality.

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I took a look at all changes but I don't fully understand yet. I'll review again later.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
// "subdir0/file-at-subdir" exists

// src is a directory and dest does not exists
CreateDirectory(adlfs_client, "subdir0");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that this is needless.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's necessary because I'm testing the scenario where the src exists. The next line moves the subdir0 to subdir1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Right. Right. Removing it.

Comment on lines +1275 to +1276
CreateDirectory(adlfs_client, "subdir1");
CreateDirectory(adlfs_client, "subdir2");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that they are needless.

latest < break_or_expires_at_ &&
!latest_known_expiry_time_.compare_exchange_weak(latest, break_or_expires_at_)) {
}
DCHECK_GE(latest_known_expiry_time_.load(), break_or_expires_at_);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it safe?
I think that latest_known_expiry_time_ may be changed between latest_known_expiry_time_.compare_exchange_weak() and latest_known_expiry_time_.load().

@felipecrvfelipecrvFeb 2, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's safe because latest_known_expiry_time_ monotonically increases (it never goes down). So even if it's changed, the [G]reater than or [E]qual will always work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see!

/// doesn't exist, otherwise a PathNotFound(location) error is produced right away
/// \return A BlobLeaseClient is wrapped as a unique_ptr so it's moveable and
/// optional (nullptr denotes blob not found)
Result<std::unique_ptr<Blobs::BlobLeaseClient>> AcquireBlobLease(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that most codes are duplicated with AcquireContainerLease(). Can we unify them?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

They use different SDK classes and the error handling is subtly different between each. Unifying these would require a lot of templating that would obfuscate the code more than clarify it.

@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting review Awaiting review labels Feb 2, 2024
@github-actionsgithub-actionsBot added awaiting change review Awaiting change review awaiting changes Awaiting changes and removed awaiting changes Awaiting changes awaiting change review Awaiting change review labels Feb 2, 2024
static constexpr std::chrono::seconds kMaxLeaseDuration{60};

public:
LeaseGuard(std::unique_ptr<Blobs::BlobLeaseClient> lease_client,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does it really make sense for this to be controlled by a consumer vs being controlled more internally? Is this a common pattern with Azure outside of our usage?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients. Without concurrent mutators, they are very cheap (lead to no delays at all) and with concurrent mutators, they lead to outcomes we and users can reason about. Note that I often use the lease acquisition as an existence check I would have to do anyways.

[1] https://martinfowler.com/articles/patterns-of-distributed-systems/lease.html

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

I'm referring to anyone using arrow::filesystem::AzureFileSystem directly, whether inside the arrow library (datasets) or not.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients

Yup, I know. I'm just referring to where the control of the lease is managed. But I also just realized that this entire leaseguard class isn't publicly exposed haha. Making my entire question here moot. So we're all good.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A-ha. Yes, the class is totally private. It will get more use-cases but they will all be within this file.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I might move it to a separate internal.h/cc but I would prefer doing it later to reduce noise in these PR as a lot would be moving.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 5, 2024
//
// NOTE: The initial constant values were chosen conservatively. If we learn,
// from experience, that they are causing issues, we can increase them. And if
// broadly applicable values aren't possible, we can make them configurable.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@zeroshade There isn't much to these numbers, but what I can say is that they work well for a client running in Brazil talking to a storage account in a US east coast zone replicated across the US.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It might make sense to make them configurable right off the bat?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think that would be premature. My plan if these constants are not good enough:

Raise lease time to 30s.
Raise operation times to 15s.

Network slow downs are unbounded, but failing without data loss risk and allowing a retry would be the way to go here IMO.

(I hardcoded GetUrl on the SDK class to debug my changes :)
@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Feb 7, 2024
kou
kou approved these changes Feb 8, 2024

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment on lines +2070 to +2072
ARROW_ASSIGN_OR_RAISE(auto src_lease_client,
AcquireContainerLease(src, kLeaseDuration));
LeaseGuard src_lease_guard{std::move(src_lease_client), kLeaseDuration};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We must specify the same lease duration to AcquireContainer() and LeaseGuard::LeaseGuard(), right?
It may be misused.
Can we return std::unique_ptr<LeaseGuard> by AcquireContainerLease() to avoid creating a LeaseGuard manually?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I considered this, but the problem is that the LeaseGuard should often live in an outer scope relative to the AcquireContainerLease call, so I considered this misuse trap less bad than writing code that declares the guard far from where it's used -- I do that now sometime with optionals and I think that communicates the intent more clearly.

try {
auto src_list_response = src_container_client.ListBlobs(list_blobs_options);
if (!src_list_response.Blobs.empty()) {
return Status::IOError("Unable to replace empty container: '", dest.all, "'");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return Status::IOError("Unable to replace empty container: '", dest.all, "'");
return Status::IOError("Unable to replace by non empty container: '", src.all, "'");

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added a comment explaining why dest is the correct here.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
}
try {
src_lease_guard.BreakBeforeDeletion(kTimeNeededForContainerDeletion);
src_container_client.DeleteIfExists(options);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to use DeleteIfExists() here? Can we use Delete() here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, Delete is enough. I'm changing.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated

// These functions are marked ARROW_NOINLINE because they are called from
// multiple locations, but are not performance-critical.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This comment is about the 3 functions below, not only the immediately next one.

Comment on lines +1013 to +1014
// TODO(felipecrv): investigate why this can't be false
select.allow_not_found = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you want to solve this in this PR?
If you want to defer this to a follow-up task, could you create an issue for it?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment on lines +1205 to +1206
GTEST_SKIP()
<< "The rest of TestMovePaths is not implemented for non-HNS scenarios";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just return here?
Should we use GTEST_SKIP() here? We have some tests for non-HNS case.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I would prefer to more loudly communicate that MOST of the tests are not in fact running. The return is too subtle.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 9, 2024
@conbench-apache-arrow

Copy link
Copy Markdown

After merging your PR, Conbench analyzed the 6 benchmarking runs that have been run so far on merge-commit 0ce54b6.

There were no benchmark performance regressions. 🎉

The full Conbench report has more details. It also includes information about 4 possible false positives for unstable benchmarks that are known to sometimes produce them.

dgreiss pushed a commit to dgreiss/arrow that referenced this pull request Feb 19, 2024
…aLake Storage Gen 2 API (apache#39904)
### Rationale for this change
We need to move directories and files via the `arrow::FileSystem` interface.
### What changes are included in this PR?
- A few filesystem error reporting improvements
- A helper class to deal with Azure Storage leases [1]
- The `Move()` implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
- Lots of tests
[1]: https://learn.microsoft.com/en-us/rest/api/storageservices/lease-blob
### Are these changes tested?
Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.
### Are there any user-facing changes?
No breaking changes, only new functionality.
* Closes: apache#38704
Authored-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Signed-off-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Sign up for freeto 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.

[C++][FS][Azure] Implement Move()

3 participants

@felipecrv@kou@zeroshade
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API - #39904

Merged
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move
Feb 10, 2024
Merged

GH-38704: [C++] Implement Azure FileSystem Move() via Azure DataLake Storage Gen 2 API#39904
felipecrv merged 25 commits into
apache:mainfrom
felipecrv:azure_move

Conversation

@felipecrv

@felipecrvfelipecrv commented Feb 2, 2024

Copy link
Copy Markdown
Contributor

Rationale for this change

We need to move directories and files via the arrow::FileSystem interface.

What changes are included in this PR?

  • A few filesystem error reporting improvements
  • A helper class to deal with Azure Storage leases 1
  • The Move() implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
  • Lots of tests

Are these changes tested?

Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.

Are there any user-facing changes?

No breaking changes, only new functionality.

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I took a look at all changes but I don't fully understand yet. I'll review again later.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs.h Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
// "subdir0/file-at-subdir" exists

// src is a directory and dest does not exists
CreateDirectory(adlfs_client, "subdir0");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that this is needless.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's necessary because I'm testing the scenario where the src exists. The next line moves the subdir0 to subdir1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Right. Right. Removing it.

Comment on lines +1275 to +1276
CreateDirectory(adlfs_client, "subdir1");
CreateDirectory(adlfs_client, "subdir2");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that they are needless.

latest < break_or_expires_at_ &&
!latest_known_expiry_time_.compare_exchange_weak(latest, break_or_expires_at_)) {
}
DCHECK_GE(latest_known_expiry_time_.load(), break_or_expires_at_);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it safe?
I think that latest_known_expiry_time_ may be changed between latest_known_expiry_time_.compare_exchange_weak() and latest_known_expiry_time_.load().

@felipecrvfelipecrvFeb 2, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's safe because latest_known_expiry_time_ monotonically increases (it never goes down). So even if it's changed, the [G]reater than or [E]qual will always work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see!

/// doesn't exist, otherwise a PathNotFound(location) error is produced right away
/// \return A BlobLeaseClient is wrapped as a unique_ptr so it's moveable and
/// optional (nullptr denotes blob not found)
Result<std::unique_ptr<Blobs::BlobLeaseClient>> AcquireBlobLease(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that most codes are duplicated with AcquireContainerLease(). Can we unify them?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

They use different SDK classes and the error handling is subtly different between each. Unifying these would require a lot of templating that would obfuscate the code more than clarify it.

@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting review Awaiting review labels Feb 2, 2024
@github-actionsgithub-actionsBot added awaiting change review Awaiting change review awaiting changes Awaiting changes and removed awaiting changes Awaiting changes awaiting change review Awaiting change review labels Feb 2, 2024
static constexpr std::chrono::seconds kMaxLeaseDuration{60};

public:
LeaseGuard(std::unique_ptr<Blobs::BlobLeaseClient> lease_client,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does it really make sense for this to be controlled by a consumer vs being controlled more internally? Is this a common pattern with Azure outside of our usage?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients. Without concurrent mutators, they are very cheap (lead to no delays at all) and with concurrent mutators, they lead to outcomes we and users can reason about. Note that I often use the lease acquisition as an existence check I would have to do anyways.

[1] https://martinfowler.com/articles/patterns-of-distributed-systems/lease.html

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What you mean by "consumer" and "more internally" here? The Arrow implementation is internal compared to the software using Arrow.

I'm referring to anyone using arrow::filesystem::AzureFileSystem directly, whether inside the arrow library (datasets) or not.

Leases are a common Distributed Systems pattern [1] and the multi-step operations being performed here would have almost unpredictable outcomes in the presence of concurrent clients

Yup, I know. I'm just referring to where the control of the lease is managed. But I also just realized that this entire leaseguard class isn't publicly exposed haha. Making my entire question here moot. So we're all good.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A-ha. Yes, the class is totally private. It will get more use-cases but they will all be within this file.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I might move it to a separate internal.h/cc but I would prefer doing it later to reduce noise in these PR as a lot would be moving.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 5, 2024
//
// NOTE: The initial constant values were chosen conservatively. If we learn,
// from experience, that they are causing issues, we can increase them. And if
// broadly applicable values aren't possible, we can make them configurable.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@zeroshade There isn't much to these numbers, but what I can say is that they work well for a client running in Brazil talking to a storage account in a US east coast zone replicated across the US.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It might make sense to make them configurable right off the bat?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think that would be premature. My plan if these constants are not good enough:

Raise lease time to 30s.
Raise operation times to 15s.

Network slow downs are unbounded, but failing without data loss risk and allowing a retry would be the way to go here IMO.

(I hardcoded GetUrl on the SDK class to debug my changes :)
@github-actionsgithub-actionsBot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Feb 7, 2024
kou
kou approved these changes Feb 8, 2024

@koukou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
Comment on lines +2070 to +2072
ARROW_ASSIGN_OR_RAISE(auto src_lease_client,
AcquireContainerLease(src, kLeaseDuration));
LeaseGuard src_lease_guard{std::move(src_lease_client), kLeaseDuration};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We must specify the same lease duration to AcquireContainer() and LeaseGuard::LeaseGuard(), right?
It may be misused.
Can we return std::unique_ptr<LeaseGuard> by AcquireContainerLease() to avoid creating a LeaseGuard manually?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I considered this, but the problem is that the LeaseGuard should often live in an outer scope relative to the AcquireContainerLease call, so I considered this misuse trap less bad than writing code that declares the guard far from where it's used -- I do that now sometime with optionals and I think that communicates the intent more clearly.

try {
auto src_list_response = src_container_client.ListBlobs(list_blobs_options);
if (!src_list_response.Blobs.empty()) {
return Status::IOError("Unable to replace empty container: '", dest.all, "'");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
return Status::IOError("Unable to replace empty container: '", dest.all, "'");
return Status::IOError("Unable to replace by non empty container: '", src.all, "'");

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added a comment explaining why dest is the correct here.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated
}
try {
src_lease_guard.BreakBeforeDeletion(kTimeNeededForContainerDeletion);
src_container_client.DeleteIfExists(options);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to use DeleteIfExists() here? Can we use Delete() here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, Delete is enough. I'm changing.

Comment threadcpp/src/arrow/filesystem/azurefs.cc Outdated

// These functions are marked ARROW_NOINLINE because they are called from
// multiple locations, but are not performance-critical.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This comment is about the 3 functions below, not only the immediately next one.

Comment on lines +1013 to +1014
// TODO(felipecrv): investigate why this can't be false
select.allow_not_found = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you want to solve this in this PR?
If you want to defer this to a follow-up task, could you create an issue for it?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment threadcpp/src/arrow/filesystem/azurefs_test.cc Outdated
Comment on lines +1205 to +1206
GTEST_SKIP()
<< "The rest of TestMovePaths is not implemented for non-HNS scenarios";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just return here?
Should we use GTEST_SKIP() here? We have some tests for non-HNS case.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I would prefer to more loudly communicate that MOST of the tests are not in fact running. The return is too subtle.

@github-actionsgithub-actionsBot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Feb 9, 2024
@conbench-apache-arrow

Copy link
Copy Markdown

After merging your PR, Conbench analyzed the 6 benchmarking runs that have been run so far on merge-commit 0ce54b6.

There were no benchmark performance regressions. 🎉

The full Conbench report has more details. It also includes information about 4 possible false positives for unstable benchmarks that are known to sometimes produce them.

dgreiss pushed a commit to dgreiss/arrow that referenced this pull request Feb 19, 2024
…aLake Storage Gen 2 API (apache#39904)
### Rationale for this change
We need to move directories and files via the `arrow::FileSystem` interface.
### What changes are included in this PR?
- A few filesystem error reporting improvements
- A helper class to deal with Azure Storage leases [1]
- The `Move()` implementation that can move files and directories within the same container on storage accounts with Hierarchical Namespace Support enabled
- Lots of tests
[1]: https://learn.microsoft.com/en-us/rest/api/storageservices/lease-blob
### Are these changes tested?
Yes, by existing and a huge number of tests added by this PR. The test code introduced here should be extracted to a reusable test module that we can use to test move in other file system implementations.
### Are there any user-facing changes?
No breaking changes, only new functionality.
* Closes: apache#38704
Authored-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Signed-off-by: Felipe Oliveira Carvalho <felipekde@gmail.com>
Sign up for freeto 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.

[C++][FS][Azure] Implement Move()

3 participants

@felipecrv@kou@zeroshade