Skip to content
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

Prefetch snapshot so as to avoid network call from critical path. #5968

Merged
merged 18 commits into from
May 3, 2021
Prev Previous commit
Next Next commit
Add support to test it with webpack fluid laoder
  • Loading branch information
jatgarg committed Apr 30, 2021
commit 024bfa7f7f4fc4f622f8f23ac76da3367d9c22e6
8 changes: 8 additions & 0 deletions packages/drivers/odsp-driver/src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,11 @@ export interface ICreateFileResponse {
itemUrl: string;
sequenceNumber: number;
}

export interface IVersionedValueWithEpoch {
value: any;
fluidEpoch: string,
version: 2,
}

export const persistedCacheValueVersion = 2;
11 changes: 1 addition & 10 deletions packages/drivers/odsp-driver/src/epochTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,13 @@ import {
IPersistedFileCache,
} from "./odspCache";
import { RateLimiter } from "./rateLimiter";
import { IVersionedValueWithEpoch, persistedCacheValueVersion } from "./contracts";

export type FetchType = "blob" | "createBlob" | "createFile" | "joinSession" | "ops" | "test" | "snapshotTree" |
"treesLatest" | "uploadSummary" | "push" | "versions";

export type FetchTypeInternal = FetchType | "cache";

// exported only of test purposes
export interface IVersionedValueWithEpoch {
value: any;
fluidEpoch: string,
version: 2,
}

// exported only of test purposes
export const persistedCacheValueVersion = 2;

/**
* This class is a wrapper around fetch calls. It adds epoch to the request made so that the
* server can match it with its epoch value in order to match the version.
Expand Down
5 changes: 5 additions & 0 deletions packages/drivers/odsp-driver/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ export * from "./odspPublicUtils";
export * from "./odspUrlHelper";
export * from "./createOdspUrl";
export * from "./checkUrl";

// prefetch latest snapshot before container load
export * from "./prefetchLatestSnapshot";

// Default implementations of odsp cache.
export * from "./odspCache";
jatgarg marked this conversation as resolved.
Show resolved Hide resolved

// Factory
export * from "./odspDocumentServiceFactoryCore";
export * from "./odspDocumentServiceFactory";
Expand Down
25 changes: 17 additions & 8 deletions packages/drivers/odsp-driver/src/prefetchLatestSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
ISnapshotCacheValue,
toInstrumentedOdspTokenFetcher,
} from "./odspUtils";
import { IOdspSnapshot } from "./contracts";
import { IOdspSnapshot, IVersionedValueWithEpoch } from "./contracts";

/**
* Function to prefetch the snapshot and cached it in the persistant cache, so that when the container is loaded
Expand Down Expand Up @@ -131,10 +131,12 @@ async function fetchSnapshot(
};

const controller: AbortController = new AbortController();
setTimeout(
() => controller.abort(),
snapshotOptions?.timeout,
);
if (snapshotOptions?.timeout !== undefined) {
setTimeout(
() => controller.abort(),
snapshotOptions.timeout,
);
}

// This event measures only successful cases of getLatest call (no tokens, no retries).
return PerformanceEvent.timedExecAsync(
Expand All @@ -149,12 +151,14 @@ async function fetchSnapshot(
{
body: postBody,
headers,
signal: controller?.signal,
signal: controller.signal,
method: "POST",
},
);
const snapshot: IOdspSnapshot = response.content;
const spReqDuration = response.headers.get("sprequestduration");
const fluidEpoch = response.headers.get("x-fluid-epoch");
assert(fluidEpoch !== undefined, "Epoch should be present in response");

const { numTrees, numBlobs, encodedBlobsSize, decodedBlobsSize } = evalBlobsAndTrees(snapshot);

Expand All @@ -175,9 +179,14 @@ async function fetchSnapshot(
logger.sendErrorEvent({ eventName: "fetchSnapshotError", sequenceNumber, seqNumberFromOps });
value.sequenceNumber = undefined;
} else if (canCache) {
persistedCache.put(
snapshotCacheEntry,
const valueWithEpoch: IVersionedValueWithEpoch = {
value,
fluidEpoch,
version: 2,
};
await persistedCache.put(
snapshotCacheEntry,
valueWithEpoch,
).then(() => success = true)
.catch(() => {});
}
Expand Down
15 changes: 5 additions & 10 deletions packages/drivers/odsp-driver/src/test/epochTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,11 @@ import {
ICacheEntry,
IEntry,
} from "@fluidframework/odsp-driver-definitions";
import {
EpochTracker,
IVersionedValueWithEpoch,
persistedCacheValueVersion,
} from "../epochTracker";
import {
LocalPersistentCache,
} from "../odspCache";
import { getHashedDocumentId } from "../odspPublicUtils";
import { mockFetchOk, mockFetchSingle, createResponse } from "./mockFetch";
import { EpochTracker } from "../epochTracker";
import { LocalPersistentCache } from "../odspCache";
import { getHashedDocumentId } from "../odspPublicUtils";
import { IVersionedValueWithEpoch, persistedCacheValueVersion } from "../contracts";
import { mockFetchOk, mockFetchSingle, createResponse } from "./mockFetch";

const createUtLocalCache = () => new LocalPersistentCache(2000);

Expand Down
1 change: 1 addition & 0 deletions packages/tools/webpack-fluid-loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@fluidframework/local-driver": "^0.40.0",
"@fluidframework/odsp-doclib-utils": "^0.40.0",
"@fluidframework/odsp-driver": "^0.40.0",
"@fluidframework/odsp-driver-definitions": "0.40.0",
"@fluidframework/protocol-definitions": "^0.1024.0-0",
"@fluidframework/routerlicious-driver": "^0.40.0",
"@fluidframework/runtime-utils": "^0.40.0",
Expand Down
31 changes: 28 additions & 3 deletions packages/tools/webpack-fluid-loader/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import * as moniker from "moniker";
import { v4 as uuid } from "uuid";
import { ContainerRuntimeFactoryWithDefaultDataStore } from "@fluidframework/aqueduct";
import { Deferred } from "@fluidframework/common-utils";
import { assert, BaseTelemetryNullLogger, Deferred } from "@fluidframework/common-utils";
import {
AttachState,
IFluidModule,
Expand All @@ -15,6 +15,8 @@ import {
isFluidBrowserPackage,
} from "@fluidframework/container-definitions";
import { Container, Loader } from "@fluidframework/container-loader";
import { LocalPersistentCache, prefetchLatestSnapshot } from "@fluidframework/odsp-driver";
import { IPersistedCache } from "@fluidframework/odsp-driver-definitions";
import { IUser } from "@fluidframework/protocol-definitions";
import { HTMLViewAdapter } from "@fluidframework/view-adapters";
import { IFluidMountableView } from "@fluidframework/view-interfaces";
Expand Down Expand Up @@ -154,8 +156,10 @@ async function createWebLoader(
urlResolver: MultiUrlResolver,
codeDetails: IFluidCodeDetails,
testOrderer: boolean = false,
odspPersistantCache?: IPersistedCache,
): Promise<Loader> {
let documentServiceFactory: IDocumentServiceFactory = getDocumentServiceFactory(documentId, options);
let documentServiceFactory: IDocumentServiceFactory =
getDocumentServiceFactory(documentId, options, odspPersistantCache);
// Create the inner document service which will be wrapped inside local driver. The inner document service
// will be used for ops(like delta connection/delta ops) while for storage, local storage would be used.
if (testOrderer) {
Expand Down Expand Up @@ -222,9 +226,17 @@ export async function start(
};

let urlResolver = new MultiUrlResolver(documentId, window.location.origin, options);
const odspPersistantCache = new LocalPersistentCache();

// Create the loader that is used to load the Container.
let loader1 = await createWebLoader(documentId, fluidModule, options, urlResolver, codeDetails, testOrderer);
let loader1 = await createWebLoader(
documentId,
fluidModule,
options,
urlResolver,
codeDetails,
testOrderer,
odspPersistantCache);

let container1: Container;
if (autoAttach || manualAttach) {
Expand All @@ -234,6 +246,19 @@ export async function start(
} else {
// For existing documents, we try to load the container with the given documentId.
const documentUrl = `${window.location.origin}/${documentId}`;
// This functionality is used in odsp driver to prefetch the latest snapshot and cache it so
// as to avoid the network call to fetch trees latest.
if (window.location.hash === "#prefetch") {
assert(options.mode === "spo-df" || options.mode === "spo", "Prefetch snapshot only available for odsp!");
const prefetched = await prefetchLatestSnapshot(
await urlResolver.resolve({ url: documentUrl }),
async () => options.odspAccessToken,
odspPersistantCache,
new BaseTelemetryNullLogger(),
undefined,
);
assert(prefetched, "Snapshot should be prefetched!");
jatgarg marked this conversation as resolved.
Show resolved Hide resolved
}
container1 = await loader1.resolve({ url: documentUrl });
containers.push(container1);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ILocalDeltaConnectionServer, LocalDeltaConnectionServer } from "@fluidf
import { MultiDocumentServiceFactory } from "@fluidframework/driver-utils";
import { LocalDocumentServiceFactory, LocalSessionStorageDbFactory } from "@fluidframework/local-driver";
import { OdspDocumentServiceFactory } from "@fluidframework/odsp-driver";
import { IPersistedCache } from "@fluidframework/odsp-driver-definitions";
import { RouterliciousDocumentServiceFactory } from "@fluidframework/routerlicious-driver";
import { getRandomName } from "@fluidframework/server-services-client";
import { InsecureTokenProvider } from "@fluidframework/test-runtime-utils";
Expand All @@ -15,7 +16,11 @@ import { IDevServerUser, IRouterliciousRouteOptions, RouteOptions } from "./load

export const deltaConns = new Map<string, ILocalDeltaConnectionServer>();

export function getDocumentServiceFactory(documentId: string, options: RouteOptions) {
export function getDocumentServiceFactory(
documentId: string,
options: RouteOptions,
odspPersistantCache: IPersistedCache,
) {
const deltaConn = deltaConns.get(documentId) ??
LocalDeltaConnectionServer.create(new LocalSessionStorageDbFactory(documentId));
deltaConns.set(documentId, deltaConn);
Expand Down Expand Up @@ -44,6 +49,7 @@ export function getDocumentServiceFactory(documentId: string, options: RouteOpti
new OdspDocumentServiceFactory(
async () => options.mode === "spo" || options.mode === "spo-df" ? options.odspAccessToken : undefined,
async () => options.mode === "spo" || options.mode === "spo-df" ? options.pushAccessToken : undefined,
odspPersistantCache,
),
new RouterliciousDocumentServiceFactory(routerliciousTokenProvider),
]);
Expand Down