apache / apache/datafusion

Performance of `distinct on (columns)`

Open
#16,620 8 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Rust
Stars
9.3k
Forks
2.4k
Avg merge
3d 7h
Merged PRs (30d)
344

Description

### Describe the bug

The query filter selects `492435` rows. As there may be duplicates, I need to execute a distinct query on a column. Then order by timestamp, and retrieve the top 10 rows. In this case, all `492435` rows were unique; there were **no** duplicates.

I executed these queries in an identical environment (AWS EC2 M7A). The binary is compiled with the following settings and using the native CPU features.
```toml
[profile.release]
opt-level = 3
codegen-units = 1
```

1. `distinct on ("SpanId")`. Approximately **2 seconds** of wall clock execution time.
```
SortPreservingMergeExec: [Timestamp@1 ASC NULLS LAST], fetch=10, metrics=[output_rows=10, elapsed_compute=56.441µs]
SortExec: TopK(fetch=10), expr=[Timestamp@1 ASC NULLS LAST], preserve_partitioning=[true], metrics=[output_rows=80, elapsed_compute=12.32639ms, row_replacements=568]
ProjectionExec: expr=[first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.SpanId)@1 as SpanId, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Timestamp)@2 as Timestamp, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.TraceId)@3 as TraceId, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.SpanName)@4 as SpanName, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ScopeName)@5 as ScopeName, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ServiceName)@6 as ServiceName, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ParentSpanId)@7 as ParentSpanId, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Duration)@8 as Duration, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Tags)@9 as Tags, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.SpanKind)@10 as SpanKind, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.MlRepoId)@11 as MlRepoId, first_value(TracingProjectId)@12 as TracingProjectId, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.StatusCode)@13 as StatusCode, first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.TsBucketStart)@14 as TsBucketStart, ((first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ServiceName)@6 = user-service OR first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ServiceName)@6 = auth-service) AND first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.SpanName)@4 LIKE %login% AND first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Duration)@8 >= 1000000 AND first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Duration)@8 <= 5000000 AND first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.StatusCode)@13 >= 200 AND first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.StatusCode)@13 <= 299) IS NOT DISTINCT FROM true as matched], metrics=[output_rows=492435, elapsed_compute=1.681826ms]
AggregateExec: mode=FinalPartitioned, gby=[SpanId@0 as SpanId], aggr=[first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.SpanId), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Timestamp), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.TraceId), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.SpanName), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ScopeName), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ServiceName), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ParentSpanId), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Duration), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Tags), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.SpanKind), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.MlRepoId), first_value(TracingProjectId), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.StatusCode), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.TsBucketStart)], metrics=[output_rows=492435, elapsed_compute=3.882494179s, spill_count=0, spilled_bytes=0, spilled_rows=0, peak_mem_used=1414466147]
CoalesceBatchesExec: target_batch_size=20000, metrics=[output_rows=492435, elapsed_compute=916.376µs]
RepartitionExec: partitioning=Hash([SpanId@0], 8), input_partitions=8, metrics=[fetch_time=3.424462188s, repartition_time=75.357473ms, send_time=327.861036ms]
AggregateExec: mode=Partial, gby=[SpanId@3 as SpanId], aggr=[first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.SpanId), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Timestamp), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.TraceId), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.SpanName), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ScopeName), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ServiceName), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.ParentSpanId), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Duration), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.Tags), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.SpanKind), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.MlRepoId), first_value(TracingProjectId), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.StatusCode), first_value(tbl_6baa3c6fd52949c18602a6b38294e8d9.TsBucketStart)], metrics=[output_rows=492435, elapsed_compute=2.742439769s, spill_count=0, spilled_bytes=0, spilled_rows=0, skipped_aggregation_rows=191476, peak_mem_used=890092194]
CoalesceBatchesExec: target_batch_size=20000, metrics=[output_rows=492435, elapsed_compute=67.421µs]
FilterExec: TraceId@2 = fb09d0c9b49136bb161464b3e32c5083 AND ParentSpanId@4 = 662b7388122dfb79 AND Timestamp@0 >= 1744702200000000 AND TsBucketStart@1 >= 1745712000, metrics=[output_rows=492435, elapsed_compute=3.831311ms]
DeltaScan, metrics=[files_pruned=96, files_scanned=205]
DataSourceExec: file_groups={8 groups: [[..., ...]}, projection=[Timestamp, TsBucketStart, TraceId, SpanId, ParentSpanId, ServiceName, SpanName, SpanKind, ScopeName, Duration, StatusCode, Tags, MlRepoId, TracingProjectId], file_type=parquet, predicate=TraceId@2 = fb09d0c9b49136bb161464b3e32c5083 AND ParentSpanId@4 = 662b7388122dfb79 AND Timestamp@0 >= 1744702200000000 AND TsBucketStart@1 >= 1745712000, pruning_predicate=TraceId_null_count@2 != row_count@3 AND TraceId_min@0 <= fb09d0c9b49136bb161464b3e32c5083 AND fb09d0c9b49136bb161464b3e32c5083 <= TraceId_max@1 AND ParentSpanId_null_count@6 != row_count@3 AND ParentSpanId_min@4 <= 662b7388122dfb79 AND 662b7388122dfb79 <= ParentSpanId_max@5 AND Timestamp_null_count@8 != row_count@3 AND Timestamp_max@7 >= 1744702200000000 AND TsBucketStart_null_count@10 != row_count@3 AND TsBucketStart_max@9 >= 1745712000, required_guarantees=[ParentSpanId in (662b7388122dfb79), TraceId in (fb09d0c9b49136bb161464b3e32c5083)], metrics=[output_rows=492435, elapsed_compute=8ns, bytes_scanned=369897090, file_open_errors=0, file_scan_errors=0, num_predicate_creation_errors=0, page_index_rows_matched=5863737, page_index_rows_pruned=0, predicate_evaluation_errors=0, pushdown_rows_matched=12712344, pushdown_rows_pruned=5371302, row_groups_matched_bloom_filter=10, row_groups_matched_statistics=205, row_groups_pruned_bloom_filter=195, row_groups_pruned_statistics=0, bloom_filter_eval_time=11.551132ms, metadata_load_time=76.928924ms, page_index_eval_time=310.055µs, row_pushdown_eval_time=28.421911ms, statistics_eval_time=4.607552ms, time_elapsed_opening=24.111618ms, time_elapsed_processing=622.896471ms, time_elapsed_scanning_total=3.532807376s, time_elapsed_scanning_until_data=417.093675ms]
```

2. Distinct all columns. Approximately **500MS** of wall clock execution time. For my use case, this works as if `SpanId` repeats all the other columns selected will have the identical value.
```
SortPreservingMergeExec: [Timestamp@1 ASC NULLS LAST], fetch=10, metrics=[output_rows=10, elapsed_compute=501.954µs]
SortExec: TopK(fetch=10), expr=[Timestamp@1 ASC NULLS LAST], preserve_partitioning=[true], metrics=[output_rows=80, elapsed_compute=12.752748ms, row_replacements=536]
ProjectionExec: expr=[SpanId@0 as SpanId, Timestamp@1 as Timestamp, TraceId@2 as TraceId, SpanName@3 as SpanName, ScopeName@4 as ScopeName, ServiceName@5 as ServiceName, ParentSpanId@6 as ParentSpanId, Duration@7 as Duration, Tags@8 as Tags, SpanKind@9 as SpanKind, MlRepoId@10 as MlRepoId, TracingProjectId@11 as TracingProjectId, StatusCode@12 as StatusCode, TsBucketStart@13 as TsBucketStart, ((ServiceName@5 = user-service OR ServiceName@5 = auth-service) AND SpanName@3 LIKE %login% AND Duration@7 >= 1000000 AND Duration@7 <= 5000000 AND StatusCode@12 >= 200 AND StatusCode@12 <= 299) IS NOT DISTINCT FROM true as matched], metrics=[output_rows=492435, elapsed_compute=1.631387ms]
AggregateExec: mode=FinalPartitioned, gby=[SpanId@0 as SpanId, Timestamp@1 as Timestamp, TraceId@2 as TraceId, SpanName@3 as SpanName, ScopeName@4 as ScopeName, ServiceName@5 as ServiceName, ParentSpanId@6 as ParentSpanId, Duration@7 as Duration, Tags@8 as Tags, SpanKind@9 as SpanKind, MlRepoId@10 as MlRepoId, TracingProjectId@11 as TracingProjectId, StatusCode@12 as StatusCode, TsBucketStart@13 as TsBucketStart], aggr=[], ordering_mode=PartiallySorted([2, 6]), metrics=[output_rows=492435, elapsed_compute=369.067634ms, spill_count=0, spilled_bytes=0, spilled_rows=0, peak_mem_used=237437570]
CoalesceBatchesExec: target_batch_size=20000, metrics=[output_rows=492435, elapsed_compute=385.467µs]
RepartitionExec: partitioning=Hash([SpanId@0, Timestamp@1, TraceId@2, SpanName@3, ScopeName@4, ServiceName@5, ParentSpanId@6, Duration@7, Tags@8, SpanKind@9, MlRepoId@10, TracingProjectId@11, StatusCode@12, TsBucketStart@13], 8), input_partitions=8, metrics=[fetch_time=1.06138683s, repartition_time=71.350859ms, send_time=24.35597ms]
AggregateExec: mode=Partial, gby=[SpanId@0 as SpanId, Timestamp@1 as Timestamp, TraceId@2 as TraceId, SpanName@3 as SpanName, ScopeName@4 as ScopeName, ServiceName@5 as ServiceName, ParentSpanId@6 as ParentSpanId, Duration@7 as Duration, Tags@8 as Tags, SpanKind@9 as SpanKind, MlRepoId@10 as MlRepoId, TracingProjectId@11 as TracingProjectId, StatusCode@12 as StatusCode, TsBucketStart@13 as TsBucketStart], aggr=[], ordering_mode=PartiallySorted([2, 6]), metrics=[output_rows=492435, elapsed_compute=409.064966ms, spill_count=0, spilled_bytes=0, spilled_rows=0, peak_mem_used=206203683]
ProjectionExec: expr=[SpanId@3 as SpanId, Timestamp@0 as Timestamp, TraceId@2 as TraceId, SpanName@6 as SpanName, ScopeName@8 as ScopeName, ServiceName@5 as ServiceName, ParentSpanId@4 as ParentSpanId, Duration@9 as Duration, Tags@11 as Tags, SpanKind@7 as SpanKind, MlRepoId@12 as MlRepoId, CAST(TracingProjectId@13 AS Utf8) as TracingProjectId, StatusCode@10 as StatusCode, TsBucketStart@1 as TsBucketStart], metrics=[output_rows=492435, elapsed_compute=4.455516ms]
CoalesceBatchesExec: target_batch_size=20000, metrics=[output_rows=492435, elapsed_compute=57µs]
FilterExec: TraceId@2 = fb09d0c9b49136bb161464b3e32c5083 AND ParentSpanId@4 = 662b7388122dfb79 AND Timestamp@0 >= 1744702200000000 AND TsBucketStart@1 >= 1745712000, metrics=[output_rows=492435, elapsed_compute=3.792731ms]
DeltaScan, metrics=[files_pruned=96, files_scanned=205]
DataSourceExec: file_groups={8 groups: [[ ...], ...]}, projection=[Timestamp, TsBucketStart, TraceId, SpanId, ParentSpanId, ServiceName, SpanName, SpanKind, ScopeName, Duration, StatusCode, Tags, MlRepoId, TracingProjectId], file_type=parquet, predicate=TraceId@2 = fb09d0c9b49136bb161464b3e32c5083 AND ParentSpanId@4 = 662b7388122dfb79 AND Timestamp@0 >= 1744702200000000 AND TsBucketStart@1 >= 1745712000, pruning_predicate=TraceId_null_count@2 != row_count@3 AND TraceId_min@0 <= fb09d0c9b49136bb161464b3e32c5083 AND fb09d0c9b49136bb161464b3e32c5083 <= TraceId_max@1 AND ParentSpanId_null_count@6 != row_count@3 AND ParentSpanId_min@4 <= 662b7388122dfb79 AND 662b7388122dfb79 <= ParentSpanId_max@5 AND Timestamp_null_count@8 != row_count@3 AND Timestamp_max@7 >= 1744702200000000 AND TsBucketStart_null_count@10 != row_count@3 AND TsBucketStart_max@9 >= 1745712000, required_guarantees=[ParentSpanId in (662b7388122dfb79), TraceId in (fb09d0c9b49136bb161464b3e32c5083)], metrics=[output_rows=492435, elapsed_compute=8ns, bytes_scanned=369885282, file_open_errors=0, file_scan_errors=0, num_predicate_creation_errors=0, page_index_rows_matched=5863737, page_index_rows_pruned=0, predicate_evaluation_errors=0, pushdown_rows_matched=12712344, pushdown_rows_pruned=5371302, row_groups_matched_bloom_filter=10, row_groups_matched_statistics=205, row_groups_pruned_bloom_filter=195, row_groups_pruned_statistics=0, bloom_filter_eval_time=11.322584ms, metadata_load_time=83.355586ms, page_index_eval_time=336.416µs, row_pushdown_eval_time=28.880893ms, statistics_eval_time=4.498338ms, time_elapsed_opening=30.859506ms, time_elapsed_processing=629.285022ms, time_elapsed_scanning_total=785.298422ms, time_elapsed_scanning_until_data=417.603661ms]
```

3. No Distinct. Approximately **300MS** of wall clock execution time.
```
SortPreservingMergeExec: [Timestamp@1 ASC NULLS LAST], fetch=10, metrics=[output_rows=10, elapsed_compute=21.35µs]
SortExec: TopK(fetch=10), expr=[Timestamp@1 ASC NULLS LAST], preserve_partitioning=[true], metrics=[output_rows=30, elapsed_compute=11.579758ms, row_replacements=161]
ProjectionExec: expr=[SpanId@3 as SpanId, Timestamp@0 as Timestamp, TraceId@2 as TraceId, SpanName@6 as SpanName, ScopeName@8 as ScopeName, ServiceName@5 as ServiceName, ParentSpanId@4 as ParentSpanId, Duration@9 as Duration, Tags@11 as Tags, SpanKind@7 as SpanKind, MlRepoId@12 as MlRepoId, CAST(TracingProjectId@13 AS Utf8) as TracingProjectId, StatusCode@10 as StatusCode, TsBucketStart@1 as TsBucketStart, ((ServiceName@5 = user-service OR ServiceName@5 = auth-service) AND SpanName@6 LIKE %login% AND Duration@9 >= 1000000 AND Duration@9 <= 5000000 AND StatusCode@10 >= 200 AND StatusCode@10 <= 299) IS NOT DISTINCT FROM true as matched], metrics=[output_rows=492435, elapsed_compute=5.709997ms]
CoalesceBatchesExec: target_batch_size=20000, metrics=[output_rows=492435, elapsed_compute=46.641µs]
FilterExec: TraceId@2 = fb09d0c9b49136bb161464b3e32c5083 AND ParentSpanId@4 = 662b7388122dfb79 AND Timestamp@0 >= 1744702200000000 AND TsBucketStart@1 >= 1745712000, metrics=[output_rows=492435, elapsed_compute=3.539565ms]
DeltaScan, metrics=[files_pruned=96, files_scanned=205]
DataSourceExec: file_groups={8 groups: [[...], ...]}, projection=[Timestamp, TsBucketStart, TraceId, SpanId, ParentSpanId, ServiceName, SpanName, SpanKind, ScopeName, Duration, StatusCode, Tags, MlRepoId, TracingProjectId], file_type=parquet, predicate=TraceId@2 = fb09d0c9b49136bb161464b3e32c5083 AND ParentSpanId@4 = 662b7388122dfb79 AND Timestamp@0 >= 1744702200000000 AND TsBucketStart@1 >= 1745712000, pruning_predicate=TraceId_null_count@2 != row_count@3 AND TraceId_min@0 <= fb09d0c9b49136bb161464b3e32c5083 AND fb09d0c9b49136bb161464b3e32c5083 <= TraceId_max@1 AND ParentSpanId_null_count@6 != row_count@3 AND ParentSpanId_min@4 <= 662b7388122dfb79 AND 662b7388122dfb79 <= ParentSpanId_max@5 AND Timestamp_null_count@8 != row_count@3 AND Timestamp_max@7 >= 1744702200000000 AND TsBucketStart_null_count@10 != row_count@3 AND TsBucketStart_max@9 >= 1745712000, required_guarantees=[ParentSpanId in (662b7388122dfb79), TraceId in (fb09d0c9b49136bb161464b3e32c5083)], metrics=[output_rows=492435, elapsed_compute=8ns, bytes_scanned=369891545, file_open_errors=0, file_scan_errors=0, num_predicate_creation_errors=0, page_index_rows_matched=5863737, page_index_rows_pruned=0, predicate_evaluation_errors=0, pushdown_rows_matched=12712344, pushdown_rows_pruned=5371302, row_groups_matched_bloom_filter=10, row_groups_matched_statistics=205, row_groups_pruned_bloom_filter=195, row_groups_pruned_statistics=0, bloom_filter_eval_time=11.370524ms, metadata_load_time=61.64128ms, page_index_eval_time=332.784µs, row_pushdown_eval_time=28.634729ms, statistics_eval_time=4.530266ms, time_elapsed_opening=7.874004ms, time_elapsed_processing=610.976289ms, time_elapsed_scanning_total=628.786969ms, time_elapsed_scanning_until_data=427.639613ms]
```

I would like to understand what I can do to make `distinct on (columns)` faster.

### To Reproduce

_No response_

### Expected behavior

_No response_

### Additional context

I am using Datafusion 47.

Contributor guide

Open the contributing guide

Research direction

No source file or test is named. Start by reproducing the two distinct-query plans and comparing their AggregateExec and RepartitionExec metrics; done means identifying and correcting the performance gap while preserving the reported query results.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, sql
Domain
databases, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.