FPersistenceManager.m 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FPersistenceManager.h"
  17. #import "FLevelDBStorageEngine.h"
  18. #import "FCacheNode.h"
  19. #import "FIndexedNode.h"
  20. #import "FTrackedQueryManager.h"
  21. #import "FTrackedQuery.h"
  22. #import "FUtilities.h"
  23. #import "FPruneForest.h"
  24. #import "FClock.h"
  25. @interface FPersistenceManager ()
  26. @property (nonatomic, strong) id<FStorageEngine> storageEngine;
  27. @property (nonatomic, strong) id<FCachePolicy> cachePolicy;
  28. @property (nonatomic, strong) FTrackedQueryManager *trackedQueryManager;
  29. @property (nonatomic) NSUInteger serverCacheUpdatesSinceLastPruneCheck;
  30. @end
  31. @implementation FPersistenceManager
  32. - (id)initWithStorageEngine:(id<FStorageEngine>)storageEngine cachePolicy:(id<FCachePolicy>)cachePolicy {
  33. self = [super init];
  34. if (self != nil) {
  35. self->_storageEngine = storageEngine;
  36. self->_cachePolicy = cachePolicy;
  37. self->_trackedQueryManager = [[FTrackedQueryManager alloc] initWithStorageEngine:self.storageEngine
  38. clock:[FSystemClock clock]];
  39. }
  40. return self;
  41. }
  42. - (void)close {
  43. [self.storageEngine close];
  44. self.storageEngine = nil;
  45. self.trackedQueryManager = nil;
  46. }
  47. - (void)saveUserOverwrite:(id<FNode>)node atPath:(FPath *)path writeId:(NSUInteger)writeId {
  48. [self.storageEngine saveUserOverwrite:node atPath:path writeId:writeId];
  49. }
  50. - (void)saveUserMerge:(FCompoundWrite *)merge atPath:(FPath *)path writeId:(NSUInteger)writeId {
  51. [self.storageEngine saveUserMerge:merge atPath:path writeId:writeId];
  52. }
  53. - (void)removeUserWrite:(NSUInteger)writeId {
  54. [self.storageEngine removeUserWrite:writeId];
  55. }
  56. - (void)removeAllUserWrites {
  57. [self.storageEngine removeAllUserWrites];
  58. }
  59. - (NSArray *)userWrites {
  60. return [self.storageEngine userWrites];
  61. }
  62. - (FCacheNode *)serverCacheForQuery:(FQuerySpec *)query {
  63. NSSet *trackedKeys;
  64. BOOL complete;
  65. // TODO[offline]: Should we use trackedKeys to find out if this location is a child of a complete query?
  66. if ([self.trackedQueryManager isQueryComplete:query]) {
  67. complete = YES;
  68. FTrackedQuery *trackedQuery = [self.trackedQueryManager findTrackedQuery:query];
  69. if (!query.loadsAllData && trackedQuery.isComplete) {
  70. trackedKeys = [self.storageEngine trackedQueryKeysForQuery:trackedQuery.queryId];
  71. } else {
  72. trackedKeys = nil;
  73. }
  74. } else {
  75. complete = NO;
  76. trackedKeys = [self.trackedQueryManager knownCompleteChildrenAtPath:query.path];
  77. }
  78. id<FNode> node;
  79. if (trackedKeys != nil) {
  80. node = [self.storageEngine serverCacheForKeys:trackedKeys atPath:query.path];
  81. } else {
  82. node = [self.storageEngine serverCacheAtPath:query.path];
  83. }
  84. FIndexedNode *indexedNode = [FIndexedNode indexedNodeWithNode:node index:query.index];
  85. return [[FCacheNode alloc] initWithIndexedNode:indexedNode isFullyInitialized:complete isFiltered:(trackedKeys != nil)];
  86. }
  87. - (void)updateServerCacheWithNode:(id<FNode>)node forQuery:(FQuerySpec *)query {
  88. BOOL merge = !query.loadsAllData;
  89. [self.storageEngine updateServerCache:node atPath:query.path merge:merge];
  90. [self setQueryComplete:query];
  91. [self doPruneCheckAfterServerUpdate];
  92. }
  93. - (void)updateServerCacheWithMerge:(FCompoundWrite *)merge atPath:(FPath *)path {
  94. [self.storageEngine updateServerCacheWithMerge:merge atPath:path];
  95. [self doPruneCheckAfterServerUpdate];
  96. }
  97. - (void)applyUserMerge:(FCompoundWrite *)merge toServerCacheAtPath:(FPath *)path {
  98. // TODO[offline]: rework this to be more efficient
  99. [merge enumerateWrites:^(FPath *relativePath, id<FNode> node, BOOL *stop) {
  100. [self applyUserWrite:node toServerCacheAtPath:[path child:relativePath]];
  101. }];
  102. }
  103. - (void)applyUserWrite:(id<FNode>)write toServerCacheAtPath:(FPath *)path {
  104. // This is a hack to guess whether we already cached this because we got a server data update for this
  105. // write via an existing active default query. If we didn't, then we'll manually cache this and add a
  106. // tracked query to mark it complete and keep it cached.
  107. // Unfortunately this is just a guess and it's possible that we *did* get an update (e.g. via a filtered
  108. // query) and by overwriting the cache here, we'll actually store an incorrect value (e.g. in the case
  109. // that we wrote a ServerValue.TIMESTAMP and the server resolved it to a different value).
  110. // TODO[offline]: Consider reworking.
  111. if (![self.trackedQueryManager hasActiveDefaultQueryAtPath:path]) {
  112. [self.storageEngine updateServerCache:write atPath:path merge:NO];
  113. [self.trackedQueryManager ensureCompleteTrackedQueryAtPath:path];
  114. }
  115. }
  116. - (void)setQueryComplete:(FQuerySpec *)query {
  117. if (query.loadsAllData) {
  118. [self.trackedQueryManager setQueriesCompleteAtPath:query.path];
  119. } else {
  120. [self.trackedQueryManager setQueryComplete:query];
  121. }
  122. }
  123. - (void)setQueryActive:(FQuerySpec *)spec {
  124. [self.trackedQueryManager setQueryActive:spec];
  125. }
  126. - (void)setQueryInactive:(FQuerySpec *)spec {
  127. [self.trackedQueryManager setQueryInactive:spec];
  128. }
  129. - (void)doPruneCheckAfterServerUpdate {
  130. self.serverCacheUpdatesSinceLastPruneCheck++;
  131. if ([self.cachePolicy shouldCheckCacheSize:self.serverCacheUpdatesSinceLastPruneCheck]) {
  132. FFDebug(@"I-RDB078001", @"Reached prune check threshold. Checking...");
  133. NSDate *date = [NSDate date];
  134. self.serverCacheUpdatesSinceLastPruneCheck = 0;
  135. BOOL canPrune = YES;
  136. NSUInteger cacheSize = [self.storageEngine serverCacheEstimatedSizeInBytes];
  137. FFDebug(@"I-RDB078002", @"Server cache size: %lu", (unsigned long)cacheSize);
  138. while (canPrune && [self.cachePolicy shouldPruneCacheWithSize:cacheSize
  139. numberOfTrackedQueries:self.trackedQueryManager.numberOfPrunableQueries]) {
  140. FPruneForest *pruneForest = [self.trackedQueryManager pruneOldQueries:self.cachePolicy];
  141. if (pruneForest.prunesAnything) {
  142. [self.storageEngine pruneCache:pruneForest atPath:[FPath empty]];
  143. } else {
  144. canPrune = NO;
  145. }
  146. cacheSize = [self.storageEngine serverCacheEstimatedSizeInBytes];
  147. FFDebug(@"I-RDB078003", @"Cache size after pruning: %lu", (unsigned long)cacheSize);
  148. }
  149. FFDebug(@"I-RDB078004", @"Pruning round took %fms", [date timeIntervalSinceNow]*-1000);
  150. }
  151. }
  152. - (void)setTrackedQueryKeys:(NSSet *)keys forQuery:(FQuerySpec *)query {
  153. NSAssert(!query.loadsAllData, @"We should only track keys for filtered queries");
  154. FTrackedQuery *trackedQuery = [self.trackedQueryManager findTrackedQuery:query];
  155. NSAssert(trackedQuery.isActive, @"We only expect tracked keys for currently-active queries.");
  156. [self.storageEngine setTrackedQueryKeys:keys forQueryId:trackedQuery.queryId];
  157. }
  158. - (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added removedKeys:(NSSet *)removed forQuery:(FQuerySpec *)query {
  159. NSAssert(!query.loadsAllData, @"We should only track keys for filtered queries");
  160. FTrackedQuery *trackedQuery = [self.trackedQueryManager findTrackedQuery:query];
  161. NSAssert(trackedQuery.isActive, @"We only expect tracked keys for currently-active queries.");
  162. [self.storageEngine updateTrackedQueryKeysWithAddedKeys:added removedKeys:removed forQueryId:trackedQuery.queryId];
  163. }
  164. @end