-
Notifications
You must be signed in to change notification settings - Fork 279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for basic cursors and limits to LookupSubjects #1379
base: main
Are you sure you want to change the base?
Conversation
da48a6b
to
19099a2
Compare
21ae960
to
c386a05
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't have time to finish the review, sorry! This is my first attempt. So far it's looking good, although I have a bunch of questions.
Something I noticed is that when one resumes with a cursor, we are evaluating the whole graph again and sending queries to the database, even though they will return empty. It works, but is a lot of work that is wasted after resuming, and will be directly proportional to the complexity of the schema. Ideally the evaluation of the schema can also resume from where the cursor left off, e.g. if permission view = a + b + c
you'd restart evaluation at c
if that's where you left when the limit was hit. It would spare a bunch of dispatching (network calls across SpiceDBs!) and DB roundtrips.
internal/services/v1/permissions.go
Outdated
excludedSubjectIDs := make([]string, 0, len(foundSubject.ExcludedSubjects)) | ||
for _, excludedSubject := range foundSubject.ExcludedSubjects { | ||
excludedSubjectIDs = append(excludedSubjectIDs, excludedSubject.SubjectId) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we merge this into the loop below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did so, but I'm thinking maybe we just remove it entirely now? The field has been marked deprecated for a number of versions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good to me!
internal/services/v1/permissions.go
Outdated
if subject == nil { | ||
continue | ||
} | ||
encodedCursor, err := cursor.EncodeFromDispatchCursor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to allocate a new encoded cursor each time here? Could we reuse the same proto instance and just modify the corresponding field? I did something as an optimization in the ReadRelationships streaming bits and helped a bunch with allocations and GC overhead. It would also spare us calling revision.String()
repeatedly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be far less maintainable, but I'll see what I can do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, but its a bit ugly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a second pass, I thought the new code could have a race. Perhaps it's not worth the trouble? I'll leave it up to you
for _, foundSubjects := range subjects { | ||
for _, foundSubject := range foundSubjects.FoundSubjects { | ||
// NOTE: wildcard is always returned, because it is needed by all branches, at all times. | ||
if foundSubject.SubjectId == tuple.PublicWildcard || (afterSubjectID == "" || foundSubject.SubjectId > afterSubjectID) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in not sure to understand how we guarantee that we do not skip found subjects that are alphabetically after the current cursor but that haven't been seen by the stream before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the stream always returns in sorted order from the root. That way, we are always guaranteed to have a defined ordering (alphabetical) coming out of each subproblem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't that mean that we have to start collecting subjects from all subproblems before we can start streaming? We can make sure that each subproblem retrieves results in order from DB, but you still need to execute all of them in order to determine what's the first subject to stream?
|
||
dispatchCount, err := responsemeta.GetIntResponseTrailerMetadata(trailer, responsemeta.DispatchedOperationsCount) | ||
req.NoError(err) | ||
req.GreaterOrEqual(dispatchCount, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there had been some regressions recently around dispatches and I wonder if we could use a new test-case that checks the number of dispatches over a cursored LR call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean over the LS call? We do now have the test for LR calls
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean testing for LS. The logic should be different enough that it warrants another set of tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. Added
internal/graph/lookupsubjects.go
Outdated
} | ||
|
||
// filterSubjectsMap filters the subjects found in the subjects map to only those allowed, returning an updated map. | ||
func filterSubjectsMap(subjects map[string]*v1.FoundSubjects, allowedSubjectIds ...string) map[string]*v1.FoundSubjects { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see no use of the variadic allowedSubjectIds
so please pass as a slice. NewSet
should also support a slice - a lot of calls in the codebase that end up doing ...
just to pass it to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed here but not NewSet. We have some places where the variadic is helpful. If you like, I can add another NewSetFromSlice
and change the call sites?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep I think that'd be a good idea
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done and changed all the call sites
c386a05
to
137e08e
Compare
Updated |
137e08e
to
1dec055
Compare
Rebased |
3202a4b
to
b00913b
Compare
b00913b
to
83a96b2
Compare
83a96b2
to
39f033f
Compare
Rebased |
57a79b9
to
07dbacd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing a complete re-review to load all the context. First batch of feedback.
excludedSubjectIDs := make([]string, 0, len(foundSubject.ExcludedSubjects)) | ||
for _, excludedSubject := range foundSubject.ExcludedSubjects { | ||
excludedSubjectIDs = append(excludedSubjectIDs, excludedSubject.SubjectId) | ||
for { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handling stream should handle all the elements up to the concrete limit, wouldn't it? From looking at the code, it would seem the only sole purpose is making sure the wildcard is sent, since in the worst-case scenario we stream up to the concrete limit, and we have to handle the potential +1 of the wildcard. Is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@josephschorr last batch of feedback. Let's address this quickly so we don't loose the context and we can land it soon.
|
||
dispatchCount, err := responsemeta.GetIntResponseTrailerMetadata(trailer, responsemeta.DispatchedOperationsCount) | ||
req.NoError(err) | ||
req.GreaterOrEqual(dispatchCount, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean testing for LS. The logic should be different enough that it warrants another set of tests?
internal/services/v1/permissions.go
Outdated
if subject == nil { | ||
continue | ||
} | ||
encodedCursor, err := cursor.EncodeFromDispatchCursor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a second pass, I thought the new code could have a race. Perhaps it's not worth the trouble? I'll leave it up to you
internal/services/v1/permissions.go
Outdated
excludedSubjectIDs := make([]string, 0, len(foundSubject.ExcludedSubjects)) | ||
for _, excludedSubject := range foundSubject.ExcludedSubjects { | ||
excludedSubjectIDs = append(excludedSubjectIDs, excludedSubject.SubjectId) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good to me!
07dbacd
to
0c9fbb7
Compare
Updated |
0c9fbb7
to
1f765d0
Compare
This change supports a limit (called the "concrete limit") on LookupSubjects and will filter concrete subjects based on the returned cursor. This change does *not* filter intermediate lookups, which will be done in a followup PR.
1f765d0
to
e74c6f3
Compare
Moved to draft due to steelthread discovering a pagination bug |
@vroldanbet Is there any progress for this pr, we are still looking forward the pagination function of LookupSubjects. |
@karlwang1983 I will defer to @josephschorr, my role has been to review the code. |
Also looking forward to this one! |
This change supports a limit (called the "concrete limit") on LookupSubjects and will filter concrete subjects based on the returned cursor.
This change does not filter intermediate lookups, which will be done in a followup PR.