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 @@ -49,6 +49,7 @@
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 All @@ -63,6 +64,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
Expand Down Expand Up @@ -105,7 +107,9 @@ public interface InstanceRegistry {
private volatile FirestoreClient client;
private final GrpcMetadataProvider metadataProvider;

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

@NonNull
private static FirebaseApp getDefaultFirebaseApp() {
Expand Down Expand Up @@ -418,14 +422,15 @@ public Task<Void> setIndexConfiguration(@NonNull String json) {
@Nullable
public PersistentCacheIndexManager getPersistentCacheIndexManager() {
ensureClientConfigured();
if (persistentCacheIndexManager != null) {
return persistentCacheIndexManager;
}
Assert.hardAssertNonNull(
persistentCacheIndexManager, "persistentCacheIndexManager has not been initialized.");

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

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 class PersistentCacheIndexManager {
public final class PersistentCacheIndexManager {
@NonNull private FirestoreClient client;

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

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 @@ -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 @@ -16,16 +16,14 @@

/** A tracker to keep a record of important details during database local query execution. */
public class QueryContext {
public QueryContext() {}

/** Counts the number of documents passed through during local query execution. */
private int documentReadCount = 0;

public int getDocumentReadCount() {
return documentReadCount;
}

public void increaseDocumentCount() {
public void incrementDocumentReadCount() {
documentReadCount++;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static com.google.firebase.firestore.util.Assert.hardAssert;

import androidx.annotation.VisibleForTesting;
import com.google.firebase.database.collection.ImmutableSortedMap;
import com.google.firebase.database.collection.ImmutableSortedSet;
import com.google.firebase.firestore.core.Query;
Expand Down Expand Up @@ -61,12 +62,27 @@
public class QueryEngine {
private static final String LOG_TAG = "QueryEngine";

private static final int MIN_COLLECTION_SIZE_TO_AUTO_CREATE_INDEX = 100;

// TODO(csi): update the temp cost.
/**
* 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 int RELATIVE_INDEX_READ_COST = 3;

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

private boolean automaticIndexingEnabled = 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 relativeIndexReadCost = RELATIVE_INDEX_READ_COST;

public void initialize(LocalDocumentsView localDocumentsView, IndexManager indexManager) {
this.localDocumentsView = localDocumentsView;
this.indexManager = indexManager;
Expand Down Expand Up @@ -100,7 +116,7 @@ public ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
QueryContext context = new QueryContext();
result = executeFullCollectionScan(query, context);
if (result != null && automaticIndexingEnabled) {
CreateCacheIndexes(query, context, result.size());
createCacheIndexes(query, context, result.size());
}
return result;
}
Expand All @@ -110,10 +126,26 @@ public ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingQuery(
* context and query result size.
*/
// TODO(csi): Auto experiment data.
private void CreateCacheIndexes(Query query, QueryContext context, int resultSize) {
private void createCacheIndexes(Query query, QueryContext context, int resultSize) {
if (context.getDocumentReadCount() < minCollectionSizeToAutoCreateIndex) {
Logger.debug(
LOG_TAG,
"SDK will only creates cache indexes for collection contains more than or equal to "
+ "%s documents.",
minCollectionSizeToAutoCreateIndex);
return;
}

if (Logger.isDebugEnabled()) {
Logger.debug(
LOG_TAG,
"Query scans %s local documents and returns %s documents as results.",
context.getDocumentReadCount(),
resultSize);
}

String decisionStr = "";
// If evaluation is updated, please update tests in SQLiteLocalStoreTest.java
if (context.getDocumentReadCount() > 2 * resultSize) {
if (context.getDocumentReadCount() > relativeIndexReadCost * resultSize) {
indexManager.createTargetIndices(query.toTarget());
} else {
decisionStr = " not";
Expand All @@ -122,11 +154,8 @@ private void CreateCacheIndexes(Query query, QueryContext context, int resultSiz
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,
"The SDK decides%s to create cache indexes for this query, as using cache indexes "
+ "may%s help improve performance.",
decisionStr,
decisionStr);
}
Expand Down Expand Up @@ -305,4 +334,14 @@ private ImmutableSortedMap<DocumentKey, Document> appendRemainingResults(
}
return remainingResults;
}

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

@VisibleForTesting
void setRelativeIndexReadCost(int newCost) {
relativeIndexReadCost = newCost;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ public void createTargetIndices(Target target) {
IndexType type = getIndexType(subTarget);
if (type == IndexType.NONE || type == IndexType.PARTIAL) {
TargetIndexMatcher targetIndexMatcher = new TargetIndexMatcher(subTarget);
addFieldIndex(targetIndexMatcher.BuildTargetIndex());
addFieldIndex(targetIndexMatcher.buildTargetIndex());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ private Map<DocumentKey, MutableDocument> getAll(
processRowInBackground(backgroundQueue, results, row, filter);
if (context != null) {
// Increases the counter by 1 for every document processed.
context.increaseDocumentCount();
context.incrementDocumentReadCount();
}
});
backgroundQueue.drain();
Expand All @@ -236,7 +236,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 +266,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 @@ -193,7 +193,7 @@ public boolean servedByIndex(FieldIndex index) {
}

/** Returns a full matched field index for this target. */
public FieldIndex BuildTargetIndex() {
public FieldIndex buildTargetIndex() {
// We want to make sure only one segment created for one field. For example, in case like
// a == 3 and a > 2, index, a ASCENDING, will only be created once.
Set<FieldPath> uniqueFields = new HashSet<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ public void disableIndexAutoCreation() {
queryEngine.disableIndexAutoCreation();
}

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

@Override
public void setRelativeIndexReadCost(int newCost) {
queryEngine.setRelativeIndexReadCost(newCost);
}

/**
* Returns the number of documents returned by the RemoteDocumentCache's `getAll()` API (since the
* last call to `resetCounts()`)
Expand Down Expand Up @@ -181,7 +191,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 @@ -232,6 +232,14 @@ protected void disableIndexAutoCreation() {
queryEngine.disableIndexAutoCreation();
}

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

protected void setRelativeIndexReadCost(int newCost) {
queryEngine.setRelativeIndexReadCost(newCost);
}

private void releaseTarget(int targetId) {
localStore.releaseTarget(targetId);
}
Expand Down
Loading