Skip to content

Commit 28bb3ad

Browse files
author
Greg Soltis
authored
Wire together LRU garbage collection (#1392)
Wire together LRU garbage collection Added a garbage collection process to on-disk persistence that removes older documents. This is enabled by default, and the SDK will attempt to periodically clean up older, unused documents once the on-disk cache passes a threshold size (default: 40 MB).
1 parent c313962 commit 28bb3ad

16 files changed

+728
-142
lines changed

packages/firebase/index.d.ts

+18
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,13 @@ declare namespace firebase.firestore {
759759
*/
760760
export type UpdateData = { [fieldPath: string]: any };
761761

762+
/**
763+
* Constant used to indicate the LRU garbage collection should be disabled.
764+
* Set this value as the `cacheSizeBytes` on the settings passed to the
765+
* `Firestore` instance.
766+
*/
767+
export const CACHE_SIZE_UNLIMITED: number;
768+
762769
/** Settings used to configure a `Firestore` instance. */
763770
export interface Settings {
764771
/** The hostname to connect to. */
@@ -785,6 +792,17 @@ declare namespace firebase.firestore {
785792
* use Timestamp now and opt-in to this new behavior as soon as you can.
786793
*/
787794
timestampsInSnapshots?: boolean;
795+
796+
/**
797+
* An approximate cache size threshold for the on-disk data. If the cache grows beyond this
798+
* size, Firestore will start removing data that hasn't been recently used. The size is not a
799+
* guarantee that the cache will stay below that size, only that if the cache exceeds the given
800+
* size, cleanup will be attempted.
801+
*
802+
* The default value is 40 MB. The threshold must be set to at least 1 MB, and can be set to
803+
* CACHE_SIZE_UNLIMITED to disable garbage collection.
804+
*/
805+
cacheSizeBytes?: number;
788806
}
789807

790808
/**

packages/firestore-types/index.d.ts

+18
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ export type DocumentData = { [field: string]: any };
2929
*/
3030
export type UpdateData = { [fieldPath: string]: any };
3131

32+
/**
33+
* Constant used to indicate the LRU garbage collection should be disabled.
34+
* Set this value as the `cacheSizeBytes` on the settings passed to the
35+
* `Firestore` instance.
36+
*/
37+
export const CACHE_SIZE_UNLIMITED: number;
38+
3239
/** Settings used to configure a `Firestore` instance. */
3340
export interface Settings {
3441
/** The hostname to connect to. */
@@ -55,6 +62,17 @@ export interface Settings {
5562
* use Timestamp now and opt-in to this new behavior as soon as you can.
5663
*/
5764
timestampsInSnapshots?: boolean;
65+
66+
/**
67+
* An approximate cache size threshold for the on-disk data. If the cache grows beyond this
68+
* size, Firestore will start removing data that hasn't been recently used. The size is not a
69+
* guarantee that the cache will stay below that size, only that if the cache exceeds the given
70+
* size, cleanup will be attempted.
71+
*
72+
* The default value is 40 MB. The threshold must be set to at least 1 MB, and can be set to
73+
* CACHE_SIZE_UNLIMITED to disable garbage collection.
74+
*/
75+
cacheSizeBytes?: number;
5876
}
5977

6078
/**

packages/firestore/src/api/database.ts

+57-42
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import { FirebaseApp } from '@firebase/app-types';
2020
import { FirebaseService } from '@firebase/app-types/private';
2121
import { DatabaseId, DatabaseInfo } from '../core/database_info';
2222
import { ListenOptions } from '../core/event_manager';
23-
import { FirestoreClient } from '../core/firestore_client';
23+
import {
24+
FirestoreClient,
25+
IndexedDbPersistenceSettings,
26+
InternalPersistenceSettings,
27+
MemoryPersistenceSettings
28+
} from '../core/firestore_client';
2429
import {
2530
Bound,
2631
Direction,
@@ -32,6 +37,7 @@ import {
3237
} from '../core/query';
3338
import { Transaction as InternalTransaction } from '../core/transaction';
3439
import { ChangeType, ViewSnapshot } from '../core/view_snapshot';
40+
import { LruParams } from '../local/lru_garbage_collector';
3541
import { Document, MaybeDocument, NoDocument } from '../model/document';
3642
import { DocumentKey } from '../model/document_key';
3743
import {
@@ -102,6 +108,13 @@ const DEFAULT_HOST = 'firestore.googleapis.com';
102108
const DEFAULT_SSL = true;
103109
const DEFAULT_TIMESTAMPS_IN_SNAPSHOTS = false;
104110

111+
/**
112+
* Constant used to indicate the LRU garbage collection should be disabled.
113+
* Set this value as the `cacheSizeBytes` on the settings passed to the
114+
* `Firestore` instance.
115+
*/
116+
export const CACHE_SIZE_UNLIMITED = LruParams.COLLECTION_DISABLED;
117+
105118
// enablePersistence() defaults:
106119
const DEFAULT_SYNCHRONIZE_TABS = false;
107120

@@ -127,12 +140,14 @@ export interface FirestoreDatabase {
127140
*/
128141
class FirestoreSettings {
129142
/** The hostname to connect to. */
130-
host: string;
143+
readonly host: string;
131144

132145
/** Whether to use SSL when connecting. */
133-
ssl: boolean;
146+
readonly ssl: boolean;
147+
148+
readonly timestampsInSnapshots: boolean;
134149

135-
timestampsInSnapshots: boolean;
150+
readonly cacheSizeBytes: number;
136151

137152
// Can be a google-auth-library or gapi client.
138153
// tslint:disable-next-line:no-any
@@ -159,7 +174,8 @@ class FirestoreSettings {
159174
'host',
160175
'ssl',
161176
'credentials',
162-
'timestampsInSnapshots'
177+
'timestampsInSnapshots',
178+
'cacheSizeBytes'
163179
]);
164180

165181
validateNamedOptionalType(
@@ -180,14 +196,39 @@ class FirestoreSettings {
180196
settings.timestampsInSnapshots,
181197
DEFAULT_TIMESTAMPS_IN_SNAPSHOTS
182198
);
199+
200+
validateNamedOptionalType(
201+
'settings',
202+
'number',
203+
'cacheSizeBytes',
204+
settings.cacheSizeBytes
205+
);
206+
if (settings.cacheSizeBytes === undefined) {
207+
this.cacheSizeBytes = LruParams.DEFAULT_CACHE_SIZE_BYTES;
208+
} else {
209+
if (
210+
settings.cacheSizeBytes !== CACHE_SIZE_UNLIMITED &&
211+
settings.cacheSizeBytes < LruParams.MINIMUM_CACHE_SIZE_BYTES
212+
) {
213+
throw new FirestoreError(
214+
Code.INVALID_ARGUMENT,
215+
`cacheSizeBytes must be at least ${
216+
LruParams.MINIMUM_CACHE_SIZE_BYTES
217+
}`
218+
);
219+
} else {
220+
this.cacheSizeBytes = settings.cacheSizeBytes;
221+
}
222+
}
183223
}
184224

185225
isEqual(other: FirestoreSettings): boolean {
186226
return (
187227
this.host === other.host &&
188228
this.ssl === other.ssl &&
189229
this.timestampsInSnapshots === other.timestampsInSnapshots &&
190-
this.credentials === other.credentials
230+
this.credentials === other.credentials &&
231+
this.cacheSizeBytes === other.cacheSizeBytes
191232
);
192233
}
193234
}
@@ -201,38 +242,6 @@ class FirestoreConfig {
201242
persistence: boolean;
202243
}
203244

204-
/**
205-
* Encapsulates the settings that can be used to configure Firestore
206-
* persistence.
207-
*/
208-
export class PersistenceSettings {
209-
/** Whether to enable multi-tab synchronization. */
210-
experimentalTabSynchronization: boolean;
211-
212-
constructor(
213-
readonly enabled: boolean,
214-
settings?: firestore.PersistenceSettings
215-
) {
216-
assert(
217-
enabled || !settings,
218-
'Can only provide PersistenceSettings with persistence enabled'
219-
);
220-
settings = settings || {};
221-
this.experimentalTabSynchronization = objUtils.defaulted(
222-
settings.experimentalTabSynchronization,
223-
DEFAULT_SYNCHRONIZE_TABS
224-
);
225-
}
226-
227-
isEqual(other: PersistenceSettings): boolean {
228-
return (
229-
this.enabled === other.enabled &&
230-
this.experimentalTabSynchronization ===
231-
other.experimentalTabSynchronization
232-
);
233-
}
234-
}
235-
236245
/**
237246
* The root reference to the database.
238247
*/
@@ -335,23 +344,29 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
335344
'any other methods on a Firestore object.'
336345
);
337346
}
338-
339347
return this.configureClient(
340-
new PersistenceSettings(/* enabled= */ true, settings)
348+
new IndexedDbPersistenceSettings(
349+
this._config.settings.cacheSizeBytes,
350+
settings !== undefined &&
351+
objUtils.defaulted(
352+
settings.experimentalTabSynchronization,
353+
DEFAULT_SYNCHRONIZE_TABS
354+
)
355+
)
341356
);
342357
}
343358

344359
ensureClientConfigured(): FirestoreClient {
345360
if (!this._firestoreClient) {
346361
// Kick off starting the client but don't actually wait for it.
347362
// tslint:disable-next-line:no-floating-promises
348-
this.configureClient(new PersistenceSettings(/* enabled= */ false));
363+
this.configureClient(new MemoryPersistenceSettings());
349364
}
350365
return this._firestoreClient as FirestoreClient;
351366
}
352367

353368
private configureClient(
354-
persistenceSettings: PersistenceSettings
369+
persistenceSettings: InternalPersistenceSettings
355370
): Promise<void> {
356371
assert(
357372
!!this._config.settings.host,

0 commit comments

Comments
 (0)