Skip to content

Memory LRU GC #6943

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 46 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e8b53c6
Initial implementation
wu-hui Jan 4, 2023
3a922f1
Add spec tests for LRU GC
wu-hui Jan 11, 2023
107a051
lint
wu-hui Jan 11, 2023
14ed4f3
Update API reports
wu-hui Jan 11, 2023
7b9d18d
Spec test clean up
wu-hui Jan 12, 2023
2ea1fad
Fix error and polishing.
wu-hui Jan 12, 2023
92b1cac
Fix a build error
wu-hui Jan 12, 2023
4ed6691
New way to config Firestore SDK Cache.
wu-hui Feb 8, 2023
48d5077
fixups
wu-hui Feb 9, 2023
7d0b186
Fix some test failures
wu-hui Feb 9, 2023
d651d88
Fixing dts file
wu-hui Feb 21, 2023
880bd55
Fixing integration tests
wu-hui Feb 28, 2023
c36e96f
rename file
wu-hui Feb 28, 2023
85ea128
Increase cache size threshold
wu-hui Feb 28, 2023
28215da
Add public comments
wu-hui Mar 1, 2023
36715cd
API report
wu-hui Mar 1, 2023
4bbcbe1
Create brown-beers-tease.md
wu-hui Mar 1, 2023
3d22995
warning messages and more tests
wu-hui Mar 1, 2023
5b16efd
Addressing comments
wu-hui Mar 2, 2023
2b9d3b8
Merge branch 'wuandy/NewCacheConfig' into wuandy/LruGC
wu-hui Mar 2, 2023
89f78c7
Update API reports
wu-hui Mar 2, 2023
93738f3
new lru gc api
wu-hui Mar 2, 2023
1d5c82f
Fix provider test failure for node w/o indexeddb
wu-hui Mar 2, 2023
04f85ab
Fix node memory test
wu-hui Mar 2, 2023
e53ae7f
Merge branch 'wuandy/NewCacheConfig' into wuandy/LruGC
wu-hui Mar 3, 2023
2489fc6
linting and exporting
wu-hui Mar 3, 2023
70b742f
rename cache to localCache
wu-hui Mar 3, 2023
95f537e
Update API reports
wu-hui Mar 3, 2023
a49b8a5
Tech writer review
wu-hui Mar 6, 2023
bb616a8
Merge branch 'wuandy/NewCacheConfig' into wuandy/LruGC
wu-hui Mar 6, 2023
0c1c12e
yarn docgen
wu-hui Mar 6, 2023
1d0af2e
Merge branch 'wuandy/NewCacheConfig' into wuandy/LruGC
wu-hui Mar 6, 2023
de64910
Merge branch 'master' into wuandy/LruGC
wu-hui Mar 17, 2023
9771e74
Fix merge error and re-build devsite
wu-hui Mar 17, 2023
f05a45e
lint and prettier
wu-hui Mar 17, 2023
57c64c7
Merge branch 'master' into wuandy/LruGC
wu-hui Mar 22, 2023
faf7aca
Merge branch 'master' into wuandy/LruGC
wu-hui Apr 12, 2023
8e62300
Update API reports
wu-hui Apr 12, 2023
b83a723
addressing feedback
wu-hui Apr 17, 2023
6e5767d
Merge branch 'master' into wuandy/LruGC
wu-hui Apr 17, 2023
c20f61f
docsite
wu-hui Apr 17, 2023
68e2f21
review auth.api.md
wu-hui Apr 17, 2023
5303e16
Update API reports
wu-hui Apr 17, 2023
5bf4ec8
changeset and revert auth changes
wu-hui Apr 17, 2023
fc4bb55
fix error message
wu-hui Apr 17, 2023
85483bf
minor bump
wu-hui Apr 18, 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
3 changes: 3 additions & 0 deletions common/api-review/firestore.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ export { EmulatorMockTokenOptions }
// @public
export function enableIndexedDbPersistence(firestore: Firestore, persistenceSettings?: PersistenceSettings): Promise<void>;

// @public
export function enableMemoryLRUGarbageCollection(firestore: Firestore): Promise<void>;

// @public
export function enableMultiTabIndexedDbPersistence(firestore: Firestore): Promise<void>;

Expand Down
1 change: 1 addition & 0 deletions packages/firestore/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export {
initializeFirestore,
getFirestore,
enableIndexedDbPersistence,
enableMemoryLRUGarbageCollection,
enableMultiTabIndexedDbPersistence,
clearIndexedDbPersistence,
waitForPendingWrites,
Expand Down
38 changes: 38 additions & 0 deletions packages/firestore/src/api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { deepEqual, getDefaultEmulatorHostnameAndPort } from '@firebase/util';
import { User } from '../auth/user';
import {
IndexedDbOfflineComponentProvider,
LruGcMemoryOfflineComponentProvider,
MultiTabOfflineComponentProvider,
OfflineComponentProvider,
OnlineComponentProvider
Expand Down Expand Up @@ -285,6 +286,43 @@ export function configureFirestore(firestore: Firestore): void {
);
}

/**
* Attempts to enable LRU garbage collection for memory persistence.
*
* Must be called before any other functions (other than
* {@link initializeFirestore}, {@link (getFirestore:1)} or
* {@link clearIndexedDbPersistence}.
*
* By default, any documents that are not part of an active query result or
* with no mutation attached to them are removed from memory immediately.
*
* This function changes the default behavior, to enable a least-recent-used
* garbage collector. Documents will be collected when their total size exceeds
* `Settings.cacheSizeBytes`, with least recently used documents get removed first.
*
* @param firestore - The {@link Firestore} instance to enable LRU garbage collection for.
* @returns A `Promise` that represents successfully enabling LRU garbage collection.
*/
export function enableMemoryLRUGarbageCollection(
firestore: Firestore
): Promise<void> {
firestore = cast(firestore, Firestore);
verifyNotInitialized(firestore);

const client = ensureFirestoreConfigured(firestore);
const settings = firestore._freezeSettings();

const onlineComponentProvider = new OnlineComponentProvider();
const offlineComponentProvider = new LruGcMemoryOfflineComponentProvider(
settings.cacheSizeBytes
);
return setPersistenceProviders(
client,
onlineComponentProvider,
offlineComponentProvider
);
}

/**
* Attempts to enable persistent storage, if possible.
*
Expand Down
33 changes: 33 additions & 0 deletions packages/firestore/src/core/component_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { LruParams } from '../local/lru_garbage_collector';
import { LruScheduler } from '../local/lru_garbage_collector_impl';
import {
MemoryEagerDelegate,
MemoryLruDelegate,
MemoryPersistence
} from '../local/memory_persistence';
import { Scheduler, Persistence } from '../local/persistence';
Expand All @@ -53,6 +54,7 @@ import {
remoteStoreShutdown
} from '../remote/remote_store';
import { JsonProtoSerializer } from '../remote/serializer';
import { hardAssert } from '../util/assert';
import { AsyncQueue } from '../util/async_queue';
import { Code, FirestoreError } from '../util/error';

Expand Down Expand Up @@ -172,6 +174,37 @@ export class MemoryOfflineComponentProvider
}
}

export class LruGcMemoryOfflineComponentProvider extends MemoryOfflineComponentProvider {
constructor(protected readonly cacheSizeBytes: number | undefined) {
super();
}

createGarbageCollectionScheduler(
cfg: ComponentConfiguration,
localStore: LocalStore
): Scheduler | null {
hardAssert(
this.persistence.referenceDelegate instanceof MemoryLruDelegate,
'referenceDelegate is expected to be an instance of MemoryLruDelegate.'
);

const garbageCollector =
this.persistence.referenceDelegate.garbageCollector;
return new LruScheduler(garbageCollector, cfg.asyncQueue, localStore);
}

createPersistence(cfg: ComponentConfiguration): Persistence {
const lruParams =
this.cacheSizeBytes !== undefined
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if we need a range check for this.cacheSizeBytes > 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually won't work because we use -1 to disable GC.

I really do not like this approach, we should do something similar to the new cache config API, using a dedicated enum branch to disable GC. Maybe something for the fixit week.

? LruParams.withCacheSize(this.cacheSizeBytes)
: LruParams.DEFAULT;
return new MemoryPersistence(
p => MemoryLruDelegate.factory(p, lruParams),
this.serializer
);
}
}

/**
* Provides all components needed for Firestore with IndexedDB persistence.
*/
Expand Down
4 changes: 3 additions & 1 deletion packages/firestore/src/local/lru_garbage_collector_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ export class LruScheduler implements Scheduler {
}
}

/** Implements the steps for LRU garbage collection. */
/**
* Implements the steps for LRU garbage collection.
*/
class LruGarbageCollectorImpl implements LruGarbageCollector {
constructor(
private readonly delegate: LruDelegate,
Expand Down
7 changes: 7 additions & 0 deletions packages/firestore/src/local/memory_persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,13 @@ export class MemoryLruDelegate implements ReferenceDelegate, LruDelegate {
this.garbageCollector = newLruGarbageCollector(this, lruParams);
}

static factory(
persistence: MemoryPersistence,
lruParams: LruParams | null
): MemoryLruDelegate {
return new MemoryLruDelegate(persistence, lruParams!!);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the double exclamation at the end of lruParams!! do? I'm not familiar with the double.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It tells complier that you are sure this is a LruParams, not a null. I removed | null in this case, it was not neccessary.

}

// No-ops, present so memory persistence doesn't have to care which delegate
// it has.
onTransactionStarted(): void {}
Expand Down
26 changes: 25 additions & 1 deletion packages/firestore/test/integration/api/database.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ import {
} from '../util/firebase_export';
import {
apiDescribe,
withEnsuredLruGcTestDb,
withTestCollection,
withTestDbsSettings,
withTestDb,
withTestDbs,
withTestDoc,
withTestDocAndInitialData,
withNamedTestDbsOrSkipUnlessUsingEmulator
withNamedTestDbsOrSkipUnlessUsingEmulator,
withEnsuredEagerGcTestDb
} from '../util/helpers';
import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings';

Expand Down Expand Up @@ -1751,4 +1753,26 @@ apiDescribe('Database', (persistence: boolean) => {
}
);
});

it('Cannot get document from cache with eager GC enabled.', () => {
const initialData = { key: 'value' };
return withEnsuredEagerGcTestDb(async db => {
const docRef = doc(collection(db, 'test-collection'));
await setDoc(docRef, initialData);
await expect(getDocFromCache(docRef)).to.be.rejectedWith('Failed to get');
});
});

it('Can get document from cache with Lru GC enabled.', () => {
const initialData = { key: 'value' };
return withEnsuredLruGcTestDb(persistence, async db => {
const docRef = doc(collection(db, 'test-collection'));
await setDoc(docRef, initialData);
return getDocFromCache(docRef).then(doc => {
expect(doc.exists()).to.be.true;
expect(doc.metadata.fromCache).to.be.true;
expect(doc.data()).to.deep.equal(initialData);
});
});
});
});
12 changes: 7 additions & 5 deletions packages/firestore/test/integration/api/get_options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import { expect } from 'chai';

import {
collection,
deleteDoc,
disableNetwork,
doc,
Expand All @@ -34,7 +35,8 @@ import {
toDataMap,
apiDescribe,
withTestCollection,
withTestDocAndInitialData
withTestDocAndInitialData,
withEnsuredLruGcTestDb
} from '../util/helpers';

apiDescribe('GetOptions', (persistence: boolean) => {
Expand Down Expand Up @@ -68,10 +70,10 @@ apiDescribe('GetOptions', (persistence: boolean) => {

it('get document while offline with default get options', () => {
const initialData = { key: 'value' };
return withTestDocAndInitialData(persistence, initialData, (docRef, db) => {
// Register a snapshot to force the data to stay in the cache and not be
// garbage collected.
onSnapshot(docRef, () => {});
// Use an instance with Gc turned on.
return withEnsuredLruGcTestDb(persistence, async db => {
const docRef = doc(collection(db, 'test-collection'));
await setDoc(docRef, initialData);
return getDoc(docRef)
.then(() => disableNetwork(db))
.then(() => getDoc(docRef))
Expand Down
35 changes: 34 additions & 1 deletion packages/firestore/test/integration/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import {
PrivateSettings,
SnapshotListenOptions,
newTestFirestore,
newTestApp
newTestApp,
enableMemoryLRUGarbageCollection
} from './firebase_export';
import {
ALT_PROJECT_ID,
Expand Down Expand Up @@ -141,6 +142,38 @@ export function withTestDb(
});
}

export function withEnsuredEagerGcTestDb(
fn: (db: Firestore) => Promise<void>
): Promise<void> {
return withTestDbsSettings(
false,
DEFAULT_PROJECT_ID,
{ ...DEFAULT_SETTINGS, cacheSizeBytes: 1 * 1024 * 1024 },
1,
async ([db]) => {
return fn(db);
}
);
}

export function withEnsuredLruGcTestDb(
persistence: boolean,
fn: (db: Firestore) => Promise<void>
): Promise<void> {
return withTestDbsSettings(
persistence,
DEFAULT_PROJECT_ID,
{ ...DEFAULT_SETTINGS, cacheSizeBytes: 1 * 1024 * 1024 },
1,
async ([db]) => {
if (!persistence) {
await enableMemoryLRUGarbageCollection(db);
}
return fn(db);
}
);
}

/** Runs provided fn with a db for an alternate project id. */
export function withAlternateTestDb(
persistence: boolean,
Expand Down
6 changes: 3 additions & 3 deletions packages/firestore/test/unit/specs/bundle_spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ describeSpec('Bundles:', ['no-ios'], () => {
spec()
// TODO(b/160878667): Figure out what happens when memory eager GC is on
// a bundle is loaded.
.withGCEnabled(false)
.ensureManualLruGC()
.userListens(query1)
.watchAcksFull(query1, 250, docA)
.expectEvents(query1, {
Expand Down Expand Up @@ -224,7 +224,7 @@ describeSpec('Bundles:', ['no-ios'], () => {

return (
spec()
.withGCEnabled(false)
.ensureManualLruGC()
.userListens(query1)
.watchAcksFull(query1, 250, docA)
.expectEvents(query1, {
Expand Down Expand Up @@ -260,7 +260,7 @@ describeSpec('Bundles:', ['no-ios'], () => {

return (
spec()
.withGCEnabled(false)
.ensureManualLruGC()
.userListens(query1)
.watchAcksFull(query1, 250)
// Backend tells is there is no such doc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describeSpec('Existence Filters:', [], () => {
const doc1 = doc('collection/1', 2000, { v: 2 });
return (
spec()
.withGCEnabled(false)
.ensureManualLruGC()
.userListens(query1)
.watchAcksFull(query1, 1000, doc1)
.expectEvents(query1, { added: [doc1] })
Expand Down
Loading