FPersistenceManager.m 7.8 KB

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