Skip to content

fix: Validate file cache identity and isolate object stores - #25858

Open
limadog9 wants to merge 1 commit into
apache:mainfrom
limadog9:fix/cache-object-identity
Open

limadog9 wants to merge 1 commit into
apache:mainfrom
limadog9:fix/cache-object-identity

Conversation

@limadog9

@limadog9 limadog9 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Closes #25841.

Rationale for this change

An overwrite can keep the same file size and modification time while changing its statistics and Parquet footer. Different object stores can also contain the same relative path with identical metadata. Reusing either cache entry can produce incorrect query results.

What changes are included in this PR?

  • Validate cached statistics and Parquet metadata using strong ETags when both are available, otherwise shared versions, otherwise size and modification time. Schema validation still applies independently. Weak HTTP ETags are treated as missing, and opaque ETag values are compared verbatim.
  • Include the object store URL in statistics, listing, and Parquet metadata cache keys, and carry it through schema inference, statistics inference, and execution readers. TableScopedPath composes the new StoreScopedPath key.
  • Keep anonymous listing-cache invalidation within the affected object store, and compute the store URL once per scan.
  • Expose object_store_url in all three CLI cache functions while keeping their path columns relative to the store.
  • Document the public API and CLI changes in the upgrade guide and CLI reference.
  • Remove a redundant semicolon flagged by strict Windows linting.

Validation still depends on current object metadata; cached listings and changes between planning and execution retain the consistency limitations described in the issue.

What is the testing strategy for this PR?

Regression tests cover identifier precedence, weak and unavailable ETags, schema changes, equal-size overwrites at an unchanged timestamp, and separate stores with identical object metadata. DataFrame tests assert correct aggregates and filtered scans. Cache tests verify isolation, reuse, and listing invalidation. A CLI regression test asserts exact relative paths and store URLs in all three cache functions for matching filenames in two stores.

Passed locally:

  • cargo test --profile ci -p datafusion-execution -p datafusion-datasource -p datafusion-datasource-parquet -p datafusion-cli --lib --bins cache --no-fail-fast (59 tests)
  • cargo test --profile ci -p datafusion -p datafusion-cli --test core_integration datasource:: (26 tests)
  • Strict Clippy (--lib --tests --all-features -- -D warnings) for datafusion, datafusion-execution, datafusion-catalog-listing, datafusion-datasource, datafusion-datasource-parquet, and datafusion-cli.
  • cargo fmt --all -- --check
  • ./ci/scripts/doc_prettier_check.sh --write --allow-dirty

Are there any user-facing changes?

File caches no longer reuse entries from another object store or from a changed strong ETag/version when fresh metadata is available. CLI cache functions expose the store URL separately from the relative path.

API changes: FileFormat inference methods now take an object store URL; TableScopedPath contains a StoreScopedPath; FileMetadataCache uses StoreScopedPath; and the cached Parquet reader factory/cache builders require the URL. See docs/source/library-user-guide/upgrading/56.0.0.md for migration details.

@github-actions github-actions Bot added documentation Improvements or additions to documentation core Core DataFusion crate catalog Related to the catalog crate common Related to common crate execution Related to the execution crate datasource Changes to the datasource crate labels Sep 29, 2026
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Thank you for opening this pull request!

Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch).

Details
     Cloning apache/main
    Building datafusion v55.1.0 (current)
       Built [  55.086s] (current)
     Parsing datafusion v55.1.0 (current)
      Parsed [   0.034s] (current)
    Building datafusion v55.1.0 (baseline)
       Built [  54.513s] (baseline)
     Parsing datafusion v55.1.0 (baseline)
      Parsed [   0.033s] (baseline)
    Checking datafusion v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.645s] 229 checks: 229 pass, 31 skip
     Summary no semver update required
    Finished [ 112.191s] datafusion
    Building datafusion-catalog-listing v55.1.0 (current)
       Built [  37.586s] (current)
     Parsing datafusion-catalog-listing v55.1.0 (current)
      Parsed [   0.007s] (current)
    Building datafusion-catalog-listing v55.1.0 (baseline)
       Built [  37.760s] (baseline)
     Parsing datafusion-catalog-listing v55.1.0 (baseline)
      Parsed [   0.008s] (baseline)
    Checking datafusion-catalog-listing v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.087s] 229 checks: 229 pass, 31 skip
     Summary no semver update required
    Finished [  76.488s] datafusion-catalog-listing
    Building datafusion-cli v55.1.0 (current)
       Built [  83.249s] (current)
     Parsing datafusion-cli v55.1.0 (current)
      Parsed [   0.029s] (current)
    Building datafusion-cli v55.1.0 (baseline)
       Built [  83.706s] (baseline)
     Parsing datafusion-cli v55.1.0 (baseline)
      Parsed [   0.032s] (baseline)
    Checking datafusion-cli v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.131s] 229 checks: 229 pass, 31 skip
     Summary no semver update required
    Finished [ 169.909s] datafusion-cli
    Building datafusion-datasource v55.1.0 (current)
       Built [  40.067s] (current)
     Parsing datafusion-datasource v55.1.0 (current)
      Parsed [   0.029s] (current)
    Building datafusion-datasource v55.1.0 (baseline)
       Built [  40.473s] (baseline)
     Parsing datafusion-datasource v55.1.0 (baseline)
      Parsed [   0.032s] (baseline)
    Checking datafusion-datasource v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.271s] 229 checks: 228 pass, 1 fail, 0 warn, 31 skip

--- failure trait_method_parameter_count_changed: pub trait method parameter count changed ---

Description:
A trait method now takes a different number of parameters.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#trait-item-signature
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.51.0/src/lints/trait_method_parameter_count_changed.ron

Failed in:
  FileFormat::infer_schema now takes 4 instead of 3 parameters, in file /home/runner/work/datafusion/datafusion/datafusion/datasource/src/file_format.rs:123
  FileFormat::infer_stats now takes 5 instead of 4 parameters, in file /home/runner/work/datafusion/datafusion/datafusion/datasource/src/file_format.rs:140
  FileFormat::infer_ordering now takes 5 instead of 4 parameters, in file /home/runner/work/datafusion/datafusion/datafusion/datasource/src/file_format.rs:160
  FileFormat::infer_stats_and_ordering now takes 5 instead of 4 parameters, in file /home/runner/work/datafusion/datafusion/datafusion/datasource/src/file_format.rs:181

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  82.078s] datafusion-datasource
    Building datafusion-datasource-arrow v55.1.0 (current)
       Built [  39.732s] (current)
     Parsing datafusion-datasource-arrow v55.1.0 (current)
      Parsed [   0.007s] (current)
    Building datafusion-datasource-arrow v55.1.0 (baseline)
       Built [  39.535s] (baseline)
     Parsing datafusion-datasource-arrow v55.1.0 (baseline)
      Parsed [   0.008s] (baseline)
    Checking datafusion-datasource-arrow v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.091s] 229 checks: 229 pass, 31 skip
     Summary no semver update required
    Finished [  80.427s] datafusion-datasource-arrow
    Building datafusion-datasource-avro v55.1.0 (current)
       Built [  41.835s] (current)
     Parsing datafusion-datasource-avro v55.1.0 (current)
      Parsed [   0.006s] (current)
    Building datafusion-datasource-avro v55.1.0 (baseline)
       Built [  42.084s] (baseline)
     Parsing datafusion-datasource-avro v55.1.0 (baseline)
      Parsed [   0.007s] (baseline)
    Checking datafusion-datasource-avro v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.079s] 229 checks: 229 pass, 31 skip
     Summary no semver update required
    Finished [  85.131s] datafusion-datasource-avro
    Building datafusion-datasource-csv v55.1.0 (current)
       Built [  40.496s] (current)
     Parsing datafusion-datasource-csv v55.1.0 (current)
      Parsed [   0.007s] (current)
    Building datafusion-datasource-csv v55.1.0 (baseline)
       Built [  40.293s] (baseline)
     Parsing datafusion-datasource-csv v55.1.0 (baseline)
      Parsed [   0.008s] (baseline)
    Checking datafusion-datasource-csv v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.111s] 229 checks: 229 pass, 31 skip
     Summary no semver update required
    Finished [  81.978s] datafusion-datasource-csv
    Building datafusion-datasource-json v55.1.0 (current)
       Built [  39.808s] (current)
     Parsing datafusion-datasource-json v55.1.0 (current)
      Parsed [   0.009s] (current)
    Building datafusion-datasource-json v55.1.0 (baseline)
       Built [  39.795s] (baseline)
     Parsing datafusion-datasource-json v55.1.0 (baseline)
      Parsed [   0.009s] (baseline)
    Checking datafusion-datasource-json v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.106s] 229 checks: 229 pass, 31 skip
     Summary no semver update required
    Finished [  80.815s] datafusion-datasource-json
    Building datafusion-datasource-parquet v55.1.0 (current)
       Built [  45.066s] (current)
     Parsing datafusion-datasource-parquet v55.1.0 (current)
      Parsed [   0.033s] (current)
    Building datafusion-datasource-parquet v55.1.0 (baseline)
       Built [  45.164s] (baseline)
     Parsing datafusion-datasource-parquet v55.1.0 (baseline)
      Parsed [   0.033s] (baseline)
    Checking datafusion-datasource-parquet v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.181s] 229 checks: 228 pass, 1 fail, 0 warn, 31 skip

--- failure method_parameter_count_changed: pub method parameter count changed ---

Description:
A publicly-visible method now takes a different number of parameters, not counting the receiver (self) parameter.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#fn-change-arity
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.51.0/src/lints/method_parameter_count_changed.ron

Failed in:
  datafusion_datasource_parquet::metadata::DFParquetMetadata::with_file_metadata_cache takes 1 parameters in /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/2b23549925df5566475543a7d9b2a7f6d89a1753/datafusion/datasource-parquet/src/metadata.rs:212, but now takes 2 parameters in /home/runner/work/datafusion/datafusion/datafusion/datasource-parquet/src/metadata.rs:215
  datafusion_datasource_parquet::CachedParquetFileReaderFactory::new takes 2 parameters in /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/2b23549925df5566475543a7d9b2a7f6d89a1753/datafusion/datasource-parquet/src/reader.rs:150, but now takes 3 parameters in /home/runner/work/datafusion/datafusion/datafusion/datasource-parquet/src/reader.rs:153
  datafusion_datasource_parquet::ParquetFileReader::with_metadata_cache takes 1 parameters in /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/2b23549925df5566475543a7d9b2a7f6d89a1753/datafusion/datasource-parquet/src/reader.rs:243, but now takes 2 parameters in /home/runner/work/datafusion/datafusion/datafusion/datasource-parquet/src/reader.rs:251

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  91.510s] datafusion-datasource-parquet
    Building datafusion-execution v55.1.0 (current)
       Built [  29.665s] (current)
     Parsing datafusion-execution v55.1.0 (current)
      Parsed [   0.027s] (current)
    Building datafusion-execution v55.1.0 (baseline)
       Built [  29.869s] (baseline)
     Parsing datafusion-execution v55.1.0 (baseline)
      Parsed [   0.028s] (baseline)
    Checking datafusion-execution v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.256s] 229 checks: 227 pass, 2 fail, 0 warn, 31 skip

--- failure constructible_struct_adds_field: struct exhaustively constructible through public API adds field ---

Description:
A pub struct that could be exhaustively constructed with a literal using only public API has a new pub field, breaking existing exhaustive literals.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.51.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field TableScopedPath.store_path in /home/runner/work/datafusion/datafusion/datafusion/execution/src/cache/mod.rs:195
  field TableScopedPath.store_path in /home/runner/work/datafusion/datafusion/datafusion/execution/src/cache/mod.rs:195

--- failure struct_pub_field_missing: pub struct's pub field removed or renamed ---

Description:
A publicly-visible struct has at least one public field that is no longer available under its prior name. It may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.51.0/src/lints/struct_pub_field_missing.ron

Failed in:
  field path of struct TableScopedPath, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/2b23549925df5566475543a7d9b2a7f6d89a1753/datafusion/execution/src/cache/mod.rs:152
  field path of struct TableScopedPath, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/2b23549925df5566475543a7d9b2a7f6d89a1753/datafusion/execution/src/cache/mod.rs:152

     Summary semver requires new major version: 2 major and 0 minor checks failed
    Finished [  60.929s] datafusion-execution

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Sep 29, 2026
@codecov-commenter

codecov-commenter commented Sep 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.36266% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.95%. Comparing base (d89a85d) to head (2ef0653).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
...afusion/core/src/datasource/file_format/parquet.rs 91.17% 0 Missing and 9 partials ⚠️
datafusion/execution/src/cache/cache_manager.rs 94.30% 9 Missing ⚠️
datafusion/datasource/src/url.rs 86.88% 0 Missing and 8 partials ⚠️
datafusion/core/src/datasource/file_format/csv.rs 54.54% 0 Missing and 5 partials ⚠️
datafusion/catalog-listing/src/table.rs 90.62% 0 Missing and 3 partials ⚠️
datafusion/datasource-arrow/src/file_format.rs 0.00% 0 Missing and 2 partials ⚠️
datafusion/datasource-parquet/src/metadata.rs 98.13% 0 Missing and 2 partials ⚠️
datafusion/datasource-parquet/src/source.rs 80.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25858      +/-   ##
==========================================
+ Coverage   82.93%   82.95%   +0.02%     
==========================================
  Files        1154     1154              
  Lines      457933   458638     +705     
  Branches   457933   458638     +705     
==========================================
+ Hits       379786   380463     +677     
- Misses      55128    55136       +8     
- Partials    23019    23039      +20     

☔ View full report in Codecov by Harness.
📢 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.

@villebro villebro 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.

If ETag or version are available, I feel we should IMO not need to compare against last_modified or size. WDYT?

Comment thread datafusion/execution/src/cache/cache_manager.rs Outdated
Comment thread datafusion/common/src/rounding.rs

@andygrove andygrove 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.

Thanks @limadog9, this looks like the right approach to me. Putting the object store URL in the cache keys, rather than checking it on the cached value, means files at the same path in different stores get separate entries instead of evicting each other. Sharing one comparison function between both caches, with a table-driven test over every ETag × version combination, is nice too.

The keys are built from the same URL on the planning path (ListingOptions::infer_schema, ListingTable statistics) and the execution path (ParquetFormat::create_physical_plan and the proto ParquetSource path), so metadata cached during planning is still reused at execution.

I left one comment I think should be addressed before merging: datafusion-cli's metadata_cache() output changes, see the comment on Display for ObjectStorePath. The others are nits. I also added some object_store caveats to the existing thread on the ETag rule.

This is an API change. The semver check flags the FileFormat inference methods, CachedParquetFileReaderFactory::new, DFParquetMetadata::with_file_metadata_cache, ParquetFileReader::with_metadata_cache and TableScopedPath, and the upgrade guide covers all of them.

Comment thread datafusion/execution/src/cache/mod.rs Outdated
Comment thread datafusion/execution/src/cache/mod.rs Outdated
Comment thread datafusion/catalog-listing/src/table.rs Outdated
Comment thread datafusion/datasource/src/url.rs
@limadog9

limadog9 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews, @andygrove and @villebro. I've updated the PR to address your feedback, including the CLI cache output and ETag handling. Thanks for taking another look.

@villebro villebro 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.

Validated cache_manager.rs changes, LGTM. Referring to @andygrove to review other changes.

@limadog9

Copy link
Copy Markdown
Contributor Author

Hi @andygrove, following up on the cache identity fix. The updates for the CLI cache output and ETag handling are in, and Villebro has approved the cache manager changes. Could you take another look at the remaining changes when you have a chance?

The latest Actions runs are still action_required; could a maintainer approve CI? GitHub is also reporting merge conflicts with main, so a rebase is needed before merging. Thanks!

Key file caches by object store URL and path. Validate cached metadata
using strong ETags, versions, or size and modification time, while
checking the schema independently.

Carry store identity through file-format inference and Parquet readers.
Expose store URLs separately from relative paths in CLI cache functions,
with regression coverage and upgrade guidance.
@limadog9
limadog9 force-pushed the fix/cache-object-identity branch from c6fdf87 to 2ef0653 Compare October 20, 2026 11:27
@github-actions github-actions Bot removed the common Related to common crate label Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto detected api change Auto detected API change catalog Related to the catalog crate core Core DataFusion crate datasource Changes to the datasource crate documentation Improvements or additions to documentation execution Related to the execution crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate cached file statistics and Parquet metadata with e_tag, not just size and mtime

4 participants