Skip to content

Commit 1d4f6e7

Browse files
authored
chore: update cost estimates for intersectionarrow lr (#2848)
1 parent 7a18b39 commit 1d4f6e7

File tree

2 files changed

+62
-6
lines changed

2 files changed

+62
-6
lines changed

pkg/query/statistics.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,22 +104,32 @@ func (s StaticStatistics) Cost(iterator Iterator) (Estimate, error) {
104104

105105
// Calculate CheckCost based on execution direction
106106
var checkCost int
107+
var iterResourcesCost int
108+
var iterSubjectsCost int
107109
switch it.direction {
108110
case leftToRight:
109111
// IterSubjects on left, then Check on right for each result
110112
checkCost = ls.IterSubjectsCost + (ls.Cardinality * rs.CheckCost)
113+
// IterResources on left, then IterResources on right for each result
114+
iterResourcesCost = ls.IterResourcesCost + (ls.Cardinality * rs.IterResourcesCost)
115+
// IterSubjects on left, then IterSubjects on right for each result
116+
iterSubjectsCost = ls.IterSubjectsCost + (ls.Cardinality * rs.IterSubjectsCost)
111117
case rightToLeft:
112118
// IterResources on right, then Check on left for each result
113119
checkCost = rs.IterResourcesCost + (rs.Cardinality * ls.CheckCost)
120+
// IterResources on right, then IterResources on left for each result
121+
iterResourcesCost = rs.IterResourcesCost + (rs.Cardinality * ls.IterResourcesCost)
122+
// IterSubjects on right, then IterSubjects on left for each result
123+
iterSubjectsCost = rs.IterSubjectsCost + (rs.Cardinality * ls.IterSubjectsCost)
114124
}
115125

116126
return Estimate{
117127
// Worst case, an arrow is the size of the cartesian product (full outer join) as we join the two subiterators.
118128
Cardinality: ls.Cardinality * rs.Cardinality,
119129
CheckCost: checkCost,
120130
CheckSelectivity: ls.CheckSelectivity * rs.CheckSelectivity,
121-
IterResourcesCost: ls.IterResourcesCost + (ls.Cardinality * rs.IterResourcesCost),
122-
IterSubjectsCost: ls.IterSubjectsCost + (ls.Cardinality * rs.IterSubjectsCost),
131+
IterResourcesCost: iterResourcesCost,
132+
IterSubjectsCost: iterSubjectsCost,
123133
}, nil
124134
case *IntersectionArrow:
125135
ls, err := s.Cost(it.left)
@@ -133,11 +143,15 @@ func (s StaticStatistics) Cost(iterator Iterator) (Estimate, error) {
133143
// IntersectionArrow also does IterSubjects on left, then Check on right,
134144
// but only yields results when ALL subjects satisfy (more selective), estimated based on the fanout from IterSubjects
135145
selectivity := math.Pow(ls.CheckSelectivity, float64(s.Fanout)) * rs.CheckSelectivity
146+
checkCost := ls.IterSubjectsCost + (ls.Cardinality * rs.CheckCost)
136147
return Estimate{
137-
Cardinality: int(float64(ls.Cardinality*rs.Cardinality) * selectivity),
138-
CheckCost: ls.IterSubjectsCost + (ls.Cardinality * rs.CheckCost),
139-
CheckSelectivity: selectivity,
140-
IterResourcesCost: ls.IterResourcesCost + (ls.Cardinality * rs.IterResourcesCost),
148+
Cardinality: int(float64(ls.Cardinality*rs.Cardinality) * selectivity),
149+
CheckCost: checkCost,
150+
CheckSelectivity: selectivity,
151+
// We have to iterResources on both the left and the right side to get the potential
152+
// candidates, but we then need to check each of the candidates, so we pay an additional
153+
// check cost.
154+
IterResourcesCost: ls.IterResourcesCost + (ls.Cardinality * rs.IterResourcesCost) + checkCost,
141155
IterSubjectsCost: ls.IterSubjectsCost + (ls.Cardinality * rs.IterSubjectsCost),
142156
}, nil
143157
case *Union:

pkg/query/statistics_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,41 @@ func TestStaticStatistics_Cost(t *testing.T) {
8282

8383
// CheckSelectivity: left * right = 0.9 * 0.9 = 0.81
8484
require.InDelta(t, 0.81, est.CheckSelectivity, 0.001)
85+
86+
// IterResourcesCost: left.IterResourcesCost + (left.Cardinality + right.IterResourcesCost)
87+
// = 2 + 2 * 1 = 4
88+
require.Equal(t, 4, est.IterResourcesCost)
89+
})
90+
91+
t.Run("reversed arrow", func(t *testing.T) {
92+
left := NewFixedIterator(
93+
MustPathFromString("document:doc1#parent@folder:folder1"),
94+
MustPathFromString("document:doc2#parent@folder:folder2"),
95+
)
96+
right := NewFixedIterator(
97+
MustPathFromString("folder:folder1#viewer@user:alice"),
98+
)
99+
arrow := NewArrow(left, right)
100+
101+
// Set arrow direction to the other way
102+
arrow.direction = rightToLeft
103+
104+
est, err := stats.Cost(arrow)
105+
require.NoError(t, err)
106+
107+
// Cardinality: left.Cardinality * right.Cardinality = 2 * 1 = 2
108+
require.Equal(t, 2, est.Cardinality)
109+
110+
// CheckCost: right.IterResourcesCost + (right.Cardinality * left.CheckCost)
111+
// = 1 + (1 * 2) = 2
112+
require.Equal(t, 2, est.CheckCost)
113+
114+
// CheckSelectivity: left * right = 0.9 * 0.9 = 0.81
115+
require.InDelta(t, 0.81, est.CheckSelectivity, 0.001)
116+
117+
// IterResourcesCost: right.IterResourcesCost + (right.Cardinality + left.IterResourcesCost)
118+
// = 1 + 1 * 2 = 3
119+
require.Equal(t, 3, est.IterResourcesCost)
85120
})
86121

87122
t.Run("with relation iterator", func(t *testing.T) {
@@ -131,6 +166,10 @@ func TestStaticStatistics_Cost(t *testing.T) {
131166
// CheckCost: left.IterSubjectsCost + (left.Cardinality * right.CheckCost)
132167
// = 3 + (3 * 1) = 6
133168
require.Equal(t, 6, est.CheckCost)
169+
170+
// IterResourcesCost: left.IterResourcesCost + (left.Cardinality * right.IterResourcesCost) + checkCost
171+
// = 3 + (3 * 1) + 6 = 12
172+
require.Equal(t, 12, est.IterResourcesCost)
134173
})
135174

136175
t.Run("Union", func(t *testing.T) {
@@ -407,6 +446,9 @@ func TestStaticStatistics_Cost(t *testing.T) {
407446

408447
// CheckSelectivity: main * exclusionFactor = 0.9 * 0.1 = 0.09
409448
require.InDelta(t, 0.09, est.CheckSelectivity, 0.001)
449+
450+
// IterResourcesCost: sum of both = 3 + 1 = 4
451+
require.Equal(t, 4, est.IterResourcesCost)
410452
})
411453

412454
t.Run("exclusion with relation iterator", func(t *testing.T) {

0 commit comments

Comments
 (0)