Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions firebase-firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Unreleased

- [changed] Increased the default gRPC flow control window size from 64KB to 256KB to speed up large document reads, and added support for configuring this window size via `FirebaseFirestoreSettings.Builder.setGrpcFlowControlWindow()`.
- [changed] Added support for caching documents larger than 1MB by reading them in chunks from the local SQLite database.
- [changed] Prevent OutOfMemory errors in debug logging by truncating large protobuf payloads and strings.
>>>>>>> main

# 26.4.0

Expand Down
4 changes: 4 additions & 0 deletions firebase-firestore/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,11 @@ package com.google.firebase.firestore {
public final class FirebaseFirestoreSettings {
method public com.google.firebase.firestore.LocalCacheSettings? getCacheSettings();
method @Deprecated public long getCacheSizeBytes();
method public int getGrpcFlowControlWindow();
method public String getHost();
method @Deprecated public boolean isPersistenceEnabled();
method public boolean isSslEnabled();
field public static final int DEFAULT_GRPC_FLOW_CONTROL_WINDOW = 262144; // 0x40000
field public static final long CACHE_SIZE_UNLIMITED = -1L; // 0xffffffffffffffffL
}

Expand All @@ -264,10 +266,12 @@ package com.google.firebase.firestore {
ctor public FirebaseFirestoreSettings.Builder(com.google.firebase.firestore.FirebaseFirestoreSettings);
method public com.google.firebase.firestore.FirebaseFirestoreSettings build();
method @Deprecated public long getCacheSizeBytes();
method public int getGrpcFlowControlWindow();
method public String getHost();
method @Deprecated public boolean isPersistenceEnabled();
method public boolean isSslEnabled();
method @Deprecated public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setCacheSizeBytes(long);
method public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setGrpcFlowControlWindow(int);
method public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setHost(String);
method public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setLocalCacheSettings(com.google.firebase.firestore.LocalCacheSettings);
method @Deprecated public com.google.firebase.firestore.FirebaseFirestoreSettings.Builder setPersistenceEnabled(boolean);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,12 @@ public void useEmulator(@NonNull String host, int port) {
private FirestoreClient newClient(AsyncQueue asyncQueue) {
synchronized (clientProvider) {
DatabaseInfo databaseInfo =
new DatabaseInfo(databaseId, persistenceKey, settings.getHost(), settings.isSslEnabled());
new DatabaseInfo(
databaseId,
persistenceKey,
settings.getHost(),
settings.isSslEnabled(),
settings.getGrpcFlowControlWindow());

return new FirestoreClient(
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class FirebaseFirestoreSettings {

static final long MINIMUM_CACHE_BYTES = 1 * 1024 * 1024; // 1 MB
static final long DEFAULT_CACHE_SIZE_BYTES = 100 * 1024 * 1024; // 100 MB
public static final int DEFAULT_GRPC_FLOW_CONTROL_WINDOW = 256 * 1024; // 256KB

/** A Builder for creating {@code FirebaseFirestoreSettings}. */
public static final class Builder {
Expand All @@ -43,6 +44,7 @@ public static final class Builder {

private long cacheSizeBytes;
private LocalCacheSettings cacheSettings;
private int grpcFlowControlWindow;

private boolean usedLegacyCacheSettings = false;

Expand All @@ -52,6 +54,7 @@ public Builder() {
sslEnabled = true;
persistenceEnabled = true;
cacheSizeBytes = DEFAULT_CACHE_SIZE_BYTES;
grpcFlowControlWindow = DEFAULT_GRPC_FLOW_CONTROL_WINDOW;
}

/**
Expand All @@ -64,6 +67,7 @@ public Builder(@NonNull FirebaseFirestoreSettings settings) {
sslEnabled = settings.sslEnabled;
persistenceEnabled = settings.persistenceEnabled;
cacheSizeBytes = settings.cacheSizeBytes;
grpcFlowControlWindow = settings.grpcFlowControlWindow;
if (!persistenceEnabled || cacheSizeBytes != DEFAULT_CACHE_SIZE_BYTES) {
usedLegacyCacheSettings = true;
}
Expand Down Expand Up @@ -214,6 +218,26 @@ public long getCacheSizeBytes() {
return cacheSizeBytes;
}

/**
* Sets the gRPC flow control window size.
*
* @param size The flow control window size in bytes. Must be non-negative.
* @return A settings object with the gRPC flow control window size set.
*/
@NonNull
public Builder setGrpcFlowControlWindow(int size) {
if (size < 0) {
throw new IllegalArgumentException("flow control window size must be non-negative");
}
this.grpcFlowControlWindow = size;
return this;
}

/** @return the gRPC flow control window size. */
public int getGrpcFlowControlWindow() {
return grpcFlowControlWindow;
}

@NonNull
public FirebaseFirestoreSettings build() {
if (!this.sslEnabled && this.host.equals(DEFAULT_HOST)) {
Expand All @@ -228,6 +252,7 @@ public FirebaseFirestoreSettings build() {
private final boolean sslEnabled;
private final boolean persistenceEnabled;
private final long cacheSizeBytes;
private final int grpcFlowControlWindow;

private LocalCacheSettings cacheSettings;

Expand All @@ -238,6 +263,7 @@ private FirebaseFirestoreSettings(Builder builder) {
persistenceEnabled = builder.persistenceEnabled;
cacheSizeBytes = builder.cacheSizeBytes;
cacheSettings = builder.cacheSettings;
grpcFlowControlWindow = builder.grpcFlowControlWindow;
}

@Override
Expand All @@ -250,6 +276,7 @@ public boolean equals(Object o) {
if (sslEnabled != that.sslEnabled) return false;
if (persistenceEnabled != that.persistenceEnabled) return false;
if (cacheSizeBytes != that.cacheSizeBytes) return false;
if (grpcFlowControlWindow != that.grpcFlowControlWindow) return false;
if (!host.equals(that.host)) return false;
return Objects.equals(cacheSettings, that.cacheSettings);
}
Expand All @@ -260,6 +287,7 @@ public int hashCode() {
result = 31 * result + (sslEnabled ? 1 : 0);
result = 31 * result + (persistenceEnabled ? 1 : 0);
result = 31 * result + (int) (cacheSizeBytes ^ (cacheSizeBytes >>> 32));
result = 31 * result + grpcFlowControlWindow;
result = 31 * result + (cacheSettings != null ? cacheSettings.hashCode() : 0);
return result;
}
Expand All @@ -268,19 +296,24 @@ public int hashCode() {
@NonNull
public String toString() {
return "FirebaseFirestoreSettings{"
+ "host="
+ host
+ ", sslEnabled="
+ sslEnabled
+ ", persistenceEnabled="
+ persistenceEnabled
+ ", cacheSizeBytes="
+ cacheSizeBytes
+ ", cacheSettings="
+ cacheSettings
== null
? "null"
: cacheSettings.toString() + "}";
+ "host="
+ host
+ ", sslEnabled="
+ sslEnabled
+ ", persistenceEnabled="
+ persistenceEnabled
+ ", cacheSizeBytes="
+ cacheSizeBytes
+ ", grpcFlowControlWindow="
+ grpcFlowControlWindow
+ ", cacheSettings="
+ (cacheSettings == null ? "null" : cacheSettings.toString())
+ "}";
}

/** Returns the gRPC flow control window size. */
public int getGrpcFlowControlWindow() {
return grpcFlowControlWindow;
}

/** Returns the host of the Cloud Firestore backend. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public final class DatabaseInfo {
private final String persistenceKey;
private final String host;
private final boolean sslEnabled;
private final int grpcFlowControlWindow;

/**
* Constructs a new DatabaseInfo.
Expand All @@ -35,10 +36,35 @@ public final class DatabaseInfo {
*/
public DatabaseInfo(
DatabaseId databaseId, String persistenceKey, String host, boolean sslEnabled) {
this(
databaseId,
persistenceKey,
host,
sslEnabled,
com.google.firebase.firestore.FirebaseFirestoreSettings.DEFAULT_GRPC_FLOW_CONTROL_WINDOW);
}

/**
* Constructs a new DatabaseInfo.
*
* @param databaseId The Google Cloud Project ID and database naming the Firestore instance.
* @param persistenceKey A unique identifier for this Firestore's local storage. Usually derived
* from FirebaseApp.name.
* @param host The hostname of the backend.
* @param sslEnabled Whether to use SSL when connecting.
* @param grpcFlowControlWindow gRPC flow control window size.
*/
public DatabaseInfo(
DatabaseId databaseId,
String persistenceKey,
String host,
boolean sslEnabled,
int grpcFlowControlWindow) {
this.databaseId = databaseId;
this.persistenceKey = persistenceKey;
this.host = host;
this.sslEnabled = sslEnabled;
this.grpcFlowControlWindow = grpcFlowControlWindow;
}

public DatabaseId getDatabaseId() {
Expand All @@ -57,6 +83,10 @@ public boolean isSslEnabled() {
return sslEnabled;
}

public int getGrpcFlowControlWindow() {
return grpcFlowControlWindow;
}

@Override
public String toString() {
return "DatabaseInfo(databaseId:" + databaseId + " host:" + host + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.grpc.ManagedChannelBuilder;
import io.grpc.MethodDescriptor;
import io.grpc.android.AndroidChannelBuilder;
import io.grpc.okhttp.OkHttpChannelBuilder;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

Expand All @@ -57,6 +58,7 @@ public class GrpcCallProvider {
// reconnecting again, rather than waiting up to 2+ minutes for gRPC to timeout.
// More details about usage can be found in GrpcCallProvider.onConnectivityStateChanged().
private static final int CONNECTIVITY_ATTEMPT_TIMEOUT_MS = 15 * 1000;

private DelayedTask connectivityAttemptTimer;

private final Context context;
Expand Down Expand Up @@ -95,13 +97,22 @@ private ManagedChannel initChannel(Context context, DatabaseInfo databaseInfo) {
ManagedChannelBuilder<?> channelBuilder;
if (overrideChannelBuilderSupplier != null) {
channelBuilder = overrideChannelBuilderSupplier.get();

if (channelBuilder instanceof OkHttpChannelBuilder) {
((OkHttpChannelBuilder) channelBuilder)
.flowControlWindow(databaseInfo.getGrpcFlowControlWindow());
}
} else {
channelBuilder = ManagedChannelBuilder.forTarget(databaseInfo.getHost());
OkHttpChannelBuilder okHttpBuilder = OkHttpChannelBuilder.forTarget(databaseInfo.getHost());
okHttpBuilder.flowControlWindow(databaseInfo.getGrpcFlowControlWindow());

if (!databaseInfo.isSslEnabled()) {
// Note that the boolean flag does *NOT* switch the wire format from Protobuf to Plaintext.
// It merely turns off SSL encryption.
channelBuilder.usePlaintext();
okHttpBuilder.usePlaintext();
}

channelBuilder = okHttpBuilder;
}

// Ensure gRPC recovers from a dead connection. (Not typically necessary, as the OS will
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public void builderWithNoModificationsShouldProduceDefaultSettings() {
assertEquals(settings.isSslEnabled(), true);
assertEquals(settings.isPersistenceEnabled(), true);
assertEquals(settings.getCacheSizeBytes(), 104857600L);
assertEquals(settings.getGrpcFlowControlWindow(), 256 * 1024);
}

@Test
Expand All @@ -43,11 +44,13 @@ public void builderWithAllValuesCustomizedShouldProduceSettingsWithThoseCustomVa
.setSslEnabled(false)
.setLocalCacheSettings(
PersistentCacheSettings.newBuilder().setSizeBytes(2000000L).build())
.setGrpcFlowControlWindow(512 * 1024)
.build();
assertEquals(settings.getHost(), "a.b.c");
assertEquals(settings.isSslEnabled(), false);
assertEquals(settings.isPersistenceEnabled(), true);
assertEquals(settings.getCacheSizeBytes(), 2000000L);
assertEquals(settings.getGrpcFlowControlWindow(), 512 * 1024);
}

@Test
Expand All @@ -57,12 +60,14 @@ public void builderConstructorShouldCopyAllValuesFromTheGivenSettings() {
.setHost("a.b.c")
.setSslEnabled(false)
.setLocalCacheSettings(MemoryCacheSettings.newBuilder().build())
.setGrpcFlowControlWindow(512 * 1024)
.build();
FirebaseFirestoreSettings settings2 = new FirebaseFirestoreSettings.Builder(settings1).build();
assertEquals(settings2.getHost(), "a.b.c");
assertEquals(settings2.isSslEnabled(), false);
assertEquals(settings2.isPersistenceEnabled(), false);
assertEquals(settings2.getCacheSizeBytes(), FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED);
assertEquals(settings2.getGrpcFlowControlWindow(), 512 * 1024);
}

@Test
Expand Down Expand Up @@ -100,4 +105,14 @@ public void cannotCustomizeCacheConfig() {
builder.setLocalCacheSettings(new LocalCacheSettings() {});
});
}

@Test
public void validatesFlowControlWindowSize() {
FirebaseFirestoreSettings.Builder builder = new FirebaseFirestoreSettings.Builder();
assertThrows(
IllegalArgumentException.class,
() -> {
builder.setGrpcFlowControlWindow(-1);
});
}
}
Loading
Loading