Skip to content

Commit

Permalink
LookupResources Postgres query optimization
Browse files Browse the repository at this point in the history
Fixes potential slow reverse lookup queries when using Postgres
by changing the order of the columns being compared in the cursor.
By comparing the numerical IDs before the string relations, the
query is able to short-circuit sooner and return faster.
  • Loading branch information
alecmerdler committed Apr 1, 2024
1 parent baa8d9d commit 1e781cf
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 27 deletions.
13 changes: 7 additions & 6 deletions internal/datastore/common/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ type nameAndValue struct {
}

func (sqf SchemaQueryFilterer) After(cursor *core.RelationTuple, order options.SortOrder) SchemaQueryFilterer {
// NOTE: The ordering of these columns can affect query performance, be aware when changing.
columnsAndValues := map[options.SortOrder][]nameAndValue{
options.ByResource: {
{
Expand All @@ -156,15 +157,15 @@ func (sqf SchemaQueryFilterer) After(cursor *core.RelationTuple, order options.S
{
sqf.schema.colObjectID, cursor.ResourceAndRelation.ObjectId,
},
{
sqf.schema.colRelation, cursor.ResourceAndRelation.Relation,
},
{
sqf.schema.colUsersetNamespace, cursor.Subject.Namespace,
},
{
sqf.schema.colUsersetObjectID, cursor.Subject.ObjectId,
},
{
sqf.schema.colRelation, cursor.ResourceAndRelation.Relation,
},
{
sqf.schema.colUsersetRelation, cursor.Subject.Relation,
},
Expand All @@ -176,9 +177,6 @@ func (sqf SchemaQueryFilterer) After(cursor *core.RelationTuple, order options.S
{
sqf.schema.colUsersetObjectID, cursor.Subject.ObjectId,
},
{
sqf.schema.colUsersetRelation, cursor.Subject.Relation,
},
{
sqf.schema.colNamespace, cursor.ResourceAndRelation.Namespace,
},
Expand All @@ -188,6 +186,9 @@ func (sqf SchemaQueryFilterer) After(cursor *core.RelationTuple, order options.S
{
sqf.schema.colRelation, cursor.ResourceAndRelation.Relation,
},
{
sqf.schema.colUsersetRelation, cursor.Subject.Relation,
},
},
}[order]

Expand Down
40 changes: 20 additions & 20 deletions internal/datastore/common/sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,8 @@ func TestSchemaQueryFilterer(t *testing.T) {
},
).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
},
"SELECT * WHERE ns = ? AND (object_id,relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?)",
[]any{"someresourcetype", "foo", "viewer", "user", "bar", "..."},
"SELECT * WHERE ns = ? AND (object_id,subject_ns,subject_object_id,relation,subject_relation) > (?,?,?,?,?)",
[]any{"someresourcetype", "foo", "user", "bar", "viewer", "..."},
map[string]int{
"ns": 1,
},
Expand Down Expand Up @@ -504,8 +504,8 @@ func TestSchemaQueryFilterer(t *testing.T) {
},
).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
},
"SELECT * WHERE ns = ? AND object_id IN (?) AND (relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?)",
[]any{"someresourcetype", "one", "viewer", "user", "bar", "..."},
"SELECT * WHERE ns = ? AND object_id IN (?) AND (subject_ns,subject_object_id,relation,subject_relation) > (?,?,?,?)",
[]any{"someresourcetype", "one", "user", "bar", "viewer", "..."},
map[string]int{
"ns": 1,
"object_id": 1,
Expand All @@ -520,8 +520,8 @@ func TestSchemaQueryFilterer(t *testing.T) {
},
).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
},
"SELECT * WHERE object_id IN (?) AND (ns,relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?)",
[]any{"one", "someresourcetype", "viewer", "user", "bar", "..."},
"SELECT * WHERE object_id IN (?) AND (ns,subject_ns,subject_object_id,relation,subject_relation) > (?,?,?,?,?)",
[]any{"one", "someresourcetype", "user", "bar", "viewer", "..."},
map[string]int{
"object_id": 1,
},
Expand All @@ -536,8 +536,8 @@ func TestSchemaQueryFilterer(t *testing.T) {
},
).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
},
"SELECT * WHERE ns = ? AND object_id IN (?, ?) AND (object_id,relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?)",
[]any{"someresourcetype", "one", "two", "foo", "viewer", "user", "bar", "..."},
"SELECT * WHERE ns = ? AND object_id IN (?, ?) AND (object_id,subject_ns,subject_object_id,relation,subject_relation) > (?,?,?,?,?)",
[]any{"someresourcetype", "one", "two", "foo", "user", "bar", "viewer", "..."},
map[string]int{
"ns": 1,
"object_id": 2,
Expand Down Expand Up @@ -567,8 +567,8 @@ func TestSchemaQueryFilterer(t *testing.T) {
OptionalSubjectType: "somesubjectype",
}).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
},
"SELECT * WHERE ((subject_ns = ?)) AND (ns,object_id,relation,subject_object_id,subject_relation) > (?,?,?,?,?)",
[]any{"somesubjectype", "someresourcetype", "foo", "viewer", "bar", "..."},
"SELECT * WHERE ((subject_ns = ?)) AND (ns,object_id,subject_object_id,relation,subject_relation) > (?,?,?,?,?)",
[]any{"somesubjectype", "someresourcetype", "foo", "bar", "viewer", "..."},
map[string]int{
"subject_ns": 1,
},
Expand All @@ -584,8 +584,8 @@ func TestSchemaQueryFilterer(t *testing.T) {
OptionalSubjectType: "anothersubjectype",
}).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
},
"SELECT * WHERE ((subject_ns = ?)) AND ((subject_ns = ?)) AND (ns,object_id,relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?,?)",
[]any{"somesubjectype", "anothersubjectype", "someresourcetype", "foo", "viewer", "user", "bar", "..."},
"SELECT * WHERE ((subject_ns = ?)) AND ((subject_ns = ?)) AND (ns,object_id,subject_ns,subject_object_id,relation,subject_relation) > (?,?,?,?,?,?)",
[]any{"somesubjectype", "anothersubjectype", "someresourcetype", "foo", "user", "bar", "viewer", "..."},
map[string]int{
"subject_ns": 2,
},
Expand All @@ -595,8 +595,8 @@ func TestSchemaQueryFilterer(t *testing.T) {
func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
return filterer.MustFilterWithResourceIDPrefix("someprefix").After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
},
"SELECT * WHERE object_id LIKE ? AND (ns,object_id,relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?,?)",
[]any{"someprefix%", "someresourcetype", "foo", "viewer", "user", "bar", "..."},
"SELECT * WHERE object_id LIKE ? AND (ns,object_id,subject_ns,subject_object_id,relation,subject_relation) > (?,?,?,?,?,?)",
[]any{"someprefix%", "someresourcetype", "foo", "user", "bar", "viewer", "..."},
map[string]int{},
},
{
Expand All @@ -621,8 +621,8 @@ func TestSchemaQueryFilterer(t *testing.T) {
OptionalSubjectType: "somesubjectype",
}).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.BySubject)
},
"SELECT * WHERE ((subject_ns = ?)) AND (subject_object_id,subject_relation,ns,object_id,relation) > (?,?,?,?,?)",
[]any{"somesubjectype", "bar", "...", "someresourcetype", "foo", "viewer"},
"SELECT * WHERE ((subject_ns = ?)) AND (subject_object_id,ns,object_id,relation,subject_relation) > (?,?,?,?,?)",
[]any{"somesubjectype", "bar", "someresourcetype", "foo", "viewer", "..."},
map[string]int{
"subject_ns": 1,
},
Expand All @@ -635,8 +635,8 @@ func TestSchemaQueryFilterer(t *testing.T) {
OptionalSubjectIds: []string{"foo"},
}).After(tuple.MustParse("someresourcetype:someresource#viewer@user:bar"), options.BySubject)
},
"SELECT * WHERE ((subject_ns = ? AND subject_object_id IN (?))) AND (subject_relation,ns,object_id,relation) > (?,?,?,?)",
[]any{"somesubjectype", "foo", "...", "someresourcetype", "someresource", "viewer"},
"SELECT * WHERE ((subject_ns = ? AND subject_object_id IN (?))) AND (ns,object_id,relation,subject_relation) > (?,?,?,?)",
[]any{"somesubjectype", "foo", "someresourcetype", "someresource", "viewer", "..."},
map[string]int{"subject_ns": 1, "subject_object_id": 1},
},
{
Expand All @@ -647,8 +647,8 @@ func TestSchemaQueryFilterer(t *testing.T) {
OptionalSubjectIds: []string{"foo", "bar"},
}).After(tuple.MustParse("someresourcetype:someresource#viewer@user:next"), options.BySubject)
},
"SELECT * WHERE ((subject_ns = ? AND subject_object_id IN (?, ?))) AND (subject_object_id,subject_relation,ns,object_id,relation) > (?,?,?,?,?)",
[]any{"somesubjectype", "foo", "bar", "next", "...", "someresourcetype", "someresource", "viewer"},
"SELECT * WHERE ((subject_ns = ? AND subject_object_id IN (?, ?))) AND (subject_object_id,ns,object_id,relation,subject_relation) > (?,?,?,?,?)",
[]any{"somesubjectype", "foo", "bar", "next", "someresourcetype", "someresource", "viewer", "..."},
map[string]int{"subject_ns": 1, "subject_object_id": 2},
},
}
Expand Down
2 changes: 1 addition & 1 deletion internal/graph/lookupresources.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (cl *CursoredLookupResources) LookupResources(
reachableResourcesCursor = newCursor

// If no additional reachable results were found or the request was unlimited, then we can stop.
if reachableCount == 0 || req.OptionalLimit == 0 {
if reachableCount == 0 {
return nil
}
}
Expand Down

0 comments on commit 1e781cf

Please sign in to comment.