Skip to content

Commit 36c419a

Browse files
committed
opt: avoid delete cascade fast path for regional by row tables
We have a fast path for cascading deletes where the filters from the parent table are applied directly to the child table instead of joining from a buffer to the child table. This can result in an efficient `DeleteRange` operation in some cases, but in others, can result in a "less constrained" scan on the child table. In particular, it can result in visiting more regions than necessary for a regional-by-row child table. This commit prevents the fast path from applying for regional-by-row child tables unless the region column is restricted to a single value. It is possible to revert to the old behavior by disabling a new session var: `optimizer_disable_cross_region_cascade_fast_path_for_rbr_tables`. Informs: #146705 Release note (performance improvement): The optimizer will no longer apply a fast-path to deletes cascading to regional-by-row tables. This prevents the cascading delete from accessing more regions than necessary.
1 parent 9b30515 commit 36c419a

File tree

11 files changed

+1156
-849
lines changed

11 files changed

+1156
-849
lines changed

pkg/ccl/logictestccl/testdata/logic_test/regional_by_row_query_behavior

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3179,11 +3179,17 @@ vectorized: true
31793179
│ └── • delete
31803180
│ │ from: user_settings_cascades
31813181
│ │
3182-
│ └── • scan
3183-
│ missing stats
3184-
│ table: user_settings_cascades@user_settings_cascades_user_id_idx
3185-
│ spans: [/'ap-southeast-2'/'5ebfedee-0dcf-41e6-a315-5fa0b51b9882' - /'ap-southeast-2'/'5ebfedee-0dcf-41e6-a315-5fa0b51b9882'] [/'ca-central-1'/'5ebfedee-0dcf-41e6-a315-5fa0b51b9882' - /'ca-central-1'/'5ebfedee-0dcf-41e6-a315-5fa0b51b9882'] [/'us-east-1'/'5ebfedee-0dcf-41e6-a315-5fa0b51b9882' - /'us-east-1'/'5ebfedee-0dcf-41e6-a315-5fa0b51b9882']
3186-
│ locking strength: for update
3182+
│ └── • lookup join
3183+
│ │ table: user_settings_cascades@user_settings_cascades_user_id_idx
3184+
│ │ equality: (crdb_region, id) = (crdb_region, user_id)
3185+
│ │
3186+
│ └── • distinct
3187+
│ │ estimated row count: 100
3188+
│ │ distinct on: id, crdb_region
3189+
│ │
3190+
│ └── • scan buffer
3191+
│ estimated row count: 100
3192+
│ label: buffer 1000000
31873193
31883194
└── • constraint-check
31893195

pkg/sql/exec_util.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4194,6 +4194,10 @@ func (m *sessionDataMutator) SetUseImprovedRoutineDependencyTracking(val bool) {
41944194
m.data.UseImprovedRoutineDependencyTracking = val
41954195
}
41964196

4197+
func (m *sessionDataMutator) SetOptimizerDisableCrossRegionCascadeFastPathForRBRTables(val bool) {
4198+
m.data.OptimizerDisableCrossRegionCascadeFastPathForRBRTables = val
4199+
}
4200+
41974201
// Utility functions related to scrubbing sensitive information on SQL Stats.
41984202

41994203
// quantizeCounts ensures that the Count field in the

pkg/sql/logictest/testdata/logic_test/information_schema

Lines changed: 207 additions & 206 deletions
Large diffs are not rendered by default.

pkg/sql/logictest/testdata/logic_test/pg_catalog

Lines changed: 427 additions & 424 deletions
Large diffs are not rendered by default.

pkg/sql/logictest/testdata/logic_test/show_source

Lines changed: 213 additions & 212 deletions
Large diffs are not rendered by default.

pkg/sql/opt/memo/memo.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ type Memo struct {
207207
internal bool
208208
usePre_25_2VariadicBuiltins bool
209209
useExistsFilterHoistRule bool
210+
disableSlowCascadeFastPathForRBRTables bool
210211

211212
// txnIsoLevel is the isolation level under which the plan was created. This
212213
// affects the planning of some locking operations, so it must be included in
@@ -311,6 +312,7 @@ func (m *Memo) Init(ctx context.Context, evalCtx *eval.Context) {
311312
internal: evalCtx.SessionData().Internal,
312313
usePre_25_2VariadicBuiltins: evalCtx.SessionData().UsePre_25_2VariadicBuiltins,
313314
useExistsFilterHoistRule: evalCtx.SessionData().OptimizerUseExistsFilterHoistRule,
315+
disableSlowCascadeFastPathForRBRTables: evalCtx.SessionData().OptimizerDisableCrossRegionCascadeFastPathForRBRTables,
314316
txnIsoLevel: evalCtx.TxnIsoLevel,
315317
}
316318
m.metadata.Init()
@@ -488,6 +490,7 @@ func (m *Memo) IsStale(
488490
m.internal != evalCtx.SessionData().Internal ||
489491
m.usePre_25_2VariadicBuiltins != evalCtx.SessionData().UsePre_25_2VariadicBuiltins ||
490492
m.useExistsFilterHoistRule != evalCtx.SessionData().OptimizerUseExistsFilterHoistRule ||
493+
m.disableSlowCascadeFastPathForRBRTables != evalCtx.SessionData().OptimizerDisableCrossRegionCascadeFastPathForRBRTables ||
491494
m.txnIsoLevel != evalCtx.TxnIsoLevel {
492495
return true, nil
493496
}

pkg/sql/opt/memo/memo_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,11 @@ func TestMemoIsStale(t *testing.T) {
579579
evalCtx.SessionData().OptimizerUseExistsFilterHoistRule = false
580580
notStale()
581581

582+
evalCtx.SessionData().OptimizerDisableCrossRegionCascadeFastPathForRBRTables = true
583+
stale()
584+
evalCtx.SessionData().OptimizerDisableCrossRegionCascadeFastPathForRBRTables = false
585+
notStale()
586+
582587
// User no longer has access to view.
583588
catalog.View(tree.NewTableNameWithSchema("t", catconstants.PublicSchemaName, "abcview")).Revoked = true
584589
_, err = o.Memo().IsStale(ctx, &evalCtx, catalog)

pkg/sql/opt/optbuilder/fk_cascade.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,39 @@ func (mb *mutationBuilder) tryNewOnDeleteFastCascadeBuilder(
225225
return nil, false
226226
}
227227

228+
// For a REGIONAL BY ROW child table, if the region column is part of the FK,
229+
// check that it is constrained to a single value. If this is not the case,
230+
// the fast path can be suboptimal, since joining against the parent buffer
231+
// will constrain the region column to a single constant value.
232+
if childTab.IsRegionalByRow() &&
233+
mb.b.evalCtx.SessionData().OptimizerDisableCrossRegionCascadeFastPathForRBRTables {
234+
// The regional column is the first in every index, including the primary.
235+
regionalColOrd := childTab.Index(cat.PrimaryIndex).Column(0).Ordinal()
236+
var regionalColID opt.ColumnID
237+
for i, colID := range fkCols {
238+
if fk.OriginColumnOrdinal(childTab, i) == regionalColOrd {
239+
regionalColID = colID
240+
break
241+
}
242+
}
243+
if regionalColID != 0 {
244+
regionalColIsConstrained := false
245+
for i := range filters {
246+
if eq, isEq := filters[i].Condition.(*memo.EqExpr); isEq {
247+
if v, leftIsVar := eq.Left.(*memo.VariableExpr); leftIsVar && v.Col == regionalColID {
248+
if opt.IsConstValueOp(eq.Right) {
249+
regionalColIsConstrained = true
250+
break
251+
}
252+
}
253+
}
254+
}
255+
if !regionalColIsConstrained {
256+
return nil, false
257+
}
258+
}
259+
}
260+
228261
var visited intsets.Fast
229262
parentTabID := parentTab.ID()
230263
childTabID := childTab.ID()

pkg/sql/opt/xform/testdata/rules/cascade

Lines changed: 232 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,11 +1013,72 @@ root
10131013
│ └── fd: ()-->(36,37)
10141014
└── filters (true)
10151015

1016-
# TODO(146705) the delete cascade fast-path prevents the cascade from being
1017-
# limited to the parent row's region.
1016+
# Avoid using the delete cascade fast-path because it would be unable to
1017+
# constrain the region column to a single value.
10181018
opt-post-queries locality=(region=east)
10191019
DELETE FROM parent WHERE k = 1
10201020
----
1021+
root
1022+
├── delete parent
1023+
│ ├── columns: <none>
1024+
│ ├── fetch columns: k:6 crdb_region:8
1025+
│ ├── input binding: &1
1026+
│ ├── cascades
1027+
│ │ └── child_crdb_region_b_fkey
1028+
│ ├── cardinality: [0 - 0]
1029+
│ ├── volatile, mutations
1030+
│ └── locality-optimized-search
1031+
│ ├── columns: k:6!null crdb_region:8!null
1032+
│ ├── left columns: k:11 crdb_region:13
1033+
│ ├── right columns: k:16 crdb_region:18
1034+
│ ├── cardinality: [0 - 1]
1035+
│ ├── key: ()
1036+
│ ├── fd: ()-->(6,8)
1037+
│ ├── scan parent
1038+
│ │ ├── columns: k:11!null crdb_region:13!null
1039+
│ │ ├── constraint: /13/11: [/'east'/1 - /'east'/1]
1040+
│ │ ├── flags: avoid-full-scan
1041+
│ │ ├── cardinality: [0 - 1]
1042+
│ │ ├── key: ()
1043+
│ │ └── fd: ()-->(11,13)
1044+
│ └── scan parent
1045+
│ ├── columns: k:16!null crdb_region:18!null
1046+
│ ├── constraint: /18/16
1047+
│ │ ├── [/'central'/1 - /'central'/1]
1048+
│ │ └── [/'west'/1 - /'west'/1]
1049+
│ ├── flags: avoid-full-scan
1050+
│ ├── cardinality: [0 - 1]
1051+
│ ├── key: ()
1052+
│ └── fd: ()-->(16,18)
1053+
└── cascade
1054+
└── delete child
1055+
├── columns: <none>
1056+
├── fetch columns: a:16 b:17 child.crdb_region:18
1057+
├── cardinality: [0 - 0]
1058+
├── volatile, mutations
1059+
└── project
1060+
├── columns: a:16!null b:17!null child.crdb_region:18!null
1061+
├── key: (16)
1062+
├── fd: (16)-->(17,18)
1063+
└── inner-join (lookup child@child_crdb_region_b_idx)
1064+
├── columns: a:16!null b:17!null child.crdb_region:18!null crdb_region:21!null k:22!null
1065+
├── key columns: [21 22] = [18 17]
1066+
├── key: (16)
1067+
├── fd: ()-->(17,18,21,22), (18)==(21), (21)==(18), (17)==(22), (22)==(17)
1068+
├── with-scan &1
1069+
│ ├── columns: crdb_region:21!null k:22!null
1070+
│ ├── mapping:
1071+
│ │ ├── parent.crdb_region:8 => crdb_region:21
1072+
│ │ └── parent.k:6 => k:22
1073+
│ ├── cardinality: [0 - 1]
1074+
│ ├── key: ()
1075+
│ └── fd: ()-->(21,22)
1076+
└── filters (true)
1077+
1078+
# Disabling the setting causes the delete cascade fast-path to be used.
1079+
opt-post-queries locality=(region=east) set=optimizer_disable_cross_region_cascade_fast_path_for_rbr_tables=false
1080+
DELETE FROM parent WHERE k = 1
1081+
----
10211082
root
10221083
├── delete parent
10231084
│ ├── columns: <none>
@@ -1065,6 +1126,39 @@ root
10651126
├── key: (16)
10661127
└── fd: ()-->(17), (16)-->(18)
10671128

1129+
# Use the delete cascade fast-path here because the region column is
1130+
# constrained to a single value.
1131+
opt-post-queries locality=(region=east)
1132+
DELETE FROM parent WHERE k = 1 AND crdb_region = 'east'
1133+
----
1134+
root
1135+
├── delete parent
1136+
│ ├── columns: <none>
1137+
│ ├── fetch columns: k:6 crdb_region:8
1138+
│ ├── cascades
1139+
│ │ └── child_crdb_region_b_fkey
1140+
│ ├── cardinality: [0 - 0]
1141+
│ ├── volatile, mutations
1142+
│ └── scan parent
1143+
│ ├── columns: k:6!null crdb_region:8!null
1144+
│ ├── constraint: /8/6: [/'east'/1 - /'east'/1]
1145+
│ ├── flags: avoid-full-scan
1146+
│ ├── cardinality: [0 - 1]
1147+
│ ├── key: ()
1148+
│ └── fd: ()-->(6,8)
1149+
└── cascade
1150+
└── delete child
1151+
├── columns: <none>
1152+
├── fetch columns: a:16 b:17 child.crdb_region:18
1153+
├── cardinality: [0 - 0]
1154+
├── volatile, mutations
1155+
└── scan child@child_crdb_region_b_idx
1156+
├── columns: a:16!null b:17!null child.crdb_region:18!null
1157+
├── constraint: /18/17/16: [/'east'/1 - /'east'/1]
1158+
├── flags: avoid-full-scan
1159+
├── key: (16)
1160+
└── fd: ()-->(17,18)
1161+
10681162
opt-post-queries locality=(region=east)
10691163
DELETE FROM parent WHERE v = 1
10701164
----
@@ -1121,3 +1215,139 @@ DROP TABLE child
11211215
exec-ddl
11221216
DROP TABLE parent
11231217
----
1218+
1219+
# --------------------------------------------------
1220+
# Test a computed region column.
1221+
# --------------------------------------------------
1222+
1223+
exec-ddl
1224+
CREATE TABLE parent (k INT PRIMARY KEY, v INT) LOCALITY REGIONAL BY ROW
1225+
----
1226+
1227+
exec-ddl
1228+
CREATE TABLE child (
1229+
a INT PRIMARY KEY,
1230+
b INT NOT NULL,
1231+
crdb_region STRING NOT NULL AS (CASE WHEN b = 1 THEN 'east' ELSE 'west' END) STORED,
1232+
FOREIGN KEY (crdb_region, b) REFERENCES parent(crdb_region, k) ON UPDATE CASCADE ON DELETE CASCADE,
1233+
INDEX (b)
1234+
) LOCALITY REGIONAL BY ROW
1235+
----
1236+
1237+
# TODO(#146705): disallowing the fast path for RBR tables regresses in this
1238+
# case because the computed column expression can be used to infer a filter
1239+
# that constrains the region column to a constant value.
1240+
opt-post-queries locality=(region=east)
1241+
DELETE FROM parent WHERE k = 1
1242+
----
1243+
root
1244+
├── delete parent
1245+
│ ├── columns: <none>
1246+
│ ├── fetch columns: k:6 crdb_region:8
1247+
│ ├── input binding: &1
1248+
│ ├── cascades
1249+
│ │ └── child_crdb_region_b_fkey
1250+
│ ├── cardinality: [0 - 0]
1251+
│ ├── volatile, mutations
1252+
│ └── locality-optimized-search
1253+
│ ├── columns: k:6!null crdb_region:8!null
1254+
│ ├── left columns: k:11 crdb_region:13
1255+
│ ├── right columns: k:16 crdb_region:18
1256+
│ ├── cardinality: [0 - 1]
1257+
│ ├── key: ()
1258+
│ ├── fd: ()-->(6,8)
1259+
│ ├── scan parent
1260+
│ │ ├── columns: k:11!null crdb_region:13!null
1261+
│ │ ├── constraint: /13/11: [/'east'/1 - /'east'/1]
1262+
│ │ ├── flags: avoid-full-scan
1263+
│ │ ├── cardinality: [0 - 1]
1264+
│ │ ├── key: ()
1265+
│ │ └── fd: ()-->(11,13)
1266+
│ └── scan parent
1267+
│ ├── columns: k:16!null crdb_region:18!null
1268+
│ ├── constraint: /18/16
1269+
│ │ ├── [/'central'/1 - /'central'/1]
1270+
│ │ └── [/'west'/1 - /'west'/1]
1271+
│ ├── flags: avoid-full-scan
1272+
│ ├── cardinality: [0 - 1]
1273+
│ ├── key: ()
1274+
│ └── fd: ()-->(16,18)
1275+
└── cascade
1276+
└── delete child
1277+
├── columns: <none>
1278+
├── fetch columns: a:16 b:17 child.crdb_region:18
1279+
├── cardinality: [0 - 0]
1280+
├── volatile, mutations
1281+
└── project
1282+
├── columns: a:16!null b:17!null child.crdb_region:18!null
1283+
├── key: (16)
1284+
├── fd: (16)-->(17,18), (17)-->(18)
1285+
└── inner-join (lookup child@child_crdb_region_b_idx)
1286+
├── columns: a:16!null b:17!null child.crdb_region:18!null crdb_region:21!null k:22!null
1287+
├── key columns: [21 22] = [18 17]
1288+
├── key: (16)
1289+
├── fd: ()-->(17,18,21,22), (18)==(21), (21)==(18), (17)==(22), (22)==(17)
1290+
├── with-scan &1
1291+
│ ├── columns: crdb_region:21!null k:22!null
1292+
│ ├── mapping:
1293+
│ │ ├── parent.crdb_region:8 => crdb_region:21
1294+
│ │ └── parent.k:6 => k:22
1295+
│ ├── cardinality: [0 - 1]
1296+
│ ├── key: ()
1297+
│ └── fd: ()-->(21,22)
1298+
└── filters (true)
1299+
1300+
opt-post-queries locality=(region=east) set=optimizer_disable_cross_region_cascade_fast_path_for_rbr_tables=false
1301+
DELETE FROM parent WHERE k = 1
1302+
----
1303+
root
1304+
├── delete parent
1305+
│ ├── columns: <none>
1306+
│ ├── fetch columns: k:6 crdb_region:8
1307+
│ ├── cascades
1308+
│ │ └── child_crdb_region_b_fkey
1309+
│ ├── cardinality: [0 - 0]
1310+
│ ├── volatile, mutations
1311+
│ └── locality-optimized-search
1312+
│ ├── columns: k:6!null crdb_region:8!null
1313+
│ ├── left columns: k:11 crdb_region:13
1314+
│ ├── right columns: k:16 crdb_region:18
1315+
│ ├── cardinality: [0 - 1]
1316+
│ ├── key: ()
1317+
│ ├── fd: ()-->(6,8)
1318+
│ ├── scan parent
1319+
│ │ ├── columns: k:11!null crdb_region:13!null
1320+
│ │ ├── constraint: /13/11: [/'east'/1 - /'east'/1]
1321+
│ │ ├── flags: avoid-full-scan
1322+
│ │ ├── cardinality: [0 - 1]
1323+
│ │ ├── key: ()
1324+
│ │ └── fd: ()-->(11,13)
1325+
│ └── scan parent
1326+
│ ├── columns: k:16!null crdb_region:18!null
1327+
│ ├── constraint: /18/16
1328+
│ │ ├── [/'central'/1 - /'central'/1]
1329+
│ │ └── [/'west'/1 - /'west'/1]
1330+
│ ├── flags: avoid-full-scan
1331+
│ ├── cardinality: [0 - 1]
1332+
│ ├── key: ()
1333+
│ └── fd: ()-->(16,18)
1334+
└── cascade
1335+
└── delete child
1336+
├── columns: <none>
1337+
├── fetch columns: a:16 b:17 child.crdb_region:18
1338+
├── cardinality: [0 - 0]
1339+
├── volatile, mutations
1340+
└── scan child@child_crdb_region_b_idx
1341+
├── columns: a:16!null b:17!null child.crdb_region:18!null
1342+
├── constraint: /18/17/16: [/'east'/1 - /'east'/1]
1343+
├── flags: avoid-full-scan
1344+
├── key: (16)
1345+
└── fd: ()-->(17,18)
1346+
1347+
exec-ddl
1348+
DROP TABLE child
1349+
----
1350+
1351+
exec-ddl
1352+
DROP TABLE parent
1353+
----

pkg/sql/sessiondatapb/local_only_session_data.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,10 @@ message LocalOnlySessionData {
678678
// routines to only consider user-specified target columns when determining the
679679
// dependencies of an INSERT statement in a routine.
680680
bool use_improved_routine_dependency_tracking = 172;
681+
// OptimizerDisableCrossRegionCascadeFastPathForRBRTables, when true, causes the optimizer
682+
// to unconditionally attempt to apply the fast path for cascading deletes.
683+
// See also #146705.
684+
bool optimizer_disable_cross_region_cascade_fast_path_for_rbr_tables = 173 [(gogoproto.customname) = "OptimizerDisableCrossRegionCascadeFastPathForRBRTables"];
681685

682686
///////////////////////////////////////////////////////////////////////////
683687
// WARNING: consider whether a session parameter you're adding needs to //

0 commit comments

Comments
 (0)