Skip to content

feat: support complex schemas in append - #2209

Merged
wjones127 merged 9 commits into
lance-format:mainfrom
wjones127:fix/write-fragments-data
Apr 18, 2024
Merged

feat: support complex schemas in append#2209
wjones127 merged 9 commits into
lance-format:mainfrom
wjones127:fix/write-fragments-data

Conversation

@wjones127

@wjones127 wjones127 commented Apr 17, 2024

Copy link
Copy Markdown
Contributor

Fixes two bugs, both associated with schemas that have holes in the field ids:

  1. write_fragments() and LanceFragment.create() assume they can derive the field ids from the Arrow schema. This is not the case if there are holes in the schema. Therefore, when the mode is Append, we check the existing schema of the dataset and use its field ids. Fixes Make write_fragments safe to work with on schemas with holes in field ids #2179
  2. PageTable assumed that there were no holes in the field ids. It was parametrized as field_id_offset and num_fields, assuming the field ids were field_id_offset..(field_id_offset + num_fields). This is changed to be parametrized by the min and max field id.

@codecov-commenter

codecov-commenter commented Apr 17, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 82.03390% with 53 lines in your changes are missing coverage. Please review.

Project coverage is 81.10%. Comparing base (5ca27ac) to head (6585e5c).
Report is 2 commits behind head on main.

Files Patch % Lines
rust/lance/src/dataset/write.rs 69.56% 31 Missing and 4 partials ⚠️
rust/lance/src/dataset/builder.rs 0.00% 9 Missing ⚠️
rust/lance-file/src/page_table.rs 96.03% 0 Missing and 4 partials ⚠️
rust/lance-file/src/writer.rs 93.33% 0 Missing and 3 partials ⚠️
rust/lance-file/src/reader.rs 80.00% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #2209    +/-   ##
========================================
  Coverage   81.10%   81.10%            
========================================
  Files         184      184            
  Lines       52469    53102   +633     
  Branches    52469    53102   +633     
========================================
+ Hits        42555    43071   +516     
- Misses       7458     7558   +100     
- Partials     2456     2473    +17     
Flag Coverage Δ
unittests 81.10% <82.03%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

let (stream, schema) = reader_to_stream(reader).await?;

let schema = if matches!(params.mode, WriteMode::Append) {
// TODO: we need to pass down params somehow.

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.

This is #2154

@wjones127
wjones127 marked this pull request as ready for review April 17, 2024 21:30
@wjones127
wjones127 requested review from eddyxu and westonpace April 17, 2024 21:30

@westonpace westonpace 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'm not sure how holes are handled given we only have min and max? Looks ok otherwise though.

Comment thread protos/table.proto Outdated
Comment thread python/python/lance/fragment.py Outdated
Comment thread python/python/lance/fragment.py Outdated
Comment on lines +84 to +88
Err(Error::DatasetNotFound { .. }) => {
// If the dataset does not exist, we can use the schema from
// the reader.
schema
}

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.

If the mode is append and the dataset does not exist isn't this an error?

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.

In write_dataset it's just a warning. It makes it a lot less complicated if we can make append default.

let mut builder = Int64Builder::with_capacity((num_columns * num_batches) as usize);
for col in 0..num_columns {
let mut builder =
Int64Builder::with_capacity(field_ids.clone().count() * num_batches as usize);

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.

Minor nit but I think you can avoid the clone with field_ids.len()

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.

ExactSizeIterator isn't implemented for i32, unfortunately. I think because it's possible to construct a range that won't fit into 32-bit usize.

Comment on lines +39 to +47
/// Parameters:
/// * `position`: The start position in the file where the page table is stored.
/// * `min_field_id`: The smallest field_id that is present in the schema.
/// * `max_field_id`: The largest field_id that is present in the schema.
/// * `num_batches`: The number of batches in the file.
///
/// The page table is stored as an array. The on-disk size is determined based
/// on the `min_field_id`, `max_field_id`, and `num_batches` parameters. If
/// these are incorrect, the page table will not be read correctly.

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.

Given only min and max how do we handle holes? Or is a "hole" only at the beginning or end (and all fields within a file are contiguous)

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.

Holes are written to the page table as empty pages, just like struct fields. (This is one of the things that makes it very inefficient. If you have field ids 0, 50, it has to store empty page data for all the non-existent fields 1..49.) I can make this explicit in the docs.

*Experimental API*. Progress tracking for writing the fragment. Pass
a custom class that defines hooks to be called when each fragment is
starting to write and finishing writing.
mode: str, default "append"

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.

Should you mention what other modes are available? Will there ever be a distinction between create and overwrite? If not, should this just be a boolean column?

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.

Are you thinking something like check_existing_schema: bool=True?

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.

I feel like "append" vs "overwrite" and "create" communicate how to use this in terms that already exist in our APIs. I don't really foresee any difference between "overwrite" and "create".

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 was thinking something like overwrite_existing_schema with a default of False but I don't feel too strongly if you like append.

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.

Yeah I think I like this as append for now.

wjones127 and others added 2 commits April 17, 2024 15:30
Co-authored-by: Weston Pace <weston.pace@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.

Make write_fragments safe to work with on schemas with holes in field ids

3 participants