Skip to content

Add option to allow SDK create cache indexes automatically to improve query execution locally #4987

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
beeb296
Add counter
cherylEnkidu May 8, 2023
d0ea51c
address feedback 1
cherylEnkidu May 10, 2023
4794dd7
add copyright
cherylEnkidu May 11, 2023
67471cf
fix concurrency bug
cherylEnkidu May 11, 2023
5d3d008
implement autoClientIndexing
cherylEnkidu May 17, 2023
6afe360
Add tests and fix bugs for BuildTargetIndex
cherylEnkidu May 23, 2023
28e9629
hide getter from public API
cherylEnkidu May 24, 2023
2502cb5
move the flag from IndexManager to QueryEngine
cherylEnkidu May 25, 2023
6d6eaec
Address feedback
cherylEnkidu Jun 25, 2023
544de86
move auto index flag to runtime
cherylEnkidu Jun 25, 2023
81b5c29
Support old way to enable persistent for PersistentCacheManager
cherylEnkidu Jun 26, 2023
00b4ea1
Polish Tests
cherylEnkidu Jun 26, 2023
0e71ee2
Add hide and copyright
cherylEnkidu Jun 27, 2023
fb9a561
clean up unused function
cherylEnkidu Jun 27, 2023
31095b0
Rename PersistentCacheManager to PersistentCacheIndexManager
cherylEnkidu Jun 28, 2023
5b56b0b
Remove unused QueryContext
cherylEnkidu Jul 11, 2023
87e3ac1
Address feedbacks other than adding tests and comments
cherylEnkidu Jul 12, 2023
32cfc69
Change the api to match the update
cherylEnkidu Jul 12, 2023
8c8ae8c
Add tests
cherylEnkidu Jul 13, 2023
39318da
Increase tests coverage
cherylEnkidu Jul 14, 2023
14a6a71
Add comments
cherylEnkidu Jul 17, 2023
39756f4
add configurable min documents to create indexes
cherylEnkidu Jul 19, 2023
3f7a970
Address Denver's feedback
cherylEnkidu Jul 20, 2023
48d649f
Address feedback
cherylEnkidu Jul 24, 2023
6c6698e
address more feedbacks
cherylEnkidu Jul 25, 2023
0336972
use the number getting from 100 ~ 1000 documents experiment
cherylEnkidu Jul 25, 2023
46cfa2f
Address feedbacks
cherylEnkidu Jul 26, 2023
fb12477
improve debug log
cherylEnkidu Jul 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testFirestore;
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitFor;
import static com.google.firebase.firestore.testutil.TestUtil.assertDoesNotThrow;
import static com.google.firebase.firestore.testutil.TestUtil.expectError;
import static com.google.firebase.firestore.testutil.TestUtil.map;
import static org.junit.Assert.assertEquals;

Expand Down Expand Up @@ -108,7 +109,7 @@ public void testBadIndexDoesNotCrashClient() {
}

@Test
public void testAutomaticIndexingSetSuccessfully() {
public void testAutoIndexCreationSetSuccessfully() {
// Use persistent disk cache (default)
FirebaseFirestore db = testFirestore();
FirebaseFirestoreSettings settings =
Expand Down Expand Up @@ -138,7 +139,7 @@ public void testAutomaticIndexingSetSuccessfully() {
}

@Test
public void testAutomaticIndexingSetSuccessfullyUseDefault() {
public void testAutoIndexCreationSetSuccessfullyUsingDefault() {
// Use persistent disk cache (default)
FirebaseFirestore db = testFirestore();

Expand All @@ -161,4 +162,18 @@ public void testAutomaticIndexingSetSuccessfullyUseDefault() {
results = waitFor(collection.whereEqualTo("match", true).get());
assertEquals(1, results.size());
}

@Test
public void testAutoIndexCreationAfterFailsTermination() {
FirebaseFirestore db = testFirestore();
waitFor(db.terminate());

expectError(
() -> db.getPersistentCacheIndexManager().enableIndexAutoCreation(),
"The client has already been terminated");

expectError(
() -> db.getPersistentCacheIndexManager().disableIndexAutoCreation(),
"The client has already been terminated");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
import com.google.firebase.firestore.model.ResourcePath;
import com.google.firebase.firestore.remote.FirestoreChannel;
import com.google.firebase.firestore.remote.GrpcMetadataProvider;
import com.google.firebase.firestore.util.Assert;
import com.google.firebase.firestore.util.AsyncQueue;
import com.google.firebase.firestore.util.ByteBufferInputStream;
import com.google.firebase.firestore.util.Executors;
Expand Down Expand Up @@ -107,8 +106,7 @@ public interface InstanceRegistry {
private volatile FirestoreClient client;
private final GrpcMetadataProvider metadataProvider;

@Nullable
private AtomicReference<PersistentCacheIndexManager> persistentCacheIndexManager =
private final AtomicReference<PersistentCacheIndexManager> persistentCacheIndexManager =
new AtomicReference<>();

@NonNull
Expand Down Expand Up @@ -422,12 +420,11 @@ public Task<Void> setIndexConfiguration(@NonNull String json) {
@Nullable
public PersistentCacheIndexManager getPersistentCacheIndexManager() {
ensureClientConfigured();
Assert.hardAssertNonNull(
persistentCacheIndexManager, "persistentCacheIndexManager has not been initialized.");

if (settings.isPersistenceEnabled()
|| settings.getCacheSettings() instanceof PersistentCacheSettings) {
persistentCacheIndexManager.compareAndSet(null, new PersistentCacheIndexManager(client));
if ((settings.isPersistenceEnabled()
|| settings.getCacheSettings() instanceof PersistentCacheSettings)
&& persistentCacheIndexManager.get() == null) {
persistentCacheIndexManager.set(new PersistentCacheIndexManager(client));
}

return persistentCacheIndexManager.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@
* A {@code PersistentCacheIndexManager} which you can config persistent cache indexes used for
* local query execution.
*
* <p>To use, calling {@link FirebaseFirestore#getPersistentCacheIndexManager()} to get an instance.
* <p>To use, call {@link FirebaseFirestore#getPersistentCacheIndexManager()} to get an instance.
*/
// TODO(csi): Remove the `hide` and scope annotations.
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class PersistentCacheIndexManager {
@NonNull private FirestoreClient client;

@RestrictTo(RestrictTo.Scope.LIBRARY)
public PersistentCacheIndexManager(FirestoreClient client) {
PersistentCacheIndexManager(FirestoreClient client) {
this.client = client;
}

Expand All @@ -42,14 +41,14 @@ public PersistentCacheIndexManager(FirestoreClient client) {
* <p>This feature is disabled by default.
*/
public void enableIndexAutoCreation() {
client.enableIndexAutoCreation();
client.setIndexAutoCreationEnabled(true);
}

/**
* Stops creating persistent cache indexes automatically for local query execution. The indexes
* which have been created by calling {@link #enableIndexAutoCreation()} still take effect.
*/
public void disableIndexAutoCreation() {
client.disableIndexAutoCreation();
client.setIndexAutoCreationEnabled(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -353,14 +353,9 @@ public Task<Void> configureFieldIndexes(List<FieldIndex> fieldIndices) {
return asyncQueue.enqueue(() -> localStore.configureFieldIndexes(fieldIndices));
}

public void enableIndexAutoCreation() {
public void setIndexAutoCreationEnabled(boolean enabled) {
verifyNotTerminated();
asyncQueue.enqueueAndForget(() -> localStore.enableIndexAutoCreation());
}

public void disableIndexAutoCreation() {
verifyNotTerminated();
asyncQueue.enqueueAndForget(() -> localStore.disableIndexAutoCreation());
asyncQueue.enqueueAndForget(() -> localStore.setIndexAutoCreationEnabled(enabled));
}

public void removeSnapshotsInSyncListener(EventListener<Void> listener) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
*/
ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
Query query, IndexOffset offset) {
return getDocumentsMatchingQuery(query, offset, null);
return getDocumentsMatchingQuery(query, offset, /*context*/ null);
}

/** Performs a simple document lookup for the given path. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -802,12 +802,8 @@ public void configureFieldIndexes(List<FieldIndex> newFieldIndexes) {
});
}

public void enableIndexAutoCreation() {
queryEngine.enableIndexAutoCreation();
}

public void disableIndexAutoCreation() {
queryEngine.disableIndexAutoCreation();
public void setIndexAutoCreationEnabled(boolean enabled) {
queryEngine.setIndexAutoCreationEnabled(enabled);
}

/** Mutable state for the transaction in allocateQuery. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public Map<DocumentKey, MutableDocument> getDocumentsMatchingQuery(
@Override
public Map<DocumentKey, MutableDocument> getDocumentsMatchingQuery(
Query query, IndexOffset offset, @Nonnull Set<DocumentKey> mutatedKeys) {
return getDocumentsMatchingQuery(query, offset, mutatedKeys, null);
return getDocumentsMatchingQuery(query, offset, mutatedKeys, /*context*/ null);
}

Iterable<Document> getDocuments() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,29 +61,35 @@
*/
public class QueryEngine {
private static final String LOG_TAG = "QueryEngine";
private static final int MIN_COLLECTION_SIZE_TO_AUTO_CREATE_INDEX = 100;

private static final int INDEX_AUTO_CREATION_DEFAULT_MIN_COLLECTION_SIZE = 100;

/**
* This cost represents the evaluation result of (([index, docKey] + [docKey, docContent]) per
* document in the result set) / ([docKey, docContent] per documents in full collection scan)
* coming from experiment https://github.com/firebase/firebase-android-sdk/pull/5064.
*/
private static final double DEFAULT_RELATIVE_INDEX_READ_COST_PER_DOCUMENT = 2;

private LocalDocumentsView localDocumentsView;
private IndexManager indexManager;
private boolean initialized;

private boolean automaticIndexingEnabled = false;
private boolean indexAutoCreationEnabled = false;

/** SDK only decides whether it should create index when collection size is larger than this. */
private int minCollectionSizeToAutoCreateIndex = MIN_COLLECTION_SIZE_TO_AUTO_CREATE_INDEX;
private int indexAutoCreationMinCollectionSize = INDEX_AUTO_CREATION_DEFAULT_MIN_COLLECTION_SIZE;

private double relativeIndexReadCostPerDocument = DEFAULT_RELATIVE_INDEX_READ_COST_PER_DOCUMENT;

public void initialize(LocalDocumentsView localDocumentsView, IndexManager indexManager) {
this.localDocumentsView = localDocumentsView;
this.indexManager = indexManager;
this.initialized = true;
}

public void enableIndexAutoCreation() {
this.automaticIndexingEnabled = true;
}

public void disableIndexAutoCreation() {
this.automaticIndexingEnabled = false;
public void setIndexAutoCreationEnabled(boolean enabled) {
this.indexAutoCreationEnabled = enabled;
}

public ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
Expand All @@ -104,7 +110,7 @@ public ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(

QueryContext context = new QueryContext();
result = executeFullCollectionScan(query, context);
if (result != null && automaticIndexingEnabled) {
if (result != null && indexAutoCreationEnabled) {
createCacheIndexes(query, context, result.size());
}
return result;
Expand All @@ -116,33 +122,32 @@ public ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
*/
// TODO(csi): Auto experiment data.
private void createCacheIndexes(Query query, QueryContext context, int resultSize) {
if (context.getDocumentReadCount() < minCollectionSizeToAutoCreateIndex) {
if (context.getDocumentReadCount() < indexAutoCreationMinCollectionSize) {
Logger.debug(
LOG_TAG,
"SDK will only creates cache indexes for collection contains more than or equal to "
+ "%s documents.",
minCollectionSizeToAutoCreateIndex);
indexAutoCreationMinCollectionSize);
return;
}

String decisionStr = "";
// If evaluation is updated, please update tests in SQLiteLocalStoreTest.java
if (context.getDocumentReadCount() > 2 * resultSize) {
Logger.debug(
LOG_TAG,
"Query scans %s local documents and returns %s documents as results.",
context.getDocumentReadCount(),
resultSize);

if (context.getDocumentReadCount() > relativeIndexReadCostPerDocument * resultSize) {
indexManager.createTargetIndices(query.toTarget());
Logger.debug(
LOG_TAG,
"The SDK decides to create cache indexes for this query, as using cache indexes "
+ "may help improve performance.");
} else {
decisionStr = " not";
}

if (Logger.isDebugEnabled()) {
Logger.debug(
LOG_TAG,
"Query ran locally using a full collection scan, walking through %s documents in total "
+ "and returning %s documents. The SDK has decided%s to create cache indexes "
+ "for this query, as using cache indexes may%s help improve performance.",
context.getDocumentReadCount(),
resultSize,
decisionStr,
decisionStr);
"The SDK decides not to create cache indexes for this query, as using cache indexes "
+ "may not help improve performance.");
}
}

Expand Down Expand Up @@ -321,7 +326,12 @@ private ImmutableSortedMap<DocumentKey, Document> appendRemainingResults(
}

@VisibleForTesting
void setMinCollectionSizeToAutoCreateIndex(int newMin) {
minCollectionSizeToAutoCreateIndex = newMin;
void setIndexAutoCreationMinCollectionSize(int newMin) {
indexAutoCreationMinCollectionSize = newMin;
}

@VisibleForTesting
void setRelativeIndexReadCostPerDocument(double newCost) {
relativeIndexReadCostPerDocument = newCost;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,6 @@ private Map<DocumentKey, MutableDocument> getAll(
row -> {
processRowInBackground(backgroundQueue, results, row, filter);
if (context != null) {
// Increases the counter by 1 for every document processed.
context.incrementDocumentReadCount();
}
});
Expand All @@ -236,7 +235,7 @@ private Map<DocumentKey, MutableDocument> getAll(
IndexOffset offset,
int count,
@Nullable Function<MutableDocument, Boolean> filter) {
return getAll(collections, offset, count, filter, null);
return getAll(collections, offset, count, filter, /*context*/ null);
}

private void processRowInBackground(
Expand Down Expand Up @@ -266,7 +265,7 @@ private void processRowInBackground(
@Override
public Map<DocumentKey, MutableDocument> getDocumentsMatchingQuery(
Query query, IndexOffset offset, @Nonnull Set<DocumentKey> mutatedKeys) {
return getDocumentsMatchingQuery(query, offset, mutatedKeys, null);
return getDocumentsMatchingQuery(query, offset, mutatedKeys, /*context*/ null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,18 @@ public ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
}

@Override
public void enableIndexAutoCreation() {
queryEngine.enableIndexAutoCreation();
public void setIndexAutoCreationEnabled(boolean enabled) {
queryEngine.setIndexAutoCreationEnabled(enabled);
}

@Override
public void disableIndexAutoCreation() {
queryEngine.disableIndexAutoCreation();
public void setIndexAutoCreationMinCollectionSize(int newMin) {
queryEngine.setIndexAutoCreationMinCollectionSize(newMin);
}

@Override
public void setMinCollectionSizeToAutoCreateIndex(int newMin) {
queryEngine.setMinCollectionSizeToAutoCreateIndex(newMin);
public void setRelativeIndexReadCostPerDocument(double newCost) {
queryEngine.setRelativeIndexReadCostPerDocument(newCost);
}

/**
Expand Down Expand Up @@ -186,7 +186,7 @@ public Map<DocumentKey, MutableDocument> getAll(
@Override
public Map<DocumentKey, MutableDocument> getDocumentsMatchingQuery(
Query query, IndexOffset offset, @NonNull Set<DocumentKey> mutatedKeys) {
return getDocumentsMatchingQuery(query, offset, mutatedKeys, null);
return getDocumentsMatchingQuery(query, offset, mutatedKeys, /*context*/ null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,23 +217,21 @@ protected void executeQuery(Query query) {
lastQueryResult = localStore.executeQuery(query, /* usePreviousResults= */ true);
}

protected void enableIndexAutoCreation() {
protected void setIndexAutoCreationEnabled(boolean enabled) {
// Noted: there are two queryEngines here, the first one is extended by CountingQueryEngine,
// which is set by localStore function; The second one a pointer inside CountingQueryEngine,
// which is set by queryEngine function.
// Only the second function takes effect in the tests. Adding first one here for compatibility.
localStore.enableIndexAutoCreation();
queryEngine.enableIndexAutoCreation();
localStore.setIndexAutoCreationEnabled(enabled);
queryEngine.setIndexAutoCreationEnabled(enabled);
}

protected void disableIndexAutoCreation() {
// Please refer to the notes in `enableIndexAutoCreation()`
localStore.disableIndexAutoCreation();
queryEngine.disableIndexAutoCreation();
protected void setMinCollectionSizeToAutoCreateIndex(int newMin) {
queryEngine.setIndexAutoCreationMinCollectionSize(newMin);
}

protected void setMinCollectionSizeToAutoCreateIndex(int newMin) {
queryEngine.setMinCollectionSizeToAutoCreateIndex(newMin);
protected void setRelativeIndexReadCostPerDocument(double newCost) {
queryEngine.setRelativeIndexReadCostPerDocument(newCost);
}

private void releaseTarget(int targetId) {
Expand Down
Loading