Skip to content

feat!: use 'static lifetime for delete_stream#524

Merged
alamb merged 2 commits into
apache:mainfrom
lakehq:delete-stream-static
Oct 31, 2025
Merged

feat!: use 'static lifetime for delete_stream#524
alamb merged 2 commits into
apache:mainfrom
lakehq:delete-stream-static

Conversation

@linhr

@linhr linhr commented Oct 30, 2025

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Closes #518.

Rationale for this change

The current delete_stream method looks like this:

    fn delete_stream<'a>(
        &'a self,
        locations: BoxStream<'a, Result<Path>>,
    ) -> BoxStream<'a, Result<Path>>;

This PR changes the delete_stream method to work with 'static lifetime. The reason for this change was discussed in the corresponding issue #518.

What changes are included in this PR?

As I work on this PR, the changes appeared to be more complex than thought. The challenge is that delete_stream has a default implementation that takes &self, so the returned stream cannot be 'static. I've considered the following options for this challenge.

Option 1: Only change the input stream to be 'static without changing the lifetime of the output stream. The method would look like this:

    fn delete_stream<'a>(
        &'a self,
        locations: BoxStream<'static, Result<Path>>,
    ) -> BoxStream<'a, Result<Path>>;

Or equivalently (with unnecessary lifetime elided):

    fn delete_stream(
        &self,
        locations: BoxStream<'static, Result<Path>>,
    ) -> BoxStream<'_, Result<Path>>;

This would result in minimum code change but the result is still not very elegant, since we're not fully migrating to BoxStream<'static, _>.

Option 2: Change both the input and output stream to be 'static and requires the implementation to define delete_stream explicitly (i.e. no more default implementation). The method would look like this:

    fn delete_stream(
        &self,
        locations: BoxStream<'static, Result<Path>>,
    ) -> BoxStream<'static, Result<Path>>;

I ended up choosing Option 2 for this PR. This is more work but gives a clean end result so that all ObjectStore methods now handle input/output BoxStream of 'static lifetime.

And fortunately, it likely won't be challenging for downstream implementations even if delete_stream is now required. Most implementations already use Arc for internal states, which can be cheaply cloned and moved into the 'static output stream. From the changes I made to various ObjectStore implementations in this PR, we can see that the logic is straightforward. And I've documented the pattern in lib.rs for downstream implementations to follow.

Are there any user-facing changes?

This is a breaking change that could be considered for 0.13 (#367).

@linhr

linhr commented Oct 30, 2025

Copy link
Copy Markdown
Contributor Author

The changes are covered by existing tests since delete_stream is used in src/integration.rs.

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you @linhr -- I went through this PR carefully and I think it makes sense to me. 🙏

I have some suggestions to improve some doc comments, but I don't think that is needed before merge

Comment thread src/lib.rs Outdated
/// let client = Arc::clone(&self.client);
/// locations
/// .map(move |location| {
/// let client = Arc::clone(&client);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍

Comment thread src/lib.rs Outdated
/// following implementation deletes each object with up to 10 concurrent
/// requests:
///
/// ```ignore

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please make this an actual example that compiles (so we are sure it won't drift over time)?

Perhaps following the model f the examples below.

We can do this as a follow on PR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done!

Comment thread src/lib.rs
/// than one object per a request. The default implementation will call
/// the single object delete method for each location, but with up to 10
/// concurrent requests.
/// than one object per a request. Otherwise, the implementation may call

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should probably call this out as a behavior change too in the API docs (but I think it is better to avoid implementing concurrent requests by default)

Also I think the documentation to explain how to get the old behavior is good

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done!

I called out the behavior change in the docs and mentioned that the example can be used to get the previous behavior.

Comment thread src/throttle.rs
self.inner.delete(location).await
}

fn delete_stream(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if we should add a test for this new implementation, following the other tests at this file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done!

@linhr

linhr commented Oct 31, 2025

Copy link
Copy Markdown
Contributor Author

Thanks @alamb for the review! I feel I can update the docs and tests in the same PR. I'll work on this today!

@linhr linhr requested a review from alamb October 31, 2025 07:50
Comment thread src/lib.rs
@@ -873,9 +873,8 @@ pub trait ObjectStore: std::fmt::Display + Send + Sync + Debug + 'static {
/// Delete all the objects at the specified locations
///
/// When supported, this method will use bulk operations that delete more

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 would be nice as an end user to know which backends (at least of those provided by object-store) support bulk delete

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you for this very nice contrbution @linhr

Comment thread src/lib.rs
/// that deletes each object with up to 10 concurrent requests. This default
/// behavior has been removed, and each implementation must now provide its
/// own `delete_stream` implementation explicitly. The following example
/// shows how to implement `delete_stream` to get the previous default

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❤️

@alamb

alamb commented Oct 31, 2025

Copy link
Copy Markdown
Contributor

Thanks for the review @kylebarron

@alamb alamb merged commit e0721f8 into apache:main Oct 31, 2025
8 checks passed
@linhr linhr deleted the delete-stream-static branch November 2, 2025 03:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use 'static lifetime for delete_stream

3 participants