Skip to content

Commit 09f5901

Browse files
(tree) Added new forest data and summary formats for incremental summaries (#26002)
Added new formats for the forest summarizer's data and summaries - `ForestFormatVersion.v2` and `ForestSummaryFormatVersion.v3` respectively. They both require `minVersionForCollab` to be 2.74.0. - `ForestFormatVersion.v2` - Incremental encoding of the forest's data is supported from this version onwards. - `ForestSummaryFormatVersion.v3` - Incremental summary in forest is supported from this version onwards. Also, the top-level forest content is stored under a summary blob with key "contents". This is the same key where incremental chunk contents are stored making the summary tree consistent. Incremental summaries will require these two versions to be enabled, i.e., it is supported from `minVersionForCollab` 2.74.0 onwards. Enabling incremental summaries is a cross-client breaking change, so this will ensure that clients can read these new formats when it is enabled. Added validation during forest summarizer load that the top-level content is present in the correct blob key as per the summary format version. [AB#41865](https://dev.azure.com/fluidframework/235294da-091d-4c29-84fc-cdfc3d90890b/_workitems/edit/41865)
1 parent d18b377 commit 09f5901

File tree

52 files changed

+393
-241
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+393
-241
lines changed

packages/dds/tree/src/codec/codec.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -465,8 +465,14 @@ export function withSchemaValidation<
465465
* If the need arises, they might be added in the future.
466466
*
467467
* @privateRemarks
468-
* Entries in these enums should document the user facing impact of opting into a particular version.
469-
* For example, document if there is an encoding efficiency improvement of oping into that version or newer.
468+
* The entries in these enums should document the following:
469+
* - The user facing impact of opting into a particular version. This will help customers decide if they want to opt into
470+
* a new version. For example, document if there is an encoding efficiency improvement of oping into that version or newer.
471+
* - Any new data formats that are introduced in that version. This will help developers tell which data formats a given
472+
* version will write. For example, document if a new summary or encoding format is added in a version.
473+
* - Whether the above features or data formats introduced in a version are enabled by default or require the
474+
* {@link minVersionForCollab} option to be set to that particular version.
475+
*
470476
* Versions with no notable impact can be omitted.
471477
*
472478
* This scheme assumes a single version will always be enough to communicate compatibility.
@@ -505,38 +511,40 @@ export const FluidClientVersion = {
505511
* Fluid Framework Client 2.43 and newer.
506512
* @remarks
507513
* New formats introduced in 2.43:
508-
* - SchemaFormatVersion.v2
509-
* - MessageFormatVersion.v4
510-
* - EditManagerFormatVersion.v4
511-
* - Sequence format version 3
514+
* - SchemaFormatVersion.v2 - written when minVersionForCollab \>= 2.43
515+
* - MessageFormatVersion.v4 - written when minVersionForCollab \>= 2.43
516+
* - EditManagerFormatVersion.v4 - written when minVersionForCollab \>= 2.43
517+
* - sequence-field/formatV3 - written when minVersionForCollab \>= 2.43
512518
*/
513519
v2_43: "2.43.0",
514520

515521
/**
516522
* Fluid Framework Client 2.52 and newer.
517523
* @remarks
518524
* New formats introduced in 2.52:
519-
* - DetachedFieldIndexFormatVersion.v2
525+
* - DetachedFieldIndexFormatVersion.v2 - written when minVersionForCollab \>= 2.52
520526
*/
521527
v2_52: "2.52.0",
522528

523529
/**
524530
* Fluid Framework Client 2.73 and newer.
525531
* @remarks
526532
* New formats introduced in 2.73:
527-
* - FieldBatchFormatVersion v2
533+
* - FieldBatchFormatVersion.v2 - written when minVersionForCollab \>= 2.73
528534
*/
529535
v2_73: "2.73.0",
530536

531537
/**
532538
* Fluid Framework Client 2.74 and newer.
533539
* @remarks
534540
* New formats introduced in 2.74:
535-
* - SharedTreeSummaryFormatVersion v2
536-
* - DetachedFieldIndexSummaryFormatVersion v2
537-
* - SchemaSummaryFormatVersion v2
538-
* - EditManagerSummaryFormatVersion v2
539-
* - ForestSummaryFormatVersion v2
541+
* - SharedTreeSummaryFormatVersion.v2 - written by default
542+
* - DetachedFieldIndexSummaryFormatVersion.v2 - written by default
543+
* - SchemaSummaryFormatVersion.v2 - written by default
544+
* - EditManagerSummaryFormatVersion.v2 - written by default
545+
* - ForestSummaryFormatVersion.v2 - written by default
546+
* - ForestFormatVersion.v2 - written when minVersionForCollab \>= 2.74
547+
* - ForestSummaryFormatVersion.v3 - written when minVersionForCollab \>= 2.74
540548
*/
541549
v2_74: "2.74.0",
542550
} as const satisfies Record<string, MinimumVersionForCollab>;

packages/dds/tree/src/feature-libraries/forest-summary/codec.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,60 @@
55

66
import { assert, oob } from "@fluidframework/core-utils/internal";
77
import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";
8+
import {
9+
getConfigForMinVersionForCollab,
10+
lowestMinVersionForCollab,
11+
} from "@fluidframework/runtime-utils/internal";
812

913
import {
1014
type CodecTree,
1115
type CodecWriteOptions,
16+
FluidClientVersion,
1217
type IJsonCodec,
1318
makeVersionedValidatedCodec,
1419
} from "../../codec/index.js";
1520
import type { FieldKey, ITreeCursorSynchronous } from "../../core/index.js";
1621
import type { FieldBatchCodec, FieldBatchEncodingContext } from "../chunked-forest/index.js";
1722

18-
import { FormatV1, ForestFormatVersion } from "./format.js";
23+
import {
24+
ForestFormatVersion,
25+
validVersions,
26+
type Format,
27+
FormatCommon,
28+
} from "./formatCommon.js";
1929
import { brand } from "../../util/index.js";
2030

2131
/**
2232
* Uses field cursors
2333
*/
2434
export type FieldSet = ReadonlyMap<FieldKey, ITreeCursorSynchronous>;
25-
export type ForestCodec = IJsonCodec<FieldSet, FormatV1, FormatV1, FieldBatchEncodingContext>;
35+
export type ForestCodec = IJsonCodec<FieldSet, Format, Format, FieldBatchEncodingContext>;
2636

2737
/**
2838
* Convert a MinimumVersionForCollab to a ForestFormatVersion.
2939
* @param clientVersion - The MinimumVersionForCollab to convert.
3040
* @returns The ForestFormatVersion that corresponds to the provided MinimumVersionForCollab.
3141
*/
32-
function clientVersionToForestSummaryFormatVersion(
42+
export function clientVersionToForestFormatVersion(
3343
clientVersion: MinimumVersionForCollab,
3444
): ForestFormatVersion {
35-
// Currently, forest summary codec only writes in version 1.
36-
return brand(ForestFormatVersion.v1);
45+
return brand(
46+
getConfigForMinVersionForCollab(clientVersion, {
47+
[lowestMinVersionForCollab]: ForestFormatVersion.v1,
48+
[FluidClientVersion.v2_74]: ForestFormatVersion.v2,
49+
}),
50+
);
3751
}
3852

3953
export function makeForestSummarizerCodec(
4054
options: CodecWriteOptions,
4155
fieldBatchCodec: FieldBatchCodec,
4256
): ForestCodec {
4357
const inner = fieldBatchCodec;
44-
// TODO: AB#41865
45-
// This needs to be updated to support multiple versions.
46-
// The second version will be used to enable incremental summarization.
47-
const writeVersion = clientVersionToForestSummaryFormatVersion(options.minVersionForCollab);
48-
return makeVersionedValidatedCodec(options, new Set([ForestFormatVersion.v1]), FormatV1, {
49-
encode: (data: FieldSet, context: FieldBatchEncodingContext): FormatV1 => {
58+
const writeVersion = clientVersionToForestFormatVersion(options.minVersionForCollab);
59+
const formatSchema = FormatCommon(writeVersion);
60+
return makeVersionedValidatedCodec(options, validVersions, formatSchema, {
61+
encode: (data: FieldSet, context: FieldBatchEncodingContext): Format => {
5062
const keys: FieldKey[] = [];
5163
const fields: ITreeCursorSynchronous[] = [];
5264
for (const [key, value] of data) {
@@ -55,7 +67,7 @@ export function makeForestSummarizerCodec(
5567
}
5668
return { keys, fields: inner.encode(fields, context), version: writeVersion };
5769
},
58-
decode: (data: FormatV1, context: FieldBatchEncodingContext): FieldSet => {
70+
decode: (data: Format, context: FieldBatchEncodingContext): FieldSet => {
5971
const out: Map<FieldKey, ITreeCursorSynchronous> = new Map();
6072
const fields = inner.decode(data.fields, context);
6173
assert(data.keys.length === fields.length, 0x891 /* mismatched lengths */);
@@ -70,5 +82,5 @@ export function makeForestSummarizerCodec(
7082
export function getCodecTreeForForestFormat(
7183
clientVersion: MinimumVersionForCollab,
7284
): CodecTree {
73-
return { name: "Forest", version: clientVersionToForestSummaryFormatVersion(clientVersion) };
85+
return { name: "Forest", version: clientVersionToForestFormatVersion(clientVersion) };
7486
}

packages/dds/tree/src/feature-libraries/forest-summary/forestSummarizer.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,26 @@ import {
4242
type IncrementalEncodingPolicy,
4343
} from "../chunked-forest/index.js";
4444

45-
import { type ForestCodec, makeForestSummarizerCodec } from "./codec.js";
45+
import {
46+
clientVersionToForestFormatVersion,
47+
type ForestCodec,
48+
makeForestSummarizerCodec,
49+
} from "./codec.js";
4650
import {
4751
ForestIncrementalSummaryBehavior,
4852
ForestIncrementalSummaryBuilder,
4953
} from "./incrementalSummaryBuilder.js";
5054
import {
51-
forestSummaryContentKey,
52-
forestSummaryKey,
5355
minVersionToForestSummaryFormatVersion,
54-
supportedForestSummaryFormatVersions,
55-
type ForestSummaryFormatVersion,
56+
getForestRootSummaryContentKey,
5657
} from "./summaryTypes.js";
5758
import { TreeCompressionStrategy } from "../treeCompressionUtils.js";
59+
import { ForestFormatVersion } from "./formatCommon.js";
60+
import {
61+
ForestSummaryFormatVersion,
62+
forestSummaryKey,
63+
supportedForestSummaryFormatVersions,
64+
} from "./summaryFormatCommon.js";
5865

5966
/**
6067
* Provides methods for summarizing and loading a forest.
@@ -66,6 +73,7 @@ export class ForestSummarizer
6673
private readonly codec: ForestCodec;
6774

6875
private readonly incrementalSummaryBuilder: ForestIncrementalSummaryBuilder;
76+
private readonly forestRootSummaryContentKey: string;
6977

7078
/**
7179
* @param encoderContext - The schema if provided here must be mutated by the caller to keep it up to date.
@@ -87,11 +95,25 @@ export class ForestSummarizer
8795
true /* supportPreVersioningFormat */,
8896
);
8997

90-
// TODO: this should take in CodecWriteOptions, and use it to pick the write version.
9198
this.codec = makeForestSummarizerCodec(options, fieldBatchCodec);
99+
100+
const forestFormatWriteVersion = clientVersionToForestFormatVersion(
101+
options.minVersionForCollab,
102+
);
103+
const summaryFormatWriteVersion = minVersionToForestSummaryFormatVersion(
104+
options.minVersionForCollab,
105+
);
106+
this.forestRootSummaryContentKey = getForestRootSummaryContentKey(
107+
summaryFormatWriteVersion,
108+
);
109+
110+
// Incremental summary is supported from ForestFormatVersion.v2 and ForestSummaryFormatVersion.v3 onwards.
111+
const enableIncrementalSummary =
112+
forestFormatWriteVersion >= ForestFormatVersion.v2 &&
113+
summaryFormatWriteVersion >= ForestSummaryFormatVersion.v3 &&
114+
encoderContext.encodeType === TreeCompressionStrategy.CompressedIncremental;
92115
this.incrementalSummaryBuilder = new ForestIncrementalSummaryBuilder(
93-
encoderContext.encodeType ===
94-
TreeCompressionStrategy.CompressedIncremental /* enableIncrementalSummary */,
116+
enableIncrementalSummary,
95117
(cursor: ITreeCursorSynchronous) => this.forest.chunkField(cursor),
96118
shouldEncodeIncrementally,
97119
initialSequenceNumber,
@@ -153,23 +175,26 @@ export class ForestSummarizer
153175

154176
this.incrementalSummaryBuilder.completeSummary({
155177
incrementalSummaryContext,
156-
forestSummaryContent: stringify(encoded),
178+
forestSummaryRootContent: stringify(encoded),
179+
forestSummaryRootContentKey: this.forestRootSummaryContentKey,
157180
builder,
158181
});
159182
}
160183

161184
protected async loadInternal(
162185
services: IChannelStorageService,
163186
parse: SummaryElementParser,
187+
version: ForestSummaryFormatVersion | undefined,
164188
): Promise<void> {
165-
// The contents of the top-level forest must be present under a summary blob named `forestSummaryContentKey`.
189+
// Get the key of the summary blob where the top-level forest content is stored based on the summary format version.
166190
// If the summary was generated as `ForestIncrementalSummaryBehavior.SingleBlob`, this blob will contain all
167191
// of forest's contents.
168192
// If the summary was generated as `ForestIncrementalSummaryBehavior.Incremental`, this blob will contain only
169193
// the top-level forest node's contents.
170194
// The contents of the incremental chunks will be in separate tree nodes and will be read later during decoding.
195+
const forestSummaryRootContentKey = getForestRootSummaryContentKey(version);
171196
assert(
172-
await services.contains(forestSummaryContentKey),
197+
await services.contains(forestSummaryRootContentKey),
173198
0xc21 /* Forest summary content missing in snapshot */,
174199
);
175200

@@ -184,7 +209,7 @@ export class ForestSummarizer
184209
// TODO: this code is parsing data without an optional validator, this should be defined in a typebox schema as part of the
185210
// forest summary format.
186211
const fields = this.codec.decode(
187-
await readAndParseSnapshotBlob(forestSummaryContentKey, services, parse),
212+
await readAndParseSnapshotBlob(forestSummaryRootContentKey, services, parse),
188213
{
189214
...this.encoderContext,
190215
incrementalEncoderDecoder: this.incrementalSummaryBuilder,

packages/dds/tree/src/feature-libraries/forest-summary/format.ts renamed to packages/dds/tree/src/feature-libraries/forest-summary/formatCommon.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,25 @@
66
import { type Static, Type } from "@sinclair/typebox";
77

88
import { schemaFormatV1 } from "../../core/index.js";
9-
import { brand, type Brand } from "../../util/index.js";
9+
import type { Brand } from "../../util/index.js";
1010
import { EncodedFieldBatch } from "../chunked-forest/index.js";
1111

1212
/**
1313
* The format version for the forest.
1414
*/
1515
export const ForestFormatVersion = {
1616
v1: 1,
17+
/** This format supports incremental encoding */
18+
v2: 2,
1719
} as const;
1820
export type ForestFormatVersion = Brand<
1921
(typeof ForestFormatVersion)[keyof typeof ForestFormatVersion],
2022
"ForestFormatVersion"
2123
>;
2224

23-
const FormatGeneric = (
25+
export const validVersions = new Set([...Object.values(ForestFormatVersion)]);
26+
27+
export const FormatCommon = (
2428
version: ForestFormatVersion,
2529
// Return type is intentionally derived.
2630
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@@ -33,6 +37,4 @@ const FormatGeneric = (
3337
},
3438
{ additionalProperties: false },
3539
);
36-
37-
export const FormatV1 = FormatGeneric(brand<ForestFormatVersion>(ForestFormatVersion.v1));
38-
export type FormatV1 = Static<typeof FormatV1>;
40+
export type Format = Static<ReturnType<typeof FormatCommon>>;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
import type { Static } from "@sinclair/typebox";
7+
8+
import { brand } from "../../util/index.js";
9+
import { FormatCommon, ForestFormatVersion } from "./formatCommon.js";
10+
11+
export const FormatV1 = FormatCommon(brand<ForestFormatVersion>(ForestFormatVersion.v1));
12+
export type FormatV1 = Static<typeof FormatV1>;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*!
2+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
import type { Static } from "@sinclair/typebox";
7+
8+
import { brand } from "../../util/index.js";
9+
import { FormatCommon, ForestFormatVersion } from "./formatCommon.js";
10+
11+
export const FormatV2 = FormatCommon(brand<ForestFormatVersion>(ForestFormatVersion.v2));
12+
export type FormatV2 = Static<typeof FormatV2>;

0 commit comments

Comments
 (0)