Skip to content

Improve performance of ClickBench Q21 by removing the cast #16591

Closed

Description

@alamb

Is your feature request related to a problem or challenge?

When looking into a purported regression in #16575 (comment) I noticed something else about clickbench 21:

SELECT "SearchPhrase", MIN("URL"), COUNT(*) AS c FROM hits WHERE "URL" LIKE '%google%' AND "SearchPhrase" <> '' GROUP BY "SearchPhrase" ORDER BY c DESC LIMIT 10;

About 30% of the overall execution time is going to CASTing!

Image

Describe the solution you'd like

Reproduce like this:

cd benchmarks
./bench.sh data clickbench_partitioned
datafusion-cli -c "SELECT \"SearchPhrase\", MIN(\"URL\"), COUNT(*) AS c FROM 'data/hits_partitioned' WHERE \"URL\" LIKE '%google%' AND \"SearchPhrase\" <> '' GROUP BY \"SearchPhrase\" ORDER BY c DESC LIMIT 10;"

you can run the explain plan like this

(venv) andrewlamb@Andrews-MacBook-Pro-3:~/Software/datafusion/benchmarks$ datafusion-cli -c "EXPLAIN SELECT \"SearchPhrase\", MIN(\"URL\"), COUNT(*) AS c FROM 'data/hits_partitioned' WHERE \"URL\" LIKE '%google%' AND \"SearchPhrase\" <> '' GROUP BY \"SearchPhrase\" ORDER BY c DESC LIMIT 10;"
DataFusion CLI v48.0.0
+---------------+-------------------------------+
| plan_type     | plan                          |
+---------------+-------------------------------+
| physical_plan | ┌───────────────────────────┐ |
|               | │  SortPreservingMergeExec  │ |
|               | │    --------------------   │ |
|               | │      c DESClimit: 10      │ |
|               | └─────────────┬─────────────┘ |
|               | ┌─────────────┴─────────────┐ |
|               | │       SortExec(TopK)      │ |
|               | │    --------------------   │ |
|               | │          c@2 DESC         │ |
|               | │                           │ |
|               | │         limit: 10         │ |
|               | └─────────────┬─────────────┘ |
|               | ┌─────────────┴─────────────┐ |
|               | │       ProjectionExec      │ |
|               | │    --------------------   │ |
|               | │       SearchPhrase:       │ |
|               | │        SearchPhrase       │ |
|               | │                           │ |
|               | │     c: count(Int64(1))    │ |
|               | │                           │ |
|               | │ min(data/hits_partitioned │ |
|               | │           .URL):          │ |
|               | │ min(data/hits_partitioned │ |
|               | │           .URL)           │ |
|               | └─────────────┬─────────────┘ |
|               | ┌─────────────┴─────────────┐ |
|               | │       AggregateExec       │ |
|               | │    --------------------   │ |
|               | │           aggr:           │ |
|               | │ min(data/hits_partitioned │ |
|               | │      .URL), count(1)      │ |
|               | │                           │ |
|               | │         group_by:         │ |
|               | │        SearchPhrase       │ |
|               | │                           │ |
|               | │           mode:           │ |
|               | │      FinalPartitioned     │ |
|               | └─────────────┬─────────────┘ |
|               | ┌─────────────┴─────────────┐ |
|               | │    CoalesceBatchesExec    │ |
|               | │    --------------------   │ |
|               | │     target_batch_size:    │ |
|               | │            8192           │ |
|               | └─────────────┬─────────────┘ |
|               | ┌─────────────┴─────────────┐ |
|               | │      RepartitionExec      │ |
|               | │    --------------------   │ |
|               | │ partition_count(in->out): │ |
|               | │          16 -> 16         │ |
|               | │                           │ |
|               | │    partitioning_scheme:   │ |
|               | │ Hash([SearchPhrase@0], 16)│ |
|               | └─────────────┬─────────────┘ |
|               | ┌─────────────┴─────────────┐ |
|               | │       AggregateExec       │ |
|               | │    --------------------   │ |
|               | │           aggr:           │ |
|               | │ min(data/hits_partitioned │ |
|               | │      .URL), count(1)      │ |
|               | │                           │ |
|               | │         group_by:         │ |
|               | │        SearchPhrase       │ |
|               | │                           │ |
|               | │       mode: Partial       │ |
|               | └─────────────┬─────────────┘ |
|               | ┌─────────────┴─────────────┐ |
|               | │    CoalesceBatchesExec    │ |
|               | │    --------------------   │ |
|               | │     target_batch_size:    │ |
|               | │            8192           │ |
|               | └─────────────┬─────────────┘ |
|               | ┌─────────────┴─────────────┐ |
|               | │         FilterExec        │ |
|               | │    --------------------   │ |
|               | │         predicate:        │ |
|               | │ CAST(URL AS Utf8View) LIKE│ |
|               | │        %google% AND       │ |
|               | │        SearchPhrase       │ |
|               | │             !=            │ |
|               | └─────────────┬─────────────┘ |
|               | ┌─────────────┴─────────────┐ |
|               | │       DataSourceExec      │ |
|               | │    --------------------   │ |
|               | │         files: 115        │ |
|               | │      format: parquet      │ |
|               | │                           │ |
|               | │         predicate:        │ |
|               | │ CAST(URL AS Utf8View) LIKE│ | <--- this CAST is the problem, for some reason the LIKE is not evaluated natively on the query
|               | │        %google% AND       │ |
|               | │        SearchPhrase       │ |
|               | │             !=            │ |
|               | └───────────────────────────┘ |
|               |                               |
+---------------+-------------------------------+
1 row(s) fetched.
Elapsed 0.058 seconds.

Describe alternatives you've considered

No response

Additional context

No response

Activity

  1. alamb commented on Jun 27, 2025

    @alamb
    ContributorAuthor

    Something strange is going on as I can't reproduce this easily

    > copy (select arrow_cast('foo', 'Utf8View') as c1) to '/tmp/repro.parquet';
    +-------+
    | count |
    +-------+
    | 1     |
    +-------+
    1 row(s) fetched.
    Elapsed 0.004 seconds.
    
    > explain select * from '/tmp/repro.parquet' where c1 LIKE '%google%';
    +---------------+-------------------------------+
    | plan_type     | plan                          |
    +---------------+-------------------------------+
    | physical_plan | ┌───────────────────────────┐ |
    |               | │    CoalesceBatchesExec    │ |
    |               | │    --------------------   │ |
    |               | │     target_batch_size:    │ |
    |               | │            8192           │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │         FilterExec        │ |
    |               | │    --------------------   │ |
    |               | │         predicate:        │ |
    |               | │      c1 LIKE %google%     │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │      RepartitionExec      │ |
    |               | │    --------------------   │ |
    |               | │ partition_count(in->out): │ |
    |               | │          1 -> 16          │ |
    |               | │                           │ |
    |               | │    partitioning_scheme:   │ |
    |               | │    RoundRobinBatch(16)    │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │       DataSourceExec      │ |
    |               | │    --------------------   │ |
    |               | │          files: 1         │ |
    |               | │      format: parquet      │ |
    |               | │                           │ |
    |               | │         predicate:        │ |
    |               | │      c1 LIKE %google%     │ |
    |               | └───────────────────────────┘ |
    |               |                               |
    +---------------+-------------------------------+
    1 row(s) fetched.
    Elapsed 0.004 seconds.
  2. changed the title [-]Improve performance of Q21 by removing the cast[/-] [+]Improve performance of ClickBench Q21 by removing the cast[/+] on Jun 27, 2025
  3. Dandandan commented on Jun 28, 2025

    @Dandandan
    Contributor

    Wasn't clickbench writing strings as binary columns (an option was added for this)?

    Though there is something to say that filtering should not have to validata the utf8-ness.

  4. zhuqi-lucas commented on Jun 28, 2025

    @zhuqi-lucas
    Contributor

    take

  5. zhuqi-lucas commented on Jun 28, 2025

    @zhuqi-lucas
    Contributor

    I want to investigate this issue, thank you @alamb @Dandandan !

  6. zhuqi-lucas commented on Jun 28, 2025

    @zhuqi-lucas
    Contributor

    When i using clickbench benchmark to debug, it does not have cast:

    cargo run --profile release-nonlto   --target aarch64-apple-darwin --bin dfbench -- clickbench --queries-path  ./benchmarks/queries/clickbench/queries/ -p ./benchmarks/data/hits.parquet -q 21 -i 1  --debug
        Finished `release-nonlto` profile [optimized] target(s) in 0.17s
         Running `target/aarch64-apple-darwin/release-nonlto/dfbench clickbench --queries-path ./benchmarks/queries/clickbench/queries/ -p ./benchmarks/data/hits.parquet -q 21 -i 1 --debug`
    Running benchmarks with the following options: RunOpt { query: Some(21), common: CommonOpt { iterations: 1, partitions: None, batch_size: None, mem_pool_type: "fair", memory_limit: None, sort_spill_reservation_bytes: None, debug: true }, path: "./benchmarks/data/hits.parquet", queries_path: "./benchmarks/queries/clickbench/queries/", output_path: None }
    Q21: SELECT "SearchPhrase", MIN("URL"), COUNT(*) AS c FROM hits WHERE "URL" LIKE '%google%' AND "SearchPhrase" <> '' GROUP BY "SearchPhrase" ORDER BY c DESC LIMIT 10;
    
    Query 21 iteration 0 took 697.8 ms and returned 10 rows
    +---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | plan_type     | plan                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    +---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | logical_plan  | Sort: c DESC NULLS FIRST, fetch=10                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    |               |   Projection: hits.SearchPhrase, min(hits.URL), count(Int64(1)) AS count(*) AS c                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
    |               |     Aggregate: groupBy=[[hits.SearchPhrase]], aggr=[[min(hits.URL), count(Int64(1))]]                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
    |               |       Filter: hits.URL LIKE Utf8View("%google%") AND hits.SearchPhrase != Utf8View("")                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
    |               |         TableScan: hits projection=[URL, SearchPhrase], partial_filters=[hits.URL LIKE Utf8View("%google%"), hits.SearchPhrase != Utf8View("")]                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    | physical_plan | SortPreservingMergeExec: [c@2 DESC], fetch=10                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
    |               |   SortExec: TopK(fetch=10), expr=[c@2 DESC], preserve_partitioning=[true]                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    |               |     ProjectionExec: expr=[SearchPhrase@0 as SearchPhrase, min(hits.URL)@1 as min(hits.URL), count(Int64(1))@2 as c]                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    |               |       AggregateExec: mode=FinalPartitioned, gby=[SearchPhrase@0 as SearchPhrase], aggr=[min(hits.URL), count(Int64(1))]                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
    |               |         CoalesceBatchesExec: target_batch_size=8192                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    |               |           RepartitionExec: partitioning=Hash([SearchPhrase@0], 14), input_partitions=14                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
    |               |             AggregateExec: mode=Partial, gby=[SearchPhrase@1 as SearchPhrase], aggr=[min(hits.URL), count(Int64(1))]                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    |               |               CoalesceBatchesExec: target_batch_size=8192                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    |               |                 FilterExec: URL@0 LIKE %google% AND SearchPhrase@1 !=                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
    |               |                   DataSourceExec: file_groups={14 groups: [[Users/zhuqi/arrow-datafusion/benchmarks/data/hits.parquet:0..1055712604], [Users/zhuqi/arrow-datafusion/benchmarks/data/hits.parquet:1055712604..2111425208], [Users/zhuqi/arrow-datafusion/benchmarks/data/hits.parquet:2111425208..3167137812], [Users/zhuqi/arrow-datafusion/benchmarks/data/hits.parquet:3167137812..4222850416], [Users/zhuqi/arrow-datafusion/benchmarks/data/hits.parquet:4222850416..5278563020], ...]}, projection=[URL, SearchPhrase], file_type=parquet, predicate=URL@0 LIKE %google% AND SearchPhrase@1 != , pruning_predicate=SearchPhrase_null_count@4 != row_count@5 AND (SearchPhrase_min@2 !=  OR  != SearchPhrase_max@3), required_guarantees=[SearchPhrase not in ()] |
    |               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    |               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    +---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    Query 21 avg time: 697.75 ms
    ```rust
    
  7. zhuqi-lucas commented on Jun 28, 2025

    @zhuqi-lucas
    Contributor

    Submitted a PR for review, it will make the datafusion-cli consistent with clickbench benchmark, so it should not have extra cast time for datafusion-cli after the PR merged:

    #16599

    Here is the testing result:

    ./datafusion-cli -c "EXPLAIN SELECT \"SearchPhrase\", MIN(\"URL\"), COUNT(*) AS c FROM 'data/hits_partitioned' WHERE \"URL\" LIKE '%google%' AND \"SearchPhrase\" <> '' GROUP BY \"SearchPhrase\" ORDER BY c DESC LIMIT 10;"
    DataFusion CLI v48.0.0
    +---------------+-------------------------------+
    | plan_type     | plan                          |
    +---------------+-------------------------------+
    | physical_plan | ┌───────────────────────────┐ |
    |               | │  SortPreservingMergeExec  │ |
    |               | │    --------------------   │ |
    |               | │      c DESClimit: 10      │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │       SortExec(TopK)      │ |
    |               | │    --------------------   │ |
    |               | │          c@2 DESC         │ |
    |               | │                           │ |
    |               | │         limit: 10         │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │       ProjectionExec      │ |
    |               | │    --------------------   │ |
    |               | │       SearchPhrase:       │ |
    |               | │        SearchPhrase       │ |
    |               | │                           │ |
    |               | │     c: count(Int64(1))    │ |
    |               | │                           │ |
    |               | │ min(data/hits_partitioned │ |
    |               | │           .URL):          │ |
    |               | │ min(data/hits_partitioned │ |
    |               | │           .URL)           │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │       AggregateExec       │ |
    |               | │    --------------------   │ |
    |               | │           aggr:           │ |
    |               | │ min(data/hits_partitioned │ |
    |               | │      .URL), count(1)      │ |
    |               | │                           │ |
    |               | │         group_by:         │ |
    |               | │        SearchPhrase       │ |
    |               | │                           │ |
    |               | │           mode:           │ |
    |               | │      FinalPartitioned     │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │    CoalesceBatchesExec    │ |
    |               | │    --------------------   │ |
    |               | │     target_batch_size:    │ |
    |               | │            8192           │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │      RepartitionExec      │ |
    |               | │    --------------------   │ |
    |               | │ partition_count(in->out): │ |
    |               | │          14 -> 14         │ |
    |               | │                           │ |
    |               | │    partitioning_scheme:   │ |
    |               | │ Hash([SearchPhrase@0], 14)│ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │       AggregateExec       │ |
    |               | │    --------------------   │ |
    |               | │           aggr:           │ |
    |               | │ min(data/hits_partitioned │ |
    |               | │      .URL), count(1)      │ |
    |               | │                           │ |
    |               | │         group_by:         │ |
    |               | │        SearchPhrase       │ |
    |               | │                           │ |
    |               | │       mode: Partial       │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │    CoalesceBatchesExec    │ |
    |               | │    --------------------   │ |
    |               | │     target_batch_size:    │ |
    |               | │            8192           │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │         FilterExec        │ |
    |               | │    --------------------   │ |
    |               | │         predicate:        │ |
    |               | │   URL LIKE %google% AND   │ |
    |               | │       SearchPhrase !=     │ |
    |               | └─────────────┬─────────────┘ |
    |               | ┌─────────────┴─────────────┐ |
    |               | │       DataSourceExec      │ |
    |               | │    --------------------   │ |
    |               | │         files: 113        │ |
    |               | │      format: parquet      │ |
    |               | │                           │ |
    |               | │         predicate:        │ |
    |               | │   URL LIKE %google% AND   │ |
    |               | │       SearchPhrase !=     │ |
    |               | └───────────────────────────┘ |
    |               |                               |
    +---------------+-------------------------------+
    1 row(s) fetched.
    Elapsed 0.058 seconds.
  8. zhuqi-lucas commented on Jun 28, 2025

    @zhuqi-lucas
    Contributor

    Wasn't clickbench writing strings as binary columns (an option was added for this)?

    Though there is something to say that filtering should not have to validata the utf8-ness.

    Meanwhile as @Dandandan pointed out, this is a good point, if we can remove the filtering for utf8view validation? Need investigation.

  9. zhuqi-lucas commented on Jun 28, 2025

    @zhuqi-lucas
    Contributor

    A minor topic for debugging the benchmark for this ticket:

    #16603

    I submit a PR try to make benchmark debug mode to use tree format for explain.

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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions