FPersistenceManager.m 8.9 KB

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