Skip to content

Commit c627b48

Browse files
author
Greg Soltis
authored
Enable LRU GC (#68)
* Enable LRU GC * Add public api and comment to CACHE_SIZE_UNLIMITED * Switch default for LRU collection to disabled
1 parent 51fb015 commit c627b48

19 files changed

+559
-49
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,7 @@ private void ensureClientConfigured() {
220220
new DatabaseInfo(databaseId, persistenceKey, settings.getHost(), settings.isSslEnabled());
221221

222222
client =
223-
new FirestoreClient(
224-
context,
225-
databaseInfo,
226-
settings.isPersistenceEnabled(),
227-
credentialsProvider,
228-
asyncQueue);
223+
new FirestoreClient(context, databaseInfo, settings, credentialsProvider, asyncQueue);
229224
}
230225
}
231226

firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestoreSettings.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@
2424
/** Settings used to configure a FirebaseFirestore instance. */
2525
@PublicApi
2626
public final class FirebaseFirestoreSettings {
27+
/**
28+
* Constant to use with {@link FirebaseFirestoreSettings.Builder#setCacheSizeBytes(long)} to
29+
* disable garbage collection.
30+
*/
31+
@PublicApi public static final long CACHE_SIZE_UNLIMITED = -1;
32+
33+
private static final long MINIMUM_CACHE_BYTES = 1 * 1024 * 1024; // 1 MB
34+
// TODO(b/121269744): Set this to be the default value after SDK is past version 1.0
35+
// private static final long DEFAULT_CACHE_SIZE_BYTES = 100 * 1024 * 1024; // 100 MB
36+
// For now, we are rolling this out with collection disabled. Once the SDK has hit version 1.0,
37+
// we will switch the default to the above value, 100 MB.
38+
private static final long DEFAULT_CACHE_SIZE_BYTES = CACHE_SIZE_UNLIMITED;
2739
private static final String DEFAULT_HOST = "firestore.googleapis.com";
2840
private static final boolean DEFAULT_TIMESTAMPS_IN_SNAPSHOTS_ENABLED = false;
2941

@@ -34,6 +46,7 @@ public static final class Builder {
3446
private boolean sslEnabled;
3547
private boolean persistenceEnabled;
3648
private boolean timestampsInSnapshotsEnabled;
49+
private long cacheSizeBytes;
3750

3851
/** Constructs a new FirebaseFirestoreSettings Builder object. */
3952
@PublicApi
@@ -42,6 +55,7 @@ public Builder() {
4255
sslEnabled = true;
4356
persistenceEnabled = true;
4457
timestampsInSnapshotsEnabled = DEFAULT_TIMESTAMPS_IN_SNAPSHOTS_ENABLED;
58+
cacheSizeBytes = DEFAULT_CACHE_SIZE_BYTES;
4559
}
4660

4761
/**
@@ -124,6 +138,30 @@ public Builder setTimestampsInSnapshotsEnabled(boolean value) {
124138
return this;
125139
}
126140

141+
/**
142+
* Sets an approximate cache size threshold for the on-disk data. If the cache grows beyond this
143+
* size, Firestore will start removing data that hasn't been recently used. The size is not a
144+
* guarantee that the cache will stay below that size, only that if the cache exceeds the given
145+
* size, cleanup will be attempted.
146+
*
147+
* <p>By default, collection is disabled (the value is set to {@link
148+
* FirebaseFirestoreSettings#CACHE_SIZE_UNLIMITED}). In a future release, collection will be
149+
* enabled by default, with a default cache size of 100 MB. The minimum value is 1 MB.
150+
*
151+
* @return A settings object on which the cache size is configured as specified by the given
152+
* {@code value}.
153+
*/
154+
@NonNull
155+
@PublicApi
156+
public Builder setCacheSizeBytes(long value) {
157+
if (value != CACHE_SIZE_UNLIMITED && value < MINIMUM_CACHE_BYTES) {
158+
throw new IllegalArgumentException(
159+
"Cache size must be set to at least " + MINIMUM_CACHE_BYTES + " bytes");
160+
}
161+
this.cacheSizeBytes = value;
162+
return this;
163+
}
164+
127165
@NonNull
128166
@PublicApi
129167
public FirebaseFirestoreSettings build() {
@@ -139,13 +177,15 @@ public FirebaseFirestoreSettings build() {
139177
private final boolean sslEnabled;
140178
private final boolean persistenceEnabled;
141179
private final boolean timestampsInSnapshotsEnabled;
180+
private final long cacheSizeBytes;
142181

143182
/** Constructs a FirebaseFirestoreSettings object based on the values in the Builder. */
144183
private FirebaseFirestoreSettings(Builder builder) {
145184
host = builder.host;
146185
sslEnabled = builder.sslEnabled;
147186
persistenceEnabled = builder.persistenceEnabled;
148187
timestampsInSnapshotsEnabled = builder.timestampsInSnapshotsEnabled;
188+
cacheSizeBytes = builder.cacheSizeBytes;
149189
}
150190

151191
@Override
@@ -161,7 +201,8 @@ public boolean equals(@Nullable Object o) {
161201
return host.equals(that.host)
162202
&& sslEnabled == that.sslEnabled
163203
&& persistenceEnabled == that.persistenceEnabled
164-
&& timestampsInSnapshotsEnabled == that.timestampsInSnapshotsEnabled;
204+
&& timestampsInSnapshotsEnabled == that.timestampsInSnapshotsEnabled
205+
&& cacheSizeBytes == that.cacheSizeBytes;
165206
}
166207

167208
@Override
@@ -170,6 +211,7 @@ public int hashCode() {
170211
result = 31 * result + (sslEnabled ? 1 : 0);
171212
result = 31 * result + (persistenceEnabled ? 1 : 0);
172213
result = 31 * result + (timestampsInSnapshotsEnabled ? 1 : 0);
214+
result = 31 * result + (int) cacheSizeBytes;
173215
return result;
174216
}
175217

@@ -211,4 +253,13 @@ public boolean isPersistenceEnabled() {
211253
public boolean areTimestampsInSnapshotsEnabled() {
212254
return timestampsInSnapshotsEnabled;
213255
}
256+
257+
/**
258+
* Returns the threshold for the cache size above which the SDK will attempt to collect the least
259+
* recently used documents.
260+
*/
261+
@PublicApi
262+
public long getCacheSizeBytes() {
263+
return cacheSizeBytes;
264+
}
214265
}

firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@
2727
import com.google.firebase.firestore.EventListener;
2828
import com.google.firebase.firestore.FirebaseFirestoreException;
2929
import com.google.firebase.firestore.FirebaseFirestoreException.Code;
30+
import com.google.firebase.firestore.FirebaseFirestoreSettings;
3031
import com.google.firebase.firestore.auth.CredentialsProvider;
3132
import com.google.firebase.firestore.auth.User;
3233
import com.google.firebase.firestore.core.EventManager.ListenOptions;
3334
import com.google.firebase.firestore.local.LocalSerializer;
3435
import com.google.firebase.firestore.local.LocalStore;
36+
import com.google.firebase.firestore.local.LruDelegate;
37+
import com.google.firebase.firestore.local.LruGarbageCollector;
3538
import com.google.firebase.firestore.local.MemoryPersistence;
3639
import com.google.firebase.firestore.local.Persistence;
3740
import com.google.firebase.firestore.local.SQLitePersistence;
@@ -71,10 +74,13 @@ public final class FirestoreClient implements RemoteStore.RemoteStoreCallback {
7174
private SyncEngine syncEngine;
7275
private EventManager eventManager;
7376

77+
// LRU-related
78+
@Nullable private LruGarbageCollector.Scheduler lruScheduler;
79+
7480
public FirestoreClient(
7581
final Context context,
7682
DatabaseInfo databaseInfo,
77-
final boolean usePersistence,
83+
FirebaseFirestoreSettings settings,
7884
CredentialsProvider credentialsProvider,
7985
final AsyncQueue asyncQueue) {
8086
this.databaseInfo = databaseInfo;
@@ -105,7 +111,11 @@ public FirestoreClient(
105111
try {
106112
// Block on initial user being available
107113
User initialUser = Tasks.await(firstUser.getTask());
108-
initialize(context, initialUser, usePersistence);
114+
initialize(
115+
context,
116+
initialUser,
117+
settings.isPersistenceEnabled(),
118+
settings.getCacheSizeBytes());
109119
} catch (InterruptedException | ExecutionException e) {
110120
throw new RuntimeException(e);
111121
}
@@ -127,6 +137,9 @@ public Task<Void> shutdown() {
127137
() -> {
128138
remoteStore.shutdown();
129139
persistence.shutdown();
140+
if (lruScheduler != null) {
141+
lruScheduler.stop();
142+
}
130143
});
131144
}
132145

@@ -194,24 +207,38 @@ public <TResult> Task<TResult> transaction(
194207
() -> syncEngine.transaction(asyncQueue, updateFunction, retries));
195208
}
196209

197-
private void initialize(Context context, User user, boolean usePersistence) {
210+
private void initialize(Context context, User user, boolean usePersistence, long cacheSizeBytes) {
198211
// Note: The initialization work must all be synchronous (we can't dispatch more work) since
199212
// external write/listen operations could get queued to run before that subsequent work
200213
// completes.
201214
Logger.debug(LOG_TAG, "Initializing. user=%s", user.getUid());
202215

216+
LruGarbageCollector gc = null;
203217
if (usePersistence) {
204218
LocalSerializer serializer =
205219
new LocalSerializer(new RemoteSerializer(databaseInfo.getDatabaseId()));
206-
persistence =
220+
LruGarbageCollector.Params params =
221+
LruGarbageCollector.Params.WithCacheSizeBytes(cacheSizeBytes);
222+
SQLitePersistence sqlitePersistence =
207223
new SQLitePersistence(
208-
context, databaseInfo.getPersistenceKey(), databaseInfo.getDatabaseId(), serializer);
224+
context,
225+
databaseInfo.getPersistenceKey(),
226+
databaseInfo.getDatabaseId(),
227+
serializer,
228+
params);
229+
LruDelegate lruDelegate = sqlitePersistence.getReferenceDelegate();
230+
gc = lruDelegate.getGarbageCollector();
231+
persistence = sqlitePersistence;
209232
} else {
210233
persistence = MemoryPersistence.createEagerGcMemoryPersistence();
211234
}
212235

213236
persistence.start();
214237
localStore = new LocalStore(persistence, user);
238+
if (gc != null) {
239+
lruScheduler = gc.newScheduler(asyncQueue, localStore);
240+
lruScheduler.start();
241+
}
215242

216243
Datastore datastore = new Datastore(databaseInfo, asyncQueue, credentialsProvider, context);
217244
remoteStore = new RemoteStore(this, localStore, datastore, asyncQueue);

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LocalStore.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,4 +570,8 @@ private void applyWriteToRemoteDocuments(MutationBatchResult batchResult) {
570570

571571
mutationQueue.removeMutationBatch(batch);
572572
}
573+
574+
public LruGarbageCollector.Results collectGarbage(LruGarbageCollector garbageCollector) {
575+
return persistence.runTransaction("Collect garbage", () -> garbageCollector.collect(targetIds));
576+
}
573577
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LruDelegate.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
* Persistence layers intending to use LRU Garbage collection should implement this interface. This
2222
* interface defines the operations that the LRU garbage collector needs from the persistence layer.
2323
*/
24-
interface LruDelegate {
24+
public interface LruDelegate {
2525

2626
/** Enumerates all the targets in the QueryCache. */
2727
void forEachTarget(Consumer<QueryData> consumer);
2828

29-
long getTargetCount();
29+
long getSequenceNumberCount();
3030

3131
/** Enumerates sequence numbers for documents not associated with a target. */
3232
void forEachOrphanedDocumentSequenceNumber(Consumer<Long> consumer);
@@ -49,4 +49,7 @@ interface LruDelegate {
4949

5050
/** Access to the underlying LRU Garbage collector instance. */
5151
LruGarbageCollector getGarbageCollector();
52+
53+
/** Return the size of the cache in bytes. */
54+
long getByteSize();
5255
}

0 commit comments

Comments
 (0)