Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support position delete writer #704

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

ZENOTME
Copy link
Contributor

@ZENOTME ZENOTME commented Nov 19, 2024

Complete #340

Copy link
Contributor

@liurenjie1024 liurenjie1024 left a comment

Choose a reason for hiding this comment

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

There are two kinds of writers in iceberg:

  1. Plain position delete writer: https://github.com/apache/iceberg/blob/da2ad389fd9ba8222f6fb3f57922209c239a7045/core/src/main/java/org/apache/iceberg/deletes/PositionDeleteWriter.java#L49
  2. Sorting position delete writer:
    https://github.com/apache/iceberg/blob/da2ad389fd9ba8222f6fb3f57922209c239a7045/core/src/main/java/org/apache/iceberg/deletes/PositionDeleteWriter.java#L49

It seems that this pr tries to implement 2, while there are some missing part there. I would suggest to implement 1 first as it's easier, what do you think?

@@ -607,6 +607,19 @@ impl SchemaVisitor for ToArrowSchemaConverter {
}
}

/// Convert iceberg field to an arrow field.
pub fn field_to_arrow_field(field: &crate::spec::NestedFieldRef) -> Result<FieldRef> {
let mut converter = ToArrowSchemaConverter;
Copy link
Contributor

Choose a reason for hiding this comment

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

The implementation is a little hack to me. How about just create a one field schema, and convert it using arrow schema, then get the result?

Comment on lines 153 to 117
fn write<'life0, 'async_trait>(
&'life0 mut self,
input: PositionDeleteInput<'a>,
) -> ::core::pin::Pin<
Box<dyn ::core::future::Future<Output = Result<()>> + ::core::marker::Send + 'async_trait>,
>
where
'life0: 'async_trait,
Self: 'async_trait,
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove these auto generated lifetime markers and prefix of types

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For here we use a sync version so that seems we need to explicitly declare these auto-generated lifetime.
The reason we need the sync version is that the input takes the reference like: struct PositionDeleteInput<'a> , we need to explicitly convert it into a record batch in the sync function part and then return a async future to write this record batch.

}

/// The memory position delete writer.
pub struct MemoryPositionDeleteWriter<B: FileWriterBuilder> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
pub struct MemoryPositionDeleteWriter<B: FileWriterBuilder> {
pub struct PositionDeleteWriter<B: FileWriterBuilder> {

I don't think we should add a Memory prefix here since it make people feel that we are storing everything in memory, and it applies to all structs.

@ZENOTME
Copy link
Contributor Author

ZENOTME commented Nov 27, 2024

There are two kinds of writers in iceberg:

  1. Plain position delete writer: https://github.com/apache/iceberg/blob/da2ad389fd9ba8222f6fb3f57922209c239a7045/core/src/main/java/org/apache/iceberg/deletes/PositionDeleteWriter.java#L49
  2. Sorting position delete writer:
    https://github.com/apache/iceberg/blob/da2ad389fd9ba8222f6fb3f57922209c239a7045/core/src/main/java/org/apache/iceberg/deletes/PositionDeleteWriter.java#L49

It seems that this pr tries to implement 2, while there are some missing part there. I would suggest to implement 1 first as it's easier, what do you think?

Is position delete must be sorted or it just be optional? From the iceberg spec, it looks like it must be sorted. https://iceberg.apache.org/spec/#position-delete-files:~:text=The%20rows%20in%20the%20delete%20file%20must%20be%20sorted%20by%20file_path%20then%20pos%20to%20optimize%20filtering%20rows%20while%20scanning.

@liurenjie1024
Copy link
Contributor

liurenjie1024 commented Nov 28, 2024

There are two kinds of writers in iceberg:

  1. Plain position delete writer: https://github.com/apache/iceberg/blob/da2ad389fd9ba8222f6fb3f57922209c239a7045/core/src/main/java/org/apache/iceberg/deletes/PositionDeleteWriter.java#L49
  2. Sorting position delete writer:
    https://github.com/apache/iceberg/blob/da2ad389fd9ba8222f6fb3f57922209c239a7045/core/src/main/java/org/apache/iceberg/deletes/PositionDeleteWriter.java#L49

It seems that this pr tries to implement 2, while there are some missing part there. I would suggest to implement 1 first as it's easier, what do you think?

Is position delete must be sorted or it just be optional? From the iceberg spec, it looks like it must be sorted. https://iceberg.apache.org/spec/#position-delete-files:~:text=The%20rows%20in%20the%20delete%20file%20must%20be%20sorted%20by%20file_path%20then%20pos%20to%20optimize%20filtering%20rows%20while%20scanning.

Yes, it's required in spec, but some compute engine could sort this before passing to writer, and writer doesn't need to handle sorting itself.

@ZENOTME
Copy link
Contributor Author

ZENOTME commented Nov 28, 2024

There are two kinds of writers in iceberg:

  1. Plain position delete writer: https://github.com/apache/iceberg/blob/da2ad389fd9ba8222f6fb3f57922209c239a7045/core/src/main/java/org/apache/iceberg/deletes/PositionDeleteWriter.java#L49
  2. Sorting position delete writer:
    https://github.com/apache/iceberg/blob/da2ad389fd9ba8222f6fb3f57922209c239a7045/core/src/main/java/org/apache/iceberg/deletes/PositionDeleteWriter.java#L49

It seems that this pr tries to implement 2, while there are some missing part there. I would suggest to implement 1 first as it's easier, what do you think?

Is position delete must be sorted or it just be optional? From the iceberg spec, it looks like it must be sorted. https://iceberg.apache.org/spec/#position-delete-files:~:text=The%20rows%20in%20the%20delete%20file%20must%20be%20sorted%20by%20file_path%20then%20pos%20to%20optimize%20filtering%20rows%20while%20scanning.

Yes, it's required in spec, but some compute engine could sort this before passing to writer, and writer doesn't need to handle sorting itself.

Make sense. Let's implement 1 first

@ZENOTME
Copy link
Contributor Author

ZENOTME commented Dec 3, 2024

I think we can resolve #741 first before this PR.

@jonathanc-n
Copy link
Contributor

@ZENOTME Are you still working on this? I'm looking to work on one of the two writers

@ZENOTME
Copy link
Contributor Author

ZENOTME commented Feb 12, 2025

@ZENOTME Are you still working on this? I'm looking to work on one of the two writers

Sorry for the late, I will work on this later. Would you like to work on sorting position delete writer after this PR?

@ZENOTME
Copy link
Contributor Author

ZENOTME commented Feb 12, 2025

Hi @liurenjie1024 @jonathanc-n. I have fixed this PR. It's ready for review.

Copy link
Contributor

@jonathanc-n jonathanc-n left a comment

Choose a reason for hiding this comment

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

Overall lgtm! I can get started on the sorting position delete writer next after merge

async fn write(&mut self, input: Vec<PositionDeleteInput>) -> Result<()> {
let mut path_column_builder = StringBuilder::new();
let mut offset_column_builder = PrimitiveBuilder::<Int64Type>::new();
for input in input.into_iter() {
Copy link
Contributor

@jonathanc-n jonathanc-n Feb 13, 2025

Choose a reason for hiding this comment

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

Change variable here? ex. pd_input

Copy link
Contributor

@liurenjie1024 liurenjie1024 left a comment

Choose a reason for hiding this comment

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

Thanks @ZENOTME for this pr, generally LGTM, left some minor suggestions.

Comment on lines 36 to 37
2147483546,
"file_path",
Copy link
Contributor

Choose a reason for hiding this comment

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

Please make these constants.

Comment on lines 41 to 42
2147483545,
"pos",
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto

Comment on lines 55 to 56
/// The offset of the position delete.
pub offsets: Vec<i64>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// The offset of the position delete.
pub offsets: Vec<i64>,
/// The row number in data file..
pub row: i64,

Copy link
Contributor

Choose a reason for hiding this comment

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

We should not ask user to think about the container.

#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Debug)]
pub struct PositionDeleteInput {
/// The path of the file.
pub path: String,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
pub path: String,
pub path: &'a str,

}

/// Position delete writer.
pub struct PositionDeleteWriter<B: FileWriterBuilder> {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should buffer in memory about for the input row number.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean that for PositionDeleteInput, we should buffer them and write them as a batch?🤔 E.g.

pub struct PositionDeleteWriter {
  // path -> row_num
  buffer: HashMap<String, Vec<i64>>
}

For here I don't add the buffer because we will add SortPositionDeleteWriter later and it will buffer the input and sort them, so I'm not sure whether we need to add a buffer here. Or we can let it be a optional choice?

partition_value: Struct,
}

impl<'a, B: FileWriterBuilder> IcebergWriter<Vec<PositionDeleteInput<'a>>>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can't simply this code using #[async_trait] because PositionDeleteInput take the reference, so in here we should convert them into RecordBatch first in sync code and then return a async function to write them. cc @liurenjie1024

@ZENOTME ZENOTME force-pushed the pos_delete branch 2 times, most recently from 797379e to 391a061 Compare February 23, 2025 06:29
Xuanwo pushed a commit that referenced this pull request Feb 24, 2025
#704 fail in msrv check and I find that's because `cargo update faststr`
will update the munge to `0.4.2` instead of `0.4.1`. The simple fix way
is to specify the precise version of munge. But I'm not sure whether
it's good practice here. Do you have any suggestions for this? cc
@Xuanwo @xxchan

Co-authored-by: ZENOTME <st810918843@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants