Skip to content

Commit 7e6b736

Browse files
author
Brian Chen
committed
use sequence numbers instead of read time in collection group
1 parent c293a6e commit 7e6b736

File tree

7 files changed

+74
-59
lines changed

7 files changed

+74
-59
lines changed

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

Lines changed: 7 additions & 5 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;
@@ -140,11 +139,14 @@ public Results backfill() {
140139
/** Writes index entries until the cap is reached. Returns the number of documents processed. */
141140
private int writeIndexEntries(LocalDocumentsView localDocumentsView) {
142141
int documentsProcessed = 0;
143-
Timestamp startingTimestamp = Timestamp.now();
142+
int currentMaxSequenceNumber = indexManager.getMaxCollectionGroupSequenceNumber();
144143

144+
// TODO(indexing): Handle pausing and resuming from the correct document if backfilling hits the
145+
// max doc limit while processing docs for a certain read time.
145146
while (documentsProcessed < maxDocumentsToProcess) {
146147
int documentsRemaining = maxDocumentsToProcess - documentsProcessed;
147-
String collectionGroup = indexManager.getNextCollectionGroupToUpdate(startingTimestamp);
148+
String collectionGroup =
149+
indexManager.getNextCollectionGroupToUpdate(currentMaxSequenceNumber);
148150
if (collectionGroup == null) {
149151
break;
150152
}
@@ -157,7 +159,7 @@ private int writeIndexEntries(LocalDocumentsView localDocumentsView) {
157159

158160
/** Writes entries for the fetched field indexes. */
159161
private int writeEntriesForCollectionGroup(
160-
LocalDocumentsView localDocumentsView, String collectionGroup, int entriesRemainingUnderCap) {
162+
LocalDocumentsView localDocumentsView, String collectionGroup, int documentsRemaining) {
161163
Query query = new Query(ResourcePath.EMPTY, collectionGroup);
162164

163165
// Use the earliest updateTime of all field indexes as the base updateTime.
@@ -169,7 +171,7 @@ private int writeEntriesForCollectionGroup(
169171
ImmutableSortedMap<DocumentKey, Document> documents =
170172
localDocumentsView.getDocumentsMatchingQuery(query, earliestUpdateTime);
171173

172-
Queue<Document> oldestDocuments = getOldestDocuments(documents, entriesRemainingUnderCap);
174+
Queue<Document> oldestDocuments = getOldestDocuments(documents, documentsRemaining);
173175
indexManager.updateIndexEntries(oldestDocuments);
174176
return oldestDocuments.size();
175177
}

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
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;
@@ -81,9 +80,15 @@ public interface IndexManager {
8180
/** Returns the documents that match the given target based on the provided index. */
8281
Set<DocumentKey> getDocumentsMatchingTarget(FieldIndex fieldIndex, Target target);
8382

84-
/** Returns the next collection group to update. */
83+
/**
84+
* Returns the next collection group to update. Returns null after reaching the provided max
85+
* sequence number.
86+
*/
8587
@Nullable
86-
String getNextCollectionGroupToUpdate(Timestamp lastUpdateTime);
88+
String getNextCollectionGroupToUpdate(int currentMaxSequenceNumber);
89+
90+
/** Returns the highest sequence number in the collection groups table. */
91+
int getMaxCollectionGroupSequenceNumber();
8792

8893
/**
8994
* Updates the index entries for the provided documents and corresponding field indexes until the

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
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;
@@ -75,11 +74,17 @@ public Set<DocumentKey> getDocumentsMatchingTarget(FieldIndex fieldIndex, Target
7574
}
7675

7776
@Override
78-
public String getNextCollectionGroupToUpdate(Timestamp startingTimestamp) {
77+
public String getNextCollectionGroupToUpdate(int currentMaxSequenceNumber) {
7978
// Field indices are not supported with memory persistence.
8079
return null;
8180
}
8281

82+
@Override
83+
public int getMaxCollectionGroupSequenceNumber() {
84+
// Field indices are not supported with memory persistence.
85+
return -1;
86+
}
87+
8388
@Override
8489
public Collection<FieldIndex> getFieldIndexes(String collectionGroup) {
8590
// Field indices are not supported with memory persistence.

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

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

2323
import androidx.annotation.Nullable;
2424
import androidx.annotation.VisibleForTesting;
25-
import com.google.firebase.Timestamp;
2625
import com.google.firebase.firestore.auth.User;
2726
import com.google.firebase.firestore.core.Bound;
2827
import com.google.firebase.firestore.core.FieldFilter;
@@ -55,6 +54,7 @@
5554
/** A persisted implementation of IndexManager. */
5655
final class SQLiteIndexManager implements IndexManager {
5756
private static final String TAG = SQLiteIndexManager.class.getSimpleName();
57+
private static final int NEW_COLLECTION_GROUP_SEQUENCE_NUMBER = 0;
5858

5959
/**
6060
* An in-memory copy of the index entries we've already written since the SDK launched. Used to
@@ -161,8 +161,7 @@ public void addFieldIndex(FieldIndex index) {
161161
index.getUpdateTime().getTimestamp().getSeconds(),
162162
index.getUpdateTime().getTimestamp().getNanoseconds());
163163

164-
// TODO(indexing): Use a 0-counter rather than the timestamp.
165-
setCollectionGroupUpdateTime(index.getCollectionGroup(), SnapshotVersion.NONE.getTimestamp());
164+
setCollectionGroup(index.getCollectionGroup(), NEW_COLLECTION_GROUP_SEQUENCE_NUMBER);
166165

167166
memoizeIndex(index);
168167
}
@@ -184,19 +183,18 @@ private void updateFieldIndex(FieldIndex index) {
184183
memoizeIndex(index);
185184
}
186185

187-
// TODO(indexing): Use a counter rather than the starting timestamp.
188186
@Override
189-
public @Nullable String getNextCollectionGroupToUpdate(Timestamp startingTimestamp) {
187+
public @Nullable String getNextCollectionGroupToUpdate(int currentMaxSequenceNumber) {
190188
hardAssert(started, "IndexManager not started");
191189

192190
final String[] nextCollectionGroup = {null};
193191
db.query(
194192
"SELECT collection_group "
195193
+ "FROM collection_group_update_times "
196-
+ "WHERE update_time_seconds <= ? AND update_time_nanos <= ?"
197-
+ "ORDER BY update_time_seconds, update_time_nanos "
194+
+ "WHERE sequence_number <= ?"
195+
+ "ORDER BY sequence_number "
198196
+ "LIMIT 1")
199-
.binding(startingTimestamp.getSeconds(), startingTimestamp.getNanoseconds())
197+
.binding(currentMaxSequenceNumber)
200198
.firstValue(
201199
row -> {
202200
nextCollectionGroup[0] = row.getString(0);
@@ -208,9 +206,7 @@ private void updateFieldIndex(FieldIndex index) {
208206
}
209207

210208
// Store that this collection group will updated.
211-
// TODO(indexing): Store progress with a counter rather than a timestamp. If using a timestamp,
212-
// use the read time of the last document read in the loop.
213-
setCollectionGroupUpdateTime(nextCollectionGroup[0], Timestamp.now());
209+
setCollectionGroup(nextCollectionGroup[0], getMaxCollectionGroupSequenceNumber() + 1);
214210

215211
return nextCollectionGroup[0];
216212
}
@@ -238,8 +234,6 @@ public void updateIndexEntries(Collection<Document> documents) {
238234
}
239235
}
240236

241-
// TODO(indexing): Use RemoteDocumentCache's readTime version rather than the document version.
242-
// This will require plumbing out the RDC's readTime into the IndexBackfiller.
243237
for (FieldIndex updatedFieldIndex : updatedFieldIndexes.values()) {
244238
updateFieldIndex(updatedFieldIndex);
245239
}
@@ -636,13 +630,20 @@ private byte[] encodeFieldIndex(FieldIndex fieldIndex) {
636630
}
637631

638632
@VisibleForTesting
639-
void setCollectionGroupUpdateTime(String collectionGroup, Timestamp updateTime) {
633+
void setCollectionGroup(String collectionGroup, int sequenceNumber) {
640634
db.execute(
641635
"INSERT OR REPLACE INTO collection_group_update_times "
642-
+ "(collection_group, update_time_seconds, update_time_nanos) "
643-
+ "VALUES (?, ?, ?)",
636+
+ "(collection_group, sequence_number) "
637+
+ "VALUES (?, ?)",
644638
collectionGroup,
645-
updateTime.getSeconds(),
646-
updateTime.getNanoseconds());
639+
sequenceNumber);
640+
}
641+
642+
@Override
643+
public int getMaxCollectionGroupSequenceNumber() {
644+
final int[] finalValue = {0};
645+
db.query("SELECT MAX(sequence_number) FROM collection_group_update_times")
646+
.first(value -> finalValue[0] = value.getInt(0));
647+
return finalValue[0];
647648
}
648649
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,7 @@ private void createCollectionGroupsTable() {
401401
db.execSQL(
402402
"CREATE TABLE collection_group_update_times ("
403403
+ "collection_group TEXT, " // Name of the collection group.
404-
+ "update_time_seconds INTEGER," // Time of last index backfill update
405-
+ "update_time_nanos INTEGER,"
404+
+ "sequence_number INTEGER, " // Used to establish ordering when indexing.
406405
+ "PRIMARY KEY (collection_group))");
407406
});
408407
}

firebase-firestore/src/test/java/com/google/firebase/firestore/local/IndexBackfillerTest.java

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

1717
import static com.google.common.truth.Truth.assertThat;
18-
import static com.google.firebase.firestore.local.SQLiteIndexManagerTest.getCollectionGroupsOrderByUpdateTime;
18+
import static com.google.firebase.firestore.local.SQLiteIndexManagerTest.getCollectionGroupsOrderBySequenceNumber;
1919
import static com.google.firebase.firestore.testutil.TestUtil.doc;
2020
import static com.google.firebase.firestore.testutil.TestUtil.field;
2121
import static com.google.firebase.firestore.testutil.TestUtil.key;
@@ -25,7 +25,6 @@
2525
import static com.google.firebase.firestore.testutil.TestUtil.version;
2626
import static junit.framework.TestCase.assertEquals;
2727

28-
import com.google.firebase.Timestamp;
2928
import com.google.firebase.firestore.auth.User;
3029
import com.google.firebase.firestore.core.Target;
3130
import com.google.firebase.firestore.model.DocumentKey;
@@ -40,7 +39,6 @@
4039
import org.junit.AfterClass;
4140
import org.junit.Before;
4241
import org.junit.BeforeClass;
43-
import org.junit.Ignore;
4442
import org.junit.Rule;
4543
import org.junit.Test;
4644
import org.junit.rules.TestName;
@@ -95,9 +93,7 @@ public void tearDown() {
9593
persistence.shutdown();
9694
}
9795

98-
// TODO(indexing): Re-enable this test once we have counters implemented.
9996
@Test
100-
@Ignore
10197
public void testBackfillWritesLatestReadTimeToFieldIndexOnCompletion() {
10298
addFieldIndex("coll1", "foo");
10399
addFieldIndex("coll2", "bar");
@@ -127,8 +123,6 @@ public void testBackfillWritesLatestReadTimeToFieldIndexOnCompletion() {
127123
}
128124

129125
@Test
130-
@Ignore("Flaky")
131-
// TODO(indexing): This test is flaky. Fix.
132126
public void testBackfillFetchesDocumentsAfterEarliestReadTime() {
133127
addFieldIndex("coll1", "foo", version(10, 0));
134128
addFieldIndex("coll1", "boo", version(20, 0));
@@ -196,9 +190,9 @@ public void testBackfillUpdatesCollectionGroups() {
196190
addFieldIndex("coll1", "foo");
197191
addFieldIndex("coll2", "foo");
198192
addFieldIndex("coll3", "foo");
199-
addCollectionGroup("coll1", new Timestamp(30, 0));
200-
addCollectionGroup("coll2", new Timestamp(30, 30));
201-
addCollectionGroup("coll3", new Timestamp(10, 0));
193+
addCollectionGroup("coll1", 2);
194+
addCollectionGroup("coll2", 3);
195+
addCollectionGroup("coll3", 1);
202196
addDoc("coll1/docA", "foo", version(10, 0));
203197
addDoc("coll2/docA", "foo", version(10, 0));
204198
addDoc("coll3/docA", "foo", version(10, 0));
@@ -208,31 +202,29 @@ public void testBackfillUpdatesCollectionGroups() {
208202

209203
// Check that index entries are written in order of the collection group update times by
210204
// verifying the collection group update times have been updated in the correct order.
211-
List<String> collectionGroups = getCollectionGroupsOrderByUpdateTime(persistence);
205+
List<String> collectionGroups = getCollectionGroupsOrderBySequenceNumber(persistence);
212206
assertEquals(3, collectionGroups.size());
213207
assertEquals("coll3", collectionGroups.get(0));
214208
assertEquals("coll1", collectionGroups.get(1));
215209
assertEquals("coll2", collectionGroups.get(2));
216210
}
217211

218212
@Test
219-
@Ignore("Flaky")
220-
// TODO(indexing): This test is flaky. Fix.
221213
public void testBackfillPrioritizesNewCollectionGroups() {
222214
// In this test case, `coll3` is a new collection group that hasn't been indexed, so it should
223215
// be processed ahead of the other collection groups.
224216
addFieldIndex("coll1", "foo");
225217
addFieldIndex("coll2", "foo");
226-
addCollectionGroup("coll1", new Timestamp(1, 0));
227-
addCollectionGroup("coll2", new Timestamp(2, 0));
218+
addCollectionGroup("coll1", 1);
219+
addCollectionGroup("coll2", 2);
228220
addFieldIndex("coll3", "foo");
229221

230222
IndexBackfiller.Results results = backfiller.backfill();
231223
assertEquals(0, results.getDocumentsProcessed());
232224

233225
// Check that index entries are written in order of the collection group update times by
234226
// verifying the collection group update times have been updated in the correct order.
235-
List<String> collectionGroups = getCollectionGroupsOrderByUpdateTime(persistence);
227+
List<String> collectionGroups = getCollectionGroupsOrderBySequenceNumber(persistence);
236228
assertEquals(3, collectionGroups.size());
237229
assertEquals("coll3", collectionGroups.get(0));
238230
assertEquals("coll1", collectionGroups.get(1));
@@ -244,7 +236,7 @@ public void testBackfillWritesUntilCap() {
244236
backfiller.setMaxDocumentsToProcess(3);
245237
addFieldIndex("coll1", "foo");
246238
addFieldIndex("coll2", "foo");
247-
addCollectionGroup("coll1", new Timestamp(1, 0));
239+
addCollectionGroup("coll1", 1);
248240
addDoc("coll1/docA", "foo", version(10, 0));
249241
addDoc("coll1/docB", "foo", version(10, 0));
250242
addDoc("coll2/docA", "foo", version(10, 0));
@@ -256,7 +248,7 @@ public void testBackfillWritesUntilCap() {
256248
// Check that collection groups are updated even if the backfiller hits the write cap. Since
257249
// `coll1` was already in the table, `coll2` should be processed first, and thus appear first
258250
// in the ordering.
259-
List<String> collectionGroups = getCollectionGroupsOrderByUpdateTime(persistence);
251+
List<String> collectionGroups = getCollectionGroupsOrderBySequenceNumber(persistence);
260252
assertEquals(2, collectionGroups.size());
261253
assertEquals("coll2", collectionGroups.get(0));
262254
assertEquals("coll1", collectionGroups.get(1));
@@ -276,8 +268,8 @@ private void addFieldIndex(String collectionGroup, String fieldName, SnapshotVer
276268
.withUpdateTime(readTime));
277269
}
278270

279-
private void addCollectionGroup(String collectionGroup, Timestamp updateTime) {
280-
indexManager.setCollectionGroupUpdateTime(collectionGroup, updateTime);
271+
private void addCollectionGroup(String collectionGroup, int sequenceNumber) {
272+
indexManager.setCollectionGroup(collectionGroup, sequenceNumber);
281273
}
282274

283275
/** Creates a document and adds it to the RemoteDocumentCache. */

firebase-firestore/src/test/java/com/google/firebase/firestore/local/SQLiteIndexManagerTest.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -606,18 +606,18 @@ public void testUpdateTime() {
606606
}
607607

608608
@Test
609-
public void testCollectionGroupUpdateTimesCanBeUpdated() {
609+
public void testCollectionGroupSequenceNumbersCanBeUpdated() {
610610
SQLiteIndexManager sqLiteIndexManager = (SQLiteIndexManager) indexManager;
611-
sqLiteIndexManager.setCollectionGroupUpdateTime("coll1", new Timestamp(30, 0));
612-
sqLiteIndexManager.setCollectionGroupUpdateTime("coll2", new Timestamp(30, 50));
611+
sqLiteIndexManager.setCollectionGroup("coll1", 1);
612+
sqLiteIndexManager.setCollectionGroup("coll2", 2);
613613
List<String> orderedCollectionGroups =
614-
getCollectionGroupsOrderByUpdateTime((SQLitePersistence) getPersistence());
614+
getCollectionGroupsOrderBySequenceNumber((SQLitePersistence) getPersistence());
615615
List<String> expected = Arrays.asList("coll1", "coll2");
616616
assertEquals(expected, orderedCollectionGroups);
617617

618-
sqLiteIndexManager.setCollectionGroupUpdateTime("coll1", new Timestamp(50, 0));
618+
sqLiteIndexManager.setCollectionGroup("coll1", 3);
619619
orderedCollectionGroups =
620-
getCollectionGroupsOrderByUpdateTime((SQLitePersistence) getPersistence());
620+
getCollectionGroupsOrderBySequenceNumber((SQLitePersistence) getPersistence());
621621
expected = Arrays.asList("coll2", "coll1");
622622
assertEquals(expected, orderedCollectionGroups);
623623
}
@@ -662,12 +662,22 @@ public void testAddFieldIndexWritesToCollectionGroup() {
662662
.withAddedField(field("value"), FieldIndex.Segment.Kind.CONTAINS)
663663
.withUpdateTime(SnapshotVersion.NONE));
664664
List<String> collectionGroups =
665-
getCollectionGroupsOrderByUpdateTime((SQLitePersistence) getPersistence());
665+
getCollectionGroupsOrderBySequenceNumber((SQLitePersistence) getPersistence());
666666
assertEquals(2, collectionGroups.size());
667667
assertEquals("coll1", collectionGroups.get(0));
668668
assertEquals("coll2", collectionGroups.get(1));
669669
}
670670

671+
@Test
672+
public void getMaxCollectionSequenceNumberOnEmptyTable() {
673+
SQLiteIndexManager sqLiteIndexManager = (SQLiteIndexManager) indexManager;
674+
List<String> collectionGroups =
675+
getCollectionGroupsOrderBySequenceNumber((SQLitePersistence) getPersistence());
676+
assertEquals(0, collectionGroups.size());
677+
int value = sqLiteIndexManager.getMaxCollectionGroupSequenceNumber();
678+
assertEquals(0, value);
679+
}
680+
671681
private void addDoc(String key, Map<String, Object> data) {
672682
MutableDocument doc = doc(key, 1, data);
673683
indexManager.handleDocumentChange(null, doc);
@@ -681,13 +691,14 @@ private void verifyResults(Query query, String... documents) {
681691
assertWithMessage("Result for %s", query).that(results).containsExactlyElementsIn(keys);
682692
}
683693

684-
public static List<String> getCollectionGroupsOrderByUpdateTime(SQLitePersistence persistence) {
694+
public static List<String> getCollectionGroupsOrderBySequenceNumber(
695+
SQLitePersistence persistence) {
685696
List<String> orderedCollectionGroups = new ArrayList<>();
686697
persistence
687698
.query(
688699
"SELECT collection_group "
689700
+ "FROM collection_group_update_times "
690-
+ "ORDER BY update_time_seconds, update_time_nanos")
701+
+ "ORDER BY sequence_number")
691702
.forEach(
692703
row -> {
693704
orderedCollectionGroups.add(row.getString(0));

0 commit comments

Comments
 (0)