Skip to content
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

Fix RTDB query getData caching behavior #9707

Closed
wants to merge 5 commits into from
Closed

Conversation

jmwski
Copy link
Contributor

@jmwski jmwski commented Apr 26, 2022

This is an attempt at fixing #8286. The root cause is that get doesn't update cache correctly, always calling FSyncTree applyServerOverwriteAtPath, even when getData was called on a query.

There is an alternative method for updating cache called applyTaggedQueryOverwriteAtPath. This method is called in onDataUpdate when the server sends a listener overwrite for a query.

In order to use this method, we need to create an FView for the getData query. I added 4 methods:

  • FSyncTree registerQuery, unregisterQuery
  • FSyncPoint registerQuery, unregisterQuery
    To maintain the view without adding an event registration.

@google-oss-bot
Copy link

1 Warning
⚠️ Did you forget to add a changelog entry? (Add #no-changelog to the PR description to silence this warning.)

Generated by 🚫 Danger

@@ -533,7 +533,7 @@ - (void)getData:(FIRDatabaseQuery *)query
}];
return;
}
[self.persistenceManager setQueryActive:querySpec];
NSNumber *tag = [self.serverSyncTree registerQuery:[query querySpec]];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think get queries are tagged on the backend, but it looks like the tag is only used retrieve the query spec in applyTaggedQueryOverwriteAtPath.

forNonExistingViewForQuery:(FQuerySpec *)query
writesCache:(FWriteTreeRef *)writesCache
serverCache:(FCacheNode *)serverCache {
- (void)registerQuery:(FQuerySpec *)query
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method does everything that addEventRegistration did before actually adding an event registration to the view.

@google-oss-bot
Copy link

google-oss-bot commented Apr 26, 2022

@@ -199,6 +205,15 @@ - (NSArray *)addEventRegistration:(id<FEventRegistration>)eventRegistration
return [view initialEvents:eventRegistration];
}

- (BOOL)unregisterQuery:(FQuerySpec *)query {
Copy link
Contributor Author

@jmwski jmwski Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method just removes the query from the view map if no other query added an event registration to it.

There is quite a bit of logic that isn't brought over from removeEventRegistration. In particular, we are not:

  • Removing all empty views from a syncpoint if the getData query is default.
  • Generating any FCancelEvents for the query since there is no event registration associated with it.

I'm least sure about this function.

@google-oss-bot
Copy link

google-oss-bot commented Apr 26, 2022

Coverage Report 1

Affected Products

  • FirebaseDatabase-iOS-FirebaseDatabase.framework

    Overall coverage changed from 56.89% (aefb2a4) to 56.63% (e6e6199) by -0.27%.

    FilenameBase (aefb2a4)Merge (e6e6199)Diff
    FRepo.m13.42%13.25%-0.18%
    FSyncPoint.m86.99%84.44%-2.56%
    FSyncTree.m70.73%66.04%-4.69%

Test Logs

  1. https://storage.googleapis.com/firebase-sdk-metric-reports/zqpv7iaFyt.html

@jmwski
Copy link
Contributor Author

jmwski commented Apr 26, 2022

@jsdt I am hoping you could give me some early feedback on this change. I added a test that checks for the reported issue. More in progress.

}
if (removed) {
[self removeTags:@[ query ]];
[self.persistenceManager setQueryInactive:query];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: only call setQueryInactive when we just removed the last query in the view. This is consistent with how removeEventRegistration works.

@jmwski jmwski requested a review from maneesht April 26, 2022 17:58

@implementation FIRDatabaseGetTests

- (void)testGetDoesntTriggerExtraListens {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also add a test to ensure that a subsequent listen for the same query returns the cached data immediately?

@@ -70,5 +70,9 @@
- (BOOL)viewExistsForQuery:(FQuerySpec *)query;
- (BOOL)hasCompleteView;
- (FView *)completeView;
- (void)registerQuery:(FQuerySpec *)query
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add comments for any new functions?

#import "FirebaseDatabase/Tests/Helpers/FTestExpectations.h"
#import "FirebaseDatabase/Tests/Helpers/FTupleEventTypeString.h"

@implementation FIRDatabaseGetTests
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a test where we perform a get, then perform a listen before the get is finished?

@@ -448,6 +457,42 @@ - (NSArray *)applyTaggedServerRangeMergeAtPath:(FPath *)path
}
}

- (NSNumber *)registerQuery:(FQuerySpec *)query {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Query is used a lot, including for listeners. Should we call this something like "registerGet"?

atPath:path];
}
NSNumber *tag = nil;
BOOL viewAlreadyExists = [syncPoint viewExistsForQuery:query];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the code up until here is reused from addEventRegistration, can we extract this out?

whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) {
foundAncestorDefaultView =
foundAncestorDefaultView || [syncPoint hasCompleteView];
return !foundAncestorDefaultView;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually think we're using this block, since we aren't checking foundAncestorDefaultView later in the function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, will remove this.

writesCache:writesCache
serverCache:serverCache];
} else if (![query loadsAllData]) {
tag = [self tagQuery:query];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be moved into the

if (!viewAlreadyExists) {
  ...
}

bock above.

@jmwski jmwski closed this Feb 17, 2023
@firebase firebase locked and limited conversation to collaborators Mar 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants