Scan metrics
Rust scans expose counters for the work a query planned and performed. Start with the question you are investigating:
| Question | Look at |
|---|---|
| How many files did the query select? | File selection |
| How much data did it receive? | Downloaded bytes and requests |
| Did work finish, or did reading stop early? | Scan progress |
| How did it choose smaller or combined requests? | Range-planning counters |
The Python API does not expose these metric handles. For diagnostics in Python, use logging.
These are reader counters, not network billing figures. Some count planned work
and others count completed work. Keep those boundaries in mind when comparing
queries. An unavailable value (None) is different from a measured zero.
Inspect a Rust scan
Keep the metrics handle before consuming the stream, then inspect it after the read. This example takes a scan you have already built:
let mut batches = scan.into_stream();
let metrics = batches.metrics();
while let Some(batch) = batches.next_batch().await? {
println!("rows={}", batch.num_rows());
}
println!("tasks={}", metrics.snapshot().file_tasks_completed);
If you stop early and drop the stream, the reader stops scheduling new files. The handle remains usable, but its counters describe only the work performed.
DeltaScanMetrics is a live, cloneable handle. Its snapshot() method returns
a point-in-time DeltaScanMetricsSnapshot, without stopping the scan.
Core scan metrics
These fields describe the table version and the files selected for the query. Input row estimates are before filters and deletions, so they are not result row counts.
| Field | Meaning |
|---|---|
snapshot_version |
Delta snapshot version used by the scan. |
parquet_backend |
Backend selected to read Parquet data files. |
scan_partitions_planned |
Final number of execution partitions, including DataFusion repartitioning. |
files_planned |
Physical data files selected during initial whole-file planning. |
add_actions_excluded_during_planning |
Best-effort count of Add actions excluded during metadata planning, when known. |
estimated_input_rows |
Estimated rows in selected input files before row predicates and deletion vectors, when every file supplied a valid estimate. |
estimated_input_bytes |
Estimated bytes in selected input files when every file supplied a size. |
add_actions_excluded_during_planning is a best-effort count, not an exact
active-file count. Delta Kernel's final selection also reconciles Add and
Remove actions.
Downloaded bytes and requests
These counters describe data-file reads through the Direct backend. They are
Some, including Some(0), for Direct and None for Delta Kernel, whose
object-store calls are outside this measurement boundary.
| Field | Meaning |
|---|---|
parquet_data_file_range_get_operations |
Ranged operations observed by the Direct Parquet wrapper. A forwarded store-provided multi-range call counts once. |
parquet_data_file_full_get_operations |
Direct Parquet data-file GET operations without a range. |
parquet_data_file_bytes_received |
Bytes delivered successfully through the Direct backend's object-store boundary. |
A forwarded multi-range call counts as one operation. Bytes can include gaps between selected ranges. Lower-level retries and provider billing units are not visible here; see the full counting rules.
Scan progress
Scheduler row and batch counts are before final filtering, column selection, and limits. Use the rows your application receives for its result count.
Started and completed tasks, partitions, and batches
| Field | Meaning |
|---|---|
scan_partitions_started |
Execution partitions that started. |
scan_partitions_completed |
Execution partitions that reached normal completion. |
file_tasks_started |
Whole-file or ranged tasks that started. |
file_tasks_completed |
Whole-file or ranged tasks that reached normal completion. |
scheduler_batches_emitted |
Batches emitted by the scheduler before final stream filtering, projection, and limits. |
scheduler_rows_emitted |
Rows emitted by the scheduler before final stream filtering, projection, and limits. |
Deletion vectors
Deletion vectors mark rows as deleted without rewriting data files. These counters show their loading, application, and failures:
Deletion-vector counters
| Field | Meaning |
|---|---|
deletion_vector_payloads_loaded |
Deletion-vector payloads loaded. |
deletion_vectors_applied |
Deletion-vector masks applied. |
deletion_vector_rows_deleted |
Rows removed by those masks. |
deletion_vector_failures |
Deletion-vector read or masking failures. |
deletion_vector_coordinate_rejections |
Deletion-vector coordinate operations rejected by safety checks. |
Range-planning counters
Use these to compare the minimum byte ranges requested with the physical reads the planner chose. They describe plans even when a later read fails, and are available only for the Direct backend.
Requested ranges, planned bytes, and decision counters
| Field | Meaning |
|---|---|
parquet_data_file_exact_ranges_requested |
Normalized, non-overlapping exact ranges requested through Direct Parquet multi-range calls. |
parquet_data_file_exact_range_bytes_requested |
Bytes covered by those normalized exact ranges. |
parquet_data_file_physical_range_requests_planned |
Physical requests selected by visible range plans, including partial-page probes and data reads. Store-delegated calls do not contribute because their physical plan is not visible. |
parquet_data_file_physical_range_bytes_planned |
Bytes covered by those planned physical requests. |
parquet_data_file_cold_start_range_plans |
Automatic plans selected without a usable transport estimate. Safety bounds may still merge a large exact plan. |
parquet_data_file_cost_based_exact_range_plans |
Automatic plans where a usable estimate favored the normalized minimum-byte ranges. |
parquet_data_file_cost_based_merged_range_plans |
Automatic plans where a usable estimate favored including gaps to reduce physical requests. |
parquet_data_file_store_delegated_range_plans |
Range-planning decisions passed to the store's own multi-range implementation. |
estimated_parquet_task_bytes_admitted |
Estimated bytes admitted across Direct backend tasks. A ranged task contributes its range length. |
DataFusion metrics
datafusion::ScanMetricsSnapshot includes the core scan snapshot and adds SQL
execution details. These include filters received after planning and tasks
skipped before reading them.
DataFusion fields
| Field | Meaning |
|---|---|
reader_metrics |
DeltaScanMetricsSnapshot for this physical scan. |
uses_arrow_view_types |
Whether the provider requested Arrow view arrays for string and binary data columns. |
configured_batch_size_rows |
DataFusion's configured batch row target, recorded when execution starts. |
dynamic_partition_tasks_pruned |
Whole-file or ranged tasks skipped by a dynamic partition filter before admission. |
dynamic_partition_tasks_kept |
Tasks kept after consulting dynamic partition filters. |
dynamic_filters_received |
Physical filters offered after optimization. |
dynamic_filters_accepted |
Offered filters retained for partition pruning. |
dynamic_filters_rejected |
Offered filters rejected by the dynamic-filter policy. |
dynamic_partition_filter_checks |
Dynamic partition filters checked against file tasks during admission. |
dynamic_partition_tasks_kept_unusable_metadata |
Tasks kept because partition metadata was missing, invalid, or could not be parsed. |
dynamic_partition_tasks_kept_unevaluable_filter |
Tasks kept because a dynamic filter was unavailable or could not be evaluated. |
datafusion::collect_scan_metrics walks a physical plan in depth-first order
and returns each distinct Delta scan metric handle once.
Parquet I/O boundaries
The counters observe the Direct backend's data-file object_store wrapper.
Use them to compare scan choices under the same conditions. They do not measure
all traffic, HTTP retries, wire compression, or provider charges.
Exactly when counters advance and what they include
The I/O counters observe calls made through the Direct backend's data-file
object_store wrapper. They are useful for comparing scan choices, but they
are not network billing counters.
- Range and full GET counters advance immediately before an operation is passed to the underlying store, so failed operations still count. A forwarded store-provided multi-range call counts once because the wrapper cannot see how the store performs that call.
- Requested range counts and bytes describe the normalized minimum-byte plan. Planned request counts and bytes describe the physical plan selected for built-in remote stores, including experimental partial-page rounds. These counters advance before the physical reads start, so they also include plans whose reads later fail.
- Exactly one decision counter advances for each ordinary non-empty multi-range call. Automatic calls count as cold start, cost-based exact, or cost-based merged. Calls that use the store's own multi-range implementation count as store-delegated; their internal physical request count and planned bytes are not observable here.
- Partial-page rounds contribute their unmerged requested ranges and selected
physical plans to the byte and request counters. Their decisions use the
delta_arrow_reader::diagnostics::intra_pagetracing target instead of the ordinary plan decision counters. Planned request waves use each round's concurrency limit. Shared capacity can add queueing time during execution. - For visible plans with requested bytes, aggregate byte amplification is
parquet_data_file_physical_range_bytes_planneddivided byparquet_data_file_exact_range_bytes_requested. Calculate the ratio from the aggregate counters rather than averaging ratios from individual calls. - Received bytes count successful response chunks delivered through the wrapper. Merged reads can include unrequested gaps. A store-provided multi-range call reports the bytes returned to Parquet; any extra work inside the store is not visible.
- Admitted task bytes add the estimated span of each task. A whole-file task contributes its file size, while a ranged task contributes its range length.
The wrapper cannot see lower-level HTTP retries, wire compression, provider
billing units, or object-store work hidden behind DeltaKernel. A local file
read may also begin before a result is delivered through the wrapper, so a
failed or dropped local result can leave the received-byte counter at zero.