@fluidframework/container-runtime 2.0.0-internal.7.1.0 → 2.0.0-internal.7.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/api-report/container-runtime.api.md +2 -1
  2. package/dist/blobManager.d.ts +3 -6
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +17 -42
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/container-runtime-alpha.d.ts +4 -4
  7. package/dist/container-runtime-beta.d.ts +4 -4
  8. package/dist/container-runtime-public.d.ts +4 -4
  9. package/dist/container-runtime.d.ts +4 -4
  10. package/dist/containerRuntime.d.ts +5 -4
  11. package/dist/containerRuntime.d.ts.map +1 -1
  12. package/dist/containerRuntime.js +33 -8
  13. package/dist/containerRuntime.js.map +1 -1
  14. package/dist/dataStoreContext.d.ts +1 -0
  15. package/dist/dataStoreContext.d.ts.map +1 -1
  16. package/dist/dataStoreContext.js +39 -34
  17. package/dist/dataStoreContext.js.map +1 -1
  18. package/dist/dataStores.d.ts +0 -16
  19. package/dist/dataStores.d.ts.map +1 -1
  20. package/dist/dataStores.js +0 -48
  21. package/dist/dataStores.js.map +1 -1
  22. package/dist/gc/garbageCollection.d.ts +12 -3
  23. package/dist/gc/garbageCollection.d.ts.map +1 -1
  24. package/dist/gc/garbageCollection.js +41 -18
  25. package/dist/gc/garbageCollection.js.map +1 -1
  26. package/dist/gc/gcConfigs.d.ts +1 -0
  27. package/dist/gc/gcConfigs.d.ts.map +1 -1
  28. package/dist/gc/gcConfigs.js +15 -2
  29. package/dist/gc/gcConfigs.js.map +1 -1
  30. package/dist/gc/gcDefinitions.d.ts +28 -9
  31. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  32. package/dist/gc/gcDefinitions.js +8 -3
  33. package/dist/gc/gcDefinitions.js.map +1 -1
  34. package/dist/gc/gcTelemetry.d.ts +12 -6
  35. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  36. package/dist/gc/gcTelemetry.js +91 -47
  37. package/dist/gc/gcTelemetry.js.map +1 -1
  38. package/dist/gc/index.d.ts +2 -2
  39. package/dist/gc/index.d.ts.map +1 -1
  40. package/dist/gc/index.js +3 -5
  41. package/dist/gc/index.js.map +1 -1
  42. package/dist/opLifecycle/opGroupingManager.d.ts +10 -2
  43. package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
  44. package/dist/opLifecycle/opGroupingManager.js +23 -3
  45. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  46. package/dist/opLifecycle/outbox.d.ts +0 -1
  47. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  48. package/dist/opLifecycle/outbox.js +2 -1
  49. package/dist/opLifecycle/outbox.js.map +1 -1
  50. package/dist/packageVersion.d.ts +1 -1
  51. package/dist/packageVersion.js +1 -1
  52. package/dist/packageVersion.js.map +1 -1
  53. package/lib/blobManager.d.ts +3 -6
  54. package/lib/blobManager.d.ts.map +1 -1
  55. package/lib/blobManager.js +18 -43
  56. package/lib/blobManager.js.map +1 -1
  57. package/lib/containerRuntime.d.ts +5 -4
  58. package/lib/containerRuntime.d.ts.map +1 -1
  59. package/lib/containerRuntime.js +34 -9
  60. package/lib/containerRuntime.js.map +1 -1
  61. package/lib/dataStoreContext.d.ts +1 -0
  62. package/lib/dataStoreContext.d.ts.map +1 -1
  63. package/lib/dataStoreContext.js +40 -35
  64. package/lib/dataStoreContext.js.map +1 -1
  65. package/lib/dataStores.d.ts +0 -16
  66. package/lib/dataStores.d.ts.map +1 -1
  67. package/lib/dataStores.js +2 -50
  68. package/lib/dataStores.js.map +1 -1
  69. package/lib/gc/garbageCollection.d.ts +12 -3
  70. package/lib/gc/garbageCollection.d.ts.map +1 -1
  71. package/lib/gc/garbageCollection.js +42 -19
  72. package/lib/gc/garbageCollection.js.map +1 -1
  73. package/lib/gc/gcConfigs.d.ts +1 -0
  74. package/lib/gc/gcConfigs.d.ts.map +1 -1
  75. package/lib/gc/gcConfigs.js +17 -4
  76. package/lib/gc/gcConfigs.js.map +1 -1
  77. package/lib/gc/gcDefinitions.d.ts +28 -9
  78. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  79. package/lib/gc/gcDefinitions.js +7 -2
  80. package/lib/gc/gcDefinitions.js.map +1 -1
  81. package/lib/gc/gcTelemetry.d.ts +12 -6
  82. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  83. package/lib/gc/gcTelemetry.js +92 -48
  84. package/lib/gc/gcTelemetry.js.map +1 -1
  85. package/lib/gc/index.d.ts +2 -2
  86. package/lib/gc/index.d.ts.map +1 -1
  87. package/lib/gc/index.js +2 -2
  88. package/lib/gc/index.js.map +1 -1
  89. package/lib/opLifecycle/opGroupingManager.d.ts +10 -2
  90. package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
  91. package/lib/opLifecycle/opGroupingManager.js +23 -3
  92. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  93. package/lib/opLifecycle/outbox.d.ts +0 -1
  94. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  95. package/lib/opLifecycle/outbox.js +2 -1
  96. package/lib/opLifecycle/outbox.js.map +1 -1
  97. package/lib/packageVersion.d.ts +1 -1
  98. package/lib/packageVersion.js +1 -1
  99. package/lib/packageVersion.js.map +1 -1
  100. package/package.json +21 -17
  101. package/src/blobManager.ts +18 -58
  102. package/src/containerRuntime.ts +50 -18
  103. package/src/dataStoreContext.ts +17 -8
  104. package/src/dataStores.ts +2 -80
  105. package/src/gc/garbageCollection.ts +53 -24
  106. package/src/gc/gcConfigs.ts +28 -4
  107. package/src/gc/gcDefinitions.ts +32 -9
  108. package/src/gc/gcTelemetry.ts +123 -65
  109. package/src/gc/index.ts +2 -4
  110. package/src/opLifecycle/opGroupingManager.ts +37 -2
  111. package/src/opLifecycle/outbox.ts +4 -2
  112. package/src/packageVersion.ts +1 -1
@@ -11,6 +11,7 @@ import {
11
11
  MonitoringContext,
12
12
  tagCodeArtifacts,
13
13
  } from "@fluidframework/telemetry-utils";
14
+ import { RuntimeHeaderData } from "../containerRuntime";
14
15
  import { ICreateContainerMetadata } from "../summary";
15
16
  import {
16
17
  disableSweepLogKey,
@@ -19,8 +20,9 @@ import {
19
20
  IGarbageCollectorConfigs,
20
21
  disableTombstoneKey,
21
22
  throwOnTombstoneUsageKey,
22
- throwOnTombstoneLoadKey,
23
+ throwOnTombstoneLoadOverrideKey,
23
24
  runSweepKey,
25
+ GCFeatureMatrix,
24
26
  } from "./gcDefinitions";
25
27
  import { UnreferencedStateTracker } from "./gcUnreferencedStateTracker";
26
28
 
@@ -32,7 +34,7 @@ interface ICommonProps {
32
34
  completedGCRuns: number;
33
35
  isTombstoned: boolean;
34
36
  lastSummaryTime?: number;
35
- viaHandle?: boolean;
37
+ headers?: RuntimeHeaderData;
36
38
  }
37
39
 
38
40
  /** The event that is logged when unreferenced node is used after a certain time. */
@@ -45,6 +47,10 @@ interface IUnreferencedEventProps extends ICreateContainerMetadata, ICommonProps
45
47
  type: GCNodeType;
46
48
  unrefTime: number;
47
49
  age: number;
50
+ // Expanding GC feature matrix. Without doing this, the configs cannot be logged in telemetry directly.
51
+ gcConfigs: Omit<IGarbageCollectorConfigs, "persistedGcFeatureMatrix"> & {
52
+ [K in keyof GCFeatureMatrix]: GCFeatureMatrix[K];
53
+ };
48
54
  timeout?: number;
49
55
  fromId?: {
50
56
  value: string;
@@ -78,12 +84,8 @@ export class GCTelemetryTracker {
78
84
 
79
85
  constructor(
80
86
  private readonly mc: MonitoringContext,
81
- private readonly configs: Pick<
82
- IGarbageCollectorConfigs,
83
- "inactiveTimeoutMs" | "sweepTimeoutMs"
84
- >,
87
+ private readonly configs: IGarbageCollectorConfigs,
85
88
  private readonly isSummarizerClient: boolean,
86
- private readonly gcTombstoneEnforcementAllowed: boolean,
87
89
  private readonly createContainerMetadata: ICreateContainerMetadata,
88
90
  private readonly getNodeType: (nodeId: string) => GCNodeType,
89
91
  private readonly getNodeStateTracker: (
@@ -95,12 +97,12 @@ export class GCTelemetryTracker {
95
97
  ) {}
96
98
 
97
99
  /**
98
- * Returns whether an event should be logged for a node that isn't active anymore. Some scenarios where we won't log:
99
- * 1. When a DDS is changed or loaded. The corresponding data store's event will be logged instead.
100
+ * Returns whether an event should be logged for a node that isn't active anymore. This does not apply to
101
+ * tombstoned nodes for which an event is always logged. Some scenarios where we won't log:
102
+ * 1. When a DDS is changed. The corresponding data store's event will be logged instead.
100
103
  * 2. An event is logged only once per container instance per event per node.
101
104
  */
102
105
  private shouldLogNonActiveEvent(
103
- nodeId: string,
104
106
  nodeType: GCNodeType,
105
107
  usageType: NodeUsageType,
106
108
  nodeStateTracker: UnreferencedStateTracker,
@@ -119,6 +121,8 @@ export class GCTelemetryTracker {
119
121
  return false;
120
122
  }
121
123
 
124
+ // Non-tombstone events are logged once per event per node. A unique id is generated by joining
125
+ // node state (inactive / sweep ready), node's id and usage (loaded / changed / revived).
122
126
  if (this.loggedUnreferencedEvents.has(uniqueEventId)) {
123
127
  return false;
124
128
  }
@@ -126,24 +130,63 @@ export class GCTelemetryTracker {
126
130
  }
127
131
 
128
132
  /**
129
- * Called when a node is used. If the node is not active, log an event indicating object is used when its not active.
133
+ * Called when a node is used. If the node is not active or tombstoned, log telemetry indicating object is used
134
+ * when it should not have been.
130
135
  */
131
136
  public nodeUsed(nodeUsageProps: INodeUsageProps) {
132
137
  // If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
133
138
  // logging as nothing interesting would have happened worth logging.
134
- // If the node is not unreferenced, skip logging.
135
- const nodeStateTracker = this.getNodeStateTracker(nodeUsageProps.id);
136
- if (!nodeStateTracker || nodeUsageProps.currentReferenceTimestampMs === undefined) {
139
+ if (nodeUsageProps.currentReferenceTimestampMs === undefined) {
137
140
  return;
138
141
  }
139
142
 
140
- // We log these events once per event per node. A unique id is generated by joining node state (inactive / sweep ready),
141
- // node's id and usage (loaded / changed / revived).
142
- const uniqueEventId = `${nodeStateTracker.state}-${nodeUsageProps.id}-${nodeUsageProps.usageType}`;
143
+ const nodeStateTracker = this.getNodeStateTracker(nodeUsageProps.id);
143
144
  const nodeType = this.getNodeType(nodeUsageProps.id);
145
+ const {
146
+ usageType,
147
+ currentReferenceTimestampMs,
148
+ packagePath,
149
+ id: untaggedId,
150
+ fromId: untaggedFromId,
151
+ ...propsToLog
152
+ } = nodeUsageProps;
153
+ const { persistedGcFeatureMatrix, ...configs } = this.configs;
154
+ const unrefEventProps: Omit<IUnreferencedEventProps, "state" | "usageType"> = {
155
+ type: nodeType,
156
+ unrefTime: nodeStateTracker?.unreferencedTimestampMs ?? -1,
157
+ age:
158
+ nodeStateTracker !== undefined
159
+ ? nodeUsageProps.currentReferenceTimestampMs -
160
+ nodeStateTracker.unreferencedTimestampMs
161
+ : -1,
162
+ timeout:
163
+ nodeStateTracker?.state === UnreferencedState.Inactive
164
+ ? this.configs.inactiveTimeoutMs
165
+ : this.configs.sweepTimeoutMs,
166
+ ...tagCodeArtifacts({ id: untaggedId, fromId: untaggedFromId }),
167
+ ...propsToLog,
168
+ ...this.createContainerMetadata,
169
+ gcConfigs: { ...configs, ...persistedGcFeatureMatrix },
170
+ };
171
+
172
+ // If the node that is used is tombstoned, log a tombstone telemetry.
173
+ // Note that this is done before checking if "nodeStateTracker" is undefined below because unreferenced
174
+ // tracking may not have yet been enabled. That happens only after the client transitions to write mode.
175
+ if (nodeUsageProps.isTombstoned) {
176
+ this.logTombstoneUsageTelemetry(nodeUsageProps, unrefEventProps, nodeType, usageType);
177
+ }
178
+
179
+ // After logging tombstone telemetry, if the node's unreferenced state is not tracked, there is nothing
180
+ // else to log.
181
+ if (nodeStateTracker === undefined) {
182
+ return;
183
+ }
184
+
185
+ const state = nodeStateTracker.state;
186
+ const uniqueEventId = `${state}-${nodeUsageProps.id}-${nodeUsageProps.usageType}`;
187
+
144
188
  if (
145
189
  !this.shouldLogNonActiveEvent(
146
- nodeUsageProps.id,
147
190
  nodeType,
148
191
  nodeUsageProps.usageType,
149
192
  nodeStateTracker,
@@ -153,42 +196,9 @@ export class GCTelemetryTracker {
153
196
  return;
154
197
  }
155
198
 
156
- // Add the unique event id so that we don't generate a log for this event again in this session..
199
+ // Add the unique event id so that we don't generate a log for this event again in this session.
157
200
  this.loggedUnreferencedEvents.add(uniqueEventId);
158
201
 
159
- const state = nodeStateTracker.state;
160
- const { usageType, currentReferenceTimestampMs, packagePath, id, fromId, ...propsToLog } =
161
- nodeUsageProps;
162
- const eventProps: Omit<IUnreferencedEventProps, "state" | "usageType"> = {
163
- type: nodeType,
164
- unrefTime: nodeStateTracker.unreferencedTimestampMs,
165
- age:
166
- nodeUsageProps.currentReferenceTimestampMs -
167
- nodeStateTracker.unreferencedTimestampMs,
168
- timeout:
169
- state === UnreferencedState.Inactive
170
- ? this.configs.inactiveTimeoutMs
171
- : this.configs.sweepTimeoutMs,
172
- ...tagCodeArtifacts({ id, fromId }),
173
- ...propsToLog,
174
- ...this.createContainerMetadata,
175
- };
176
-
177
- // This will log the following events:
178
- // GC_Tombstone_DataStore_Revived, GC_Tombstone_SubDataStore_Revived, GC_Tombstone_Blob_Revived
179
- if (nodeUsageProps.usageType === "Revived" && nodeUsageProps.isTombstoned) {
180
- sendGCUnexpectedUsageEvent(
181
- this.mc,
182
- {
183
- eventName: `GC_Tombstone_${nodeType}_Revived`,
184
- category: "generic",
185
- ...tagCodeArtifacts({ url: id }),
186
- gcTombstoneEnforcementAllowed: this.gcTombstoneEnforcementAllowed,
187
- },
188
- undefined /* packagePath */,
189
- );
190
- }
191
-
192
202
  // For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
193
203
  // For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
194
204
  // but it's a good signal nonetheless and we can consume it with a grain of salt.
@@ -196,7 +206,7 @@ export class GCTelemetryTracker {
196
206
  // SweepReady errors are usages of Objects that will be deleted by GC Sweep!
197
207
  if (this.isSummarizerClient) {
198
208
  this.pendingEventsQueue.push({
199
- ...eventProps,
209
+ ...unrefEventProps,
200
210
  usageType: nodeUsageProps.usageType,
201
211
  state,
202
212
  });
@@ -206,16 +216,16 @@ export class GCTelemetryTracker {
206
216
  // Events generated:
207
217
  // InactiveObject_Loaded, SweepReadyObject_Loaded
208
218
  if (nodeUsageProps.usageType === "Loaded") {
209
- const { id: taggedId, fromId: taggedFromId, ...otherProps } = eventProps;
219
+ const { id, fromId, headers, gcConfigs, ...detailedProps } = unrefEventProps;
210
220
  const event = {
211
221
  eventName: `${state}Object_${nodeUsageProps.usageType}`,
212
- pkg: tagCodeArtifacts({ pkg: nodeUsageProps.packagePath?.join("/") }).pkg,
222
+ ...tagCodeArtifacts({ pkg: nodeUsageProps.packagePath?.join("/") }),
213
223
  stack: generateStack(),
214
- id: taggedId,
215
- fromId: taggedFromId,
216
- details: JSON.stringify({
217
- ...otherProps,
218
- }),
224
+ id,
225
+ fromId,
226
+ headers: { ...headers },
227
+ details: detailedProps,
228
+ gcConfigs,
219
229
  };
220
230
 
221
231
  // Do not log the inactive object x events as error events as they are not the best signal for
@@ -229,6 +239,52 @@ export class GCTelemetryTracker {
229
239
  }
230
240
  }
231
241
 
242
+ /**
243
+ * Logs telemetry when a tombstoned object is changed, revived or loaded.
244
+ */
245
+ private logTombstoneUsageTelemetry(
246
+ nodeUsageProps: INodeUsageProps,
247
+ unrefEventProps: Omit<IUnreferencedEventProps, "state" | "usageType">,
248
+ nodeType: GCNodeType,
249
+ usageType: NodeUsageType,
250
+ ) {
251
+ // This will log the following events:
252
+ // GC_Tombstone_DataStore_Requested, GC_Tombstone_DataStore_Changed, GC_Tombstone_DataStore_Revived
253
+ // GC_Tombstone_SubDataStore_Requested, GC_Tombstone_SubDataStore_Changed, GC_Tombstone_SubDataStore_Revived
254
+ // GC_Tombstone_Blob_Requested, GC_Tombstone_Blob_Changed, GC_Tombstone_Blob_Revived
255
+ const { id, fromId, headers, gcConfigs, ...detailedProps } = unrefEventProps;
256
+ const eventUsageName = usageType === "Loaded" ? "Requested" : usageType;
257
+ const event = {
258
+ eventName: `GC_Tombstone_${nodeType}_${eventUsageName}`,
259
+ pkg: tagCodeArtifacts({ pkg: nodeUsageProps.packagePath?.join("/") }).pkg,
260
+ stack: generateStack(),
261
+ id,
262
+ fromId,
263
+ headers: { ...headers },
264
+ details: detailedProps,
265
+ gcConfigs,
266
+ tombstoneFlags: {
267
+ DisableTombstone: this.mc.config.getBoolean(disableTombstoneKey),
268
+ ThrowOnTombstoneUsage: this.mc.config.getBoolean(throwOnTombstoneUsageKey),
269
+ ThrowOnTombstoneLoad: this.mc.config.getBoolean(throwOnTombstoneLoadOverrideKey),
270
+ },
271
+ sweepFlags: {
272
+ EnableSweepFlag: this.mc.config.getBoolean(runSweepKey),
273
+ },
274
+ };
275
+
276
+ if (
277
+ (usageType === "Loaded" &&
278
+ this.configs.throwOnTombstoneLoad &&
279
+ !headers?.allowTombstone) ||
280
+ (usageType === "Changed" && this.configs.throwOnTombstoneUsage)
281
+ ) {
282
+ this.mc.logger.sendErrorEvent(event);
283
+ } else {
284
+ this.mc.logger.sendTelemetryEvent(event);
285
+ }
286
+ }
287
+
232
288
  /**
233
289
  * Log all new references or outbound routes in the current graph that haven't been explicitly notified to GC.
234
290
  * The principle is that every new reference or outbound route must be notified to GC via the
@@ -295,7 +351,9 @@ export class GCTelemetryTracker {
295
351
  // InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
296
352
  // SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
297
353
  for (const eventProps of this.pendingEventsQueue) {
298
- const { usageType, state, id, fromId, ...propsToLog } = eventProps;
354
+ // const { usageType, state, id, fromId, ...propsToLog } = eventProps;
355
+ const { usageType, state, id, fromId, headers, gcConfigs, ...detailedProps } =
356
+ eventProps;
299
357
  /**
300
358
  * Revived event is logged only if the node is active. If the node is not active, the reference to it was
301
359
  * from another unreferenced node and this scenario is not interesting to log.
@@ -313,11 +371,11 @@ export class GCTelemetryTracker {
313
371
  : undefined;
314
372
  const event = {
315
373
  eventName: `${state}Object_${usageType}`,
316
- details: JSON.stringify({
317
- ...propsToLog,
318
- }),
319
374
  id,
320
375
  fromId,
376
+ headers: { ...headers },
377
+ details: detailedProps,
378
+ gcConfigs,
321
379
  ...tagCodeArtifacts({
322
380
  pkg: pkg?.join("/"),
323
381
  fromPkg: fromPkg?.join("/"),
@@ -404,7 +462,7 @@ export function sendGCUnexpectedUsageEvent(
404
462
  event.tombstoneFlags = JSON.stringify({
405
463
  DisableTombstone: mc.config.getBoolean(disableTombstoneKey),
406
464
  ThrowOnTombstoneUsage: mc.config.getBoolean(throwOnTombstoneUsageKey),
407
- ThrowOnTombstoneLoad: mc.config.getBoolean(throwOnTombstoneLoadKey),
465
+ ThrowOnTombstoneLoad: mc.config.getBoolean(throwOnTombstoneLoadOverrideKey),
408
466
  });
409
467
  event.sweepFlags = JSON.stringify({
410
468
  EnableSweepFlag: mc.config.getBoolean(runSweepKey),
package/src/gc/index.ts CHANGED
@@ -12,6 +12,7 @@ export {
12
12
  GCNodeType,
13
13
  gcTestModeKey,
14
14
  gcTombstoneGenerationOptionName,
15
+ gcThrowOnTombstoneLoadOptionName,
15
16
  gcSweepGenerationOptionName,
16
17
  GCFeatureMatrix,
17
18
  GCVersion,
@@ -31,15 +32,12 @@ export {
31
32
  stableGCVersion,
32
33
  disableAttachmentBlobSweepKey,
33
34
  disableDatastoreSweepKey,
34
- throwOnTombstoneLoadKey,
35
- throwOnTombstoneUsageKey,
36
35
  UnreferencedState,
36
+ throwOnTombstoneLoadOverrideKey,
37
37
  } from "./gcDefinitions";
38
38
  export {
39
39
  cloneGCData,
40
40
  concatGarbageCollectionStates,
41
- shouldAllowGcTombstoneEnforcement,
42
- shouldAllowGcSweep,
43
41
  trimLeadingAndTrailingSlashes,
44
42
  unpackChildNodesGCDetails,
45
43
  } from "./gcHelpers";
@@ -5,6 +5,8 @@
5
5
 
6
6
  import { assert } from "@fluidframework/core-utils";
7
7
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
8
+ import { createChildLogger } from "@fluidframework/telemetry-utils";
9
+ import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
8
10
  import { ContainerMessageType } from "../messageTypes";
9
11
  import { IBatch } from "./definitions";
10
12
 
@@ -26,16 +28,38 @@ function isGroupContents(opContents: any): opContents is IGroupedBatchMessageCon
26
28
  return opContents?.type === OpGroupingManager.groupedBatchOp;
27
29
  }
28
30
 
31
+ export interface OpGroupingManagerConfig {
32
+ readonly groupedBatchingEnabled: boolean;
33
+ readonly opCountThreshold: number;
34
+ readonly reentrantBatchGroupingEnabled: boolean;
35
+ }
36
+
29
37
  export class OpGroupingManager {
30
38
  static readonly groupedBatchOp = "groupedBatch";
39
+ private readonly logger;
31
40
 
32
- constructor(private readonly groupedBatchingEnabled: boolean) {}
41
+ constructor(
42
+ private readonly config: OpGroupingManagerConfig,
43
+ logger: ITelemetryBaseLogger,
44
+ ) {
45
+ this.logger = createChildLogger({ logger, namespace: "OpGroupingManager" });
46
+ }
33
47
 
34
48
  public groupBatch(batch: IBatch): IBatch {
35
- if (batch.content.length < 2 || !this.groupedBatchingEnabled) {
49
+ if (!this.shouldGroup(batch)) {
36
50
  return batch;
37
51
  }
38
52
 
53
+ if (batch.content.length >= 1000) {
54
+ this.logger.sendTelemetryEvent({
55
+ eventName: "GroupLargeBatch",
56
+ length: batch.content.length,
57
+ threshold: this.config.opCountThreshold,
58
+ reentrant: batch.hasReentrantOps,
59
+ referenceSequenceNumber: batch.content[0].referenceSequenceNumber,
60
+ });
61
+ }
62
+
39
63
  for (const message of batch.content) {
40
64
  if (message.metadata) {
41
65
  const keys = Object.keys(message.metadata);
@@ -86,4 +110,15 @@ export class OpGroupingManager {
86
110
  compression: subMessage.compression,
87
111
  }));
88
112
  }
113
+
114
+ public shouldGroup(batch: IBatch): boolean {
115
+ return (
116
+ // Grouped batching must be enabled
117
+ this.config.groupedBatchingEnabled &&
118
+ // The number of ops in the batch must surpass the configured threshold
119
+ batch.content.length >= this.config.opCountThreshold &&
120
+ // Support for reentrant batches must be explicitly enabled
121
+ (this.config.reentrantBatchGroupingEnabled || batch.hasReentrantOps !== true)
122
+ );
123
+ }
89
124
  }
@@ -30,7 +30,6 @@ export interface IOutboxConfig {
30
30
  // The maximum size of a batch that we can send over the wire.
31
31
  readonly maxBatchSizeInBytes: number;
32
32
  readonly disablePartialFlush: boolean;
33
- readonly enableGroupedBatching: boolean;
34
33
  }
35
34
 
36
35
  export interface IOutboxParameters {
@@ -270,7 +269,10 @@ export class Outbox {
270
269
  }
271
270
 
272
271
  const rawBatch = batchManager.popBatch();
273
- if (rawBatch.hasReentrantOps === true && this.params.config.enableGroupedBatching) {
272
+ if (
273
+ rawBatch.hasReentrantOps === true &&
274
+ this.params.groupingManager.shouldGroup(rawBatch)
275
+ ) {
274
276
  assert(!this.rebasing, 0x6fa /* A rebased batch should never have reentrant ops */);
275
277
  // If a batch contains reentrant ops (ops created as a result from processing another op)
276
278
  // it needs to be rebased so that we can ensure consistent reference sequence numbers
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.0.0-internal.7.1.0";
9
+ export const pkgVersion = "2.0.0-internal.7.1.2";