Skip to content

Conversation

@BlakeOrth
Copy link
Contributor

Which issue does this PR close?

Rationale for this change

Now that the DefaultListFilesCache can be configured by users it's safe to enable it by default and fix the tests that caching broke!

What changes are included in this PR?

  • Sets the DefaultListFilesCache to be enabled by default
  • Adds additional object store access tests to show list caching behavior
  • Adds variable setting/reading sqllogic test cases
  • Updates tests to disable caching when they relied on COPY commands so changes can be detected for each query
  • Updates docs to help users upgrade

Are these changes tested?

Yes, additional test cases have been added to help show the behavior of the caching

Are there any user-facing changes?

Yes, this changes the default behavior of DataFusion, however this information is already captured in the upgrade guide.

cc @alamb

@github-actions github-actions bot added documentation Improvements or additions to documentation core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) execution Related to the execution crate labels Dec 17, 2025
@BlakeOrth BlakeOrth force-pushed the feature/enable_list_cache branch 2 times, most recently from 10b740d to 73c6672 Compare December 17, 2025 00:24
 - Sets the DefaultListFilesCache to be enabled by default
 - Adds additional object store access tests to show list caching
   behavior
 - Adds variable setting/reading sqllogic test cases
 - Updates tests to disable caching when they relied on COPY commands so
   changes can be detected for each query
 - Updates docs to help users upgrade
You can configure the maximum cache size and cache entry expiration time via configuration options:

See <https://github.com/apache/datafusion/issues/19056> for more details.
`datafusion.runtime.list_files_cache_limit`
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
`datafusion.runtime.list_files_cache_limit`
* `datafusion.runtime.list_files_cache_limit`


See <https://github.com/apache/datafusion/issues/19056> for more details.
`datafusion.runtime.list_files_cache_limit`
`datafusion.runtime.list_files_cache_ttl`
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
`datafusion.runtime.list_files_cache_ttl`
* `datafusion.runtime.list_files_cache_ttl`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

* made prettiercomplain so I had to go for - instead. In either case these are formatting better now, thanks!

You can configure the maximum cache size and cache entry expiration time via configuration options:

See <https://github.com/apache/datafusion/issues/19056> for more details.
`datafusion.runtime.list_files_cache_limit`
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added the technical "unit" and a quick explanation for both of these, but since the actual user input is a string that undergoes parsing (e.g. "1m30s") I also linked out to the user guide to direct users to the more detailed information.

`datafusion.runtime.list_files_cache_limit`
`datafusion.runtime.list_files_cache_ttl`

Caching can be disable by setting the limit to 0:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Caching can be disable by setting the limit to 0:
Caching can be disabled by setting the limit to 0:

));
Some(lfc)
}
_ => None,
Copy link
Member

Choose a reason for hiding this comment

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

If the user has disabled caching with list_files_cache_limit = "0K" then None will be returned here, but in this case get_list_files_cache_limit() will return "1M"

@BlakeOrth BlakeOrth force-pushed the feature/enable_list_cache branch from 766ccbe to da08e25 Compare December 17, 2025 22:10
Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

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

Thank you @BlakeOrth -- this looks great to me and does what we set out to do -- which is make a session local list files cache.

I tested it locally and you can see the second query doesn't make any object store requests

Here is what it looks like on this branch:

DataFusion CLI v51.0.0
> \object_store_profiling summary
ObjectStore Profile mode set to Summary
> CREATE EXTERNAL TABLE maps STORED AS PARQUET LOCATION 's3://overturemaps-us-west-2/release/2025-12-17.0/theme=base/type=infrastructure/';
0 row(s) fetched.
Elapsed 6.369 seconds.

Object Store Profiling
Instrumented Object Store: instrument_mode: Summary, inner: AmazonS3(overturemaps-us-west-2)
Summaries:
+-----------+----------+-----------+-----------+------------+------------+-------+
| Operation | Metric   | min       | max       | avg        | sum        | count |
+-----------+----------+-----------+-----------+------------+------------+-------+
| Get       | duration | 0.104099s | 0.455794s | 0.237693s  | 5.229248s  | 22    |
| Get       | size     | 524288 B  | 857230 B  | 677976.1 B | 14915475 B | 22    |
| List      | duration | 0.377573s | 0.377573s | 0.377573s  | 0.377573s  | 1     |
| List      | size     |           |           |            |            | 1     |
+-----------+----------+-----------+-----------+------------+------------+-------+
> drop table maps;
0 row(s) fetched.
Elapsed 0.002 seconds.

Object Store Profiling
> CREATE EXTERNAL TABLE maps STORED AS PARQUET LOCATION 's3://overturemaps-us-west-2/release/2025-12-17.0/theme=base/type=infrastructure/';
0 row(s) fetched.
Elapsed 0.657 seconds.

Object Store Profiling
>

(note there are no object store requests on the second call to CREATE EXTERNAL TABLE)

Whereas on main there is a second call

Object Store Profiling
> CREATE EXTERNAL TABLE maps STORED AS PARQUET LOCATION 's3://overturemaps-us-west-2/release/2025-12-17.0/theme=base/type=infrastructure/';
0 row(s) fetched.
Elapsed 1.155 seconds.

Object Store Profiling
Instrumented Object Store: instrument_mode: Summary, inner: AmazonS3(overturemaps-us-west-2)
Summaries:
+-----------+----------+-----+-----+-----+-----+-------+
| Operation | Metric   | min | max | avg | sum | count |
+-----------+----------+-----+-----+-----+-----+-------+
| List      | duration |     |     |     |     | 3     |
| List      | size     |     |     |     |     | 3     |
+-----------+----------+-----+-----+-----+-----+-------+

COncernsl

As I played around with this, it was somewhat akward. Maybe we want to adjust the behavior somehow --

Confusing aspects:

  1. I can't set a TTL to 0
Object Store Profiling
> SET datafusion.runtime.list_files_cache_ttl = '0s';
Error during planning: Duration must be greater than 0 seconds
  1. Even when I set a ttl, the cache didn't reset
> SET datafusion.runtime.list_files_cache_ttl = "1s";
0 row(s) fetched.
Elapsed 0.002 seconds.
-- wait over a second
-- cache is still used (creates really fast with no requests)
> CREATE EXTERNAL TABLE maps STORED AS PARQUET LOCATION 's3://overturemaps-us-west-2/release/2025-12-17.0/theme=base/type=infrastructure/';
0 row(s) fetched.
Elapsed 0.647 seconds.

Object Store Profiling

I wonder if it would make more sense to scope the cache to a particular table somehow (kind of like is done today, but still impose a global limit / visibility)

those changes until the `ListingTableProvider` instance is dropped and recreated.

You will be able to configure the maximum cache size and cache expiration time via a configuration option:
You can configure the maximum cache size and cache entry expiration time via configuration options:
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@BlakeOrth
Copy link
Contributor Author

@alamb

I can't set a TTL to 0

This was an intentional decision implemented in #19108 because setting a TTL of 0 conceptually doesn't make much sense to me because it would mean that all cache entries would immediately expire. Functionally it would allow the cache to consume memory and never hit, which also seems undesirable. That being said, the fact that it caused confusion probably means we can improve the user experience in some aspects. What would you expect a TTL of 0 to do?

Even when I set a ttl, the cache didn't reset

This does seem odd. I think what you have shown here should be working as you expect and I'm concerned there might be a bug with timing/expiration that's working in unit tests but not in regular usage. I will devote some time to looking into this!

@alamb
Copy link
Contributor

alamb commented Dec 18, 2025

This does seem odd. I think what you have shown here should be working as you expect and I'm concerned there might be a bug with timing/expiration that's working in unit tests but not in regular usage. I will devote some time to looking into this!

I suggest we merge this PR as is to unblock the work to display the contents of the cache. We can file follow on tickets to refine the behavior afterwards.

Is that acceptable to you @BlakeOrth ?

@BlakeOrth
Copy link
Contributor Author

This does seem odd. I think what you have shown here should be working as you expect and I'm concerned there might be a bug with timing/expiration that's working in unit tests but not in regular usage. I will devote some time to looking into this!

I suggest we merge this PR as is to unblock the work to display the contents of the cache. We can file follow on tickets to refine the behavior afterwards.

Is that acceptable to you @BlakeOrth ?

@alamb Yep, that works for me! Would we want to run benchmarks against this PR since it's performance related?

@alamb
Copy link
Contributor

alamb commented Dec 21, 2025

run benchmarks

@alamb-ghbot
Copy link

🤖 ./gh_compare_branch.sh gh_compare_branch.sh Running
Linux aal-dev 6.14.0-1018-gcp #19~24.04.1-Ubuntu SMP Wed Sep 24 23:23:09 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Comparing feature/enable_list_cache (fef57a3) to 2e3707e diff using: tpch_mem clickbench_partitioned clickbench_extended
Results will be posted here when complete

@alamb-ghbot
Copy link

🤖: Benchmark completed

Details

Comparing HEAD and feature_enable_list_cache
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Query        ┃        HEAD ┃ feature_enable_list_cache ┃    Change ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ QQuery 0     │  2760.58 ms │                2695.12 ms │ no change │
│ QQuery 1     │  1214.38 ms │                1216.39 ms │ no change │
│ QQuery 2     │  2339.50 ms │                2238.91 ms │ no change │
│ QQuery 3     │  1155.36 ms │                1140.49 ms │ no change │
│ QQuery 4     │  2272.86 ms │                2267.97 ms │ no change │
│ QQuery 5     │ 28143.91 ms │               28510.94 ms │ no change │
│ QQuery 6     │  3999.31 ms │                4048.27 ms │ no change │
│ QQuery 7     │  3567.61 ms │                3475.16 ms │ no change │
└──────────────┴─────────────┴───────────────────────────┴───────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                        ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                        │ 45453.50ms │
│ Total Time (feature_enable_list_cache)   │ 45593.26ms │
│ Average Time (HEAD)                      │  5681.69ms │
│ Average Time (feature_enable_list_cache) │  5699.16ms │
│ Queries Faster                           │          0 │
│ Queries Slower                           │          0 │
│ Queries with No Change                   │          8 │
│ Queries with Failure                     │          0 │
└──────────────────────────────────────────┴────────────┘
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query        ┃        HEAD ┃ feature_enable_list_cache ┃        Change ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0     │     2.43 ms │                   1.42 ms │ +1.72x faster │
│ QQuery 1     │    51.35 ms │                  49.71 ms │     no change │
│ QQuery 2     │   137.09 ms │                 136.97 ms │     no change │
│ QQuery 3     │   154.33 ms │                 154.65 ms │     no change │
│ QQuery 4     │  1049.06 ms │                1229.91 ms │  1.17x slower │
│ QQuery 5     │  1463.81 ms │                1608.58 ms │  1.10x slower │
│ QQuery 6     │     2.03 ms │                   1.42 ms │ +1.43x faster │
│ QQuery 7     │    56.31 ms │                  54.04 ms │     no change │
│ QQuery 8     │  1430.48 ms │                1565.21 ms │  1.09x slower │
│ QQuery 9     │  1875.89 ms │                1871.54 ms │     no change │
│ QQuery 10    │   362.96 ms │                 362.00 ms │     no change │
│ QQuery 11    │   415.24 ms │                 415.66 ms │     no change │
│ QQuery 12    │  1307.02 ms │                1518.34 ms │  1.16x slower │
│ QQuery 13    │  1960.01 ms │                2107.41 ms │  1.08x slower │
│ QQuery 14    │  1252.78 ms │                1392.97 ms │  1.11x slower │
│ QQuery 15    │  1192.47 ms │                1366.34 ms │  1.15x slower │
│ QQuery 16    │  2594.17 ms │                2756.76 ms │  1.06x slower │
│ QQuery 17    │  2579.45 ms │                2744.71 ms │  1.06x slower │
│ QQuery 18    │  5083.93 ms │                5100.76 ms │     no change │
│ QQuery 19    │   123.36 ms │                 121.78 ms │     no change │
│ QQuery 20    │  1926.29 ms │                1907.24 ms │     no change │
│ QQuery 21    │  2228.28 ms │                2243.96 ms │     no change │
│ QQuery 22    │  3806.98 ms │                3828.38 ms │     no change │
│ QQuery 23    │ 12234.38 ms │               12376.28 ms │     no change │
│ QQuery 24    │   212.29 ms │                 219.92 ms │     no change │
│ QQuery 25    │   492.42 ms │                 471.39 ms │     no change │
│ QQuery 26    │   221.62 ms │                 220.92 ms │     no change │
│ QQuery 27    │  2838.75 ms │                2738.37 ms │     no change │
│ QQuery 28    │ 23774.59 ms │               24549.39 ms │     no change │
│ QQuery 29    │   962.69 ms │                 984.38 ms │     no change │
│ QQuery 30    │  1318.40 ms │                1400.83 ms │  1.06x slower │
│ QQuery 31    │  1349.01 ms │                1368.80 ms │     no change │
│ QQuery 32    │  4895.79 ms │                4569.15 ms │ +1.07x faster │
│ QQuery 33    │  5886.57 ms │                5875.79 ms │     no change │
│ QQuery 34    │  6555.90 ms │                6177.13 ms │ +1.06x faster │
│ QQuery 35    │  1876.35 ms │                2046.90 ms │  1.09x slower │
│ QQuery 36    │    66.94 ms │                  65.71 ms │     no change │
│ QQuery 37    │    45.74 ms │                  44.62 ms │     no change │
│ QQuery 38    │    67.54 ms │                  66.91 ms │     no change │
│ QQuery 39    │   100.64 ms │                 106.34 ms │  1.06x slower │
│ QQuery 40    │    25.92 ms │                  26.59 ms │     no change │
│ QQuery 41    │    22.79 ms │                  22.48 ms │     no change │
│ QQuery 42    │    20.87 ms │                  19.47 ms │ +1.07x faster │
└──────────────┴─────────────┴───────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary                        ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)                        │ 94024.91ms │
│ Total Time (feature_enable_list_cache)   │ 95891.13ms │
│ Average Time (HEAD)                      │  2186.63ms │
│ Average Time (feature_enable_list_cache) │  2230.03ms │
│ Queries Faster                           │          5 │
│ Queries Slower                           │         12 │
│ Queries with No Change                   │         26 │
│ Queries with Failure                     │          0 │
└──────────────────────────────────────────┴────────────┘
--------------------
Benchmark tpch_mem_sf1.json
--------------------
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query        ┃      HEAD ┃ feature_enable_list_cache ┃        Change ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 1     │ 128.70 ms │                 131.42 ms │     no change │
│ QQuery 2     │  27.70 ms │                  29.70 ms │  1.07x slower │
│ QQuery 3     │  38.29 ms │                  35.57 ms │ +1.08x faster │
│ QQuery 4     │  28.42 ms │                  29.48 ms │     no change │
│ QQuery 5     │  89.84 ms │                  88.38 ms │     no change │
│ QQuery 6     │  19.57 ms │                  20.00 ms │     no change │
│ QQuery 7     │ 227.52 ms │                 240.17 ms │  1.06x slower │
│ QQuery 8     │  37.70 ms │                  36.62 ms │     no change │
│ QQuery 9     │ 108.38 ms │                 104.11 ms │     no change │
│ QQuery 10    │  65.22 ms │                  61.19 ms │ +1.07x faster │
│ QQuery 11    │  18.08 ms │                  17.17 ms │     no change │
│ QQuery 12    │  49.70 ms │                  50.85 ms │     no change │
│ QQuery 13    │  49.15 ms │                  47.07 ms │     no change │
│ QQuery 14    │  13.41 ms │                  13.81 ms │     no change │
│ QQuery 15    │  23.89 ms │                  24.14 ms │     no change │
│ QQuery 16    │  24.94 ms │                  24.92 ms │     no change │
│ QQuery 17    │ 146.93 ms │                 150.20 ms │     no change │
│ QQuery 18    │ 278.80 ms │                 284.62 ms │     no change │
│ QQuery 19    │  37.36 ms │                  37.17 ms │     no change │
│ QQuery 20    │  54.30 ms │                  53.81 ms │     no change │
│ QQuery 21    │ 312.72 ms │                 299.66 ms │     no change │
│ QQuery 22    │  17.52 ms │                  17.42 ms │     no change │
└──────────────┴───────────┴───────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                        ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                        │ 1798.14ms │
│ Total Time (feature_enable_list_cache)   │ 1797.48ms │
│ Average Time (HEAD)                      │   81.73ms │
│ Average Time (feature_enable_list_cache) │   81.70ms │
│ Queries Faster                           │         2 │
│ Queries Slower                           │         2 │
│ Queries with No Change                   │        18 │
│ Queries with Failure                     │         0 │
└──────────────────────────────────────────┴───────────┘

@BlakeOrth
Copy link
Contributor Author

I think these benchmark results are generally in line with my expectations for a benchmark running against local files. Some meaningful improvement on queries that already run quickly (single milliseconds range) and generally no change on anything where file access, which are already pretty fast, is an overall small proportion of the work that needs to be done. It looks like most of the reported slow downs are on the long-running queries. I can't really imagine this work having any meaningful effect on those queries; Considering file listing happens at the beginning of the query processing I'm inclined to say that may be run-to-run variation more than this work actually slowing things down.

@alamb
Copy link
Contributor

alamb commented Dec 23, 2025

Yes -- thank you @BlakeOrth -- the more I have been thinking about this PR the more I am worried about the behavior of

-- calls list to get th efiles
create external table foo...
drop table foo;
-- reuses the cached file list
create external table foo ...

Specifically it seems like most people would expect that calling create external table would take a while to find the files, etc. Reusing the same cached list I think will be pretty confusing

What do you think about keeping the caches table scoped?

We can do it either in this PR or as a follow on?

@BlakeOrth
Copy link
Contributor Author

@alamb I think there's a pretty strong justification for doing something around table initialization as well. I'm not sure what solution you had in mind, but I think it would potentially be reasonable to treat a DROP command like INSERT where we manually invalidate the cache entries for that table's path.

Somewhat selfishly I think I would prefer this in a follow-on PR. I'm currently in the middle of a high priority bug hunt and I'm trying to take some time off until the new year, so I personally won't have time to pursue a solution for a little over a week. It would be nice to keep things moving and I hope a follow-on issue might allow someone else to implement it while I'm recharging a bit!

@alamb
Copy link
Contributor

alamb commented Dec 24, 2025

Somewhat selfishly I think I would prefer this in a follow-on PR. I'm currently in the middle of a high priority bug hunt and I'm trying to take some time off until the new year, so I personally won't have time to pursue a solution for a little over a week. It would be nice to keep things moving and I hope a follow-on issue might allow someone else to implement it while I'm recharging a bit!

Yes absolutely -- makes sense. I will plan to file a follow on issue shortly and then merge this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core DataFusion crate documentation Improvements or additions to documentation execution Related to the execution crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

4 participants