Skip to content

Commit dd12f2e

Browse files
[v2int/7.4] GC: Follow-up changes to support successful roll-out of GC Sweep (#19598)
This brings over some GC fixes and minor features requested by a 1P partner who is preparing to ship GC Sweep: * Some telemetry fixes that will help us to monitor the accuracy and impact of GC better * A new option to allow enabling sleep only for attachment blobs at first Porting the following commits to the `release/v2int/7.4` branch to be released in internal.7.4.7: * 0d05377 - previously ported but backed out to support 1P partner's livesite, contains the following commits from `main`: * 40ad2d4 * a61a831 * d1d5c58 * bc11aa4 Related port PRs for other releases: * #19087 * #19591 * #19589 Co-authored-by: Navin Agarwal <[email protected]>
1 parent a2544ce commit dd12f2e

23 files changed

+703
-688
lines changed

packages/runtime/container-runtime/src/blobManager.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import {
3939
} from "@fluidframework/runtime-definitions";
4040

4141
import { canRetryOnError, runWithRetry } from "@fluidframework/driver-utils";
42-
import { disableAttachmentBlobSweepKey } from "./gc";
4342
import { IBlobMetadata } from "./metadata";
4443

4544
/**
@@ -740,11 +739,6 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
740739
* @returns The routes of blobs that were deleted.
741740
*/
742741
public deleteSweepReadyNodes(sweepReadyBlobRoutes: readonly string[]): readonly string[] {
743-
// If sweep for attachment blobs is not enabled, return empty list indicating nothing is deleted.
744-
if (this.mc.config.getBoolean(disableAttachmentBlobSweepKey) === true) {
745-
return [];
746-
}
747-
748742
this.deleteBlobsFromRedirectTable(sweepReadyBlobRoutes);
749743
return Array.from(sweepReadyBlobRoutes);
750744
}

packages/runtime/container-runtime/src/containerRuntime.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,9 +1449,6 @@ export class ContainerRuntime
14491449
getNodePackagePath: async (nodePath: string) => this.getGCNodePackagePath(nodePath),
14501450
getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
14511451
readAndParseBlob: async <T>(id: string) => readAndParse<T>(this.storage, id),
1452-
// GC runs in summarizer client and needs access to the real (non-proxy) active information. The proxy
1453-
// delta manager would always return false for summarizer client.
1454-
activeConnection: () => this.innerDeltaManager.active,
14551452
submitMessage: (message: ContainerRuntimeGCMessage) => this.submit(message),
14561453
});
14571454

packages/runtime/container-runtime/src/dataStores.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ import {
6060
LocalDetachedFluidDataStoreContext,
6161
} from "./dataStoreContext";
6262
import { StorageServiceWithAttachBlobs } from "./storageServiceWithAttachBlobs";
63+
import { GCNodeType } from "./gc";
6364
import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
64-
import { GCNodeType, disableDatastoreSweepKey } from "./gc";
6565
import { IContainerRuntimeMetadata, nonDataStorePaths, rootHasIsolatedChannels } from "./summary";
6666

6767
type PendingAliasResolve = (success: boolean) => void;
@@ -830,16 +830,13 @@ export class DataStores implements IDisposable {
830830
* @returns The routes of data stores and its objects that were deleted.
831831
*/
832832
public deleteSweepReadyNodes(sweepReadyDataStoreRoutes: readonly string[]): readonly string[] {
833-
// If sweep for data stores is not enabled, return empty list indicating nothing is deleted.
834-
if (this.mc.config.getBoolean(disableDatastoreSweepKey) === true) {
835-
return [];
836-
}
837833
for (const route of sweepReadyDataStoreRoutes) {
838834
const pathParts = route.split("/");
839835
const dataStoreId = pathParts[1];
840836

841837
// Ignore sub-data store routes because a data store and its sub-routes are deleted together, so, we only
842838
// need to delete the data store.
839+
// These routes will still be returned below as among the deleted routes
843840
if (pathParts.length > 2) {
844841
continue;
845842
}

packages/runtime/container-runtime/src/gc/garbageCollection.ts

Lines changed: 136 additions & 142 deletions
Large diffs are not rendered by default.

packages/runtime/container-runtime/src/gc/gcConfigs.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import {
3131
gcDisableThrowOnTombstoneLoadOptionName,
3232
defaultSweepGracePeriodMs,
3333
gcGenerationOptionName,
34+
disableDatastoreSweepKey,
35+
gcDisableDataStoreSweepOptionName,
3436
} from "./gcDefinitions";
3537
import { getGCVersion, shouldAllowGcSweep } from "./gcHelpers";
3638

@@ -133,16 +135,24 @@ export function generateGCConfigs(
133135
*
134136
* Assuming overall GC is enabled and sweepTimeout is provided, the following conditions have to be met to run sweep:
135137
*
136-
* 1. Sweep should be enabled for this container.
137-
* 2. Sweep should be enabled for this session.
138+
* 1. Sweep should be allowed in this container.
139+
* 2. Sweep should be enabled for this session, optionally restricted to attachment blobs only.
138140
*
139141
* These conditions can be overridden via the RunSweep feature flag.
140142
*/
141-
const shouldRunSweep =
143+
const sweepEnabled: boolean =
142144
!shouldRunGC || sweepTimeoutMs === undefined
143145
? false
144146
: mc.config.getBoolean(runSweepKey) ??
145147
(sweepAllowed && createParams.gcOptions.enableGCSweep === true);
148+
const disableDatastoreSweep =
149+
mc.config.getBoolean(disableDatastoreSweepKey) === true ||
150+
createParams.gcOptions[gcDisableDataStoreSweepOptionName] === true;
151+
const shouldRunSweep: IGarbageCollectorConfigs["shouldRunSweep"] = sweepEnabled
152+
? disableDatastoreSweep
153+
? "ONLY_BLOBS"
154+
: "YES"
155+
: "NO";
146156

147157
// Override inactive timeout if test config or gc options to override it is set.
148158
const inactiveTimeoutMs =

packages/runtime/container-runtime/src/gc/gcDefinitions.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ export const nextGCVersion: GCVersion = 4;
4040
*/
4141
export const gcDisableThrowOnTombstoneLoadOptionName = "gcDisableThrowOnTombstoneLoad";
4242

43+
/**
44+
* This undocumented GC Option (on ContainerRuntime Options) allows an app to enable Sweep for blobs only.
45+
* Only applies if enableGCSweep option is set to true.
46+
*/
47+
export const gcDisableDataStoreSweepOptionName = "disableDataStoreSweep";
48+
4349
/**
4450
* This undocumented GC Option (on ContainerRuntime Options) allows configuring which documents can have Sweep enabled.
4551
* This provides a way to disable both Tombstone Enforcement and Sweep.
@@ -70,10 +76,8 @@ export const throwOnTombstoneLoadOverrideKey =
7076
export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
7177
/** Config key to enable GC version upgrade. */
7278
export const gcVersionUpgradeToV4Key = "Fluid.GarbageCollection.GCVersionUpgradeToV4";
73-
/** Config key to disable GC sweep for datastores. */
79+
/** Config key to disable GC sweep for datastores. They'll merely be Tombstoned. */
7480
export const disableDatastoreSweepKey = "Fluid.GarbageCollection.DisableDataStoreSweep";
75-
/** Config key to disable GC sweep for attachment blobs. */
76-
export const disableAttachmentBlobSweepKey = "Fluid.GarbageCollection.DisableAttachmentBlobSweep";
7781

7882
// One day in milliseconds.
7983
export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
@@ -353,7 +357,6 @@ export interface IGarbageCollectorCreateParams {
353357
readonly getNodePackagePath: (nodePath: string) => Promise<readonly string[] | undefined>;
354358
readonly getLastSummaryTimestampMs: () => number | undefined;
355359
readonly readAndParseBlob: ReadAndParseBlob;
356-
readonly activeConnection: () => boolean;
357360
readonly submitMessage: (message: ContainerRuntimeGCMessage) => void;
358361
}
359362

@@ -426,7 +429,7 @@ export interface IGarbageCollectorConfigs {
426429
*/
427430
readonly gcEnabled: boolean;
428431
/**
429-
* Tracks if sweep phase is enabled for this document. This is specified during document creation and doesn't change
432+
* Tracks if sweep phase is allowed for this document. This is specified during document creation and doesn't change
430433
* throughout its lifetime.
431434
*/
432435
readonly sweepEnabled: boolean;
@@ -436,10 +439,11 @@ export interface IGarbageCollectorConfigs {
436439
*/
437440
readonly shouldRunGC: boolean;
438441
/**
439-
* Tracks if sweep phase should run or not. Even if the sweep phase is enabled for a document (see sweepEnabled), it
440-
* can be explicitly disabled via feature flags. It also won't run if session expiry is not enabled.
442+
* Tracks if sweep phase should run or not, or if it should run only for attachment blobs.
443+
* Even if the sweep phase is allowed for a document (see sweepEnabled), it may be disabled or partially enabled
444+
* for the session, depending on a variety of other configurations present.
441445
*/
442-
readonly shouldRunSweep: boolean;
446+
readonly shouldRunSweep: "YES" | "ONLY_BLOBS" | "NO";
443447
/**
444448
* If true, bypass optimizations and generate GC data for all nodes irrespective of whether a node changed or not.
445449
*/

packages/runtime/container-runtime/src/gc/gcSummaryStateTracker.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,11 @@ export class GCSummaryStateTracker {
111111
/**
112112
* Called during GC initialization. Initialize the latest summary data from the base snapshot data.
113113
*/
114-
public initializeBaseState(baseSnapshotData: IGarbageCollectionSnapshotData) {
114+
public initializeBaseState(baseSnapshotData: IGarbageCollectionSnapshotData | undefined) {
115+
if (baseSnapshotData === undefined) {
116+
return;
117+
}
118+
115119
// If tracking state across summaries, update latest summary data from the snapshot's GC data.
116120
this.latestSummaryData = {
117121
serializedGCState: baseSnapshotData.gcState

packages/runtime/container-runtime/src/gc/gcTelemetry.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -240,13 +240,9 @@ export class GCTelemetryTracker {
240240
gcConfigs,
241241
};
242242

243-
// Do not log the inactive object x events as error events as they are not the best signal for
244-
// detecting something wrong with GC either from the partner or from the runtime itself.
245-
if (state === UnreferencedState.Inactive) {
246-
this.mc.logger.sendTelemetryEvent(event);
247-
} else {
248-
this.mc.logger.sendErrorEvent(event);
249-
}
243+
// These are logged as generic events and not errors because there can be false positives. The Tombstone
244+
// and Delete errors are separately logged and are reliable.
245+
this.mc.logger.sendTelemetryEvent(event);
250246
}
251247
}
252248
}
@@ -393,12 +389,7 @@ export class GCTelemetryTracker {
393389
fromPkg: fromPkg?.join("/"),
394390
}),
395391
};
396-
397-
if (state === UnreferencedState.Inactive) {
398-
logger.sendTelemetryEvent(event);
399-
} else {
400-
logger.sendErrorEvent(event);
401-
}
392+
logger.sendTelemetryEvent(event);
402393
}
403394
}
404395
this.pendingEventsQueue = [];

packages/runtime/container-runtime/src/gc/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export {
1111
defaultSessionExpiryDurationMs,
1212
GCNodeType,
1313
gcTestModeKey,
14+
gcDisableDataStoreSweepOptionName,
1415
gcDisableThrowOnTombstoneLoadOptionName,
1516
gcGenerationOptionName,
1617
GCFeatureMatrix,
@@ -31,7 +32,6 @@ export {
3132
runSessionExpiryKey,
3233
runSweepKey,
3334
stableGCVersion,
34-
disableAttachmentBlobSweepKey,
3535
disableDatastoreSweepKey,
3636
UnreferencedState,
3737
throwOnTombstoneLoadOverrideKey,

packages/runtime/container-runtime/src/test/blobManager.spec.ts

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import {
3131
LoggingError,
3232
} from "@fluidframework/telemetry-utils";
3333
import { BlobManager, IBlobManagerLoadInfo, IBlobManagerRuntime } from "../blobManager";
34-
import { disableAttachmentBlobSweepKey } from "../gc";
3534

3635
const MIN_TTL = 24 * 60 * 60; // same as ODSP
3736
abstract class BaseMockBlobStorage
@@ -941,47 +940,33 @@ describe("BlobManager", () => {
941940
);
942941
});
943942

944-
it("disableAttachmentBlobsSweep true - DOESN'T delete unused blobs ", async () => {
945-
injectedSettings[disableAttachmentBlobSweepKey] = true;
946-
947-
await runtime.attach();
948-
await runtime.connect();
949-
950-
const blob1 = await createBlobAndGetIds("blob1");
951-
const blob2 = await createBlobAndGetIds("blob2");
952-
953-
// Delete blob1's local id. The local id and the storage id should both be deleted from the redirect table
954-
// since the blob only had one reference.
955-
runtime.blobManager.deleteSweepReadyNodes([blob1.localGCNodeId]);
956-
assert(redirectTable.has(blob1.localId));
957-
assert(redirectTable.has(blob1.storageId));
958-
959-
// Delete blob2's local id. The local id and the storage id should both be deleted from the redirect table
960-
// since the blob only had one reference.
961-
runtime.blobManager.deleteSweepReadyNodes([blob2.localGCNodeId]);
962-
assert(redirectTable.has(blob2.localId));
963-
assert(redirectTable.has(blob2.storageId));
964-
});
965-
966-
it("deletes unused blobs", async () => {
967-
await runtime.attach();
968-
await runtime.connect();
969-
970-
const blob1 = await createBlobAndGetIds("blob1");
971-
const blob2 = await createBlobAndGetIds("blob2");
972-
973-
// Delete blob1's local id. The local id and the storage id should both be deleted from the redirect table
974-
// since the blob only had one reference.
975-
runtime.blobManager.deleteSweepReadyNodes([blob1.localGCNodeId]);
976-
assert(!redirectTable.has(blob1.localId));
977-
assert(!redirectTable.has(blob1.storageId));
978-
979-
// Delete blob2's local id. The local id and the storage id should both be deleted from the redirect table
980-
// since the blob only had one reference.
981-
runtime.blobManager.deleteSweepReadyNodes([blob2.localGCNodeId]);
982-
assert(!redirectTable.has(blob2.localId));
983-
assert(!redirectTable.has(blob2.storageId));
984-
});
943+
// Support for this config has been removed.
944+
const legacyKey_disableAttachmentBlobSweep =
945+
"Fluid.GarbageCollection.DisableAttachmentBlobSweep";
946+
[true, undefined].forEach((disableAttachmentBlobsSweep) =>
947+
it(`deletes unused blobs regardless of DisableAttachmentBlobsSweep setting [DisableAttachmentBlobsSweep=${disableAttachmentBlobsSweep}]`, async () => {
948+
injectedSettings[legacyKey_disableAttachmentBlobSweep] =
949+
disableAttachmentBlobsSweep;
950+
951+
await runtime.attach();
952+
await runtime.connect();
953+
954+
const blob1 = await createBlobAndGetIds("blob1");
955+
const blob2 = await createBlobAndGetIds("blob2");
956+
957+
// Delete blob1's local id. The local id and the storage id should both be deleted from the redirect table
958+
// since the blob only had one reference.
959+
runtime.blobManager.deleteSweepReadyNodes([blob1.localGCNodeId]);
960+
assert(!redirectTable.has(blob1.localId));
961+
assert(!redirectTable.has(blob1.storageId));
962+
963+
// Delete blob2's local id. The local id and the storage id should both be deleted from the redirect table
964+
// since the blob only had one reference.
965+
runtime.blobManager.deleteSweepReadyNodes([blob2.localGCNodeId]);
966+
assert(!redirectTable.has(blob2.localId));
967+
assert(!redirectTable.has(blob2.storageId));
968+
}),
969+
);
985970

986971
it("deletes unused de-duped blobs", async () => {
987972
await runtime.attach();

0 commit comments

Comments
 (0)