Skip to content

Memory lru #695

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

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
941c74c
Start implementing percentile and sequence number calculations
Jan 19, 2018
0a85568
C++\!
Jan 19, 2018
a64cb8e
Clear queryData
Jan 19, 2018
65a2a30
Most of the meat of LRU GC done for memory persistence
Jan 22, 2018
bd8e9f9
Fix header include
Jan 22, 2018
fa31549
Auto-styling
Jan 22, 2018
41ee4c9
Add mutated document tracking
Jan 23, 2018
e7d5f86
Style
Jan 23, 2018
a5b2d17
Drop changes to FSTReferenceSet
Jan 23, 2018
b6559c3
Review feedback
Jan 25, 2018
4890398
Automated style fixes
Jan 25, 2018
60d36af
Implement schema versions
Jan 29, 2018
52ad8cc
Style fixes
Jan 29, 2018
93a84b1
newlines, copyrights, assumptions
Jan 29, 2018
0580b9a
Fix nullability
Jan 29, 2018
77e4ed2
Raw ptr -> shared_ptr
Jan 30, 2018
d3f269c
kVersionTableGlobal -> kVersionGlobalTable
Jan 30, 2018
a6fa651
Drop utils, move into static methods
Jan 30, 2018
578a020
Drop extra include
Jan 30, 2018
d7290cc
Add a few more comments
Jan 30, 2018
9dfd89d
Move version constant into migrations file
Jan 30, 2018
ba24516
formatting?
Jan 30, 2018
638a9d5
Fix comment
Jan 30, 2018
09ac0d3
Split add and update queryData
Jan 26, 2018
2019e8d
Work on adding targetCount
Jan 26, 2018
55ec377
More work on count
Jan 30, 2018
8ecba99
Using shared_ptr
Jan 30, 2018
f2308ef
merging in master
Jan 30, 2018
87767f6
Implement count for query cache
Jan 31, 2018
187fc36
Merge branch 'master' into count_queries
Jan 31, 2018
1636f59
use quotes
Jan 31, 2018
8e51477
Merge in master
Jan 31, 2018
e38bf80
Fixup
Feb 1, 2018
b5c47d9
Merging in master
Feb 13, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
312 changes: 312 additions & 0 deletions Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
/*
* Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <XCTest/XCTest.h>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: separate framework imports from the rest

Copy link
Author

Choose a reason for hiding this comment

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

Done.


#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Local/FSTMemoryMutationQueue.h"
#import "Firestore/Source/Local/FSTMemoryQueryCache.h"
#import "Firestore/Source/Local/FSTMemoryRemoteDocumentCache.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Local/FSTWriteGroup.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTMutation.h"

#import "Firestore/Example/Tests/Util/FSTHelpers.h"
#import "Firestore/Source/Local/FSTLRUGarbageCollector.h"

NS_ASSUME_NONNULL_BEGIN

@interface FSTLRUGarbageCollectorTests : XCTestCase
@end

@implementation FSTLRUGarbageCollectorTests {
FSTListenSequenceNumber _previousSequenceNumber;
FSTTargetID _previousTargetID;
NSUInteger _previousDocNum;
FSTObjectValue *_testValue;
}

- (void)setUp {
[super setUp];

_previousSequenceNumber = 1000;
_previousTargetID = 500;
_previousDocNum = 10;
_testValue = FSTTestObjectValue(@{ @"baz" : @YES, @"ok" : @"fine" });
}

- (FSTQueryData *)nextTestQuery {
FSTTargetID targetID = ++_previousTargetID;
FSTListenSequenceNumber listenSequenceNumber = ++_previousSequenceNumber;
FSTQuery *query = FSTTestQuery([NSString stringWithFormat:@"path%i", targetID]);
return [[FSTQueryData alloc] initWithQuery:query
targetID:targetID
listenSequenceNumber:listenSequenceNumber
purpose:FSTQueryPurposeListen];
}

- (FSTDocumentKey *)nextTestDocKey {
NSString *path = [NSString stringWithFormat:@"docs/doc_%lu", (unsigned long)++_previousDocNum];
return FSTTestDocKey(path);
}

- (FSTDocument *)nextTestDocument {
FSTDocumentKey *key = [self nextTestDocKey];
FSTTestSnapshotVersion version = 2;
BOOL hasMutations = NO;
return [FSTDocument documentWithData:_testValue
key:key
version:FSTTestVersion(version)
hasLocalMutations:hasMutations];
}

- (void)testPickSequenceNumberPercentile {
const int numTestCases = 5;
Copy link
Contributor

Choose a reason for hiding this comment

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

Note you can define a struct locally here within this function and initialize it just as you've done. Something like this would allow you to avoid the fiddly indexes in the code below.

struct Case {
  int queries;
  int expected;
};
Case testCases[numTestCases] = {{0, 0}, {10, 1}, {9, 0}, {50, 5}, {49, 4}};

Copy link
Author

Choose a reason for hiding this comment

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

Cool, I didn't know you could just define structs wherever. I definitely like that better.

// 0 - number of queries to cache, 1 - number expected to be calculated as 10%
struct Case {
int queries;
int expected;
};
struct Case testCases[numTestCases] = {{0, 0}, {10, 1}, {9, 0}, {50, 5}, {49, 4}};

for (int i = 0; i < numTestCases; i++) {
// Fill the query cache.
FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"];
int numQueries = testCases[i].queries;
int expectedTenthPercentile = testCases[i].expected;
FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init];
for (int j = 0; j < numQueries; j++) {
[queryCache addQueryData:[self nextTestQuery] group:group];
}

FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache];
FSTListenSequenceNumber tenth = [gc queryCountForPercentile:10];
XCTAssertEqual(expectedTenthPercentile, tenth, @"Total query count: %i", numQueries);
}
}

- (void)testSequenceNumberForQueryCount {
// Sequence numbers in this test start at 1001 and are incremented by one.

// No queries... should get invalid sequence number (-1)
{
FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init];
FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache];
FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:0];
XCTAssertEqual(kFSTListenSequenceNumberInvalid, highestToCollect);
}

// 50 queries, want 10. Should get 1010.
{
_previousSequenceNumber = 1000;
FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init];
FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache];
FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"];
for (int i = 0; i < 50; i++) {
[queryCache addQueryData:[self nextTestQuery] group:group];
}
FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:10];
XCTAssertEqual(1010, highestToCollect);
}

// 50 queries, 9 with 1001, incrementing from there. Should get 1002.
{
_previousSequenceNumber = 1000;
FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init];
FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache];
FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"];
for (int i = 0; i < 9; i++) {
[queryCache addQueryData:[self nextTestQuery] group:group];
_previousSequenceNumber = 1000;
}
_previousSequenceNumber = 1001;
for (int i = 9; i < 50; i++) {
[queryCache addQueryData:[self nextTestQuery] group:group];
}
FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:10];
XCTAssertEqual(1002, highestToCollect);
}

// 50 queries, 11 with 1001, incrementing from there. Should get 1001.
{
_previousSequenceNumber = 1000;
FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init];
FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache];
FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"];
for (int i = 0; i < 11; i++) {
[queryCache addQueryData:[self nextTestQuery] group:group];
_previousSequenceNumber = 1000;
}
_previousSequenceNumber = 1001;
for (int i = 11; i < 50; i++) {
[queryCache addQueryData:[self nextTestQuery] group:group];
}
FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:10];
XCTAssertEqual(1001, highestToCollect);
}

// A mutated doc at 1000, 50 queries 1001-1050. Should get 1009.
{
_previousSequenceNumber = 1000;
FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init];
FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache];
FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"];
FSTDocumentKey *key = [self nextTestDocKey];
FSTDocumentKeySet *set = [[FSTDocumentKeySet keySet] setByAddingObject:key];
[queryCache addMutatedDocuments:set atSequenceNumber:1000 group:group];
for (int i = 0; i < 50; i++) {
[queryCache addQueryData:[self nextTestQuery] group:group];
}
FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:10];
XCTAssertEqual(1009, highestToCollect);
}

// Add mutated docs, then add one of them to a query target so it doesn't get GC'd.
// Expect 1002.
{
_previousSequenceNumber = 1000;
FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init];
FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache];
FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"];
FSTDocument *docInQuery = [self nextTestDocument];
FSTDocumentKeySet *set = [[FSTDocumentKeySet keySet] setByAddingObject:docInQuery.key];
FSTDocumentKeySet *docInQuerySet = set;
for (int i = 0; i < 8; i++) {
set = [set setByAddingObject:[self nextTestDocKey]];
}
// Adding 9 doc keys at 1000. If we remove one of them, we'll have room for two actual queries.
[queryCache addMutatedDocuments:set atSequenceNumber:1000 group:group];
for (int i = 0; i < 49; i++) {
[queryCache addQueryData:[self nextTestQuery] group:group];
}
FSTQueryData *queryData = [self nextTestQuery];
[queryCache addQueryData:queryData group:group];
// This should bump one document out of the mutated documents cache.
[queryCache addMatchingKeys:docInQuerySet forTargetID:queryData.targetID group:group];

// This should catch the remaining 8 documents, plus the first two queries we added.
FSTListenSequenceNumber highestToCollect = [gc sequenceNumberForQueryCount:10];
XCTAssertEqual(1002, highestToCollect);
}
}

- (void)testRemoveQueriesUpThroughSequenceNumber {
FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init];
FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache];
FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"];
NSMutableDictionary<NSNumber *, FSTQueryData *> *liveQueries = [[NSMutableDictionary alloc] init];
for (int i = 0; i < 100; i++) {
FSTQueryData *queryData = [self nextTestQuery];
// Mark odd queries as live so we can test filtering out live queries.
if (queryData.targetID % 2 == 1) {
liveQueries[@(queryData.targetID)] = queryData;
}
[queryCache addQueryData:queryData group:group];
}

// GC up through 1015, which is 15%.
// Expect to have GC'd 7 targets (even values of 1001-1015).
FSTWriteGroup *gcGroup = [FSTWriteGroup groupWithAction:@"gc"];
NSUInteger removed =
[gc removeQueriesUpThroughSequenceNumber:1015 liveQueries:liveQueries group:gcGroup];
XCTAssertEqual(7, removed);
}

- (void)testRemoveOrphanedDocuments {
FSTMemoryQueryCache *queryCache = [[FSTMemoryQueryCache alloc] init];
FSTMemoryRemoteDocumentCache *documentCache = [[FSTMemoryRemoteDocumentCache alloc] init];
FSTMemoryMutationQueue *mutationQueue = [[FSTMemoryMutationQueue alloc] init];
FSTLRUGarbageCollector *gc = [[FSTLRUGarbageCollector alloc] initWithQueryCache:queryCache];
FSTWriteGroup *group = [FSTWriteGroup groupWithAction:@"Ignored"];
[mutationQueue startWithGroup:group];

// Add docs to mutation queue, as well as keep some queries. verify that correct documents are
// removed.
NSMutableSet<FSTDocumentKey *> *toBeRetained = [NSMutableSet set];

NSMutableArray *mutations = [NSMutableArray arrayWithCapacity:2];
// Add two documents to first target, and register a mutation on the second one
{
FSTQueryData *queryData = [self nextTestQuery];
[queryCache addQueryData:queryData group:group];
FSTDocumentKeySet *keySet = [FSTImmutableSortedSet keySet];
FSTDocument *doc1 = [self nextTestDocument];
[documentCache addEntry:doc1 group:group];
keySet = [keySet setByAddingObject:doc1.key];
[toBeRetained addObject:doc1.key];
FSTDocument *doc2 = [self nextTestDocument];
[documentCache addEntry:doc2 group:group];
keySet = [keySet setByAddingObject:doc2.key];
[toBeRetained addObject:doc2.key];
[queryCache addMatchingKeys:keySet forTargetID:queryData.targetID group:group];

FSTObjectValue *newValue = [[FSTObjectValue alloc] initWithDictionary:@{@"foo" : @"@bar"}];
[mutations addObject:[[FSTSetMutation alloc] initWithKey:doc2.key
value:newValue
precondition:[FSTPrecondition none]]];
}

// Add one document to the second target
{
FSTQueryData *queryData = [self nextTestQuery];
[queryCache addQueryData:queryData group:group];
FSTDocumentKeySet *keySet = [FSTImmutableSortedSet keySet];
FSTDocument *doc1 = [self nextTestDocument];
[documentCache addEntry:doc1 group:group];
keySet = [keySet setByAddingObject:doc1.key];
[toBeRetained addObject:doc1.key];
[queryCache addMatchingKeys:keySet forTargetID:queryData.targetID group:group];
}

{
FSTDocument *doc1 = [self nextTestDocument];
[mutations addObject:[[FSTSetMutation alloc] initWithKey:doc1.key
value:doc1.data
precondition:[FSTPrecondition none]]];
[documentCache addEntry:doc1 group:group];
[toBeRetained addObject:doc1.key];
}

FSTTimestamp *writeTime = [FSTTimestamp timestamp];
[mutationQueue addMutationBatchWithWriteTime:writeTime mutations:mutations group:group];

// Now add the docs we expect to get resolved.
NSUInteger expectedRemoveCount = 5;
NSMutableSet<FSTDocumentKey *> *toBeRemoved = [NSMutableSet setWithCapacity:expectedRemoveCount];
for (int i = 0; i < expectedRemoveCount; i++) {
FSTDocument *doc = [self nextTestDocument];
[toBeRemoved addObject:doc.key];
[documentCache addEntry:doc group:group];
}

FSTWriteGroup *gcGroup = [FSTWriteGroup groupWithAction:@"gc"];
NSUInteger removed =
[gc removeOrphanedDocuments:documentCache mutationQueue:mutationQueue group:gcGroup];

XCTAssertEqual(expectedRemoveCount, removed);
for (FSTDocumentKey *key in toBeRemoved) {
XCTAssertNil([documentCache entryForKey:key]);
}
for (FSTDocumentKey *key in toBeRetained) {
XCTAssertNotNil([documentCache entryForKey:key], @"Missing document %@", key);
}
}

@end

NS_ASSUME_NONNULL_END
2 changes: 1 addition & 1 deletion Firestore/Protos/objc/firestore/local/Target.pbobjc.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 Google
* Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion Firestore/Protos/objc/firestore/local/Target.pbobjc.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 Google
* Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
29 changes: 29 additions & 0 deletions Firestore/Source/Local/FSTLRUGarbageCollector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#import <Foundation/Foundation.h>
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing copyright (and that's why Travis failed your build).


#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
#import "Firestore/Source/Local/FSTWriteGroup.h"

@protocol FSTQueryCache;

extern const FSTListenSequenceNumber kFSTListenSequenceNumberInvalid;

@interface FSTLRUGarbageCollector : NSObject

- (instancetype)initWithQueryCache:(id<FSTQueryCache>)queryCache;

- (NSUInteger)queryCountForPercentile:(NSUInteger)percentile;

- (FSTListenSequenceNumber)sequenceNumberForQueryCount:(NSUInteger)queryCount;

- (NSUInteger)removeQueriesUpThroughSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
liveQueries:
(NSDictionary<NSNumber *, FSTQueryData *> *)liveQueries
group:(FSTWriteGroup *)group;

- (NSUInteger)removeOrphanedDocuments:(id<FSTRemoteDocumentCache>)remoteDocumentCache
mutationQueue:(id<FSTMutationQueue>)mutationQueue
group:(FSTWriteGroup *)group;

@end
Loading