Skip to content

Conversation

@roeap
Copy link
Collaborator

@roeap roeap commented Oct 2, 2025

Description

When loading the active add files into memory, we concatenate the batches read from the log. For very large logs, we may exceed the admissible size for an individual array, specifically likely for large stats fields.

@rtyler - mind checking out if this fixes the issue we see on large tables? And, do we have an issue for this?

closes: #3767

@roeap roeap requested review from hntd187 and rtyler as code owners October 2, 2025 12:04
@github-actions github-actions bot added the binding/rust Issues for the Rust crate label Oct 2, 2025
@codecov
Copy link

codecov bot commented Oct 2, 2025

Codecov Report

❌ Patch coverage is 77.50473% with 119 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.37%. Comparing base (c29d637) to head (670a681).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...tes/core/src/kernel/snapshot/iterators/scan_row.rs 72.39% 64 Missing and 18 partials ⚠️
crates/core/src/kernel/snapshot/mod.rs 86.90% 7 Missing and 4 partials ⚠️
crates/core/src/kernel/snapshot/log_data.rs 87.01% 7 Missing and 3 partials ⚠️
crates/core/src/kernel/snapshot/serde.rs 52.94% 0 Missing and 8 partials ⚠️
crates/core/src/table/state.rs 72.72% 0 Missing and 3 partials ⚠️
crates/core/src/delta_datafusion/table_provider.rs 84.61% 1 Missing and 1 partial ⚠️
crates/core/src/kernel/snapshot/iterators.rs 85.71% 0 Missing and 1 partial ⚠️
crates/core/src/operations/restore.rs 50.00% 0 Missing and 1 partial ⚠️
python/src/lib.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3801      +/-   ##
==========================================
+ Coverage   74.31%   74.37%   +0.06%     
==========================================
  Files         145      145              
  Lines       39441    39482      +41     
  Branches    39441    39482      +41     
==========================================
+ Hits        29309    29365      +56     
+ Misses       8729     8719      -10     
+ Partials     1403     1398       -5     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@roeap roeap requested a review from ion-elgreco October 2, 2025 12:08
@rtyler
Copy link
Member

rtyler commented Oct 2, 2025

#3767

@rtyler
Copy link
Member

rtyler commented Oct 2, 2025

I can confirm that this does not exhaust memory on said table:

@roeap roeap enabled auto-merge (squash) October 2, 2025 14:42
@github-actions github-actions bot added the binding/python Issues for the Python package label Oct 4, 2025
@roeap roeap linked an issue Oct 4, 2025 that may be closed by this pull request
@roeap
Copy link
Collaborator Author

roeap commented Oct 4, 2025

@rtyler - the PR grew a bit in size, but hopefully for good cause. We may now see that we are no longer using as much memory, since we are no longer tracking the serialised stats as part of the file data.

Would you mind confirming? 😄

@roeap roeap disabled auto-merge October 4, 2025 21:33
Comment on lines +652 to +666
let mut pruned_batches = Vec::new();
let mut mask_offset = 0;

for batch in &self.snapshot.files {
let batch_size = batch.num_rows();
let batch_mask = &mask[mask_offset..mask_offset + batch_size];
let batch_mask_array = BooleanArray::from(batch_mask.to_vec());
let pruned_batch = filter_record_batch(batch, &batch_mask_array)?;
if pruned_batch.num_rows() > 0 {
pruned_batches.push(pruned_batch);
}
mask_offset += batch_size;
}

LogDataHandler::new(&pruned_batches, es.table_configuration()).statistics()
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was not really nice before and unfortunately got a bit less nice.

Longer therm i think we may have to decide if we need additional skipping from datafsuion, or rely on the file skipping in delta-kernel to be selective (we have no reason to believe it would not be :)).

let stats_schema = self.stats_schema()?;
let stats_schema: ArrowSchema = stats_schema.as_ref().try_into_arrow()?;
fields.push(Arc::new(Field::new(
fields[stats_idx] = Arc::new(Field::new(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we are now replacing the existing stats field with the stats_parsed rather than amending the parsed ones.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

While not entirely clean yet, we aim to isolate processing of the data we get from kernels log replay in this module. Essentially we need to revert when we do when receiving data when we feed it back into a scan / replay.

Comment on lines +51 to +60
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.project();
match this.stream.poll_next(cx) {
Poll::Ready(Some(Ok(batch))) => match parse_stats_column(&this.snapshot, &batch) {
Ok(batch) => Poll::Ready(Some(Ok(batch))),
Err(err) => Poll::Ready(Some(Err(err))),
},
other => other,
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it seems work has started to support async/streams directly from kernel. As such we start to move some processing onto streams rather that doing it in iterator world.

This should also align well when we work on the datafusion integrations, since there we find the same model.

Copy link
Member

Choose a reason for hiding this comment

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

[citation needed] 😆

There's been a lot of talk that I have heard but I haven't seen any concrete changes, do you have some to link?

Comment on lines +160 to +165
pub fn stats(&self) -> Option<String> {
let stats = self.stats_parsed()?.slice(self.index, 1);
let value = to_json(&stats)
.ok()
.map(|arr| arr.as_string::<i32>().value(0).to_string());
value.and_then(|v| (!v.is_empty()).then_some(v))
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we now need to serialise individual fields to get the json stats (for add actions) since we are no longer stacking the stats column.

///
/// A stream of [`LogicalFileView`] objects.
pub fn files(
pub fn file_views(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

renamed this for consistency since it is returning file views after all.

.map(|file| evaluator.evaluate_arrow(file.clone()))
.collect::<Result<Vec<_>, _>>()?;

let result = concat_batches(results[0].schema_ref(), &results)?;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we still concatenate the add actions table. in a follow-up we should also move this to a stream and expose that via record batch readers in python.

Opened #3811 to track this.

itertools = "0.14"
parking_lot = "0.12"
percent-encoding = "2"
pin-project-lite = "^0.2.7"
Copy link
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 why this dependency crept back in, I'll just have to remove it again 😆

Comment on lines +51 to +60
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.project();
match this.stream.poll_next(cx) {
Poll::Ready(Some(Ok(batch))) => match parse_stats_column(&this.snapshot, &batch) {
Ok(batch) => Poll::Ready(Some(Ok(batch))),
Err(err) => Poll::Ready(Some(Err(err))),
},
other => other,
}
}
Copy link
Member

Choose a reason for hiding this comment

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

[citation needed] 😆

There's been a lot of talk that I have heard but I haven't seen any concrete changes, do you have some to link?

@rtyler rtyler merged commit aad4499 into delta-io:main Oct 5, 2025
38 of 39 checks passed
@roeap roeap deleted the fix/file-concat branch October 5, 2025 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

binding/python Issues for the Python package binding/rust Issues for the Rust crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

larger table causes overflow deep in arrow land Delta rs seems to allocate a lot of memory

2 participants