Skip to content

Commit 8122d55

Browse files
author
Brian Chen
authored
Initial base classes for index backfill (#2950)
1 parent e41b233 commit 8122d55

File tree

12 files changed

+356
-26
lines changed

12 files changed

+356
-26
lines changed

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

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
import androidx.annotation.Nullable;
1919
import com.google.firebase.firestore.FirebaseFirestoreSettings;
2020
import com.google.firebase.firestore.auth.User;
21-
import com.google.firebase.firestore.local.GarbageCollectionScheduler;
2221
import com.google.firebase.firestore.local.LocalStore;
2322
import com.google.firebase.firestore.local.Persistence;
23+
import com.google.firebase.firestore.local.Scheduler;
2424
import com.google.firebase.firestore.remote.ConnectivityMonitor;
2525
import com.google.firebase.firestore.remote.Datastore;
2626
import com.google.firebase.firestore.remote.RemoteStore;
@@ -38,8 +38,9 @@ public abstract class ComponentProvider {
3838
private SyncEngine syncEngine;
3939
private RemoteStore remoteStore;
4040
private EventManager eventManager;
41-
private ConnectivityMonitor connectityMonitor;
42-
@Nullable private GarbageCollectionScheduler gargabeCollectionScheduler;
41+
private ConnectivityMonitor connectivityMonitor;
42+
@Nullable private Scheduler garbageCollectionScheduler;
43+
@Nullable private Scheduler indexBackfillScheduler;
4344

4445
/** Configuration options for the component provider. */
4546
public static class Configuration {
@@ -103,8 +104,13 @@ public Persistence getPersistence() {
103104
}
104105

105106
@Nullable
106-
public GarbageCollectionScheduler getGargabeCollectionScheduler() {
107-
return gargabeCollectionScheduler;
107+
public Scheduler getGarbageCollectionScheduler() {
108+
return garbageCollectionScheduler;
109+
}
110+
111+
@Nullable
112+
public Scheduler getIndexBackfillScheduler() {
113+
return indexBackfillScheduler;
108114
}
109115

110116
public LocalStore getLocalStore() {
@@ -124,24 +130,26 @@ public EventManager getEventManager() {
124130
}
125131

126132
protected ConnectivityMonitor getConnectivityMonitor() {
127-
return connectityMonitor;
133+
return connectivityMonitor;
128134
}
129135

130136
public void initialize(Configuration configuration) {
131137
persistence = createPersistence(configuration);
132138
persistence.start();
133139
localStore = createLocalStore(configuration);
134-
connectityMonitor = createConnectivityMonitor(configuration);
140+
connectivityMonitor = createConnectivityMonitor(configuration);
135141
remoteStore = createRemoteStore(configuration);
136142
syncEngine = createSyncEngine(configuration);
137143
eventManager = createEventManager(configuration);
138144
localStore.start();
139145
remoteStore.start();
140-
gargabeCollectionScheduler = createGarbageCollectionScheduler(configuration);
146+
garbageCollectionScheduler = createGarbageCollectionScheduler(configuration);
147+
indexBackfillScheduler = createIndexBackfillScheduler(configuration);
141148
}
142149

143-
protected abstract GarbageCollectionScheduler createGarbageCollectionScheduler(
144-
Configuration configuration);
150+
protected abstract Scheduler createGarbageCollectionScheduler(Configuration configuration);
151+
152+
protected abstract Scheduler createIndexBackfillScheduler(Configuration configuration);
145153

146154
protected abstract EventManager createEventManager(Configuration configuration);
147155

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
import com.google.firebase.firestore.bundle.BundleSerializer;
3333
import com.google.firebase.firestore.bundle.NamedQuery;
3434
import com.google.firebase.firestore.core.EventManager.ListenOptions;
35-
import com.google.firebase.firestore.local.GarbageCollectionScheduler;
3635
import com.google.firebase.firestore.local.LocalStore;
3736
import com.google.firebase.firestore.local.Persistence;
3837
import com.google.firebase.firestore.local.QueryResult;
38+
import com.google.firebase.firestore.local.Scheduler;
3939
import com.google.firebase.firestore.model.Document;
4040
import com.google.firebase.firestore.model.DocumentKey;
4141
import com.google.firebase.firestore.model.FieldIndex;
@@ -74,7 +74,9 @@ public final class FirestoreClient {
7474
private EventManager eventManager;
7575

7676
// LRU-related
77-
@Nullable private GarbageCollectionScheduler gcScheduler;
77+
@Nullable private Scheduler gcScheduler;
78+
79+
@Nullable private Scheduler indexBackfillScheduler;
7880

7981
public FirestoreClient(
8082
final Context context,
@@ -143,6 +145,10 @@ public Task<Void> terminate() {
143145
if (gcScheduler != null) {
144146
gcScheduler.stop();
145147
}
148+
149+
if (indexBackfillScheduler != null) {
150+
indexBackfillScheduler.stop();
151+
}
146152
});
147153
}
148154

@@ -255,7 +261,7 @@ private void initialize(Context context, User user, FirebaseFirestoreSettings se
255261
: new MemoryComponentProvider();
256262
provider.initialize(configuration);
257263
persistence = provider.getPersistence();
258-
gcScheduler = provider.getGargabeCollectionScheduler();
264+
gcScheduler = provider.getGarbageCollectionScheduler();
259265
localStore = provider.getLocalStore();
260266
remoteStore = provider.getRemoteStore();
261267
syncEngine = provider.getSyncEngine();
@@ -264,6 +270,12 @@ private void initialize(Context context, User user, FirebaseFirestoreSettings se
264270
if (gcScheduler != null) {
265271
gcScheduler.start();
266272
}
273+
274+
if (Persistence.INDEXING_SUPPORT_ENABLED && settings.isPersistenceEnabled()) {
275+
indexBackfillScheduler = provider.getIndexBackfillScheduler();
276+
hardAssert(indexBackfillScheduler != null, "Index backfill scheduler should not be null.");
277+
indexBackfillScheduler.start();
278+
}
267279
}
268280

269281
public void addSnapshotsInSyncListener(EventListener<Void> listener) {

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
import androidx.annotation.Nullable;
1818
import com.google.firebase.database.collection.ImmutableSortedSet;
1919
import com.google.firebase.firestore.local.DefaultQueryEngine;
20-
import com.google.firebase.firestore.local.GarbageCollectionScheduler;
2120
import com.google.firebase.firestore.local.LocalStore;
2221
import com.google.firebase.firestore.local.MemoryPersistence;
2322
import com.google.firebase.firestore.local.Persistence;
23+
import com.google.firebase.firestore.local.Scheduler;
2424
import com.google.firebase.firestore.model.DocumentKey;
2525
import com.google.firebase.firestore.model.mutation.MutationBatchResult;
2626
import com.google.firebase.firestore.remote.AndroidConnectivityMonitor;
@@ -36,8 +36,13 @@ public class MemoryComponentProvider extends ComponentProvider {
3636

3737
@Override
3838
@Nullable
39-
protected GarbageCollectionScheduler createGarbageCollectionScheduler(
40-
Configuration configuration) {
39+
protected Scheduler createGarbageCollectionScheduler(Configuration configuration) {
40+
return null;
41+
}
42+
43+
@Override
44+
@Nullable
45+
protected Scheduler createIndexBackfillScheduler(Configuration configuration) {
4146
return null;
4247
}
4348

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,31 @@
1414

1515
package com.google.firebase.firestore.core;
1616

17-
import com.google.firebase.firestore.local.GarbageCollectionScheduler;
17+
import com.google.firebase.firestore.local.IndexBackfiller;
1818
import com.google.firebase.firestore.local.LocalSerializer;
1919
import com.google.firebase.firestore.local.LruDelegate;
2020
import com.google.firebase.firestore.local.LruGarbageCollector;
2121
import com.google.firebase.firestore.local.Persistence;
2222
import com.google.firebase.firestore.local.SQLitePersistence;
23+
import com.google.firebase.firestore.local.Scheduler;
2324
import com.google.firebase.firestore.remote.RemoteSerializer;
2425

2526
/** Provides all components needed for Firestore with SQLite persistence. */
2627
public class SQLiteComponentProvider extends MemoryComponentProvider {
2728

2829
@Override
29-
protected GarbageCollectionScheduler createGarbageCollectionScheduler(
30-
Configuration configuration) {
30+
protected Scheduler createGarbageCollectionScheduler(Configuration configuration) {
3131
LruDelegate lruDelegate = ((SQLitePersistence) getPersistence()).getReferenceDelegate();
3232
LruGarbageCollector gc = lruDelegate.getGarbageCollector();
3333
return gc.newScheduler(configuration.getAsyncQueue(), getLocalStore());
3434
}
3535

36+
@Override
37+
protected Scheduler createIndexBackfillScheduler(Configuration configuration) {
38+
IndexBackfiller indexBackfiller = ((SQLitePersistence) getPersistence()).getIndexBackfiller();
39+
return indexBackfiller.newScheduler(configuration.getAsyncQueue(), getLocalStore());
40+
}
41+
3642
@Override
3743
protected Persistence createPersistence(Configuration configuration) {
3844
LocalSerializer serializer =
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.index;
16+
17+
/**
18+
* Represents an index entry saved by the SDK in the local storage. Temporary placeholder, since
19+
* we'll probably serialize the indexValue right away rather than store it.
20+
*/
21+
// TODO(indexing)
22+
public class IndexEntry {
23+
private final int indexId;
24+
private final byte[] indexValue;
25+
private final String uid;
26+
private final String documentId;
27+
28+
public IndexEntry(int indexId, byte[] indexValue, String uid, String documentId) {
29+
this.indexId = indexId;
30+
this.indexValue = indexValue;
31+
this.uid = uid;
32+
this.documentId = documentId;
33+
}
34+
35+
public int getIndexId() {
36+
return indexId;
37+
}
38+
39+
public byte[] getIndexValue() {
40+
return indexValue;
41+
}
42+
43+
public String getUid() {
44+
return uid;
45+
}
46+
47+
public String getDocumentId() {
48+
return documentId;
49+
}
50+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.local;
16+
17+
import androidx.annotation.Nullable;
18+
import androidx.annotation.VisibleForTesting;
19+
import com.google.firebase.firestore.index.IndexEntry;
20+
import com.google.firebase.firestore.util.AsyncQueue;
21+
import java.util.concurrent.TimeUnit;
22+
23+
/** Implements the steps for backfilling indexes. */
24+
public class IndexBackfiller {
25+
/** How long we wait to try running index backfill after SDK initialization. */
26+
private static final long INITIAL_BACKFILL_DELAY_MS = TimeUnit.SECONDS.toMillis(15);
27+
/** Minimum amount of time between backfill checks, after the first one. */
28+
private static final long REGULAR_BACKFILL_DELAY_MS = TimeUnit.MINUTES.toMillis(1);
29+
30+
private final SQLitePersistence persistence;
31+
32+
public IndexBackfiller(SQLitePersistence sqLitePersistence) {
33+
this.persistence = sqLitePersistence;
34+
}
35+
36+
public static class Results {
37+
private final boolean hasRun;
38+
39+
private final int entriesAdded;
40+
private final int entriesRemoved;
41+
42+
static IndexBackfiller.Results DidNotRun() {
43+
return new IndexBackfiller.Results(/* hasRun= */ false, 0, 0);
44+
}
45+
46+
Results(boolean hasRun, int entriesAdded, int entriesRemoved) {
47+
this.hasRun = hasRun;
48+
this.entriesAdded = entriesAdded;
49+
this.entriesRemoved = entriesRemoved;
50+
}
51+
52+
public boolean hasRun() {
53+
return hasRun;
54+
}
55+
56+
public int getEntriesAdded() {
57+
return entriesAdded;
58+
}
59+
60+
public int getEntriesRemoved() {
61+
return entriesRemoved;
62+
}
63+
}
64+
65+
public class BackfillScheduler implements Scheduler {
66+
private final AsyncQueue asyncQueue;
67+
private final LocalStore localStore;
68+
private boolean hasRun = false;
69+
@Nullable private AsyncQueue.DelayedTask backfillTask;
70+
71+
public BackfillScheduler(AsyncQueue asyncQueue, LocalStore localStore) {
72+
this.asyncQueue = asyncQueue;
73+
this.localStore = localStore;
74+
}
75+
76+
@Override
77+
public void start() {
78+
scheduleBackfill();
79+
}
80+
81+
@Override
82+
public void stop() {
83+
if (backfillTask != null) {
84+
backfillTask.cancel();
85+
}
86+
}
87+
88+
private void scheduleBackfill() {
89+
long delay = hasRun ? REGULAR_BACKFILL_DELAY_MS : INITIAL_BACKFILL_DELAY_MS;
90+
backfillTask =
91+
asyncQueue.enqueueAfterDelay(
92+
AsyncQueue.TimerId.INDEX_BACKFILL,
93+
delay,
94+
() -> {
95+
localStore.backfillIndexes(IndexBackfiller.this);
96+
hasRun = true;
97+
scheduleBackfill();
98+
});
99+
}
100+
}
101+
102+
public BackfillScheduler newScheduler(AsyncQueue asyncQueue, LocalStore localStore) {
103+
return new BackfillScheduler(asyncQueue, localStore);
104+
}
105+
106+
// TODO(indexing): Figure out which index entries to backfill.
107+
public Results backfill() {
108+
int numIndexesWritten = 0;
109+
int numIndexesRemoved = 0;
110+
return new Results(/* hasRun= */ true, numIndexesWritten, numIndexesRemoved);
111+
}
112+
113+
@VisibleForTesting
114+
void addIndexEntry(IndexEntry entry) {
115+
persistence.execute(
116+
"INSERT OR IGNORE INTO index_entries ("
117+
+ "index_id, "
118+
+ "index_value, "
119+
+ "uid, "
120+
+ "document_id) VALUES(?, ?, ?, ?)",
121+
entry.getIndexId(),
122+
entry.getIndexValue(),
123+
entry.getUid(),
124+
entry.getDocumentId());
125+
}
126+
127+
@VisibleForTesting
128+
void removeIndexEntry(int indexId, String uid, String documentId) {
129+
persistence.execute(
130+
"DELETE FROM index_entries "
131+
+ "WHERE index_id = ? "
132+
+ "AND uid = ?"
133+
+ "AND document_id = ?",
134+
indexId,
135+
uid,
136+
documentId);
137+
;
138+
}
139+
140+
@Nullable
141+
@VisibleForTesting
142+
IndexEntry getIndexEntry(int indexId) {
143+
return persistence
144+
.query("SELECT index_value, uid, document_id FROM index_entries WHERE index_id = ?")
145+
.binding(indexId)
146+
.firstValue(
147+
row ->
148+
row == null
149+
? null
150+
: new IndexEntry(indexId, row.getBlob(0), row.getString(1), row.getString(2)));
151+
}
152+
}

0 commit comments

Comments
 (0)