@fluidframework/container-runtime 2.0.0-internal.3.0.1 → 2.0.0-internal.3.1.0

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 (327) hide show
  1. package/.eslintrc.js +19 -19
  2. package/.mocharc.js +2 -2
  3. package/api-extractor.json +2 -2
  4. package/dist/batchTracker.d.ts.map +1 -1
  5. package/dist/batchTracker.js +2 -1
  6. package/dist/batchTracker.js.map +1 -1
  7. package/dist/blobManager.d.ts +9 -2
  8. package/dist/blobManager.d.ts.map +1 -1
  9. package/dist/blobManager.js +80 -33
  10. package/dist/blobManager.js.map +1 -1
  11. package/dist/connectionTelemetry.d.ts.map +1 -1
  12. package/dist/connectionTelemetry.js +11 -9
  13. package/dist/connectionTelemetry.js.map +1 -1
  14. package/dist/containerHandleContext.d.ts.map +1 -1
  15. package/dist/containerHandleContext.js +3 -1
  16. package/dist/containerHandleContext.js.map +1 -1
  17. package/dist/containerRuntime.d.ts +10 -0
  18. package/dist/containerRuntime.d.ts.map +1 -1
  19. package/dist/containerRuntime.js +140 -72
  20. package/dist/containerRuntime.js.map +1 -1
  21. package/dist/dataStore.d.ts.map +1 -1
  22. package/dist/dataStore.js +11 -9
  23. package/dist/dataStore.js.map +1 -1
  24. package/dist/dataStoreContext.d.ts +18 -1
  25. package/dist/dataStoreContext.d.ts.map +1 -1
  26. package/dist/dataStoreContext.js +66 -15
  27. package/dist/dataStoreContext.js.map +1 -1
  28. package/dist/dataStoreContexts.d.ts.map +1 -1
  29. package/dist/dataStoreContexts.js +7 -3
  30. package/dist/dataStoreContexts.js.map +1 -1
  31. package/dist/dataStoreRegistry.d.ts.map +1 -1
  32. package/dist/dataStoreRegistry.js +3 -1
  33. package/dist/dataStoreRegistry.js.map +1 -1
  34. package/dist/dataStores.d.ts +26 -1
  35. package/dist/dataStores.d.ts.map +1 -1
  36. package/dist/dataStores.js +103 -18
  37. package/dist/dataStores.js.map +1 -1
  38. package/dist/deltaScheduler.d.ts.map +1 -1
  39. package/dist/deltaScheduler.js +8 -3
  40. package/dist/deltaScheduler.js.map +1 -1
  41. package/dist/garbageCollection.d.ts +34 -14
  42. package/dist/garbageCollection.d.ts.map +1 -1
  43. package/dist/garbageCollection.js +188 -93
  44. package/dist/garbageCollection.js.map +1 -1
  45. package/dist/garbageCollectionConstants.d.ts +3 -0
  46. package/dist/garbageCollectionConstants.d.ts.map +1 -1
  47. package/dist/garbageCollectionConstants.js +6 -1
  48. package/dist/garbageCollectionConstants.js.map +1 -1
  49. package/dist/garbageCollectionHelpers.d.ts +26 -0
  50. package/dist/garbageCollectionHelpers.d.ts.map +1 -0
  51. package/dist/garbageCollectionHelpers.js +45 -0
  52. package/dist/garbageCollectionHelpers.js.map +1 -0
  53. package/dist/gcSweepReadyUsageDetection.d.ts +5 -5
  54. package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
  55. package/dist/gcSweepReadyUsageDetection.js +14 -10
  56. package/dist/gcSweepReadyUsageDetection.js.map +1 -1
  57. package/dist/index.d.ts +2 -2
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/opLifecycle/batchManager.d.ts +5 -5
  61. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  62. package/dist/opLifecycle/batchManager.js +19 -12
  63. package/dist/opLifecycle/batchManager.js.map +1 -1
  64. package/dist/opLifecycle/definitions.d.ts.map +1 -1
  65. package/dist/opLifecycle/definitions.js.map +1 -1
  66. package/dist/opLifecycle/index.d.ts.map +1 -1
  67. package/dist/opLifecycle/index.js.map +1 -1
  68. package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
  69. package/dist/opLifecycle/opCompressor.js.map +1 -1
  70. package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
  71. package/dist/opLifecycle/opDecompressor.js +5 -2
  72. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  73. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  74. package/dist/opLifecycle/opSplitter.js +4 -1
  75. package/dist/opLifecycle/opSplitter.js.map +1 -1
  76. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  77. package/dist/opLifecycle/outbox.js +19 -17
  78. package/dist/opLifecycle/outbox.js.map +1 -1
  79. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  80. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  81. package/dist/opProperties.d.ts.map +1 -1
  82. package/dist/opProperties.js +1 -3
  83. package/dist/opProperties.js.map +1 -1
  84. package/dist/orderedClientElection.d.ts.map +1 -1
  85. package/dist/orderedClientElection.js +10 -4
  86. package/dist/orderedClientElection.js.map +1 -1
  87. package/dist/packageVersion.d.ts +1 -1
  88. package/dist/packageVersion.js +1 -1
  89. package/dist/packageVersion.js.map +1 -1
  90. package/dist/pendingStateManager.d.ts +7 -0
  91. package/dist/pendingStateManager.d.ts.map +1 -1
  92. package/dist/pendingStateManager.js +7 -4
  93. package/dist/pendingStateManager.js.map +1 -1
  94. package/dist/runWhileConnectedCoordinator.d.ts.map +1 -1
  95. package/dist/runWhileConnectedCoordinator.js.map +1 -1
  96. package/dist/runningSummarizer.d.ts.map +1 -1
  97. package/dist/runningSummarizer.js +34 -21
  98. package/dist/runningSummarizer.js.map +1 -1
  99. package/dist/scheduleManager.d.ts.map +1 -1
  100. package/dist/scheduleManager.js +3 -2
  101. package/dist/scheduleManager.js.map +1 -1
  102. package/dist/serializedSnapshotStorage.d.ts +2 -2
  103. package/dist/serializedSnapshotStorage.d.ts.map +1 -1
  104. package/dist/serializedSnapshotStorage.js +5 -3
  105. package/dist/serializedSnapshotStorage.js.map +1 -1
  106. package/dist/summarizer.d.ts +2 -2
  107. package/dist/summarizer.d.ts.map +1 -1
  108. package/dist/summarizer.js +37 -17
  109. package/dist/summarizer.js.map +1 -1
  110. package/dist/summarizerClientElection.d.ts.map +1 -1
  111. package/dist/summarizerClientElection.js.map +1 -1
  112. package/dist/summarizerHandle.d.ts.map +1 -1
  113. package/dist/summarizerHandle.js.map +1 -1
  114. package/dist/summarizerHeuristics.d.ts.map +1 -1
  115. package/dist/summarizerHeuristics.js +6 -9
  116. package/dist/summarizerHeuristics.js.map +1 -1
  117. package/dist/summarizerTypes.d.ts +21 -21
  118. package/dist/summarizerTypes.d.ts.map +1 -1
  119. package/dist/summarizerTypes.js.map +1 -1
  120. package/dist/summaryCollection.d.ts.map +1 -1
  121. package/dist/summaryCollection.js +18 -8
  122. package/dist/summaryCollection.js.map +1 -1
  123. package/dist/summaryFormat.d.ts +22 -0
  124. package/dist/summaryFormat.d.ts.map +1 -1
  125. package/dist/summaryFormat.js +18 -10
  126. package/dist/summaryFormat.js.map +1 -1
  127. package/dist/summaryGenerator.d.ts.map +1 -1
  128. package/dist/summaryGenerator.js +34 -15
  129. package/dist/summaryGenerator.js.map +1 -1
  130. package/dist/summaryManager.d.ts.map +1 -1
  131. package/dist/summaryManager.js +21 -9
  132. package/dist/summaryManager.js.map +1 -1
  133. package/dist/throttler.d.ts +2 -2
  134. package/dist/throttler.d.ts.map +1 -1
  135. package/dist/throttler.js +4 -4
  136. package/dist/throttler.js.map +1 -1
  137. package/garbageCollection.md +15 -2
  138. package/lib/batchTracker.d.ts.map +1 -1
  139. package/lib/batchTracker.js +2 -1
  140. package/lib/batchTracker.js.map +1 -1
  141. package/lib/blobManager.d.ts +9 -2
  142. package/lib/blobManager.d.ts.map +1 -1
  143. package/lib/blobManager.js +82 -35
  144. package/lib/blobManager.js.map +1 -1
  145. package/lib/connectionTelemetry.d.ts.map +1 -1
  146. package/lib/connectionTelemetry.js +11 -9
  147. package/lib/connectionTelemetry.js.map +1 -1
  148. package/lib/containerHandleContext.d.ts.map +1 -1
  149. package/lib/containerHandleContext.js +3 -1
  150. package/lib/containerHandleContext.js.map +1 -1
  151. package/lib/containerRuntime.d.ts +10 -0
  152. package/lib/containerRuntime.d.ts.map +1 -1
  153. package/lib/containerRuntime.js +146 -78
  154. package/lib/containerRuntime.js.map +1 -1
  155. package/lib/dataStore.d.ts.map +1 -1
  156. package/lib/dataStore.js +11 -9
  157. package/lib/dataStore.js.map +1 -1
  158. package/lib/dataStoreContext.d.ts +18 -1
  159. package/lib/dataStoreContext.d.ts.map +1 -1
  160. package/lib/dataStoreContext.js +68 -17
  161. package/lib/dataStoreContext.js.map +1 -1
  162. package/lib/dataStoreContexts.d.ts.map +1 -1
  163. package/lib/dataStoreContexts.js +7 -3
  164. package/lib/dataStoreContexts.js.map +1 -1
  165. package/lib/dataStoreRegistry.d.ts.map +1 -1
  166. package/lib/dataStoreRegistry.js +3 -1
  167. package/lib/dataStoreRegistry.js.map +1 -1
  168. package/lib/dataStores.d.ts +26 -1
  169. package/lib/dataStores.d.ts.map +1 -1
  170. package/lib/dataStores.js +109 -24
  171. package/lib/dataStores.js.map +1 -1
  172. package/lib/deltaScheduler.d.ts.map +1 -1
  173. package/lib/deltaScheduler.js +9 -4
  174. package/lib/deltaScheduler.js.map +1 -1
  175. package/lib/garbageCollection.d.ts +34 -14
  176. package/lib/garbageCollection.d.ts.map +1 -1
  177. package/lib/garbageCollection.js +190 -95
  178. package/lib/garbageCollection.js.map +1 -1
  179. package/lib/garbageCollectionConstants.d.ts +3 -0
  180. package/lib/garbageCollectionConstants.d.ts.map +1 -1
  181. package/lib/garbageCollectionConstants.js +5 -0
  182. package/lib/garbageCollectionConstants.js.map +1 -1
  183. package/lib/garbageCollectionHelpers.d.ts +26 -0
  184. package/lib/garbageCollectionHelpers.d.ts.map +1 -0
  185. package/lib/garbageCollectionHelpers.js +40 -0
  186. package/lib/garbageCollectionHelpers.js.map +1 -0
  187. package/lib/gcSweepReadyUsageDetection.d.ts +5 -5
  188. package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
  189. package/lib/gcSweepReadyUsageDetection.js +14 -10
  190. package/lib/gcSweepReadyUsageDetection.js.map +1 -1
  191. package/lib/index.d.ts +2 -2
  192. package/lib/index.d.ts.map +1 -1
  193. package/lib/index.js +1 -1
  194. package/lib/index.js.map +1 -1
  195. package/lib/opLifecycle/batchManager.d.ts +5 -5
  196. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  197. package/lib/opLifecycle/batchManager.js +19 -12
  198. package/lib/opLifecycle/batchManager.js.map +1 -1
  199. package/lib/opLifecycle/definitions.d.ts.map +1 -1
  200. package/lib/opLifecycle/definitions.js.map +1 -1
  201. package/lib/opLifecycle/index.d.ts.map +1 -1
  202. package/lib/opLifecycle/index.js.map +1 -1
  203. package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
  204. package/lib/opLifecycle/opCompressor.js.map +1 -1
  205. package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
  206. package/lib/opLifecycle/opDecompressor.js +5 -2
  207. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  208. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  209. package/lib/opLifecycle/opSplitter.js +5 -2
  210. package/lib/opLifecycle/opSplitter.js.map +1 -1
  211. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  212. package/lib/opLifecycle/outbox.js +19 -17
  213. package/lib/opLifecycle/outbox.js.map +1 -1
  214. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  215. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  216. package/lib/opProperties.d.ts.map +1 -1
  217. package/lib/opProperties.js +1 -3
  218. package/lib/opProperties.js.map +1 -1
  219. package/lib/orderedClientElection.d.ts.map +1 -1
  220. package/lib/orderedClientElection.js +10 -4
  221. package/lib/orderedClientElection.js.map +1 -1
  222. package/lib/packageVersion.d.ts +1 -1
  223. package/lib/packageVersion.js +1 -1
  224. package/lib/packageVersion.js.map +1 -1
  225. package/lib/pendingStateManager.d.ts +7 -0
  226. package/lib/pendingStateManager.d.ts.map +1 -1
  227. package/lib/pendingStateManager.js +7 -4
  228. package/lib/pendingStateManager.js.map +1 -1
  229. package/lib/runWhileConnectedCoordinator.d.ts.map +1 -1
  230. package/lib/runWhileConnectedCoordinator.js.map +1 -1
  231. package/lib/runningSummarizer.d.ts.map +1 -1
  232. package/lib/runningSummarizer.js +35 -22
  233. package/lib/runningSummarizer.js.map +1 -1
  234. package/lib/scheduleManager.d.ts.map +1 -1
  235. package/lib/scheduleManager.js +3 -2
  236. package/lib/scheduleManager.js.map +1 -1
  237. package/lib/serializedSnapshotStorage.d.ts +2 -2
  238. package/lib/serializedSnapshotStorage.d.ts.map +1 -1
  239. package/lib/serializedSnapshotStorage.js +5 -3
  240. package/lib/serializedSnapshotStorage.js.map +1 -1
  241. package/lib/summarizer.d.ts +2 -2
  242. package/lib/summarizer.d.ts.map +1 -1
  243. package/lib/summarizer.js +37 -17
  244. package/lib/summarizer.js.map +1 -1
  245. package/lib/summarizerClientElection.d.ts.map +1 -1
  246. package/lib/summarizerClientElection.js.map +1 -1
  247. package/lib/summarizerHandle.d.ts.map +1 -1
  248. package/lib/summarizerHandle.js.map +1 -1
  249. package/lib/summarizerHeuristics.d.ts.map +1 -1
  250. package/lib/summarizerHeuristics.js +6 -9
  251. package/lib/summarizerHeuristics.js.map +1 -1
  252. package/lib/summarizerTypes.d.ts +21 -21
  253. package/lib/summarizerTypes.d.ts.map +1 -1
  254. package/lib/summarizerTypes.js.map +1 -1
  255. package/lib/summaryCollection.d.ts.map +1 -1
  256. package/lib/summaryCollection.js +18 -8
  257. package/lib/summaryCollection.js.map +1 -1
  258. package/lib/summaryFormat.d.ts +22 -0
  259. package/lib/summaryFormat.d.ts.map +1 -1
  260. package/lib/summaryFormat.js +20 -12
  261. package/lib/summaryFormat.js.map +1 -1
  262. package/lib/summaryGenerator.d.ts.map +1 -1
  263. package/lib/summaryGenerator.js +34 -15
  264. package/lib/summaryGenerator.js.map +1 -1
  265. package/lib/summaryManager.d.ts.map +1 -1
  266. package/lib/summaryManager.js +21 -9
  267. package/lib/summaryManager.js.map +1 -1
  268. package/lib/throttler.d.ts +2 -2
  269. package/lib/throttler.d.ts.map +1 -1
  270. package/lib/throttler.js +4 -4
  271. package/lib/throttler.js.map +1 -1
  272. package/package.json +121 -149
  273. package/prettier.config.cjs +1 -1
  274. package/src/batchTracker.ts +54 -49
  275. package/src/blobManager.ts +793 -672
  276. package/src/connectionTelemetry.ts +280 -249
  277. package/src/containerHandleContext.ts +27 -29
  278. package/src/containerRuntime.ts +3168 -2940
  279. package/src/dataStore.ts +172 -159
  280. package/src/dataStoreContext.ts +1098 -996
  281. package/src/dataStoreContexts.ts +178 -161
  282. package/src/dataStoreRegistry.ts +25 -20
  283. package/src/dataStores.ts +884 -728
  284. package/src/deltaScheduler.ts +158 -150
  285. package/src/garbageCollection.ts +1883 -1692
  286. package/src/garbageCollectionConstants.ts +6 -0
  287. package/src/garbageCollectionHelpers.ts +61 -0
  288. package/src/gcSweepReadyUsageDetection.ts +89 -83
  289. package/src/index.ts +67 -66
  290. package/src/opLifecycle/README.md +152 -0
  291. package/src/opLifecycle/batchManager.ts +145 -141
  292. package/src/opLifecycle/definitions.ts +29 -29
  293. package/src/opLifecycle/index.ts +5 -5
  294. package/src/opLifecycle/opCompressor.ts +54 -53
  295. package/src/opLifecycle/opDecompressor.ts +100 -81
  296. package/src/opLifecycle/opSplitter.ts +214 -188
  297. package/src/opLifecycle/outbox.ts +204 -194
  298. package/src/opLifecycle/remoteMessageProcessor.ts +62 -62
  299. package/src/opProperties.ts +11 -9
  300. package/src/orderedClientElection.ts +489 -457
  301. package/src/packageVersion.ts +1 -1
  302. package/src/pendingStateManager.ts +384 -338
  303. package/src/runWhileConnectedCoordinator.ts +78 -71
  304. package/src/runningSummarizer.ts +619 -581
  305. package/src/scheduleManager.ts +299 -269
  306. package/src/serializedSnapshotStorage.ts +126 -112
  307. package/src/summarizer.ts +417 -381
  308. package/src/summarizerClientElection.ts +107 -100
  309. package/src/summarizerHandle.ts +11 -9
  310. package/src/summarizerHeuristics.ts +183 -186
  311. package/src/summarizerTypes.ts +344 -330
  312. package/src/summaryCollection.ts +378 -349
  313. package/src/summaryFormat.ts +170 -126
  314. package/src/summaryGenerator.ts +465 -406
  315. package/src/summaryManager.ts +377 -348
  316. package/src/throttler.ts +131 -122
  317. package/tsconfig.esnext.json +6 -6
  318. package/tsconfig.json +9 -13
  319. package/dist/garbageCollectionTombstoneUtils.d.ts +0 -14
  320. package/dist/garbageCollectionTombstoneUtils.d.ts.map +0 -1
  321. package/dist/garbageCollectionTombstoneUtils.js +0 -23
  322. package/dist/garbageCollectionTombstoneUtils.js.map +0 -1
  323. package/lib/garbageCollectionTombstoneUtils.d.ts +0 -14
  324. package/lib/garbageCollectionTombstoneUtils.d.ts.map +0 -1
  325. package/lib/garbageCollectionTombstoneUtils.js +0 -19
  326. package/lib/garbageCollectionTombstoneUtils.js.map +0 -1
  327. package/src/garbageCollectionTombstoneUtils.ts +0 -28
@@ -10,6 +10,9 @@ export const stableGCVersion: GCVersion = 1;
10
10
  /** The current version of garbage collection. */
11
11
  export const currentGCVersion: GCVersion = 2;
12
12
 
13
+ /** This undocumented GC Option (on ContainerRuntime Options) allows an app to disable enforcing GC on old documents by incrementing this value */
14
+ export const gcTombstoneGenerationOptionName = "gcTombstoneGeneration";
15
+
13
16
  // Feature gate key to turn GC on / off.
14
17
  export const runGCKey = "Fluid.GarbageCollection.RunGC";
15
18
  // Feature gate key to turn GC sweep on / off.
@@ -30,6 +33,9 @@ export const throwOnTombstoneLoadKey = "Fluid.GarbageCollection.ThrowOnTombstone
30
33
  export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
31
34
  // Feature gate to enable GC version upgrade.
32
35
  export const gcVersionUpgradeToV2Key = "Fluid.GarbageCollection.GCVersionUpgradeToV2";
36
+ // Feature gate to enable GC sweep for datastores.
37
+ // TODO: Remove Test from the flag when we are confident to turn on sweep
38
+ export const sweepDatastoresKey = "Fluid.GarbageCollection.Test.SweepDataStores";
33
39
 
34
40
  // One day in milliseconds.
35
41
  export const oneDayMs = 1 * 24 * 60 * 60 * 1000;
@@ -0,0 +1,61 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ITelemetryGenericEvent } from "@fluidframework/common-definitions";
7
+ import { packagePathToTelemetryProperty } from "@fluidframework/runtime-utils";
8
+ import { MonitoringContext } from "@fluidframework/telemetry-utils";
9
+ import {
10
+ disableTombstoneKey,
11
+ runSweepKey,
12
+ throwOnTombstoneLoadKey,
13
+ throwOnTombstoneUsageKey,
14
+ } from "./garbageCollectionConstants";
15
+
16
+ /**
17
+ * Consolidates info / logic for logging when we encounter unexpected usage of GC'd objects. For example, when a
18
+ * tombstoned or deleted object is loaded.
19
+ */
20
+ export function sendGCUnexpectedUsageEvent(
21
+ mc: MonitoringContext,
22
+ event: ITelemetryGenericEvent & {
23
+ category: "error" | "generic";
24
+ gcTombstoneEnforcementAllowed: boolean | undefined;
25
+ },
26
+ packagePath: readonly string[] | undefined,
27
+ error?: unknown,
28
+ ) {
29
+ event.pkg = packagePathToTelemetryProperty(packagePath);
30
+ event.tombstoneFlags = JSON.stringify({
31
+ DisableTombstone: mc.config.getBoolean(disableTombstoneKey),
32
+ ThrowOnTombstoneUsage: mc.config.getBoolean(throwOnTombstoneUsageKey),
33
+ ThrowOnTombstoneLoad: mc.config.getBoolean(throwOnTombstoneLoadKey),
34
+ });
35
+ event.sweepFlags = JSON.stringify({
36
+ EnableSweepFlag: mc.config.getBoolean(runSweepKey),
37
+ });
38
+
39
+ mc.logger.sendTelemetryEvent(event, error);
40
+ }
41
+
42
+ /**
43
+ * In order to protect old documents that were created at a time when known bugs exist that violate GC's invariants
44
+ * such that enforcing GC (Fail on Tombstone load/usage, GC Sweep) would cause legitimate data loss,
45
+ * the container author may increment the generation value for Tombstone such that containers created
46
+ * with a different value will not be subjected to GC enforcement.
47
+ * If no generation is provided at runtime, this defaults to return true to maintain expected default behavior
48
+ * @param persistedGeneration - The persisted feature support value
49
+ * @param currentGeneration - The current app-provided feature support value
50
+ * @returns true if GC Enforcement (Fail on Tombstone load/usage) should be allowed
51
+ */
52
+ export function shouldAllowGcTombstoneEnforcement(
53
+ persistedGeneration: number | undefined,
54
+ currentGeneration: number | undefined,
55
+ ): boolean {
56
+ // If no Generation value is provided for this session, then we should default to letting Tombstone feature behave as intended.
57
+ if (currentGeneration === undefined) {
58
+ return true;
59
+ }
60
+ return persistedGeneration === currentGeneration;
61
+ }
@@ -6,10 +6,10 @@
6
6
  import { ITelemetryProperties } from "@fluidframework/common-definitions";
7
7
  import { ICriticalContainerError } from "@fluidframework/container-definitions";
8
8
  import {
9
- IConfigProvider,
10
- IFluidErrorBase,
11
- LoggingError,
12
- MonitoringContext,
9
+ IConfigProvider,
10
+ IFluidErrorBase,
11
+ LoggingError,
12
+ MonitoringContext,
13
13
  } from "@fluidframework/telemetry-utils";
14
14
  import { oneDayMs } from "./garbageCollectionConstants";
15
15
 
@@ -17,30 +17,33 @@ import { oneDayMs } from "./garbageCollectionConstants";
17
17
  * Feature Gate Key -
18
18
  * How many days between closing the container from this error (avoids locking user out of their file altogether)
19
19
  */
20
- export const skipClosureForXDaysKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.SkipClosureForXDays";
20
+ export const skipClosureForXDaysKey =
21
+ "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.SkipClosureForXDays";
21
22
 
22
23
  /**
23
24
  * LocalStorage key (NOT via feature gate / monitoring context)
24
25
  * A map from docId to info about the last time we closed due to this error
25
26
  */
26
- export const closuresMapLocalStorageKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.Closures";
27
+ export const closuresMapLocalStorageKey =
28
+ "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection.Closures";
27
29
 
28
30
  /**
29
31
  * Feature gate key to enable closing the container if SweepReady objects are used.
30
32
  * Value should contain keywords "interactiveClient" and/or "summarizer" to enable detection in each container type
31
33
  */
32
34
  const sweepReadyUsageDetectionSetting = {
33
- read(config: IConfigProvider) {
34
- const sweepReadyUsageDetectionKey = "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection";
35
- const value = config.getString(sweepReadyUsageDetectionKey);
36
- if (value === undefined) {
37
- return { interactiveClient: false, summarizer: false };
38
- }
39
- return {
40
- interactiveClient: value.includes("interactiveClient"),
41
- summarizer: value.includes("summarizer"),
42
- };
43
- },
35
+ read(config: IConfigProvider) {
36
+ const sweepReadyUsageDetectionKey =
37
+ "Fluid.GarbageCollection.Dogfood.SweepReadyUsageDetection";
38
+ const value = config.getString(sweepReadyUsageDetectionKey);
39
+ if (value === undefined) {
40
+ return { interactiveClient: false, summarizer: false };
41
+ }
42
+ return {
43
+ interactiveClient: value.includes("interactiveClient"),
44
+ summarizer: value.includes("summarizer"),
45
+ };
46
+ },
44
47
  };
45
48
 
46
49
  /**
@@ -52,8 +55,8 @@ const sweepReadyUsageDetectionSetting = {
52
55
  * since only the Summarizer has the latest truth about unreferenced node tracking
53
56
  */
54
57
  export class SweepReadyUsageError extends LoggingError implements IFluidErrorBase {
55
- /** This errorType will be in temporary use (until Sweep is fully implemented) so don't add to any errorType type */
56
- public errorType: string = "unreferencedObjectUsedAfterGarbageCollected";
58
+ /** This errorType will be in temporary use (until Sweep is fully implemented) so don't add to any errorType type */
59
+ public errorType: string = "unreferencedObjectUsedAfterGarbageCollected";
57
60
  }
58
61
 
59
62
  /**
@@ -65,75 +68,78 @@ export class SweepReadyUsageError extends LoggingError implements IFluidErrorBas
65
68
  * (via skipClosureForXDaysKey above. Uses localStorage and closuresMapLocalStorageKey to implement this behavior)
66
69
  */
67
70
  export class SweepReadyUsageDetectionHandler {
68
- private readonly localStorage: Pick<Storage, "getItem" | "setItem">;
71
+ private readonly localStorage: Pick<Storage, "getItem" | "setItem">;
69
72
 
70
- constructor(
71
- private readonly uniqueContainerKey: string,
72
- private readonly mc: MonitoringContext,
73
- private readonly closeFn: (error?: ICriticalContainerError) => void,
74
- localStorageOverride?: Pick<Storage, "getItem" | "setItem">,
75
- ) {
76
- const noopStorage = { getItem: () => null, setItem: () => {} };
77
- // localStorage is not defined in Node environment, so fall back to noopStorage if needed.
78
- this.localStorage = localStorageOverride ?? globalThis.localStorage ?? noopStorage;
73
+ constructor(
74
+ private readonly uniqueContainerKey: string,
75
+ private readonly mc: MonitoringContext,
76
+ private readonly closeFn: (error?: ICriticalContainerError) => void,
77
+ localStorageOverride?: Pick<Storage, "getItem" | "setItem">,
78
+ ) {
79
+ const noopStorage = { getItem: () => null, setItem: () => {} };
80
+ // localStorage is not defined in Node environment, so fall back to noopStorage if needed.
81
+ this.localStorage = localStorageOverride ?? globalThis.localStorage ?? noopStorage;
79
82
 
80
- if (this.localStorage === noopStorage) {
81
- // This means the Skip Closure Period logic will not work.
82
- this.mc.logger.sendTelemetryEvent({ eventName: "SweepReadyUsageDetectionHandlerNoopStorage" });
83
- }
84
- }
83
+ if (this.localStorage === noopStorage) {
84
+ // This means the Skip Closure Period logic will not work.
85
+ this.mc.logger.sendTelemetryEvent({
86
+ eventName: "SweepReadyUsageDetectionHandlerNoopStorage",
87
+ });
88
+ }
89
+ }
85
90
 
86
- /**
87
- * If SweepReady Usage Detection is enabled, close the interactive container.
88
- * If the SkipClosureForXDays setting is set, don't close the container more than once in that period.
89
- *
90
- * Once Sweep is fully implemented, this will be removed since the objects will be gone
91
- * and errors will arise elsewhere in the runtime
92
- */
93
- public usageDetectedInInteractiveClient(errorProps: ITelemetryProperties) {
94
- if (!sweepReadyUsageDetectionSetting.read(this.mc.config).interactiveClient) {
95
- return;
96
- }
91
+ /**
92
+ * If SweepReady Usage Detection is enabled, close the interactive container.
93
+ * If the SkipClosureForXDays setting is set, don't close the container more than once in that period.
94
+ *
95
+ * Once Sweep is fully implemented, this will be removed since the objects will be gone
96
+ * and errors will arise elsewhere in the runtime
97
+ */
98
+ public usageDetectedInInteractiveClient(errorProps: ITelemetryProperties) {
99
+ if (!sweepReadyUsageDetectionSetting.read(this.mc.config).interactiveClient) {
100
+ return;
101
+ }
97
102
 
98
- // Default stance is we close every time - this reflects the severity of SweepReady Object Usage.
99
- // However, we may choose to "throttle" the closures by setting the SkipClosureForXDays setting,
100
- // which will only allow the container to close once during that period, to avoid locking users out.
101
- let shouldClose: boolean = true;
102
- let pastClosuresMap: Record<string, { lastCloseTime: number; } | undefined> = {};
103
- let lastCloseTime: number | undefined;
104
- const skipClosureForXDays = this.mc.config.getNumber(skipClosureForXDaysKey);
105
- if (skipClosureForXDays !== undefined) {
106
- // Read pastClosuresMap from localStorage then extract the lastCloseTime from the map
107
- try {
108
- const rawValue = this.localStorage.getItem(closuresMapLocalStorageKey);
109
- const parsedValue = rawValue === null ? {} : JSON.parse(rawValue);
110
- if (typeof parsedValue === "object") {
111
- pastClosuresMap = parsedValue;
112
- }
113
- } catch (e) {
114
- }
115
- lastCloseTime = pastClosuresMap[this.uniqueContainerKey]?.lastCloseTime;
103
+ // Default stance is we close every time - this reflects the severity of SweepReady Object Usage.
104
+ // However, we may choose to "throttle" the closures by setting the SkipClosureForXDays setting,
105
+ // which will only allow the container to close once during that period, to avoid locking users out.
106
+ let shouldClose: boolean = true;
107
+ let pastClosuresMap: Record<string, { lastCloseTime: number } | undefined> = {};
108
+ let lastCloseTime: number | undefined;
109
+ const skipClosureForXDays = this.mc.config.getNumber(skipClosureForXDaysKey);
110
+ if (skipClosureForXDays !== undefined) {
111
+ // Read pastClosuresMap from localStorage then extract the lastCloseTime from the map
112
+ try {
113
+ const rawValue = this.localStorage.getItem(closuresMapLocalStorageKey);
114
+ const parsedValue = rawValue === null ? {} : JSON.parse(rawValue);
115
+ if (typeof parsedValue === "object") {
116
+ pastClosuresMap = parsedValue;
117
+ }
118
+ } catch (e) {}
119
+ lastCloseTime = pastClosuresMap[this.uniqueContainerKey]?.lastCloseTime;
116
120
 
117
- // Don't close if we did already within the Skip Closure Period
118
- if (lastCloseTime !== undefined && Date.now() < lastCloseTime + skipClosureForXDays * oneDayMs) {
119
- shouldClose = false;
120
- }
121
- }
121
+ // Don't close if we did already within the Skip Closure Period
122
+ if (
123
+ lastCloseTime !== undefined &&
124
+ Date.now() < lastCloseTime + skipClosureForXDays * oneDayMs
125
+ ) {
126
+ shouldClose = false;
127
+ }
128
+ }
122
129
 
123
- const error = new SweepReadyUsageError(
124
- "SweepReady object used in Non-Summarizer Client",
125
- { errorDetails: JSON.stringify({ ...errorProps, lastCloseTime, skipClosureForXDays }) },
126
- );
127
- if (shouldClose) {
128
- // Update closures map in localStorage before closing
129
- // Note there is a race condition between different tabs updating localStorage and overwriting
130
- // each others' updates. If so, some tab will crash again. Just reload one at a time to get unstuck
131
- pastClosuresMap[this.uniqueContainerKey] = { lastCloseTime: Date.now() };
132
- this.localStorage.setItem(closuresMapLocalStorageKey, JSON.stringify(pastClosuresMap));
130
+ const error = new SweepReadyUsageError("SweepReady object used in Non-Summarizer Client", {
131
+ errorDetails: JSON.stringify({ ...errorProps, lastCloseTime, skipClosureForXDays }),
132
+ });
133
+ if (shouldClose) {
134
+ // Update closures map in localStorage before closing
135
+ // Note there is a race condition between different tabs updating localStorage and overwriting
136
+ // each others' updates. If so, some tab will crash again. Just reload one at a time to get unstuck
137
+ pastClosuresMap[this.uniqueContainerKey] = { lastCloseTime: Date.now() };
138
+ this.localStorage.setItem(closuresMapLocalStorageKey, JSON.stringify(pastClosuresMap));
133
139
 
134
- this.closeFn(error);
135
- } else {
136
- this.mc.logger.sendErrorEvent({ eventName: "SweepReadyObject_UsageAllowed" }, error);
137
- }
138
- }
140
+ this.closeFn(error);
141
+ } else {
142
+ this.mc.logger.sendErrorEvent({ eventName: "SweepReadyObject_UsageAllowed" }, error);
143
+ }
144
+ }
139
145
  }
package/src/index.ts CHANGED
@@ -4,79 +4,80 @@
4
4
  */
5
5
 
6
6
  export {
7
- ContainerMessageType,
8
- ContainerRuntimeMessage,
9
- IGCRuntimeOptions,
10
- ISummaryRuntimeOptions,
11
- ISummaryBaseConfiguration,
12
- ISummaryConfigurationHeuristics,
13
- ISummaryConfigurationDisableSummarizer,
14
- ISummaryConfigurationDisableHeuristics,
15
- IContainerRuntimeOptions,
16
- IRootSummaryTreeWithStats,
17
- isRuntimeMessage,
18
- RuntimeMessage,
19
- agentSchedulerId,
20
- ContainerRuntime,
21
- RuntimeHeaders,
22
- AllowTombstoneRequestHeaderKey,
23
- TombstoneResponseHeaderKey,
24
- ISummaryConfiguration,
25
- DefaultSummaryConfiguration,
26
- ICompressionRuntimeOptions,
27
- CompressionAlgorithms,
7
+ ContainerMessageType,
8
+ ContainerRuntimeMessage,
9
+ IGCRuntimeOptions,
10
+ ISummaryRuntimeOptions,
11
+ ISummaryBaseConfiguration,
12
+ ISummaryConfigurationHeuristics,
13
+ ISummaryConfigurationDisableSummarizer,
14
+ ISummaryConfigurationDisableHeuristics,
15
+ IContainerRuntimeOptions,
16
+ IRootSummaryTreeWithStats,
17
+ isRuntimeMessage,
18
+ RuntimeMessage,
19
+ agentSchedulerId,
20
+ ContainerRuntime,
21
+ RuntimeHeaders,
22
+ AllowTombstoneRequestHeaderKey,
23
+ TombstoneResponseHeaderKey,
24
+ ISummaryConfiguration,
25
+ DefaultSummaryConfiguration,
26
+ ICompressionRuntimeOptions,
27
+ CompressionAlgorithms,
28
28
  } from "./containerRuntime";
29
29
  export { FluidDataStoreRegistry } from "./dataStoreRegistry";
30
+ export { IGCStats } from "./garbageCollection";
30
31
  export {
31
- IGCStats,
32
- } from "./garbageCollection";
33
- export {
34
- IPendingFlush,
35
- IPendingLocalState,
36
- IPendingMessage,
37
- IPendingState,
32
+ IPendingFlush,
33
+ IPendingLocalState,
34
+ IPendingMessage,
35
+ IPendingState,
38
36
  } from "./pendingStateManager";
39
37
  export { Summarizer } from "./summarizer";
40
38
  export {
41
- EnqueueSummarizeResult,
42
- IAckSummaryResult,
43
- IBaseSummarizeResult,
44
- IBroadcastSummaryResult,
45
- ICancellationToken,
46
- IConnectableRuntime,
47
- IEnqueueSummarizeOptions,
48
- IGenerateSummaryTreeResult,
49
- IGeneratedSummaryStats,
50
- INackSummaryResult,
51
- IOnDemandSummarizeOptions,
52
- IProvideSummarizer,
53
- IRefreshSummaryAckOptions,
54
- ISubmitSummaryOpResult,
55
- ISubmitSummaryOptions,
56
- ISummarizeOptions,
57
- ISummarizeResults,
58
- ISummarizer,
59
- ISummarizerEvents,
60
- ISummarizerInternalsProvider,
61
- ISummarizerRuntime,
62
- ISummarizingWarning,
63
- ISummaryCancellationToken,
64
- IUploadSummaryResult,
65
- SubmitSummaryResult,
66
- SummarizeResultPart,
67
- SummarizerStopReason,
39
+ EnqueueSummarizeResult,
40
+ IAckSummaryResult,
41
+ IBaseSummarizeResult,
42
+ IBroadcastSummaryResult,
43
+ ICancellationToken,
44
+ IConnectableRuntime,
45
+ IEnqueueSummarizeOptions,
46
+ IGenerateSummaryTreeResult,
47
+ IGeneratedSummaryStats,
48
+ INackSummaryResult,
49
+ IOnDemandSummarizeOptions,
50
+ IProvideSummarizer,
51
+ IRefreshSummaryAckOptions,
52
+ ISubmitSummaryOpResult,
53
+ ISubmitSummaryOptions,
54
+ ISummarizeOptions,
55
+ ISummarizeResults,
56
+ ISummarizer,
57
+ ISummarizerEvents,
58
+ ISummarizerInternalsProvider,
59
+ ISummarizerRuntime,
60
+ ISummarizingWarning,
61
+ ISummaryCancellationToken,
62
+ IUploadSummaryResult,
63
+ SubmitSummaryResult,
64
+ SummarizeResultPart,
65
+ SummarizerStopReason,
68
66
  } from "./summarizerTypes";
69
67
  export {
70
- IAckedSummary,
71
- IClientSummaryWatcher,
72
- ISummary,
73
- ISummaryCollectionOpEvents,
74
- ISummaryAckMessage,
75
- ISummaryNackMessage,
76
- ISummaryOpMessage,
77
- OpActionEventListener,
78
- OpActionEventName,
79
- SummaryCollection,
68
+ IAckedSummary,
69
+ IClientSummaryWatcher,
70
+ ISummary,
71
+ ISummaryCollectionOpEvents,
72
+ ISummaryAckMessage,
73
+ ISummaryNackMessage,
74
+ ISummaryOpMessage,
75
+ OpActionEventListener,
76
+ OpActionEventName,
77
+ SummaryCollection,
80
78
  } from "./summaryCollection";
81
- export { ICancellableSummarizerController, neverCancelledSummaryToken } from "./runWhileConnectedCoordinator";
79
+ export {
80
+ ICancellableSummarizerController,
81
+ neverCancelledSummaryToken,
82
+ } from "./runWhileConnectedCoordinator";
82
83
  export { IChunkedOp, unpackRuntimeMessage } from "./opLifecycle";
@@ -0,0 +1,152 @@
1
+ # Configs and feature gates for solving the 1MB limit.
2
+
3
+ ## Introduction
4
+
5
+ There is a current limitation regarding the size of the payload a Fluid client can send and receive. [The limit is 1MB per payload](https://github.com/microsoft/FluidFramework/issues/9023) and it is currently enforced explicitly with the `BatchTooLarge` error which closes the container.
6
+
7
+ There are two features which can be used to work around this size limit, batch compression and compressed batch chunking. This document describes how to enable/disable them, along with a brief description of how they work.
8
+
9
+ **The features are still considered experimental and for safety and back-compat reasons they are disabled by default.**
10
+
11
+ ## Table of contents
12
+
13
+ - [Introduction](#introduction)
14
+ - [Enabling compression](#enabling-compression)
15
+ - [Enabling chunking for compression](#enabling-chunking-for-compression)
16
+ - [Disabling in case of emergency](#disabling-in-case-of-emergency)
17
+ - [Example configs](#example-configs)
18
+ - [How it works](#how-it-works)
19
+
20
+ ## Enabling compression
21
+
22
+ **Compression targets payloads which exceed the max batch size (1MB) and it is disabled by default.** To enable compression, use the `IContainerRuntimeOptions.compressionOptions` property, of type `ICompressionRuntimeOptions`.
23
+
24
+ `ICompressionRuntimeOptions` has two properties:
25
+
26
+ - `minimumBatchSizeInBytes` – the minimum size of the batch for which compression should kick in. If the payload is too small, compression may not yield too many benefits. To target the original 1MB issue, a good value here would be to match the default maxBatchSizeInBytes (972800), however, experimentally, a good lower value could be at around 614400 bytes.
27
+ - `compressionAlgorithm` – currently, only `lz4` is supported.
28
+
29
+ ## Enabling chunking for compression
30
+
31
+ **Op chunking for compression targets payloads which exceed the max batch size (1MB) after compression.** So, only payloads which are already compressed. By default, the feature is disabled.
32
+
33
+ To enable, use the `IContainerRuntimeOptions.chunkSizeInBytes` property, which represents the size of the chunked ops, when chunking is necessary. When chunking is performed, the large op is split into smaller ops (chunks). This config represents the size of the chunks. The value enables a trade-off between large chunks / few ops and small chunks / many ops. A good value for this would be at around 614400.
34
+
35
+ This config would govern chunking compressed batches only. We will not be enabling chunking across all types of ops/batches but **only when compression is enabled and when the batch is compressed**, and its payload size is more than `maxBatchSizeInBytes`. Therefore, for this feature to be working, it is required that compression is enabled using `IContainerRuntimeOptions.compressionOptions`.
36
+
37
+ It is recommended to also change the `maxBatchSizeInBytes` property in `IContainerRuntimeOptions`. By default, if unspecified it is 972800. We recommend a lower value such as `716800`, to account for any overhead on the server side. The reason for this is that chunking will only kick in after this configuration limit is exceeded. If the limit is too high, we might allow batches which are under 1MB but can increase in size due to overhead after they reach the server.
38
+
39
+ ## Disabling in case of emergency
40
+
41
+ If the features are enabled using the configs, they can be disabled at runtime via feature gates as following:
42
+
43
+ - `Fluid.ContainerRuntime.DisableCompression` - if set to true, will disable compression (this has a side effect of also disabling chunking, as chunking is invoked only for compressed payloads).
44
+ - `Fluid.ContainerRuntime.DisableCompressionChunking` - if set to true, will disable chunking for compression.
45
+
46
+ ## Example configs
47
+
48
+ Enable only compression:
49
+
50
+ ```
51
+ const runtimeOptions: IContainerRuntimeOptions = {
52
+ compressionOptions: {
53
+ minimumBatchSizeInBytes: 614400,
54
+ compressionAlgorithm: CompressionAlgorithms.lz4,
55
+ },
56
+ maxBatchSizeInBytes: 716800,
57
+     }
58
+ ```
59
+
60
+ Enable compression and chunking:
61
+
62
+ ```
63
+ const runtimeOptions: IContainerRuntimeOptions = {
64
+ compressionOptions: {
65
+ minimumBatchSizeInBytes: 614400,
66
+ compressionAlgorithm: CompressionAlgorithms.lz4,
67
+ },
68
+ chunkSizeInBytes: 614400,
69
+ maxBatchSizeInBytes: 716800,
70
+     }
71
+ ```
72
+
73
+ ## How it works
74
+
75
+ Compression currently works as a runtime layer over the regular op sending/receiving pipeline.
76
+
77
+ If we have a batch with a size larger than the configured minimum required for compression (in the example let’s say it’s 850 bytes), as following:
78
+
79
+ ```
80
+ +-----------+-----------+-----------+-----------+
81
+ | Op 1 | Op 2 | Op 3 | Op 4 |
82
+ | SeqNum: 1 | SeqNum: 2 | SeqNum: 3 | SeqNum: 4 |
83
+ | Size: 100 | Size: 150 | Size: 200 | Size: 400 |
84
+ +-----------+-----------+-----------+-----------+
85
+ ```
86
+
87
+ The total size of the batch is 850 bytes. The client which needs to send the batch would compress the batch to a smaller size (200 bytes) and will send a new batch like the following:
88
+
89
+ ```
90
+ +--------------------+-----------+-----------+-----------+
91
+ | Op 1 | Op 2 | Op 3 | Op 4 |
92
+ | SeqNum: 1 | SeqNum: 2 | SeqNum: 3 | SeqNum: 4 |
93
+ | Size: 200 | Size: 0 | Size: 0 | Size: 0 |
94
+ | Compression: 'lz4' | | | |
95
+ +--------------------+-----------+-----------+-----------+
96
+ ```
97
+
98
+ The first op in the batch is the only one with content (which is opaque due to it being compressed), the rest of the ops serve only to reserve the sequence numbers so that the state machine which rebuilds the original batch on the receiving client can reconstruct the original batch.
99
+
100
+ When the batch is received by a client, it will detect the first op as being compressed, it will decompress it and store it in memory. For each empty op subsequently received, it will fetch the uncompressed content from memory and rebuild the original ops. The original ops are then processed by the runtime and applied accordingly.
101
+ So, compression virtualizes the batch.
102
+
103
+ After compression, the first op in the batch can exceed 1MB, therefore it would still be rejected. In this case, another layer of virtualization is added after compression (and before decompression, symmetrically on the receiving end).
104
+
105
+ The first op in the compressed batch can be chunked into smaller ops which can be sent outside the original batch. However, to conveniently maintain the batch semantics, the last chunk (the chunk which triggers rebuilding the original op) is the first op in the new batch.
106
+
107
+ To illustrate, let’s take the large batch below:
108
+
109
+ ```
110
+ +--------------------+-----------+-----------+-----------+
111
+ | Op 1 | Op 2 | Op 3 | Op 4 |
112
+ | SeqNum: 1 | SeqNum: 2 | SeqNum: 3 | SeqNum: 4 |
113
+ | Size: 900 | Size: 0 | Size: 0 | Size: 0 |
114
+ | Compression: 'lz4' | | | |
115
+ +--------------------+-----------+-----------+-----------+
116
+ ```
117
+
118
+ This will produce the following batches:
119
+
120
+ ```
121
+ +-----------+
122
+ | Chunk 1/3 |
123
+ | SeqNum: 1 |
124
+ | Size: 300 |
125
+ +-----------+
126
+
127
+ ```
128
+
129
+ ```
130
+ +-----------+
131
+ | Chunk 2/3 |
132
+ | SeqNum: 2 |
133
+ | Size: 300 |
134
+ +-----------+
135
+
136
+ ```
137
+
138
+ ```
139
+ +-----------+-----------+-----------+-----------+
140
+ | Chunk 3/3 | Op 2 | Op 3 | Op 4 |
141
+ | SeqNum: 3 | SeqNum: 4 | SeqNum: 5 | SeqNum: 6 |
142
+ | Size: 300 | Size: 0 | Size: 0 | Size: 0 |
143
+ +-----------+-----------+-----------+-----------+
144
+ ```
145
+
146
+ The first 2 chunks are sent in their own batches, while the last chunk is the first op in the last batch which contains the ops reserving the required sequence numbers.
147
+
148
+ Notice that the sequence numbers don’t matter here, as all ops will be based off the same reference sequence number, so the sequence number will be recalculated for all, without additional work.
149
+
150
+ Additionally, as compression preserves the original uncompressed batch layout in terms of the number of ops by using empty ops to reserve the sequence numbers, this ensures that the clients will always receive the exact count of ops to rebuild the uncompressed batch sequentially.
151
+
152
+ On the receiving end, the client will accumulate chunks 1 and 2 and keep them in memory. When chunk 3 is received, the original large, decompressed op will be rebuilt, and the runtime will then process the batch as if it is a compressed batch.