-
Notifications
You must be signed in to change notification settings - Fork 366
fix: hook up schema caching proxies with schemareader #2868
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
Conversation
tstirrat15
left a comment
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.
See comments
| // that caching logic stays in place. | ||
| func (r *definitionCachingReader) SchemaReader() (datastore.SchemaReader, error) { | ||
| return r.Reader.SchemaReader() | ||
| return schemautil.NewLegacySchemaReaderAdapter(r), nil |
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 took the expedient approach in this file under the understanding that this code will be completely discarded once new schema storage is in place.
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.
this code will be completely discarded
this entire file?
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.
Yes
| p *watchingCachingProxy | ||
| } | ||
|
|
||
| var _ datastore.SchemaReader = (*watchingCachingReader)(nil) |
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 was slightly more thorough in this file under the understanding that the new schema storage mechanism will reuse some of this logic.
|
|
||
| // ListAllTypeDefinitions delegates to the underlying reader, as the cache doesn't currently | ||
| // support reading all definitions directly. | ||
| // TODO: implement this in the cache |
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.
Question for reviewers: does this seem sane? Listing all will bypass the cache, but I don't have a sense of whether that's actually used in places where caching matters.
|
|
||
| // LookupSchemaDefinitionsByNames looks up type and caveat definitions by name. | ||
| // TODO: implement this directly in the cache, rather than overfetching from the caches | ||
| func (w *watchingCachingReader) LookupSchemaDefinitionsByNames(ctx context.Context, names []string) (map[string]datastore.SchemaDefinition, error) { |
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.
This function is slightly inefficient because it's always going to make twice as many requests as necessary, but the caches should hold a notFound after their first request so it should be amortized (?).
Codecov Report❌ Patch coverage is ❌ Your project status has failed because the head coverage (74.39%) is below the target coverage (75.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #2868 +/- ##
==========================================
- Coverage 74.43% 74.39% -0.04%
==========================================
Files 484 484
Lines 57764 57792 +28
==========================================
- Hits 42991 42987 -4
- Misses 11754 11779 +25
- Partials 3019 3026 +7 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
7396adf to
496d3f6
Compare
496d3f6 to
303e311
Compare
tstirrat15
left a comment
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.
See comments
| } | ||
|
|
||
| type testerDef struct { | ||
| type oldTesterDef struct { |
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.
Renaming here was mostly about ensuring that I didn't miss a test.
|
|
||
| func TestRWTCaching(t *testing.T) { | ||
| for _, tester := range testers { | ||
| func TestSnapshotCaching(t *testing.T) { |
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.
Most of the refactors in these two files look roughly like this - flattening the tables and refactoring to use the new methods.
|
|
||
| oneReader := &proxy_test.MockReader{} | ||
| dsMock.On("SnapshotReader", one).Return(oneReader) | ||
| oneReader.On("LegacyReadNamespaceByName", nsA).Return(nil, old, nil).Once() |
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.
Note that the mock still references the legacy method - this is because the wrapper is just invoking the legacy method.
| dsMock.On("SnapshotReader", one).Return(oneReader) | ||
| oneReader. | ||
| On("LegacyReadNamespaceByName", nsA). | ||
| WaitUntil(time.After(50*time.Millisecond)). |
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.
Using synctest here to avoid needing to actually sleep for the given period of time.
|
|
||
| // singleflightReader is used to test singleflight context cancellation | ||
| // behavior. | ||
| type singleflightReader struct { |
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.
Renaming this to make its use a bit clearer.
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.
nit: don't name things based on how they are expected to be used, name them after what they do. in this case, it's a slow reader that reacts to context cancellation
| } | ||
|
|
||
| // TODO | ||
| func TestMixedCaching(t *testing.T) { |
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.
This is the test whose semantics differ the most from the test it's based on.
Most of it comes down to the LookupSchemaDefinitionsByNames function taking namespace or caveat names as args, which means that it needs to look up the names as both caveats and namespaces.
Because the caches don't hold negative results, you can't make the assertion that the lookup function doesn't go to the underlying reader, because the lookup function is always going to be looking in the wrong place for a name half of the time.
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 was able to remedy this by adding separate Lookup methods that return just caveats and namespaces.
| } | ||
|
|
||
| func (r *watchingCachingReader) LegacyReadNamespaceByName( | ||
| func (w *watchingCachingReader) LegacyReadNamespaceByName( |
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.
This bugged me so I updated it.
| require.NoError(t, err) | ||
| require.Equal(t, "somenamespace", nsRevDef.Definition.Name) | ||
|
|
||
| // NOTE: we don't read via lookup because we can't retain the semantic of the test |
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.
This test's semantic also differs from the previous one, for the same reason as previously - we can't assert that the Lookup doesn't go to the underlying datastore because it's always going to miss the cache half the time.
303e311 to
f5101b9
Compare
| go readCaveat() | ||
| go readCaveat() | ||
|
|
||
| // Let the timers elapse |
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.
what timer?
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 timers on the singleflight reader mock
| dsMock := &proxy_test.MockDatastore{} | ||
| ctx1, cancel1 := context.WithCancel(t.Context()) | ||
| defer cancel1() | ||
| // Note that ctx2 will not be cancelled |
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.
?
t.Context returns a context that is canceled just before Cleanup-registered functions are called.
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 the context of the test :P I meant that the second context isn't imperatively cancelled during test execution
fd224db to
502cbd2
Compare
tstirrat15
left a comment
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.
See comments
| return err | ||
| } | ||
| foundDefs, err := schemaReader.LookupSchemaDefinitionsByNames(ctx, caveatNames.AsSlice()) | ||
| foundDefs, err := schemaReader.LookupCaveatDefinitionsByNames(ctx, caveatNames.AsSlice()) |
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.
Using the specific lookup should prevent an NS lookup.
| go readCaveat() | ||
| go readCaveat() | ||
|
|
||
| // Let the timers elapse |
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 timers on the singleflight reader mock
| dsMock := &proxy_test.MockDatastore{} | ||
| ctx1, cancel1 := context.WithCancel(t.Context()) | ||
| defer cancel1() | ||
| // Note that ctx2 will not be cancelled |
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 the context of the test :P I meant that the second context isn't imperatively cancelled during test execution
| } | ||
|
|
||
| // TODO | ||
| func TestMixedCaching(t *testing.T) { |
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 was able to remedy this by adding separate Lookup methods that return just caveats and namespaces.
| require.Equal(t, "somenamespace", nsRevDef.Definition.Name) | ||
|
|
||
| // Repeat with lookup | ||
| nsDefMap, err := schemaReader.LookupTypeDefinitionsByNames(t.Context(), []string{"somenamespace"}) |
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.
Same in this file - using the new methods means that the new test better reflects the old one.
| return err | ||
| } | ||
| foundDefs, err := schemaReader.LookupSchemaDefinitionsByNames(ctx, nsNames.AsSlice()) | ||
| foundDefs, err := schemaReader.LookupTypeDefinitionsByNames(ctx, nsNames.AsSlice()) |
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.
This only handles typedefs so we can use the more specific function.
| var referencedNamespaceMap map[string]*schema.Definition | ||
| var referencedCaveatMap map[string]*core.CaveatDefinition | ||
|
|
||
| if !referencedNamespaceNames.IsEmpty() || !referencedCaveatNamesWithContext.IsEmpty() { |
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'd welcome another pair of eyes on this file.
The old logic combined the namespace and caveat names and then made a single Lookup call. The new one keeps them separate and handles them separately, which should reduce the work and also makes the code slightly easier to follow at the expense of some duplication.
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.
This looks good to me
| numWritten += streamWritten | ||
|
|
||
| // The stream has terminated because we're awaiting namespace and/or caveat information | ||
| if len(adapter.awaitingNamespaces) > 0 || len(adapter.awaitingCaveats) > 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.
This is the exact same refactor.
| numWritten += streamWritten | ||
|
|
||
| // The stream has terminated because we're awaiting namespace and/or caveat information | ||
| if len(adapter.awaitingNamespaces) > 0 || len(adapter.awaitingCaveats) > 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.
And this too.
| LookupSchemaDefinitionsByNames(ctx context.Context, names []string) (map[string]SchemaDefinition, error) | ||
|
|
||
| // LookupTypeDefinitionsByNames looks up type definitions by name. | ||
| LookupTypeDefinitionsByNames(ctx context.Context, names []string) (map[string]SchemaDefinition, error) |
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.
These are the new methods added.
josephschorr
left a comment
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.
LGTM
502cbd2 to
8f3e04b
Compare
| @@ -42,6 +42,12 @@ type SchemaReader interface { | |||
|
|
|||
| // LookupSchemaDefinitionsByNames looks up type and caveat definitions by name. | |||
| LookupSchemaDefinitionsByNames(ctx context.Context, names []string) (map[string]SchemaDefinition, error) | |||
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.
LookupSchemaDefinitionsByNames isn't used anymore (well except in tests). Can we mark it as deprecated with pointers to the new ones?
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.
We'll use it when we move to the new schema storage mechanism, because it will make sense when the cost is the same to go look at the entire schema.
| } | ||
|
|
||
| referencedNamespaceMap = make(map[string]*schema.Definition, len(foundNamespaceDefs)) | ||
| ts := schema.NewTypeSystem(schema.ResolverForDatastoreReader(reader)) |
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.
this is funny.. if you remove ts and pass schema.NewDefinition(nil,...) the unit tests still pass 💀
not saying you change that but i think the schema.Definition struct needs a review
Co-authored-by: Maria Ines Parnisari <maria.ines.parnisari@authzed.com>
ace7341 to
0aabd07
Compare
Description
In #2805 we refactored the caching proxies, and a part of that refactor made
SchemaReader()return the underlyingReaderinstance rather than a reference to the current proxy. This meant that the proxy was effectively disabled for anything that interacted with schema reading/writing, because there wasn't a way to get a handle on the proxy that exercised the schema reading logic.This remedies that by returning a wrapped version of the proxy that adds adapters for the new methods.
It also breaks out the
LookupSchemaDefinitions's definition into two other functions that are caveat and typedef specific, which means that we can use the specific version at the callsite and prevent cache misses and overfetching.Changes
Will annotate.
Testing
Review. Load-test this against the previous commit and see that the schema cache is actually populated and used.