Skip to content

Commit

Permalink
Removed GC blobs from channel context (DDS). The GC blobs in data sto…
Browse files Browse the repository at this point in the history
…re is used to generate initial GC data for DDS (#5007)
  • Loading branch information
agarwal-navin authored Feb 5, 2021
1 parent 64e1efe commit d02bf26
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 35 deletions.
6 changes: 3 additions & 3 deletions packages/runtime/container-runtime/src/dataStoreContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData

protected abstract getInitialSnapshotDetails(): Promise<ISnapshotDetails>;

protected abstract getInitialGCSummaryDetails(): Promise<IGarbageCollectionSummaryDetails>;
public abstract getInitialGCSummaryDetails(): Promise<IGarbageCollectionSummaryDetails>;

public reSubmit(contents: any, localOpMetadata: unknown) {
assert(!!this.channel, "Channel must exist when resubmitting ops");
Expand Down Expand Up @@ -768,7 +768,7 @@ export class RemotedFluidDataStoreContext extends FluidDataStoreContext {
return this.initialSnapshotDetailsP;
}

protected async getInitialGCSummaryDetails(): Promise<IGarbageCollectionSummaryDetails> {
public async getInitialGCSummaryDetails(): Promise<IGarbageCollectionSummaryDetails> {
return this.gcDetailsInInitialSummaryP;
}

Expand Down Expand Up @@ -861,7 +861,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
};
}

protected async getInitialGCSummaryDetails(): Promise<IGarbageCollectionSummaryDetails> {
public async getInitialGCSummaryDetails(): Promise<IGarbageCollectionSummaryDetails> {
// Local data store does not have initial summary.
return {};
}
Expand Down
10 changes: 0 additions & 10 deletions packages/runtime/datastore/src/channelContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import { IChannel } from "@fluidframework/datastore-definitions";
import { IDocumentStorageService } from "@fluidframework/driver-definitions";
import { ISequencedDocumentMessage, ISnapshotTree } from "@fluidframework/protocol-definitions";
import {
gcBlobKey,
IChannelSummarizeResult,
IContextSummarizeResult,
IGarbageCollectionData,
IGarbageCollectionSummaryDetails,
} from "@fluidframework/runtime-definitions";
import { addBlobToSummary } from "@fluidframework/runtime-utils";
import { ChannelDeltaConnection } from "./channelDeltaConnection";
Expand Down Expand Up @@ -76,13 +74,5 @@ export function summarizeChannel(

// Add the channel attributes to the returned result.
addBlobToSummary(summarizeResult, attributesBlobKey, JSON.stringify(channel.attributes));

// Add GC details to the summary.
const gcDetails: IGarbageCollectionSummaryDetails = {
usedRoutes: [""],
gcData: summarizeResult.gcData,
};
addBlobToSummary(summarizeResult, gcBlobKey, JSON.stringify(gcDetails));

return summarizeResult;
}
75 changes: 73 additions & 2 deletions packages/runtime/datastore/src/dataStoreRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import {
assert,
Deferred,
LazyPromise,
TypedEventEmitter,
unreachableCase,
} from "@fluidframework/common-utils";
Expand All @@ -48,6 +49,7 @@ import {
IFluidDataStoreContext,
IFluidDataStoreChannel,
IGarbageCollectionData,
IGarbageCollectionSummaryDetails,
IInboundSignalMessage,
ISummaryTreeWithStats,
} from "@fluidframework/runtime-definitions";
Expand All @@ -66,7 +68,13 @@ import {
IChannelFactory,
IChannelAttributes,
} from "@fluidframework/datastore-definitions";
import { GCDataBuilder, getChildNodesUsedRoutes } from "@fluidframework/garbage-collector";
import {
cloneGCData,
GCDataBuilder,
getChildNodesGCData,
getChildNodesUsedRoutes,
removeRouteFromAllNodes,
} from "@fluidframework/garbage-collector";
import { v4 as uuid } from "uuid";
import { IChannelContext, summarizeChannel } from "./channelContext";
import { LocalChannelContext } from "./localChannelContext";
Expand Down Expand Up @@ -183,6 +191,14 @@ IFluidDataStoreChannel, IFluidDataStoreRuntime, IFluidHandleContext {
private readonly audience: IAudience;
public readonly logger: ITelemetryLogger;

// A map of child channel context ids to the context's used routes in the initial summary of this data store. This
// is used to initialize the context with data from the previous summary.
private readonly initialChannelUsedRoutesP: LazyPromise<Map<string, string[]>>;

// A map of child channel context ids to the channel context's GC data in the initial summary of this data store.
// This is used to initialize the context with data from the previous summary.
private readonly initialChannelGCDataP: LazyPromise<Map<string, IGarbageCollectionData>>;

public constructor(
private readonly dataStoreContext: IFluidDataStoreContext,
private readonly sharedObjectRegistry: ISharedObjectRegistry,
Expand All @@ -200,6 +216,35 @@ IFluidDataStoreChannel, IFluidDataStoreRuntime, IFluidHandleContext {

const tree = dataStoreContext.baseSnapshot;

this.initialChannelUsedRoutesP = new LazyPromise(async () => {
// back-compat: 0.35.0. getInitialGCSummaryDetails is added to IFluidDataStoreContext in 0.35.0. Remove
// undefined check when N > 0.36.0.
const gcDetailsInInitialSummary = await this.dataStoreContext.getInitialGCSummaryDetails?.();
if (gcDetailsInInitialSummary?.usedRoutes !== undefined) {
// Remove the route to this data store, if it exists.
const usedRoutes = gcDetailsInInitialSummary.usedRoutes.filter(
(id: string) => { return id !== "/" && id !== ""; },
);
return getChildNodesUsedRoutes(usedRoutes);
}
return new Map();
});

this.initialChannelGCDataP = new LazyPromise(async () => {
// back-compat: 0.35.0. getInitialGCSummaryDetails is added to IFluidDataStoreContext in 0.35.0. Remove
// undefined check when N > 0.36.0.
const gcDetailsInInitialSummary = await this.dataStoreContext.getInitialGCSummaryDetails?.();
if (gcDetailsInInitialSummary?.gcData !== undefined) {
const gcData = cloneGCData(gcDetailsInInitialSummary.gcData);
// Remove GC node for this data store, if any.
delete gcData.gcNodes["/"];
// Remove the back route to this data store that was added when generating each child's GC nodes.
removeRouteFromAllNodes(gcData.gcNodes, this.absolutePath);
return getChildNodesGCData(gcData);
}
return new Map();
});

// Must always receive the data store type inside of the attributes
if (tree?.trees !== undefined) {
Object.keys(tree.trees).forEach((path) => {
Expand Down Expand Up @@ -246,7 +291,8 @@ IFluidDataStoreChannel, IFluidDataStoreRuntime, IFluidHandleContext {
this.dataStoreContext.getCreateChildSummarizerNodeFn(
path,
{ type: CreateSummarizerNodeSource.FromSummary },
));
),
async () => this.getChannelInitialGCDetails(path));
}
const deferred = new Deferred<IChannelContext>();
deferred.resolve(channelContext);
Expand Down Expand Up @@ -512,6 +558,7 @@ IFluidDataStoreChannel, IFluidDataStoreRuntime, IFluidHandleContext {
snapshot: attachMessage.snapshot,
},
),
async () => this.getChannelInitialGCDetails(id),
attachMessage.type);

this.contexts.set(id, remoteChannelContext);
Expand Down Expand Up @@ -638,6 +685,30 @@ IFluidDataStoreChannel, IFluidDataStoreRuntime, IFluidHandleContext {
}
}

/**
* Returns the GC details in initial summary for the channel with the given id. The initial summary of the data
* store contains the GC details of all the child channel contexts that were created before the summary was taken.
* We find the GC details belonging to the given channel context and return it.
* @param channelId - The id of the channel context that is asked for the initial GC details.
* @returns the requested channel's GC details in the initial summary.
*/
private async getChannelInitialGCDetails(channelId: string): Promise<IGarbageCollectionSummaryDetails> {
const channelInitialUsedRoutes = await this.initialChannelUsedRoutesP;
const channelInitialGCData = await this.initialChannelGCDataP;

let channelUsedRoutes = channelInitialUsedRoutes.get(channelId);
// Currently, channel context's are always considered used. So, it there is no used route for it, we still
// need to mark it as used. Add self-route (empty string) to the channel context's used routes.
if (channelUsedRoutes === undefined || channelUsedRoutes.length === 0) {
channelUsedRoutes = [""];
}

return {
usedRoutes: channelUsedRoutes,
gcData: channelInitialGCData.get(channelId),
};
}

/**
* Returns a summary at the current sequence number.
* @param fullTree - true to bypass optimizations and force a full summary tree
Expand Down
17 changes: 3 additions & 14 deletions packages/runtime/datastore/src/remoteChannelContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/

import { assert, LazyPromise } from "@fluidframework/common-utils";
import { assert } from "@fluidframework/common-utils";
import { CreateContainerError, DataCorruptionError } from "@fluidframework/container-utils";
import {
IChannel,
Expand All @@ -19,7 +19,6 @@ import {
} from "@fluidframework/protocol-definitions";
import {
CreateChildSummarizerNodeFn,
gcBlobKey,
IContextSummarizeResult,
IFluidDataStoreContext,
IGarbageCollectionData,
Expand Down Expand Up @@ -49,17 +48,6 @@ export class RemoteChannelContext implements IChannelContext {
};
private readonly summarizerNode: ISummarizerNodeWithGC;

/**
* This loads the GC details from the base snapshot of this context.
*/
private readonly gcDetailsInInitialSummaryP = new LazyPromise<IGarbageCollectionSummaryDetails>(async () => {
if (await this.services.objectStorage.contains(gcBlobKey)) {
return readAndParse<IGarbageCollectionSummaryDetails>(this.services.objectStorage, gcBlobKey);
} else {
return {};
}
});

constructor(
private readonly runtime: IFluidDataStoreRuntime,
private readonly dataStoreContext: IFluidDataStoreContext,
Expand All @@ -71,6 +59,7 @@ export class RemoteChannelContext implements IChannelContext {
private readonly registry: ISharedObjectRegistry,
extraBlobs: Map<string, string> | undefined,
createSummarizerNode: CreateChildSummarizerNodeFn,
gcDetailsInInitialSummary: () => Promise<IGarbageCollectionSummaryDetails>,
private readonly attachMessageType?: string,
) {
this.services = createServiceEndpoints(
Expand All @@ -88,7 +77,7 @@ export class RemoteChannelContext implements IChannelContext {
this.summarizerNode = createSummarizerNode(
thisSummarizeInternal,
async () => this.getGCDataInternal(),
async () => this.gcDetailsInInitialSummaryP,
async () => gcDetailsInInitialSummary(),
);
}

Expand Down
54 changes: 49 additions & 5 deletions packages/runtime/garbage-collector/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,69 @@ export function cloneGCData(gcData: IGarbageCollectionData): IGarbageCollectionD
}

/**
* Helper function that generates the used routes of the children from a given node's used routes.
* Helper function that generates the used routes of children from a given node's used routes.
* @param usedRoutes - The used routes of a node.
* @returns A map of used routes of each children of the the given node.
*/
export function getChildNodesUsedRoutes(usedRoutes: string[]) {
const usedNodesRoutes: Map<string, string[]> = new Map();
const childUsedRoutesMap: Map<string, string[]> = new Map();
for (const route of usedRoutes) {
assert(route.startsWith("/"), "Used route should always be an absolute route");
const childId = route.split("/")[1];
const childUsedRoute = route.slice(childId.length + 1);

const childUsedRoutes = usedNodesRoutes.get(childId);
const childUsedRoutes = childUsedRoutesMap.get(childId);
if (childUsedRoutes !== undefined) {
childUsedRoutes.push(childUsedRoute);
} else {
usedNodesRoutes.set(childId, [ childUsedRoute ]);
childUsedRoutesMap.set(childId, [ childUsedRoute ]);
}
}
return childUsedRoutesMap;
}

/**
* Helper function that generates the GC data of children from a given node's GC data.
* @param gcData - The GC data of a node.
* @returns A map of GC data of each children of the the given node.
*/
export function getChildNodesGCData(gcData: IGarbageCollectionData) {
const childGCDataMap: Map<string, IGarbageCollectionData> = new Map();
for (const [id, outboundRoutes] of Object.entries(gcData.gcNodes)) {
assert(id.startsWith("/"), "id should always be an absolute route");
const childId = id.split("/")[1];
let childGCNodeId = id.slice(childId.length + 1);
// GC node id always begins with "/". Handle the special case where the id in parent's GC nodes is of the
// for `/root`. This would make `childId=root` and `childGCNodeId=""`.
if (childGCNodeId === "") {
childGCNodeId = "/";
}

// Create a copy of the outbound routes array in the parents GC data.
const childOutboundRoutes = Array.from(outboundRoutes);

let childGCData = childGCDataMap.get(childId);
if (childGCData === undefined) {
childGCData = { gcNodes: {} };
}
childGCData.gcNodes[childGCNodeId] = childOutboundRoutes;
childGCDataMap.set(childId, childGCData);
}
return childGCDataMap;
}

/**
* Removes the given route from the outbound routes of all the given GC nodes.
* @param gcNodes - The nodes from which the route is to be removed.
* @param outboundRoute - The route to be removed.
*/
export function removeRouteFromAllNodes(gcNodes: { [ id: string ]: string[] }, outboundRoute: string) {
for (const outboundRoutes of Object.values(gcNodes)) {
const index = outboundRoutes.indexOf(outboundRoute);
if (index > -1) {
outboundRoutes.splice(index, 1);
}
}
return usedNodesRoutes;
}

export class GCDataBuilder implements IGarbageCollectionData {
Expand Down
6 changes: 6 additions & 0 deletions packages/runtime/runtime-definitions/src/dataStoreContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,12 @@ IEventProvider<IFluidDataStoreContextEvents>, Partial<IProvideFluidDataStoreRegi
): CreateChildSummarizerNodeFn;

uploadBlob(blob: ArrayBufferLike): Promise<IFluidHandle<ArrayBufferLike>>;

/**
* Returns the GC details in the initial summary of this data store. This is used to initialize the data store
* and its children with the GC details from the previous summary.
*/
getInitialGCSummaryDetails(): Promise<IGarbageCollectionSummaryDetails>;
}

export interface IFluidDataStoreContextDetached extends IFluidDataStoreContext {
Expand Down
2 changes: 1 addition & 1 deletion packages/test/snapshots/content
Submodule content updated 168 files

0 comments on commit d02bf26

Please sign in to comment.