Skip to content

Commit d69fbd5

Browse files
Store per-user state for indexes
1 parent 4e336c6 commit d69fbd5

19 files changed

+421
-410
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import com.google.firebase.firestore.model.FieldIndex;
4545
import com.google.firebase.firestore.model.FieldPath;
4646
import com.google.firebase.firestore.model.ResourcePath;
47-
import com.google.firebase.firestore.model.SnapshotVersion;
4847
import com.google.firebase.firestore.remote.FirestoreChannel;
4948
import com.google.firebase.firestore.remote.GrpcMetadataProvider;
5049
import com.google.firebase.firestore.util.AsyncQueue;
@@ -340,7 +339,8 @@ Task<Void> configureIndices(String json) {
340339
}
341340
}
342341

343-
parsedIndices.add(FieldIndex.create(-1, collectionGroup, segments, SnapshotVersion.NONE));
342+
parsedIndices.add(
343+
FieldIndex.create(-1, collectionGroup, segments, FieldIndex.IndexState.DEFAULT));
344344
}
345345
}
346346
} catch (JSONException e) {

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

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import androidx.annotation.Nullable;
2020
import androidx.annotation.VisibleForTesting;
21-
import com.google.firebase.Timestamp;
2221
import com.google.firebase.database.collection.ImmutableSortedMap;
2322
import com.google.firebase.firestore.core.Query;
2423
import com.google.firebase.firestore.model.Document;
@@ -27,10 +26,11 @@
2726
import com.google.firebase.firestore.model.ResourcePath;
2827
import com.google.firebase.firestore.model.SnapshotVersion;
2928
import com.google.firebase.firestore.util.AsyncQueue;
29+
import java.util.ArrayList;
3030
import java.util.Collection;
31+
import java.util.Collections;
32+
import java.util.List;
3133
import java.util.Map;
32-
import java.util.PriorityQueue;
33-
import java.util.Queue;
3434
import java.util.concurrent.TimeUnit;
3535

3636
/** Implements the steps for backfilling indexes. */
@@ -44,13 +44,16 @@ public class IndexBackfiller {
4444

4545
private final Scheduler scheduler;
4646
private final Persistence persistence;
47+
private final RemoteDocumentCache remoteDocumentCache;
4748
private LocalDocumentsView localDocumentsView;
4849
private IndexManager indexManager;
4950
private int maxDocumentsToProcess = MAX_DOCUMENTS_TO_PROCESS;
51+
private long currentSequenceNumber = -1;
5052

5153
public IndexBackfiller(Persistence persistence, AsyncQueue asyncQueue) {
5254
this.persistence = persistence;
5355
this.scheduler = new Scheduler(asyncQueue);
56+
this.remoteDocumentCache = persistence.getRemoteDocumentCache();
5457
}
5558

5659
public void setLocalDocumentsView(LocalDocumentsView localDocumentsView) {
@@ -131,7 +134,7 @@ public Results backfill() {
131134
return persistence.runTransaction(
132135
"Backfill Indexes",
133136
() -> {
134-
// TODO(indexing): Handle field indexes that are removed by the user.
137+
currentSequenceNumber = indexManager.getHighestSequenceNumber() + 1;
135138
int documentsProcessed = writeIndexEntries(localDocumentsView);
136139
return new Results(/* hasRun= */ true, documentsProcessed);
137140
});
@@ -140,11 +143,10 @@ public Results backfill() {
140143
/** Writes index entries until the cap is reached. Returns the number of documents processed. */
141144
private int writeIndexEntries(LocalDocumentsView localDocumentsView) {
142145
int documentsProcessed = 0;
143-
Timestamp startingTimestamp = Timestamp.now();
144146

145147
while (documentsProcessed < maxDocumentsToProcess) {
146148
int documentsRemaining = maxDocumentsToProcess - documentsProcessed;
147-
String collectionGroup = indexManager.getNextCollectionGroupToUpdate(startingTimestamp);
149+
String collectionGroup = indexManager.getNextCollectionGroupToUpdate(currentSequenceNumber);
148150
if (collectionGroup == null) {
149151
break;
150152
}
@@ -160,42 +162,61 @@ private int writeEntriesForCollectionGroup(
160162
LocalDocumentsView localDocumentsView, String collectionGroup, int entriesRemainingUnderCap) {
161163
Query query = new Query(ResourcePath.EMPTY, collectionGroup);
162164

163-
// Use the earliest updateTime of all field indexes as the base updateTime.
164-
SnapshotVersion earliestUpdateTime =
165-
getEarliestUpdateTime(indexManager.getFieldIndexes(collectionGroup));
165+
// Use the earliest readTime of all field indexes as the base readtime.
166+
SnapshotVersion earliestReadTime =
167+
getEarliestReadTime(indexManager.getFieldIndexes(collectionGroup));
166168

167169
// TODO(indexing): Use limit queries to only fetch the required number of entries.
168170
// TODO(indexing): Support mutation batch Ids when sorting and writing indexes.
169171
ImmutableSortedMap<DocumentKey, Document> documents =
170-
localDocumentsView.getDocumentsMatchingQuery(query, earliestUpdateTime);
172+
localDocumentsView.getDocumentsMatchingQuery(query, earliestReadTime);
171173

172-
Queue<Document> oldestDocuments = getOldestDocuments(documents, entriesRemainingUnderCap);
174+
List<Document> oldestDocuments = getOldestDocuments(documents, entriesRemainingUnderCap);
173175
indexManager.updateIndexEntries(oldestDocuments);
176+
177+
SnapshotVersion latestReadTime = getPostUpdateReadTime(oldestDocuments, earliestReadTime);
178+
indexManager.updateCollectionGroup(collectionGroup, currentSequenceNumber, latestReadTime);
174179
return oldestDocuments.size();
175180
}
176181

182+
/** Returns the new read time for the index. */
183+
private SnapshotVersion getPostUpdateReadTime(
184+
List<Document> documents, SnapshotVersion currentReadTime) {
185+
SnapshotVersion latestReadTime =
186+
documents.isEmpty()
187+
? remoteDocumentCache.getLatestReadTime()
188+
: documents.get(documents.size() - 1).getReadTime();
189+
// Make sure the index does not go back in time
190+
latestReadTime =
191+
latestReadTime.compareTo(currentReadTime) > 0 ? latestReadTime : currentReadTime;
192+
return latestReadTime;
193+
}
194+
177195
/** Returns up to {@code count} documents sorted by read time. */
178-
private Queue<Document> getOldestDocuments(
196+
private List<Document> getOldestDocuments(
179197
ImmutableSortedMap<DocumentKey, Document> documents, int count) {
180-
Queue<Document> oldestDocuments =
181-
new PriorityQueue<>(count + 1, (l, r) -> r.getReadTime().compareTo(l.getReadTime()));
198+
List<Document> oldestDocuments = new ArrayList<>();
182199
for (Map.Entry<DocumentKey, Document> entry : documents) {
183200
oldestDocuments.add(entry.getValue());
184-
if (oldestDocuments.size() > count) {
185-
oldestDocuments.poll();
186-
}
187201
}
188-
return oldestDocuments;
202+
Collections.sort(
203+
oldestDocuments,
204+
(l, r) -> {
205+
int cmp = l.getReadTime().compareTo(r.getReadTime());
206+
if (cmp != 0) return cmp;
207+
return l.getKey().compareTo(r.getKey());
208+
});
209+
return oldestDocuments.subList(0, Math.min(count, oldestDocuments.size()));
189210
}
190211

191-
private SnapshotVersion getEarliestUpdateTime(Collection<FieldIndex> fieldIndexes) {
212+
private SnapshotVersion getEarliestReadTime(Collection<FieldIndex> fieldIndexes) {
192213
SnapshotVersion lowestVersion = null;
193214
for (FieldIndex fieldIndex : fieldIndexes) {
194215
lowestVersion =
195216
lowestVersion == null
196-
? fieldIndex.getUpdateTime()
197-
: fieldIndex.getUpdateTime().compareTo(lowestVersion) < 0
198-
? fieldIndex.getUpdateTime()
217+
? fieldIndex.getIndexState().getReadTime()
218+
: fieldIndex.getIndexState().getReadTime().compareTo(lowestVersion) < 0
219+
? fieldIndex.getIndexState().getReadTime()
199220
: lowestVersion;
200221
}
201222
return lowestVersion;

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
package com.google.firebase.firestore.local;
1616

1717
import androidx.annotation.Nullable;
18-
import com.google.firebase.Timestamp;
1918
import com.google.firebase.firestore.core.Target;
2019
import com.google.firebase.firestore.model.Document;
2120
import com.google.firebase.firestore.model.DocumentKey;
2221
import com.google.firebase.firestore.model.FieldIndex;
2322
import com.google.firebase.firestore.model.ResourcePath;
23+
import com.google.firebase.firestore.model.SnapshotVersion;
2424
import java.util.Collection;
2525
import java.util.List;
2626
import java.util.Set;
@@ -87,14 +87,22 @@ public interface IndexManager {
8787
/** Returns the documents that match the given target based on the provided index. */
8888
Set<DocumentKey> getDocumentsMatchingTarget(FieldIndex fieldIndex, Target target);
8989

90-
/** Returns the next collection group to update. */
90+
/**
91+
* Returns the next collection group to update. Returns {@code null} if no group exists or the
92+
* next collection group is already at {@code maxSequenceNumber}.
93+
*/
9194
@Nullable
92-
String getNextCollectionGroupToUpdate(Timestamp lastUpdateTime);
95+
String getNextCollectionGroupToUpdate(long maxSequenceNumber);
96+
97+
/** Updates the sequence number for the collection group and sets its latest read time. */
98+
void updateCollectionGroup(String collectionGroup, long sequenceNumber, SnapshotVersion readTime);
9399

94100
/**
95101
* Updates the index entries for the provided documents and corresponding field indexes until the
96-
* cap is reached. Updates the field indexes in persistence with the latest read time that was
97-
* processed.
102+
* cap is reached.
98103
*/
99104
void updateIndexEntries(Collection<Document> documents);
105+
106+
/** Returns the largest currently used sequence number (as used by the backfiller). */
107+
long getHighestSequenceNumber();
100108
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ private ImmutableSortedMap<DocumentKey, Document> performCollectionQuery(Query q
8484
ImmutableSortedMap<DocumentKey, Document> indexedDocuments =
8585
localDocuments.getDocuments(keys);
8686
ImmutableSortedMap<DocumentKey, Document> additionalDocuments =
87-
localDocuments.getDocumentsMatchingQuery(query, fieldIndex.getUpdateTime());
87+
localDocuments.getDocumentsMatchingQuery(query, fieldIndex.getIndexState().getReadTime());
8888
for (Map.Entry<DocumentKey, Document> entry : additionalDocuments) {
8989
indexedDocuments = indexedDocuments.insert(entry.getKey(), entry.getValue());
9090
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
import static com.google.firebase.firestore.util.Assert.hardAssert;
1717

1818
import androidx.annotation.Nullable;
19-
import com.google.firebase.Timestamp;
2019
import com.google.firebase.firestore.core.Target;
2120
import com.google.firebase.firestore.model.Document;
2221
import com.google.firebase.firestore.model.DocumentKey;
2322
import com.google.firebase.firestore.model.FieldIndex;
2423
import com.google.firebase.firestore.model.ResourcePath;
24+
import com.google.firebase.firestore.model.SnapshotVersion;
2525
import java.util.ArrayList;
2626
import java.util.Collection;
2727
import java.util.Collections;
@@ -79,12 +79,19 @@ public Set<DocumentKey> getDocumentsMatchingTarget(FieldIndex fieldIndex, Target
7979
return Collections.emptySet();
8080
}
8181

82+
@Nullable
8283
@Override
83-
public String getNextCollectionGroupToUpdate(Timestamp startingTimestamp) {
84+
public String getNextCollectionGroupToUpdate(long maxSequenceNumber) {
8485
// Field indices are not supported with memory persistence.
8586
return null;
8687
}
8788

89+
@Override
90+
public void updateCollectionGroup(
91+
String collectionGroup, long sequenceNumber, SnapshotVersion readTime) {
92+
// Field indices are not supported with memory persistence.
93+
}
94+
8895
@Override
8996
public Collection<FieldIndex> getFieldIndexes(String collectionGroup) {
9097
// Field indices are not supported with memory persistence.
@@ -102,6 +109,12 @@ public void updateIndexEntries(Collection<Document> documents) {
102109
// Field indices are not supported with memory persistence.
103110
}
104111

112+
@Override
113+
public long getHighestSequenceNumber() {
114+
// Field indices are not supported with memory persistence.
115+
return -1;
116+
}
117+
105118
/**
106119
* Internal implementation of the collection-parent index. Also used for in-memory caching by
107120
* SQLiteIndexManager and initial index population in SQLiteSchema.

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@ final class MemoryRemoteDocumentCache implements RemoteDocumentCache {
3636
private ImmutableSortedMap<DocumentKey, Pair<MutableDocument, SnapshotVersion>> docs;
3737
/** Manages the collection group index. */
3838
private IndexManager indexManager;
39+
/** The latest read time of any document in the cache. */
40+
private SnapshotVersion latestReadTime;
3941

4042
MemoryRemoteDocumentCache() {
4143
docs = ImmutableSortedMap.Builder.emptyMap(DocumentKey.comparator());
44+
latestReadTime = SnapshotVersion.NONE;
4245
}
4346

4447
@Override
@@ -53,6 +56,7 @@ public void add(MutableDocument document, SnapshotVersion readTime) {
5356
!readTime.equals(SnapshotVersion.NONE),
5457
"Cannot add document to the RemoteDocumentCache with a read time of zero");
5558
docs = docs.insert(document.getKey(), new Pair<>(document.clone(), readTime));
59+
latestReadTime = readTime.compareTo(latestReadTime) > 0 ? readTime : latestReadTime;
5660

5761
indexManager.addToCollectionParentIndex(document.getKey().getPath().popLast());
5862
}
@@ -120,6 +124,11 @@ public ImmutableSortedMap<DocumentKey, MutableDocument> getAllDocumentsMatchingQ
120124
return result;
121125
}
122126

127+
@Override
128+
public SnapshotVersion getLatestReadTime() {
129+
return latestReadTime;
130+
}
131+
123132
Iterable<MutableDocument> getDocuments() {
124133
return new DocumentIterable();
125134
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,7 @@ interface RemoteDocumentCache {
7979
*/
8080
ImmutableSortedMap<DocumentKey, MutableDocument> getAllDocumentsMatchingQuery(
8181
Query query, SnapshotVersion sinceReadTime);
82+
83+
/** Returns the latest read time of any document in the cache. */
84+
SnapshotVersion getLatestReadTime();
8285
}

0 commit comments

Comments
 (0)