Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
50674: opt: modify PushLimitIntoIndexJoin to not directly create limited scans r=DrewKimball a=DrewKimball

Previously, the PushLimitIntoIndexJoin rule only matched when the index
join input was a constrained scan that could take the limit value as a
hard limit.

This patch relaxes the match condition to allow any index join with any
input. In addition, rather than directly creating a limited scan, the
rule now simply pushes the limit on top of the input of the index join.
This is useful because index joins can be expensive, and discarding
input rows can decrease the cost. It also allows the
SplitScansIntoUnionScans rule to fire where previously the index join
would have prevented this.

Co-authored-by: Drew Kimball <[email protected]>
  • Loading branch information
craig[bot] and DrewKimball committed Jun 26, 2020
2 parents 6fec17d + 82f3696 commit c5133c6
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 110 deletions.
24 changes: 12 additions & 12 deletions pkg/sql/opt/exec/execbuilder/testdata/select_index_flags
Original file line number Diff line number Diff line change
Expand Up @@ -142,18 +142,18 @@ index-join · ·
query TTT
EXPLAIN SELECT * FROM abcd@{FORCE_INDEX=b,ASC} ORDER BY b DESC LIMIT 5
----
· distribution local
· vectorized true
limit · ·
count 5
└── sort · ·
order -b
└── index-join · ·
table abcd@primary
key columns a
└── scan · ·
· table abcd@b
· spans FULL SCAN
· distribution local
· vectorized true
index-join · ·
table abcd@primary
key columns a
└── limit · ·
count 5
└── sort · ·
order -b
└── scan · ·
· table abcd@b
· spans FULL SCAN

# Force index cd
query TTT
Expand Down
17 changes: 6 additions & 11 deletions pkg/sql/opt/xform/rules/limit.opt
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,18 @@
=>
(Scan (LimitScanPrivate $scanPrivate $limit $ordering))

# PushLimitIntoIndexJoin pushes a limit through an index join and constructs a
# new Scan operator that incorporates it. Since index lookup can be expensive,
# it's always better to discard rows beforehand.
# PushLimitIntoIndexJoin pushes a limit through an index join. Since index
# lookup can be expensive, it's always better to discard rows beforehand.
#
# TODO(radu): we can similarly push Offset too.
[PushLimitIntoIndexJoin, Explore]
(Limit
(IndexJoin (Scan $scanPrivate:*) $indexJoinPrivate:*)
(Const $limit:* & (IsPositiveInt $limit))
$ordering:* &
(CanLimitConstrainedScan $scanPrivate $ordering)
(IndexJoin $input:* $indexJoinPrivate:*)
$limitExpr:(Const $limit:* & (IsPositiveInt $limit))
$ordering:*
)
=>
(IndexJoin
(Scan (LimitScanPrivate $scanPrivate $limit $ordering))
$indexJoinPrivate
)
(IndexJoin (Limit $input $limitExpr $ordering) $indexJoinPrivate)

# SplitScanIntoUnionScans splits a scan under a limit into a union of limited
# scans. Example:
Expand Down
98 changes: 49 additions & 49 deletions pkg/sql/opt/xform/testdata/external/customer
Original file line number Diff line number Diff line change
Expand Up @@ -86,35 +86,35 @@ project
├── key: (1)
├── fd: (1)-->(2,3,5-8,10)
├── ordering: +1
└── limit
└── index-join article
├── columns: id:1!null feed:2!null folder:3!null title:5 summary:6 content:7 link:8 read:9!null date:10
├── internal-ordering: +1 opt(9)
├── cardinality: [0 - 50]
├── key: (1)
├── fd: ()-->(9), (1)-->(2,3,5-8,10)
├── ordering: +1 opt(9) [actual: +1]
├── index-join article
│ ├── columns: id:1!null feed:2!null folder:3!null title:5 summary:6 content:7 link:8 read:9!null date:10
│ ├── key: (1)
│ ├── fd: ()-->(9), (1)-->(2,3,5-8,10)
│ ├── ordering: +1 opt(9) [actual: +1]
│ ├── limit hint: 50.00
│ └── select
│ ├── columns: id:1!null feed:2!null folder:3!null read:9!null
│ ├── key: (1)
│ ├── fd: ()-->(9), (1)-->(2,3)
│ ├── ordering: +1 opt(9) [actual: +1]
│ ├── limit hint: 50.00
│ ├── scan article@article_idx_read_key
│ │ ├── columns: id:1!null feed:2!null folder:3!null read:9
│ │ ├── constraint: /1: [/1 - ]
│ │ ├── key: (1)
│ │ ├── fd: (1)-->(2,3,9)
│ │ ├── ordering: +1 opt(9) [actual: +1]
│ │ └── limit hint: 101.01
│ └── filters
│ └── NOT read:9 [outer=(9), constraints=(/9: [/false - /false]; tight), fd=()-->(9)]
└── 50
└── limit
├── columns: id:1!null feed:2!null folder:3!null read:9!null
├── internal-ordering: +1 opt(9)
├── cardinality: [0 - 50]
├── key: (1)
├── fd: ()-->(9), (1)-->(2,3)
├── ordering: +1 opt(9) [actual: +1]
├── select
│ ├── columns: id:1!null feed:2!null folder:3!null read:9!null
│ ├── key: (1)
│ ├── fd: ()-->(9), (1)-->(2,3)
│ ├── ordering: +1 opt(9) [actual: +1]
│ ├── limit hint: 50.00
│ ├── scan article@article_idx_read_key
│ │ ├── columns: id:1!null feed:2!null folder:3!null read:9
│ │ ├── constraint: /1: [/1 - ]
│ │ ├── key: (1)
│ │ ├── fd: (1)-->(2,3,9)
│ │ ├── ordering: +1 opt(9) [actual: +1]
│ │ └── limit hint: 101.01
│ └── filters
│ └── NOT read:9 [outer=(9), constraints=(/9: [/false - /false]; tight), fd=()-->(9)]
└── 50

# Check that forcing the index works as well.
opt
Expand All @@ -128,36 +128,36 @@ project
├── key: (1)
├── fd: (1)-->(2,3,5-8,10)
├── ordering: +1
└── limit
└── index-join article
├── columns: id:1!null feed:2!null folder:3!null title:5 summary:6 content:7 link:8 read:9!null date:10
├── internal-ordering: +1 opt(9)
├── cardinality: [0 - 50]
├── key: (1)
├── fd: ()-->(9), (1)-->(2,3,5-8,10)
├── ordering: +1 opt(9) [actual: +1]
├── index-join article
│ ├── columns: id:1!null feed:2!null folder:3!null title:5 summary:6 content:7 link:8 read:9!null date:10
│ ├── key: (1)
│ ├── fd: ()-->(9), (1)-->(2,3,5-8,10)
│ ├── ordering: +1 opt(9) [actual: +1]
│ ├── limit hint: 50.00
│ └── select
│ ├── columns: id:1!null feed:2!null folder:3!null read:9!null
│ ├── key: (1)
│ ├── fd: ()-->(9), (1)-->(2,3)
│ ├── ordering: +1 opt(9) [actual: +1]
│ ├── limit hint: 50.00
│ ├── scan article@article_idx_read_key
│ │ ├── columns: id:1!null feed:2!null folder:3!null read:9
│ │ ├── constraint: /1: [/1 - ]
│ │ ├── flags: force-index=article_idx_read_key
│ │ ├── key: (1)
│ │ ├── fd: (1)-->(2,3,9)
│ │ ├── ordering: +1 opt(9) [actual: +1]
│ │ └── limit hint: 101.01
│ └── filters
│ └── NOT read:9 [outer=(9), constraints=(/9: [/false - /false]; tight), fd=()-->(9)]
└── 50
└── limit
├── columns: id:1!null feed:2!null folder:3!null read:9!null
├── internal-ordering: +1 opt(9)
├── cardinality: [0 - 50]
├── key: (1)
├── fd: ()-->(9), (1)-->(2,3)
├── ordering: +1 opt(9) [actual: +1]
├── select
│ ├── columns: id:1!null feed:2!null folder:3!null read:9!null
│ ├── key: (1)
│ ├── fd: ()-->(9), (1)-->(2,3)
│ ├── ordering: +1 opt(9) [actual: +1]
│ ├── limit hint: 50.00
│ ├── scan article@article_idx_read_key
│ │ ├── columns: id:1!null feed:2!null folder:3!null read:9
│ │ ├── constraint: /1: [/1 - ]
│ │ ├── flags: force-index=article_idx_read_key
│ │ ├── key: (1)
│ │ ├── fd: (1)-->(2,3,9)
│ │ ├── ordering: +1 opt(9) [actual: +1]
│ │ └── limit hint: 101.01
│ └── filters
│ └── NOT read:9 [outer=(9), constraints=(/9: [/false - /false]; tight), fd=()-->(9)]
└── 50

# Use only columns covered by the index.
opt
Expand Down
81 changes: 43 additions & 38 deletions pkg/sql/opt/xform/testdata/rules/limit
Original file line number Diff line number Diff line change
Expand Up @@ -229,36 +229,39 @@ index-join kuv
├── fd: (1)-->(2)
└── ordering: +2

# Verify we don't push the limit if the ordering depends on a column not in the
# input index.
opt
SELECT * FROM kuv WHERE u > 1 AND u < 10 ORDER BY u, v LIMIT 5
----
limit
sort
├── columns: k:1!null u:2!null v:3
├── internal-ordering: +2,+3
├── cardinality: [0 - 5]
├── key: (1)
├── fd: (1)-->(2,3)
├── ordering: +2,+3
├── sort (segmented)
│ ├── columns: k:1!null u:2!null v:3
│ ├── key: (1)
│ ├── fd: (1)-->(2,3)
│ ├── ordering: +2,+3
│ ├── limit hint: 5.00
│ └── index-join kuv
│ ├── columns: k:1!null u:2!null v:3
│ ├── key: (1)
│ ├── fd: (1)-->(2,3)
│ ├── ordering: +2
│ └── scan kuv@secondary
│ ├── columns: k:1!null u:2!null
│ ├── constraint: /2/1: [/2 - /9]
│ ├── key: (1)
│ ├── fd: (1)-->(2)
│ └── ordering: +2
└── 5
└── index-join kuv
├── columns: k:1!null u:2!null v:3
├── cardinality: [0 - 5]
├── key: (1)
├── fd: (1)-->(2,3)
└── limit
├── columns: k:1!null u:2!null
├── internal-ordering: +2,+3
├── cardinality: [0 - 5]
├── key: (1)
├── fd: (1)-->(2)
├── sort (segmented)
│ ├── columns: k:1!null u:2!null
│ ├── key: (1)
│ ├── fd: (1)-->(2)
│ ├── ordering: +2,+3
│ ├── limit hint: 5.00
│ └── scan kuv@secondary
│ ├── columns: k:1!null u:2!null
│ ├── constraint: /2/1: [/2 - /9]
│ ├── key: (1)
│ ├── fd: (1)-->(2)
│ └── ordering: +2
└── 5

exec-ddl
CREATE TABLE abcd (
Expand Down Expand Up @@ -719,6 +722,24 @@ limit
│ └── limit: 10
└── 10

# Case with index join.
opt expect=SplitScanIntoUnionScans format=hide-all
SELECT *
FROM index_tab WHERE region = 'US_WEST' OR region = 'US_EAST'
ORDER BY data1 LIMIT 10
----
index-join index_tab
└── limit
├── sort
│ └── union
│ ├── scan index_tab@c
│ │ ├── constraint: /10/13/14/8: [/'US_EAST' - /'US_EAST']
│ │ └── limit: 10
│ └── scan index_tab@c
│ ├── constraint: /17/20/21/15: [/'US_WEST' - /'US_WEST']
│ └── limit: 10
└── 10

# No-op case because the multi-key span isn't countable.
opt expect-not=SplitScanIntoUnionScans format=hide-all
SELECT max(data1) FROM index_tab WHERE region > 'US_EAST' AND region < 'US_WEST'
Expand Down Expand Up @@ -793,19 +814,3 @@ scalar-group-by
└── aggregations
└── max
└── data1

# No-op case because the limit input is an index-join. TODO(drewk): modify
# the rule to push limits through index joins.
opt expect-not=SplitScanIntoUnionScans format=hide-all
SELECT *
FROM index_tab WHERE region = 'US_WEST' OR region = 'US_EAST'
ORDER BY data1 LIMIT 10
----
limit
├── sort
│ └── index-join index_tab
│ └── scan index_tab@c
│ └── constraint: /3/6/7/1
│ ├── [/'US_EAST' - /'US_EAST']
│ └── [/'US_WEST' - /'US_WEST']
└── 10

0 comments on commit c5133c6

Please sign in to comment.