@lix-js/sdk 0.6.0-preview.4 → 0.6.0-preview.5

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 (234) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +65 -64
  3. package/dist/engine-wasm/index.js +4 -4
  4. package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -5
  5. package/dist/engine-wasm/wasm/lix_engine.js +130 -118
  6. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  7. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +9 -8
  8. package/dist/generated/builtin-schemas.d.ts +69 -69
  9. package/dist/generated/builtin-schemas.js +94 -94
  10. package/dist/open-lix.d.ts +33 -26
  11. package/dist/open-lix.js +10 -10
  12. package/dist/sqlite/index.js +86 -30
  13. package/dist-engine-src/README.md +3 -3
  14. package/dist-engine-src/src/backend/capabilities.rs +67 -0
  15. package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
  16. package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
  17. package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
  18. package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
  19. package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
  20. package/dist-engine-src/src/backend/conformance/model.rs +28 -0
  21. package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
  22. package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
  23. package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
  24. package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
  25. package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
  26. package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
  27. package/dist-engine-src/src/backend/conformance/write.rs +16 -0
  28. package/dist-engine-src/src/backend/error.rs +94 -0
  29. package/dist-engine-src/src/backend/in_memory.rs +670 -0
  30. package/dist-engine-src/src/backend/mod.rs +36 -9
  31. package/dist-engine-src/src/backend/predicate.rs +80 -0
  32. package/dist-engine-src/src/backend/traits.rs +260 -0
  33. package/dist-engine-src/src/backend/types.rs +224 -81
  34. package/dist-engine-src/src/binary_cas/context.rs +8 -8
  35. package/dist-engine-src/src/binary_cas/kv.rs +234 -259
  36. package/dist-engine-src/src/{version → branch}/context.rs +12 -12
  37. package/dist-engine-src/src/branch/lifecycle.rs +221 -0
  38. package/dist-engine-src/src/branch/mod.rs +13 -0
  39. package/dist-engine-src/src/branch/refs.rs +321 -0
  40. package/dist-engine-src/src/branch/stage_rows.rs +67 -0
  41. package/dist-engine-src/src/branch/types.rs +21 -0
  42. package/dist-engine-src/src/catalog/context.rs +18 -18
  43. package/dist-engine-src/src/catalog/snapshot.rs +8 -8
  44. package/dist-engine-src/src/changelog/bench_support.rs +785 -0
  45. package/dist-engine-src/src/changelog/change.rs +1 -0
  46. package/dist-engine-src/src/changelog/codec.rs +497 -0
  47. package/dist-engine-src/src/changelog/commit.rs +1 -0
  48. package/dist-engine-src/src/changelog/context.rs +1614 -0
  49. package/dist-engine-src/src/changelog/mod.rs +29 -0
  50. package/dist-engine-src/src/changelog/store.rs +163 -0
  51. package/dist-engine-src/src/changelog/test_support.rs +54 -0
  52. package/dist-engine-src/src/changelog/types.rs +213 -0
  53. package/dist-engine-src/src/commit_graph/context.rs +317 -274
  54. package/dist-engine-src/src/commit_graph/mod.rs +2 -4
  55. package/dist-engine-src/src/commit_graph/types.rs +22 -42
  56. package/dist-engine-src/src/commit_graph/walker.rs +133 -103
  57. package/dist-engine-src/src/common/error.rs +52 -18
  58. package/dist-engine-src/src/common/identity.rs +2 -2
  59. package/dist-engine-src/src/common/mod.rs +1 -1
  60. package/dist-engine-src/src/domain.rs +42 -46
  61. package/dist-engine-src/src/engine.rs +74 -96
  62. package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
  63. package/dist-engine-src/src/functions/context.rs +56 -52
  64. package/dist-engine-src/src/functions/state.rs +51 -52
  65. package/dist-engine-src/src/init.rs +288 -154
  66. package/dist-engine-src/src/json_store/context.rs +15 -266
  67. package/dist-engine-src/src/json_store/mod.rs +26 -0
  68. package/dist-engine-src/src/json_store/store.rs +103 -718
  69. package/dist-engine-src/src/json_store/types.rs +4 -9
  70. package/dist-engine-src/src/lib.rs +49 -19
  71. package/dist-engine-src/src/live_state/context.rs +654 -790
  72. package/dist-engine-src/src/live_state/mod.rs +9 -3
  73. package/dist-engine-src/src/live_state/overlay.rs +4 -4
  74. package/dist-engine-src/src/live_state/types.rs +30 -21
  75. package/dist-engine-src/src/live_state/visibility.rs +514 -71
  76. package/dist-engine-src/src/plugin/install.rs +48 -48
  77. package/dist-engine-src/src/plugin/manifest.rs +7 -7
  78. package/dist-engine-src/src/plugin/materializer.rs +0 -275
  79. package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
  80. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
  81. package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
  82. package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
  83. package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
  84. package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
  85. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
  86. package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
  87. package/dist-engine-src/src/schema/compatibility.rs +11 -11
  88. package/dist-engine-src/src/schema/definition.json +2 -2
  89. package/dist-engine-src/src/schema/definition.rs +5 -5
  90. package/dist-engine-src/src/schema/key.rs +3 -3
  91. package/dist-engine-src/src/schema/mod.rs +1 -1
  92. package/dist-engine-src/src/schema/tests.rs +18 -18
  93. package/dist-engine-src/src/session/context.rs +803 -148
  94. package/dist-engine-src/src/session/create_branch.rs +94 -0
  95. package/dist-engine-src/src/session/execute.rs +223 -83
  96. package/dist-engine-src/src/session/merge/analysis.rs +9 -3
  97. package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
  98. package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
  99. package/dist-engine-src/src/session/merge/mod.rs +5 -6
  100. package/dist-engine-src/src/session/merge/stats.rs +7 -11
  101. package/dist-engine-src/src/session/mod.rs +15 -12
  102. package/dist-engine-src/src/session/switch_branch.rs +113 -0
  103. package/dist-engine-src/src/session/transaction.rs +495 -14
  104. package/dist-engine-src/src/sql2/{classify.rs → bind/classify.rs} +3 -75
  105. package/dist-engine-src/src/sql2/bind/error.rs +5 -0
  106. package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
  107. package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
  108. package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +71 -3
  109. package/dist-engine-src/src/sql2/bind/read.rs +65 -0
  110. package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
  111. package/dist-engine-src/src/sql2/bind/table.rs +273 -0
  112. package/dist-engine-src/src/sql2/bind/write.rs +86 -0
  113. package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
  114. package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
  115. package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
  116. package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
  117. package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
  118. package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
  119. package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
  120. package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
  121. package/dist-engine-src/src/sql2/context.rs +36 -30
  122. package/dist-engine-src/src/sql2/error.rs +1 -1
  123. package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
  124. package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
  125. package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
  126. package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
  127. package/dist-engine-src/src/sql2/exec/write.rs +661 -0
  128. package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
  129. package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
  130. package/dist-engine-src/src/sql2/history_projection.rs +8 -8
  131. package/dist-engine-src/src/sql2/history_route.rs +35 -31
  132. package/dist-engine-src/src/sql2/mod.rs +28 -23
  133. package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
  134. package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
  135. package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
  136. package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
  137. package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
  138. package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
  139. package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
  140. package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
  141. package/dist-engine-src/src/sql2/plan/write.rs +147 -0
  142. package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
  143. package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
  144. package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
  145. package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
  146. package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
  147. package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
  148. package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
  149. package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
  150. package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
  151. package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
  152. package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
  153. package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
  154. package/dist-engine-src/src/sql2/read_only.rs +2 -2
  155. package/dist-engine-src/src/sql2/session.rs +47 -96
  156. package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
  157. package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
  158. package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
  159. package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
  160. package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
  161. package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
  162. package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
  163. package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
  164. package/dist-engine-src/src/storage/conformance.rs +399 -0
  165. package/dist-engine-src/src/storage/context.rs +552 -288
  166. package/dist-engine-src/src/storage/mod.rs +48 -10
  167. package/dist-engine-src/src/storage/point.rs +440 -0
  168. package/dist-engine-src/src/storage/read_scope.rs +43 -64
  169. package/dist-engine-src/src/storage/reader.rs +867 -0
  170. package/dist-engine-src/src/storage/scan.rs +784 -0
  171. package/dist-engine-src/src/storage/spaces.rs +236 -0
  172. package/dist-engine-src/src/storage/stats.rs +80 -0
  173. package/dist-engine-src/src/storage/write_set.rs +962 -0
  174. package/dist-engine-src/src/storage_bench.rs +136 -4828
  175. package/dist-engine-src/src/test_support.rs +360 -138
  176. package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
  177. package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
  178. package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
  179. package/dist-engine-src/src/tracked_state/context.rs +1927 -993
  180. package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
  181. package/dist-engine-src/src/tracked_state/merge.rs +74 -88
  182. package/dist-engine-src/src/tracked_state/mod.rs +19 -16
  183. package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
  184. package/dist-engine-src/src/tracked_state/storage.rs +243 -191
  185. package/dist-engine-src/src/tracked_state/tree.rs +247 -371
  186. package/dist-engine-src/src/tracked_state/types.rs +49 -42
  187. package/dist-engine-src/src/transaction/bench_support.rs +407 -0
  188. package/dist-engine-src/src/transaction/commit.rs +821 -713
  189. package/dist-engine-src/src/transaction/context.rs +705 -600
  190. package/dist-engine-src/src/transaction/mod.rs +13 -2
  191. package/dist-engine-src/src/transaction/normalization.rs +63 -76
  192. package/dist-engine-src/src/transaction/prep.rs +13 -13
  193. package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
  194. package/dist-engine-src/src/transaction/staging.rs +228 -434
  195. package/dist-engine-src/src/transaction/types.rs +41 -98
  196. package/dist-engine-src/src/transaction/validation.rs +382 -446
  197. package/dist-engine-src/src/untracked_state/codec.rs +337 -29
  198. package/dist-engine-src/src/untracked_state/context.rs +7 -7
  199. package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
  200. package/dist-engine-src/src/untracked_state/mod.rs +1 -1
  201. package/dist-engine-src/src/untracked_state/storage.rs +659 -157
  202. package/dist-engine-src/src/untracked_state/types.rs +21 -21
  203. package/package.json +71 -68
  204. package/dist-engine-src/src/backend/kv.rs +0 -358
  205. package/dist-engine-src/src/backend/testing.rs +0 -658
  206. package/dist-engine-src/src/commit_store/codec.rs +0 -887
  207. package/dist-engine-src/src/commit_store/context.rs +0 -944
  208. package/dist-engine-src/src/commit_store/materialization.rs +0 -84
  209. package/dist-engine-src/src/commit_store/mod.rs +0 -16
  210. package/dist-engine-src/src/commit_store/storage.rs +0 -600
  211. package/dist-engine-src/src/commit_store/types.rs +0 -215
  212. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
  213. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
  214. package/dist-engine-src/src/session/create_version.rs +0 -88
  215. package/dist-engine-src/src/session/merge/apply.rs +0 -23
  216. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
  217. package/dist-engine-src/src/session/switch_version.rs +0 -110
  218. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  219. package/dist-engine-src/src/sql2/execute.rs +0 -3533
  220. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  221. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  222. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
  223. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
  224. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  225. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  226. package/dist-engine-src/src/storage/types.rs +0 -501
  227. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  228. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  229. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  230. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  231. package/dist-engine-src/src/version/mod.rs +0 -13
  232. package/dist-engine-src/src/version/refs.rs +0 -330
  233. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  234. package/dist-engine-src/src/version/types.rs +0 -21
@@ -1,231 +1,30 @@
1
- use crate::binary_cas::{BinaryCasContext, BlobHash, BlobWrite};
2
- use crate::catalog::CatalogContext;
3
- use crate::commit_graph::CommitGraphChangeHistoryRequest;
4
- use crate::commit_store::{
5
- Change, ChangeScanRequest, CommitDraftRef, CommitStoreContext, MaterializedChange,
6
- };
7
- use crate::entity_identity::EntityIdentity;
8
- use crate::json_store::context::JsonStoreContext;
9
- use crate::json_store::types::{
10
- JsonLoadRequestRef, JsonProjectionLoadRequestRef, JsonProjectionPath, JsonReadScopeRef,
11
- JsonRef, JsonWritePlacementRef, NormalizedJsonRef,
12
- };
13
- use crate::live_state::LiveStateContext;
14
- use crate::session::SessionMode;
15
- use crate::storage::{
16
- KvGetGroup, KvGetRequest, KvScanRange, KvScanRequest, KvWriteBatch, StorageContext,
17
- StorageWriteSet,
18
- };
19
- use crate::tracked_state::{
20
- MaterializedTrackedStateRow, TrackedStateContext, TrackedStateDeltaRef,
21
- TrackedStateDiffRequest, TrackedStateFilter, TrackedStateProjection, TrackedStateRowRequest,
22
- TrackedStateScanRequest,
23
- };
24
- use crate::transaction::open_transaction;
25
- use crate::transaction::types::{
26
- TransactionJson, TransactionWrite, TransactionWriteMode, TransactionWriteRow,
27
- };
28
- use crate::untracked_state::{
29
- MaterializedUntrackedStateRow, UntrackedStateContext, UntrackedStateFilter,
30
- UntrackedStateProjection, UntrackedStateRowRequest, UntrackedStateScanRequest,
31
- };
32
- use crate::version::VersionContext;
33
- use crate::{Backend, LixError, NullableKeyFilter};
34
- use std::collections::{BTreeMap, HashSet};
35
- use std::sync::atomic::{AtomicUsize, Ordering};
36
- use std::sync::Arc;
37
- use std::sync::Mutex;
38
- use std::sync::OnceLock;
39
- use std::time::{Duration, Instant};
40
-
41
- fn prepare_json_ref(document: &[u8]) -> Result<JsonRef, LixError> {
42
- let text = std::str::from_utf8(document).map_err(|error| {
43
- LixError::new(
44
- LixError::CODE_UNKNOWN,
45
- format!("benchmark JSON document is invalid UTF-8: {error}"),
46
- )
47
- })?;
48
- Ok(JsonRef::for_content(text.as_bytes()))
49
- }
50
-
51
- #[derive(Debug, Clone, Copy)]
52
- pub struct StorageBenchConfig {
53
- pub rows: usize,
54
- pub blob_bytes: usize,
55
- pub state_payload_bytes: usize,
56
- pub key_pattern: StorageBenchKeyPattern,
57
- pub selectivity: StorageBenchSelectivity,
58
- pub update_fraction: StorageBenchUpdateFraction,
59
- }
60
-
61
- impl StorageBenchConfig {
62
- pub fn with_rows(mut self, rows: usize) -> Self {
63
- self.rows = rows;
64
- self
65
- }
66
-
67
- pub fn with_blob_bytes(mut self, blob_bytes: usize) -> Self {
68
- self.blob_bytes = blob_bytes;
69
- self
70
- }
71
-
72
- pub fn with_state_payload_bytes(mut self, state_payload_bytes: usize) -> Self {
73
- self.state_payload_bytes = state_payload_bytes;
74
- self
75
- }
76
-
77
- pub fn with_key_pattern(mut self, key_pattern: StorageBenchKeyPattern) -> Self {
78
- self.key_pattern = key_pattern;
79
- self
80
- }
81
-
82
- pub fn with_selectivity(mut self, selectivity: StorageBenchSelectivity) -> Self {
83
- self.selectivity = selectivity;
84
- self
85
- }
86
-
87
- pub fn with_update_fraction(mut self, update_fraction: StorageBenchUpdateFraction) -> Self {
88
- self.update_fraction = update_fraction;
89
- self
90
- }
91
- }
92
-
93
- #[derive(Debug, Clone, Copy)]
94
- pub enum StorageBenchKeyPattern {
95
- Sequential,
96
- Random,
97
- }
98
-
99
- #[derive(Debug, Clone, Copy)]
100
- pub enum StorageBenchSelectivity {
101
- Percent1,
102
- Percent10,
103
- Percent100,
104
- }
105
-
106
- impl StorageBenchSelectivity {
107
- fn matches(self, index: usize) -> bool {
108
- match self {
109
- Self::Percent1 => index % 100 == 0,
110
- Self::Percent10 => index % 10 == 0,
111
- Self::Percent100 => true,
112
- }
113
- }
114
-
115
- fn expected_rows(self, rows: usize) -> usize {
116
- (0..rows).filter(|index| self.matches(*index)).count()
117
- }
118
- }
119
-
120
- #[derive(Debug, Clone, Copy)]
121
- pub enum StorageBenchUpdateFraction {
122
- Percent10,
123
- Percent100,
124
- }
125
-
126
- impl StorageBenchUpdateFraction {
127
- fn rows(self, total_rows: usize) -> usize {
128
- match self {
129
- Self::Percent10 => total_rows.div_ceil(10),
130
- Self::Percent100 => total_rows,
131
- }
132
- }
133
- }
134
-
135
- #[derive(Debug, Clone, Copy)]
136
- pub struct StorageBenchReport {
137
- pub measured_rows: usize,
138
- pub verified_rows: usize,
139
- pub elapsed: Duration,
140
- }
141
-
142
- #[derive(Debug, Clone, Default, PartialEq, Eq)]
143
- pub struct TransactionBenchCounters {
144
- pub rows_staged: usize,
145
- pub untracked_rows: usize,
146
- pub validation_version_count: usize,
147
- pub schema_catalog_loads: usize,
148
- pub json_store_stage_bytes_calls: usize,
149
- pub unique_json_refs: usize,
150
- }
151
-
152
- #[derive(Debug, Clone, Default, PartialEq, Eq)]
153
- pub struct TransactionAccountingReport {
154
- pub counters: TransactionBenchCounters,
155
- pub storage_write_batches: usize,
156
- pub kv_puts_by_namespace: BTreeMap<String, usize>,
157
- pub bytes_by_namespace: BTreeMap<String, usize>,
158
- }
159
-
160
- pub struct StorageApiFixture {
161
- storage: StorageContext,
162
- rows: usize,
163
- }
164
-
165
- pub struct TransactionBenchFixture {
166
- storage: StorageContext,
167
- live_state: Arc<LiveStateContext>,
168
- tracked_state: Arc<TrackedStateContext>,
169
- binary_cas: Arc<BinaryCasContext>,
170
- commit_store: Arc<CommitStoreContext>,
171
- version_ctx: Arc<VersionContext>,
172
- catalog_context: Arc<CatalogContext>,
173
- rows: Vec<TransactionWriteRow>,
174
- }
1
+ use std::sync::atomic::{AtomicU64, Ordering};
175
2
 
176
- pub struct TransactionCommitOnlyFixture {
177
- runtime_functions: crate::functions::FunctionContext,
178
- transaction: crate::transaction::Transaction,
179
- rows: usize,
180
- }
181
-
182
- static TRANSACTION_ROWS_STAGED: AtomicUsize = AtomicUsize::new(0);
183
- static TRANSACTION_UNTRACKED_ROWS: AtomicUsize = AtomicUsize::new(0);
184
- static TRANSACTION_VALIDATION_VERSION_COUNT: AtomicUsize = AtomicUsize::new(0);
185
- static TRANSACTION_SCHEMA_CATALOG_LOADS: AtomicUsize = AtomicUsize::new(0);
186
- static JSON_STORE_STAGE_BYTES_CALLS: AtomicUsize = AtomicUsize::new(0);
187
- static JSON_STORE_UNIQUE_REFS: OnceLock<Mutex<HashSet<[u8; 32]>>> = OnceLock::new();
3
+ use bytes::Bytes;
188
4
 
189
- const STORAGE_API_NAMESPACE: &str = "bench.storage_api";
190
- const STORAGE_API_ALT_NAMESPACE: &str = "bench.storage_api.alt";
191
- const TRANSACTION_BENCH_SCHEMA_KEY: &str = "bench_transaction_entity";
192
-
193
- pub fn reset_transaction_bench_counters() {
194
- TRANSACTION_ROWS_STAGED.store(0, Ordering::Relaxed);
195
- TRANSACTION_UNTRACKED_ROWS.store(0, Ordering::Relaxed);
196
- TRANSACTION_VALIDATION_VERSION_COUNT.store(0, Ordering::Relaxed);
197
- TRANSACTION_SCHEMA_CATALOG_LOADS.store(0, Ordering::Relaxed);
198
- JSON_STORE_STAGE_BYTES_CALLS.store(0, Ordering::Relaxed);
199
- json_store_unique_refs()
200
- .lock()
201
- .expect("json store unique ref counter mutex should lock")
202
- .clear();
203
- }
5
+ use crate::entity_pk::EntityPk;
6
+ use crate::storage::{
7
+ ScanPlan, StorageCoreProjection, StorageKey, StoragePrefix, StorageProjectedValue, StorageRead,
8
+ StorageScanOptions, StorageValue, StorageWriteOptions, StorageWriteSet, StorageWriteSetError,
9
+ };
10
+ use crate::untracked_state::UntrackedStateRowRef;
204
11
 
205
- pub fn transaction_bench_counters() -> TransactionBenchCounters {
206
- TransactionBenchCounters {
207
- rows_staged: TRANSACTION_ROWS_STAGED.load(Ordering::Relaxed),
208
- untracked_rows: TRANSACTION_UNTRACKED_ROWS.load(Ordering::Relaxed),
209
- validation_version_count: TRANSACTION_VALIDATION_VERSION_COUNT.load(Ordering::Relaxed),
210
- schema_catalog_loads: TRANSACTION_SCHEMA_CATALOG_LOADS.load(Ordering::Relaxed),
211
- json_store_stage_bytes_calls: JSON_STORE_STAGE_BYTES_CALLS.load(Ordering::Relaxed),
212
- unique_json_refs: json_store_unique_refs()
213
- .lock()
214
- .expect("json store unique ref counter mutex should lock")
215
- .len(),
216
- }
217
- }
12
+ static TRANSACTION_ROWS_STAGED: AtomicU64 = AtomicU64::new(0);
13
+ static TRANSACTION_UNTRACKED_ROWS: AtomicU64 = AtomicU64::new(0);
14
+ static TRANSACTION_VALIDATION_BRANCHS: AtomicU64 = AtomicU64::new(0);
15
+ static TRANSACTION_SCHEMA_CATALOG_LOADS: AtomicU64 = AtomicU64::new(0);
16
+ static JSON_STORE_STAGE_BYTES: AtomicU64 = AtomicU64::new(0);
218
17
 
219
- pub(crate) fn record_transaction_rows_staged(rows: usize) {
220
- TRANSACTION_ROWS_STAGED.fetch_add(rows, Ordering::Relaxed);
18
+ pub(crate) fn record_transaction_rows_staged(count: usize) {
19
+ TRANSACTION_ROWS_STAGED.fetch_add(count as u64, Ordering::Relaxed);
221
20
  }
222
21
 
223
- pub(crate) fn record_transaction_untracked_rows(rows: usize) {
224
- TRANSACTION_UNTRACKED_ROWS.fetch_add(rows, Ordering::Relaxed);
22
+ pub(crate) fn record_transaction_untracked_rows(count: usize) {
23
+ TRANSACTION_UNTRACKED_ROWS.fetch_add(count as u64, Ordering::Relaxed);
225
24
  }
226
25
 
227
- pub(crate) fn record_transaction_validation_version() {
228
- TRANSACTION_VALIDATION_VERSION_COUNT.fetch_add(1, Ordering::Relaxed);
26
+ pub(crate) fn record_transaction_validation_branch() {
27
+ TRANSACTION_VALIDATION_BRANCHS.fetch_add(1, Ordering::Relaxed);
229
28
  }
230
29
 
231
30
  pub(crate) fn record_transaction_schema_catalog_load() {
@@ -233,4631 +32,140 @@ pub(crate) fn record_transaction_schema_catalog_load() {
233
32
  }
234
33
 
235
34
  pub(crate) fn record_json_store_stage_bytes(hash: [u8; 32]) {
236
- JSON_STORE_STAGE_BYTES_CALLS.fetch_add(1, Ordering::Relaxed);
237
- json_store_unique_refs()
238
- .lock()
239
- .expect("json store unique ref counter mutex should lock")
240
- .insert(hash);
241
- }
242
-
243
- fn json_store_unique_refs() -> &'static Mutex<HashSet<[u8; 32]>> {
244
- JSON_STORE_UNIQUE_REFS.get_or_init(|| Mutex::new(HashSet::new()))
245
- }
246
-
247
- pub async fn prepare_transaction_commit_empty(
248
- backend: Arc<dyn Backend + Send + Sync>,
249
- ) -> Result<TransactionBenchFixture, LixError> {
250
- prepare_transaction_fixture(backend, Vec::new()).await
251
- }
252
-
253
- pub async fn prepare_transaction_commit_schema_only(
254
- backend: Arc<dyn Backend + Send + Sync>,
255
- ) -> Result<TransactionBenchFixture, LixError> {
256
- prepare_transaction_fixture(backend, vec![transaction_registered_schema_row()]).await
257
- }
258
-
259
- pub async fn prepare_transaction_commit_entities_no_payload(
260
- backend: Arc<dyn Backend + Send + Sync>,
261
- rows: usize,
262
- ) -> Result<TransactionBenchFixture, LixError> {
263
- prepare_transaction_fixture(
264
- backend,
265
- transaction_entity_rows(TransactionEntityRows {
266
- rows,
267
- payload_bytes: 0,
268
- payload_pattern: TransactionPayloadPattern::Unique,
269
- metadata_pattern: TransactionPayloadPattern::None,
270
- untracked: false,
271
- key_prefix: "entity-no-payload",
272
- }),
273
- )
274
- .await
275
- }
276
-
277
- pub async fn prepare_transaction_commit_entities_payload_1k_unique(
278
- backend: Arc<dyn Backend + Send + Sync>,
279
- rows: usize,
280
- ) -> Result<TransactionBenchFixture, LixError> {
281
- prepare_transaction_payload_fixture(
282
- backend,
283
- rows,
284
- 1024,
285
- TransactionPayloadPattern::Unique,
286
- false,
287
- "entity-payload-1k-unique",
288
- )
289
- .await
290
- }
291
-
292
- pub async fn prepare_transaction_commit_entities_payload_1k_same(
293
- backend: Arc<dyn Backend + Send + Sync>,
294
- rows: usize,
295
- ) -> Result<TransactionBenchFixture, LixError> {
296
- prepare_transaction_payload_fixture(
297
- backend,
298
- rows,
299
- 1024,
300
- TransactionPayloadPattern::Same,
301
- false,
302
- "entity-payload-1k-same",
303
- )
304
- .await
305
- }
306
-
307
- pub async fn prepare_transaction_commit_entities_payload_1k_half_duplicate(
308
- backend: Arc<dyn Backend + Send + Sync>,
309
- rows: usize,
310
- ) -> Result<TransactionBenchFixture, LixError> {
311
- prepare_transaction_payload_fixture(
312
- backend,
313
- rows,
314
- 1024,
315
- TransactionPayloadPattern::HalfDuplicate,
316
- false,
317
- "entity-payload-1k-half-duplicate",
318
- )
319
- .await
320
- }
321
-
322
- pub async fn prepare_transaction_commit_entities_metadata_1k_same(
323
- backend: Arc<dyn Backend + Send + Sync>,
324
- rows: usize,
325
- ) -> Result<TransactionBenchFixture, LixError> {
326
- prepare_transaction_fixture(
327
- backend,
328
- transaction_entity_rows(TransactionEntityRows {
329
- rows,
330
- payload_bytes: 0,
331
- payload_pattern: TransactionPayloadPattern::Unique,
332
- metadata_pattern: TransactionPayloadPattern::Same,
333
- untracked: false,
334
- key_prefix: "entity-metadata-1k-same",
335
- }),
336
- )
337
- .await
338
- }
339
-
340
- pub async fn prepare_transaction_commit_entities_payload_16k_unique(
341
- backend: Arc<dyn Backend + Send + Sync>,
342
- rows: usize,
343
- ) -> Result<TransactionBenchFixture, LixError> {
344
- prepare_transaction_payload_fixture(
345
- backend,
346
- rows,
347
- 16 * 1024,
348
- TransactionPayloadPattern::Unique,
349
- false,
350
- "entity-payload-16k-unique",
351
- )
352
- .await
353
- }
354
-
355
- pub async fn prepare_transaction_commit_untracked_payload_1k_same(
356
- backend: Arc<dyn Backend + Send + Sync>,
357
- rows: usize,
358
- ) -> Result<TransactionBenchFixture, LixError> {
359
- prepare_transaction_payload_fixture(
360
- backend,
361
- rows,
362
- 1024,
363
- TransactionPayloadPattern::Same,
364
- true,
365
- "untracked-payload-1k-same",
366
- )
367
- .await
368
- }
369
-
370
- pub async fn prepare_transaction_update_existing_payload_1k(
371
- backend: Arc<dyn Backend + Send + Sync>,
372
- root_rows: usize,
373
- update_rows: usize,
374
- ) -> Result<TransactionBenchFixture, LixError> {
375
- let fixture = prepare_transaction_payload_fixture(
376
- backend,
377
- root_rows,
378
- 1024,
379
- TransactionPayloadPattern::Unique,
380
- false,
381
- "update-existing-root",
382
- )
383
- .await?;
384
- transaction_commit_prepared(&fixture).await?;
385
- let rows = transaction_entity_rows(TransactionEntityRows {
386
- rows: update_rows,
387
- payload_bytes: 1024,
388
- payload_pattern: TransactionPayloadPattern::Unique,
389
- metadata_pattern: TransactionPayloadPattern::None,
390
- untracked: false,
391
- key_prefix: "update-existing-root",
392
- });
393
- Ok(TransactionBenchFixture { rows, ..fixture })
394
- }
395
-
396
- pub async fn transaction_commit_prepared(
397
- fixture: &TransactionBenchFixture,
398
- ) -> Result<StorageBenchReport, LixError> {
399
- let opened = open_transaction(
400
- &SessionMode::Pinned {
401
- version_id: crate::GLOBAL_VERSION_ID.to_string(),
402
- },
403
- fixture.storage.clone(),
404
- Arc::clone(&fixture.live_state),
405
- Arc::clone(&fixture.tracked_state),
406
- Arc::clone(&fixture.binary_cas),
407
- Arc::clone(&fixture.commit_store),
408
- Arc::clone(&fixture.version_ctx),
409
- Arc::clone(&fixture.catalog_context),
410
- )
411
- .await?;
412
- let mut transaction = opened.transaction;
413
- let runtime_functions = opened.runtime_functions;
414
- let started_at = Instant::now();
415
- if !fixture.rows.is_empty() {
416
- transaction
417
- .stage_write(TransactionWrite::Rows {
418
- mode: TransactionWriteMode::Replace,
419
- rows: fixture.rows.clone(),
420
- })
421
- .await?;
422
- }
423
- transaction.commit(&runtime_functions).await?;
424
- Ok(StorageBenchReport {
425
- measured_rows: fixture.rows.len(),
426
- verified_rows: fixture.rows.len(),
427
- elapsed: started_at.elapsed(),
428
- })
35
+ JSON_STORE_STAGE_BYTES.fetch_add(hash.len() as u64, Ordering::Relaxed);
36
+ }
37
+
38
+ #[derive(Clone, Debug)]
39
+ pub struct StorageLayoutAccounting {
40
+ pub space_id: u32,
41
+ pub space: &'static str,
42
+ pub rows: u64,
43
+ pub key_bytes: u64,
44
+ pub value_bytes: u64,
45
+ }
46
+
47
+ pub(crate) fn commit_write_set_for_bench<B>(
48
+ storage: &crate::storage::StorageContext<B>,
49
+ writes: StorageWriteSet,
50
+ ) -> Result<crate::storage::StorageWriteSetStats, StorageWriteSetError>
51
+ where
52
+ B: crate::storage::StorageBackend,
53
+ {
54
+ let (_commit, stats) = storage.commit_write_set(writes, StorageWriteOptions::default())?;
55
+ Ok(stats)
56
+ }
57
+
58
+ pub fn layout_accounting<R>(read: &R) -> Vec<StorageLayoutAccounting>
59
+ where
60
+ R: StorageRead,
61
+ {
62
+ native_storage_spaces()
63
+ .iter()
64
+ .map(|space| scan_layout_space(read, *space))
65
+ .collect()
66
+ }
67
+
68
+ fn native_storage_spaces() -> &'static [crate::storage::StorageSpace] {
69
+ &[
70
+ crate::untracked_state::storage::UNTRACKED_STATE_ROW_SPACE,
71
+ crate::json_store::store::JSON_SPACE,
72
+ crate::tracked_state::TRACKED_STATE_TREE_CHUNK_SPACE,
73
+ crate::tracked_state::TRACKED_STATE_COMMIT_ROOT_SPACE,
74
+ crate::binary_cas::kv::BINARY_CAS_MANIFEST_SPACE,
75
+ crate::binary_cas::kv::BINARY_CAS_MANIFEST_CHUNK_SPACE,
76
+ crate::binary_cas::kv::BINARY_CAS_CHUNK_SPACE,
77
+ crate::changelog::COMMIT_SPACE,
78
+ crate::changelog::CHANGE_SPACE,
79
+ crate::changelog::COMMIT_CHANGE_REF_CHUNK_SPACE,
80
+ ]
429
81
  }
430
82
 
431
- pub async fn transaction_open_empty_prepared(
432
- fixture: &TransactionBenchFixture,
433
- ) -> Result<StorageBenchReport, LixError> {
434
- let started_at = Instant::now();
435
- let opened = open_transaction(
436
- &SessionMode::Pinned {
437
- version_id: crate::GLOBAL_VERSION_ID.to_string(),
83
+ fn scan_layout_space<R>(read: &R, space: crate::storage::StorageSpace) -> StorageLayoutAccounting
84
+ where
85
+ R: StorageRead,
86
+ {
87
+ let result = ScanPlan::prefix(
88
+ space,
89
+ StoragePrefix {
90
+ bytes: Bytes::new(),
438
91
  },
439
- fixture.storage.clone(),
440
- Arc::clone(&fixture.live_state),
441
- Arc::clone(&fixture.tracked_state),
442
- Arc::clone(&fixture.binary_cas),
443
- Arc::clone(&fixture.commit_store),
444
- Arc::clone(&fixture.version_ctx),
445
- Arc::clone(&fixture.catalog_context),
446
92
  )
447
- .await?;
448
- let elapsed = started_at.elapsed();
449
- opened.transaction.rollback().await?;
450
- Ok(StorageBenchReport {
451
- measured_rows: 0,
452
- verified_rows: 0,
453
- elapsed,
454
- })
455
- }
456
-
457
- pub async fn transaction_stage_only_prepared(
458
- fixture: &TransactionBenchFixture,
459
- ) -> Result<StorageBenchReport, LixError> {
460
- let opened = open_transaction(
461
- &SessionMode::Pinned {
462
- version_id: crate::GLOBAL_VERSION_ID.to_string(),
93
+ .collect(
94
+ read,
95
+ StorageScanOptions {
96
+ projection: StorageCoreProjection::FullValue,
97
+ limit_rows: 1_000_000,
98
+ ..StorageScanOptions::default()
463
99
  },
464
- fixture.storage.clone(),
465
- Arc::clone(&fixture.live_state),
466
- Arc::clone(&fixture.tracked_state),
467
- Arc::clone(&fixture.binary_cas),
468
- Arc::clone(&fixture.commit_store),
469
- Arc::clone(&fixture.version_ctx),
470
- Arc::clone(&fixture.catalog_context),
471
100
  )
472
- .await?;
473
- let mut transaction = opened.transaction;
474
- let started_at = Instant::now();
475
- if !fixture.rows.is_empty() {
476
- transaction
477
- .stage_write(TransactionWrite::Rows {
478
- mode: TransactionWriteMode::Replace,
479
- rows: fixture.rows.clone(),
480
- })
481
- .await?;
482
- }
483
- let elapsed = started_at.elapsed();
484
- transaction.rollback().await?;
485
- Ok(StorageBenchReport {
486
- measured_rows: fixture.rows.len(),
487
- verified_rows: fixture.rows.len(),
488
- elapsed,
489
- })
490
- }
101
+ .expect("scan storage bench layout space");
491
102
 
492
- pub async fn prepare_transaction_commit_only(
493
- fixture: TransactionBenchFixture,
494
- ) -> Result<TransactionCommitOnlyFixture, LixError> {
495
- let opened = open_transaction(
496
- &SessionMode::Pinned {
497
- version_id: crate::GLOBAL_VERSION_ID.to_string(),
498
- },
499
- fixture.storage.clone(),
500
- Arc::clone(&fixture.live_state),
501
- Arc::clone(&fixture.tracked_state),
502
- Arc::clone(&fixture.binary_cas),
503
- Arc::clone(&fixture.commit_store),
504
- Arc::clone(&fixture.version_ctx),
505
- Arc::clone(&fixture.catalog_context),
506
- )
507
- .await?;
508
- let mut transaction = opened.transaction;
509
- let runtime_functions = opened.runtime_functions;
510
- let rows = fixture.rows.len();
511
- if !fixture.rows.is_empty() {
512
- transaction
513
- .stage_write(TransactionWrite::Rows {
514
- mode: TransactionWriteMode::Replace,
515
- rows: fixture.rows,
103
+ StorageLayoutAccounting {
104
+ space_id: space.id.0,
105
+ space: space.name,
106
+ rows: result.value.entries.len() as u64,
107
+ key_bytes: result
108
+ .value
109
+ .entries
110
+ .iter()
111
+ .map(|entry| entry.key.0.len() as u64 + 4)
112
+ .sum(),
113
+ value_bytes: result
114
+ .value
115
+ .entries
116
+ .iter()
117
+ .map(|entry| match &entry.value {
118
+ StorageProjectedValue::KeyOnly => 0,
119
+ StorageProjectedValue::FullValue(value) => value.len() as u64,
516
120
  })
517
- .await?;
121
+ .sum(),
518
122
  }
519
- Ok(TransactionCommitOnlyFixture {
520
- runtime_functions,
521
- transaction,
522
- rows,
523
- })
524
- }
525
-
526
- pub async fn transaction_commit_only_prepared(
527
- fixture: TransactionCommitOnlyFixture,
528
- ) -> Result<StorageBenchReport, LixError> {
529
- let rows = fixture.rows;
530
- let started_at = Instant::now();
531
- fixture
532
- .transaction
533
- .commit(&fixture.runtime_functions)
534
- .await?;
535
- Ok(StorageBenchReport {
536
- measured_rows: rows,
537
- verified_rows: rows,
538
- elapsed: started_at.elapsed(),
539
- })
540
- }
541
-
542
- async fn prepare_transaction_payload_fixture(
543
- backend: Arc<dyn Backend + Send + Sync>,
544
- rows: usize,
545
- payload_bytes: usize,
546
- payload_pattern: TransactionPayloadPattern,
547
- untracked: bool,
548
- key_prefix: &'static str,
549
- ) -> Result<TransactionBenchFixture, LixError> {
550
- prepare_transaction_fixture(
551
- backend,
552
- transaction_entity_rows(TransactionEntityRows {
553
- rows,
554
- payload_bytes,
555
- payload_pattern,
556
- metadata_pattern: TransactionPayloadPattern::None,
557
- untracked,
558
- key_prefix,
559
- }),
560
- )
561
- .await
562
- }
563
-
564
- async fn prepare_transaction_fixture(
565
- backend: Arc<dyn Backend + Send + Sync>,
566
- rows: Vec<TransactionWriteRow>,
567
- ) -> Result<TransactionBenchFixture, LixError> {
568
- let storage = StorageContext::new(backend);
569
- let tracked_state = Arc::new(TrackedStateContext::new());
570
- let untracked_state = Arc::new(UntrackedStateContext::new());
571
- let commit_store = Arc::new(CommitStoreContext::new());
572
- let live_state = Arc::new(LiveStateContext::new(
573
- tracked_state.as_ref().clone(),
574
- untracked_state.as_ref().clone(),
575
- crate::commit_graph::CommitGraphContext::new(),
576
- ));
577
- let binary_cas = Arc::new(BinaryCasContext::new());
578
- let version_ctx = Arc::new(VersionContext::new(untracked_state));
579
- let catalog_context = Arc::new(CatalogContext::new());
580
- seed_transaction_visible_schema_rows(storage.clone()).await?;
581
- Ok(TransactionBenchFixture {
582
- storage,
583
- live_state,
584
- tracked_state,
585
- binary_cas,
586
- commit_store,
587
- version_ctx,
588
- catalog_context,
589
- rows,
590
- })
591
- }
592
-
593
- async fn seed_transaction_visible_schema_rows(storage: StorageContext) -> Result<(), LixError> {
594
- let mut writes = StorageWriteSet::new();
595
- let rows = crate::schema::seed_schema_definitions()
596
- .into_iter()
597
- .cloned()
598
- .chain(std::iter::once(transaction_entity_schema_definition()))
599
- .map(|schema| {
600
- let key = crate::schema::schema_key_from_definition(&schema)
601
- .expect("seed schema key should derive");
602
- let snapshot_content = serde_json::json!({ "value": schema }).to_string();
603
- Ok(crate::untracked_state::UntrackedStateRow {
604
- entity_id: crate::schema::registered_schema_entity_id(&key.schema_key)
605
- .expect("registered schema identity should derive"),
606
- schema_key: "lix_registered_schema".to_string(),
607
- file_id: None,
608
- version_id: crate::GLOBAL_VERSION_ID.to_string(),
609
- snapshot_content: Some(snapshot_content),
610
- metadata: None,
611
- created_at: "1970-01-01T00:00:00.000Z".to_string(),
612
- updated_at: "1970-01-01T00:00:00.000Z".to_string(),
613
- global: true,
614
- })
615
- })
616
- .collect::<Result<Vec<_>, LixError>>()?;
617
- let mut transaction = storage.begin_write_transaction().await?;
618
- UntrackedStateContext::new()
619
- .writer(&mut writes)
620
- .stage_rows(rows.iter().map(|row| row.as_ref()))?;
621
- writes.apply(&mut transaction.as_mut()).await?;
622
- transaction.commit().await
623
- }
624
-
625
- fn transaction_entity_schema_definition() -> serde_json::Value {
626
- serde_json::json!({
627
- "x-lix-key": TRANSACTION_BENCH_SCHEMA_KEY,
628
- "type": "object",
629
- "properties": {
630
- "value": {
631
- "anyOf": [
632
- { "type": "string" },
633
- { "type": "object" },
634
- { "type": "array" },
635
- { "type": "number" },
636
- { "type": "boolean" },
637
- { "type": "null" }
638
- ]
639
- }
640
- },
641
- "required": ["value"],
642
- "additionalProperties": false
643
- })
644
123
  }
645
124
 
646
- #[derive(Debug, Clone, Copy)]
647
- enum TransactionPayloadPattern {
648
- None,
649
- Unique,
650
- Same,
651
- HalfDuplicate,
125
+ pub fn untracked_state_row_key_value(
126
+ entity_pk: &str,
127
+ snapshot_content: &str,
128
+ ) -> (StorageKey, StorageValue) {
129
+ untracked_state_row_key_value_with_payload(entity_pk, snapshot_content, false)
652
130
  }
653
131
 
654
- struct TransactionEntityRows {
655
- rows: usize,
656
- payload_bytes: usize,
657
- payload_pattern: TransactionPayloadPattern,
658
- metadata_pattern: TransactionPayloadPattern,
659
- untracked: bool,
660
- key_prefix: &'static str,
132
+ pub fn untracked_state_full_row_key_value(
133
+ entity_pk: &str,
134
+ snapshot_content: &str,
135
+ ) -> (StorageKey, StorageValue) {
136
+ untracked_state_row_key_value_with_payload(entity_pk, snapshot_content, true)
661
137
  }
662
138
 
663
- fn transaction_entity_rows(config: TransactionEntityRows) -> Vec<TransactionWriteRow> {
664
- (0..config.rows)
665
- .map(|index| {
666
- let key = format!("{}-{index:06}", config.key_prefix);
667
- let value_index = payload_pattern_index(config.payload_pattern, index);
668
- let metadata_index = payload_pattern_index(config.metadata_pattern, index);
669
- TransactionWriteRow {
670
- entity_id: Some(EntityIdentity::single(key.clone())),
671
- schema_key: TRANSACTION_BENCH_SCHEMA_KEY.to_string(),
672
- file_id: None,
673
- snapshot: Some(transaction_snapshot_json(
674
- &key,
675
- value_index,
676
- config.payload_bytes,
677
- )),
678
- metadata: transaction_metadata(config.metadata_pattern, metadata_index),
679
- origin: None,
680
- created_at: None,
681
- updated_at: None,
682
- global: true,
683
- change_id: None,
684
- commit_id: None,
685
- untracked: config.untracked,
686
- version_id: crate::GLOBAL_VERSION_ID.to_string(),
687
- }
688
- })
689
- .collect()
690
- }
691
-
692
- fn transaction_registered_schema_row() -> TransactionWriteRow {
693
- let schema = serde_json::json!({
694
- "x-lix-key": "bench_transaction_schema",
695
- "x-lix-primary-key": ["/id"],
696
- "type": "object",
697
- "properties": {
698
- "id": { "type": "string" },
699
- "value": { "type": "string" }
700
- },
701
- "required": ["id", "value"],
702
- "additionalProperties": false
703
- });
704
- let key =
705
- crate::schema::schema_key_from_definition(&schema).expect("seed schema key should derive");
706
- TransactionWriteRow {
707
- entity_id: Some(
708
- crate::schema::registered_schema_entity_id(&key.schema_key)
709
- .expect("registered schema identity should derive"),
710
- ),
711
- schema_key: "lix_registered_schema".to_string(),
712
- file_id: None,
713
- snapshot: Some(TransactionJson::from_value_unchecked(
714
- serde_json::json!({ "value": schema }),
715
- )),
139
+ fn untracked_state_row_key_value_with_payload(
140
+ entity_pk: &str,
141
+ snapshot_content: &str,
142
+ include_identity_in_value: bool,
143
+ ) -> (StorageKey, StorageValue) {
144
+ let entity_pk = EntityPk::single(entity_pk);
145
+ let row = UntrackedStateRowRef {
146
+ entity_pk: &entity_pk,
147
+ schema_key: "json_pointer",
148
+ file_id: Some(""),
149
+ snapshot_content: Some(snapshot_content),
716
150
  metadata: None,
717
- origin: None,
718
- created_at: None,
719
- updated_at: None,
720
- global: true,
721
- change_id: None,
722
- commit_id: None,
723
- untracked: false,
724
- version_id: crate::GLOBAL_VERSION_ID.to_string(),
725
- }
726
- }
727
-
728
- fn transaction_snapshot_json(
729
- _key: &str,
730
- payload_index: usize,
731
- target_bytes: usize,
732
- ) -> TransactionJson {
733
- let base_value = format!("/entities/{payload_index}/value");
734
- let value = if target_bytes == 0 {
735
- base_value
151
+ created_at: "2026-01-01T00:00:00.000Z",
152
+ updated_at: "2026-01-01T00:00:00.000Z",
153
+ global: false,
154
+ branch_id: "bench-branch",
155
+ };
156
+ let value = if include_identity_in_value {
157
+ crate::untracked_state::codec::encode_row_ref(row).expect("encode untracked bench row")
736
158
  } else {
737
- let current = serde_json::json!({
738
- "value": base_value,
739
- })
740
- .to_string()
741
- .len();
742
- let padding = target_bytes.saturating_sub(current);
743
- format!("{base_value}:{}", "x".repeat(padding))
159
+ crate::untracked_state::codec::encode_payload_ref(row)
160
+ .expect("encode untracked bench payload")
744
161
  };
745
- let mut object = serde_json::Map::new();
746
- object.insert("value".to_string(), serde_json::Value::String(value));
747
- TransactionJson::from_value_unchecked(serde_json::Value::Object(object))
748
- }
749
-
750
- fn transaction_metadata(
751
- pattern: TransactionPayloadPattern,
752
- metadata_index: usize,
753
- ) -> Option<TransactionJson> {
754
- match pattern {
755
- TransactionPayloadPattern::None => None,
756
- TransactionPayloadPattern::Unique
757
- | TransactionPayloadPattern::Same
758
- | TransactionPayloadPattern::HalfDuplicate => {
759
- let mut object = serde_json::Map::new();
760
- object.insert(
761
- "source".to_string(),
762
- serde_json::Value::String("transaction-bench".to_string()),
763
- );
764
- object.insert(
765
- "metadata_index".to_string(),
766
- serde_json::Value::String(metadata_index.to_string()),
767
- );
768
- pad_json_object(&mut object, 1024);
769
- Some(TransactionJson::from_value_unchecked(
770
- serde_json::Value::Object(object),
771
- ))
772
- }
773
- }
774
- }
775
-
776
- fn payload_pattern_index(pattern: TransactionPayloadPattern, index: usize) -> usize {
777
- match pattern {
778
- TransactionPayloadPattern::None | TransactionPayloadPattern::Unique => index,
779
- TransactionPayloadPattern::Same => 0,
780
- TransactionPayloadPattern::HalfDuplicate => index % 2,
781
- }
782
- }
783
-
784
- pub async fn storage_api_write_kv_batch_puts(
785
- backend: Arc<dyn Backend + Send + Sync>,
786
- rows: usize,
787
- ) -> Result<StorageBenchReport, LixError> {
788
- let storage = StorageContext::new(backend);
789
- let mut transaction = storage.begin_write_transaction().await?;
790
- let mut batch = KvWriteBatch::new();
791
- for index in 0..rows {
792
- batch.put(
793
- STORAGE_API_NAMESPACE,
794
- storage_api_key(index),
795
- storage_api_value(index),
796
- );
797
- }
798
- let started_at = Instant::now();
799
- let stats = transaction.write_kv_batch(batch).await?;
800
- transaction.commit().await?;
801
- Ok(StorageBenchReport {
802
- measured_rows: stats.puts,
803
- verified_rows: rows,
804
- elapsed: started_at.elapsed(),
805
- })
806
- }
807
-
808
- pub async fn storage_api_write_kv_batch_mixed_put_delete(
809
- backend: Arc<dyn Backend + Send + Sync>,
810
- rows: usize,
811
- ) -> Result<StorageBenchReport, LixError> {
812
- let fixture = prepare_storage_api_read(backend, rows).await?;
813
- let mut transaction = fixture.storage.begin_write_transaction().await?;
814
- let mut batch = KvWriteBatch::new();
815
- for index in 0..rows {
816
- if index % 2 == 0 {
817
- batch.put(
818
- STORAGE_API_NAMESPACE,
819
- storage_api_key(index),
820
- storage_api_updated_value(index),
821
- );
822
- } else {
823
- batch.delete(STORAGE_API_NAMESPACE, storage_api_key(index));
824
- }
825
- }
826
- let started_at = Instant::now();
827
- let stats = transaction.write_kv_batch(batch).await?;
828
- transaction.commit().await?;
829
- Ok(StorageBenchReport {
830
- measured_rows: stats.puts + stats.deletes,
831
- verified_rows: rows,
832
- elapsed: started_at.elapsed(),
833
- })
834
- }
835
-
836
- pub async fn storage_api_write_kv_batch_multi_namespace(
837
- backend: Arc<dyn Backend + Send + Sync>,
838
- rows: usize,
839
- ) -> Result<StorageBenchReport, LixError> {
840
- let storage = StorageContext::new(backend);
841
- let mut transaction = storage.begin_write_transaction().await?;
842
- let mut batch = KvWriteBatch::new();
843
- for index in 0..rows {
844
- let namespace = if index % 2 == 0 {
845
- STORAGE_API_NAMESPACE
846
- } else {
847
- STORAGE_API_ALT_NAMESPACE
848
- };
849
- batch.put(namespace, storage_api_key(index), storage_api_value(index));
850
- }
851
- let started_at = Instant::now();
852
- let stats = transaction.write_kv_batch(batch).await?;
853
- transaction.commit().await?;
854
- Ok(StorageBenchReport {
855
- measured_rows: stats.puts,
856
- verified_rows: rows,
857
- elapsed: started_at.elapsed(),
858
- })
859
- }
860
-
861
- pub async fn storage_api_write_kv_batch_duplicate_keys(
862
- backend: Arc<dyn Backend + Send + Sync>,
863
- rows: usize,
864
- ) -> Result<StorageBenchReport, LixError> {
865
- let storage = StorageContext::new(backend);
866
- let mut transaction = storage.begin_write_transaction().await?;
867
- let mut batch = KvWriteBatch::new();
868
- for index in 0..rows {
869
- batch.put(
870
- STORAGE_API_NAMESPACE,
871
- storage_api_key(index % 100),
872
- storage_api_value(index),
873
- );
874
- }
875
- let started_at = Instant::now();
876
- let stats = transaction.write_kv_batch(batch).await?;
877
- transaction.commit().await?;
878
- Ok(StorageBenchReport {
879
- measured_rows: stats.puts,
880
- verified_rows: rows,
881
- elapsed: started_at.elapsed(),
882
- })
883
- }
884
-
885
- pub async fn storage_api_write_kv_batch_value_size(
886
- backend: Arc<dyn Backend + Send + Sync>,
887
- rows: usize,
888
- value_bytes: usize,
889
- ) -> Result<StorageBenchReport, LixError> {
890
- let storage = StorageContext::new(backend);
891
- let mut transaction = storage.begin_write_transaction().await?;
892
- let mut batch = KvWriteBatch::new();
893
- for index in 0..rows {
894
- batch.put(
895
- STORAGE_API_NAMESPACE,
896
- storage_api_key(index),
897
- storage_api_value_with_bytes(index, value_bytes),
898
- );
899
- }
900
- let started_at = Instant::now();
901
- let stats = transaction.write_kv_batch(batch).await?;
902
- transaction.commit().await?;
903
- Ok(StorageBenchReport {
904
- measured_rows: stats.puts,
905
- verified_rows: rows,
906
- elapsed: started_at.elapsed(),
907
- })
908
- }
909
-
910
- pub async fn storage_api_write_and_commit(
911
- backend: Arc<dyn Backend + Send + Sync>,
912
- rows: usize,
913
- ) -> Result<StorageBenchReport, LixError> {
914
- let storage = StorageContext::new(backend);
915
- let started_at = Instant::now();
916
- let mut transaction = storage.begin_write_transaction().await?;
917
- let mut batch = KvWriteBatch::new();
918
- for index in 0..rows {
919
- batch.put(
920
- STORAGE_API_NAMESPACE,
921
- storage_api_key(index),
922
- storage_api_value(index),
923
- );
924
- }
925
- let stats = transaction.write_kv_batch(batch).await?;
926
- transaction.commit().await?;
927
- Ok(StorageBenchReport {
928
- measured_rows: stats.puts,
929
- verified_rows: rows,
930
- elapsed: started_at.elapsed(),
931
- })
932
- }
933
-
934
- pub async fn storage_api_rollback_after_write(
935
- backend: Arc<dyn Backend + Send + Sync>,
936
- rows: usize,
937
- ) -> Result<StorageBenchReport, LixError> {
938
- let storage = StorageContext::new(backend);
939
- let started_at = Instant::now();
940
- let mut transaction = storage.begin_write_transaction().await?;
941
- let mut batch = KvWriteBatch::new();
942
- for index in 0..rows {
943
- batch.put(
944
- STORAGE_API_NAMESPACE,
945
- storage_api_key(index),
946
- storage_api_value(index),
947
- );
948
- }
949
- let stats = transaction.write_kv_batch(batch).await?;
950
- transaction.rollback().await?;
951
- Ok(StorageBenchReport {
952
- measured_rows: stats.puts,
953
- verified_rows: rows,
954
- elapsed: started_at.elapsed(),
955
- })
956
- }
957
-
958
- pub async fn prepare_storage_api_read(
959
- backend: Arc<dyn Backend + Send + Sync>,
960
- rows: usize,
961
- ) -> Result<StorageApiFixture, LixError> {
962
- let storage = StorageContext::new(backend);
963
- let mut transaction = storage.begin_write_transaction().await?;
964
- let mut batch = KvWriteBatch::new();
965
- for index in 0..rows {
966
- batch.put(
967
- STORAGE_API_NAMESPACE,
968
- storage_api_key(index),
969
- storage_api_value(index),
970
- );
971
- }
972
- transaction.write_kv_batch(batch).await?;
973
- transaction.commit().await?;
974
- Ok(StorageApiFixture { storage, rows })
975
- }
976
-
977
- pub async fn storage_api_get_values_hits_prepared(
978
- fixture: &StorageApiFixture,
979
- reads: usize,
980
- ) -> Result<StorageBenchReport, LixError> {
981
- let mut transaction = fixture.storage.begin_read_transaction().await?;
982
- let keys = (0..reads)
983
- .map(|index| storage_api_key(index % fixture.rows))
984
- .collect::<Vec<_>>();
985
- let started_at = Instant::now();
986
- let result = transaction
987
- .get_values(KvGetRequest {
988
- groups: vec![KvGetGroup {
989
- namespace: STORAGE_API_NAMESPACE.to_string(),
990
- keys,
991
- }],
992
- })
993
- .await?;
994
- transaction.rollback().await?;
995
- let verified_rows = result.groups[0]
996
- .values_iter()
997
- .filter(|value| value.is_some())
998
- .count();
999
- Ok(StorageBenchReport {
1000
- measured_rows: reads,
1001
- verified_rows,
1002
- elapsed: started_at.elapsed(),
1003
- })
1004
- }
1005
-
1006
- pub async fn storage_api_exists_many_prepared(
1007
- fixture: &StorageApiFixture,
1008
- reads: usize,
1009
- ) -> Result<StorageBenchReport, LixError> {
1010
- let mut transaction = fixture.storage.begin_read_transaction().await?;
1011
- let keys = (0..reads)
1012
- .map(|index| storage_api_key(index % fixture.rows))
1013
- .collect::<Vec<_>>();
1014
- let started_at = Instant::now();
1015
- let result = transaction
1016
- .exists_many(KvGetRequest {
1017
- groups: vec![KvGetGroup {
1018
- namespace: STORAGE_API_NAMESPACE.to_string(),
1019
- keys,
1020
- }],
1021
- })
1022
- .await?;
1023
- transaction.rollback().await?;
1024
- let verified_rows = result.groups[0]
1025
- .exists
1026
- .iter()
1027
- .filter(|exists| **exists)
1028
- .count();
1029
- Ok(StorageBenchReport {
1030
- measured_rows: reads,
1031
- verified_rows,
1032
- elapsed: started_at.elapsed(),
1033
- })
1034
- }
1035
-
1036
- pub async fn storage_api_get_values_misses_prepared(
1037
- fixture: &StorageApiFixture,
1038
- reads: usize,
1039
- ) -> Result<StorageBenchReport, LixError> {
1040
- let mut transaction = fixture.storage.begin_read_transaction().await?;
1041
- let keys = (0..reads)
1042
- .map(|index| storage_api_missing_key(index))
1043
- .collect::<Vec<_>>();
1044
- let started_at = Instant::now();
1045
- let result = transaction
1046
- .get_values(KvGetRequest {
1047
- groups: vec![KvGetGroup {
1048
- namespace: STORAGE_API_NAMESPACE.to_string(),
1049
- keys,
1050
- }],
1051
- })
1052
- .await?;
1053
- transaction.rollback().await?;
1054
- let verified_rows = result.groups[0]
1055
- .values_iter()
1056
- .filter(|value| value.is_none())
1057
- .count();
1058
- Ok(StorageBenchReport {
1059
- measured_rows: reads,
1060
- verified_rows,
1061
- elapsed: started_at.elapsed(),
1062
- })
1063
- }
1064
-
1065
- pub async fn storage_api_get_values_mixed_hit_miss_prepared(
1066
- fixture: &StorageApiFixture,
1067
- reads: usize,
1068
- ) -> Result<StorageBenchReport, LixError> {
1069
- let mut transaction = fixture.storage.begin_read_transaction().await?;
1070
- let keys = (0..reads)
1071
- .map(|index| {
1072
- if index % 2 == 0 {
1073
- storage_api_key(index % fixture.rows)
1074
- } else {
1075
- storage_api_missing_key(index)
1076
- }
1077
- })
1078
- .collect::<Vec<_>>();
1079
- let started_at = Instant::now();
1080
- let result = transaction
1081
- .get_values(KvGetRequest {
1082
- groups: vec![KvGetGroup {
1083
- namespace: STORAGE_API_NAMESPACE.to_string(),
1084
- keys,
1085
- }],
1086
- })
1087
- .await?;
1088
- transaction.rollback().await?;
1089
- let verified_rows = result.groups[0]
1090
- .values_iter()
1091
- .filter(|value| value.is_some())
1092
- .count();
1093
- Ok(StorageBenchReport {
1094
- measured_rows: reads,
1095
- verified_rows,
1096
- elapsed: started_at.elapsed(),
1097
- })
1098
- }
1099
-
1100
- pub async fn storage_api_get_values_multi_namespace(
1101
- backend: Arc<dyn Backend + Send + Sync>,
1102
- reads: usize,
1103
- ) -> Result<StorageBenchReport, LixError> {
1104
- let storage = StorageContext::new(backend);
1105
- let mut transaction = storage.begin_write_transaction().await?;
1106
- let mut batch = KvWriteBatch::new();
1107
- for index in 0..reads {
1108
- let namespace = if index % 2 == 0 {
1109
- STORAGE_API_NAMESPACE
1110
- } else {
1111
- STORAGE_API_ALT_NAMESPACE
1112
- };
1113
- batch.put(namespace, storage_api_key(index), storage_api_value(index));
1114
- }
1115
- transaction.write_kv_batch(batch).await?;
1116
- transaction.commit().await?;
1117
-
1118
- let mut transaction = storage.begin_read_transaction().await?;
1119
- let even_keys = (0..reads)
1120
- .step_by(2)
1121
- .map(storage_api_key)
1122
- .collect::<Vec<_>>();
1123
- let odd_keys = (1..reads)
1124
- .step_by(2)
1125
- .map(storage_api_key)
1126
- .collect::<Vec<_>>();
1127
- let started_at = Instant::now();
1128
- let result = transaction
1129
- .get_values(KvGetRequest {
1130
- groups: vec![
1131
- KvGetGroup {
1132
- namespace: STORAGE_API_NAMESPACE.to_string(),
1133
- keys: even_keys,
1134
- },
1135
- KvGetGroup {
1136
- namespace: STORAGE_API_ALT_NAMESPACE.to_string(),
1137
- keys: odd_keys,
1138
- },
1139
- ],
1140
- })
1141
- .await?;
1142
- transaction.rollback().await?;
1143
- let verified_rows = result
1144
- .groups
1145
- .iter()
1146
- .map(|group| group.values_iter().filter(|value| value.is_some()).count())
1147
- .sum();
1148
- Ok(StorageBenchReport {
1149
- measured_rows: reads,
1150
- verified_rows,
1151
- elapsed: started_at.elapsed(),
1152
- })
1153
- }
1154
-
1155
- pub async fn storage_api_get_values_duplicate_keys_prepared(
1156
- fixture: &StorageApiFixture,
1157
- reads: usize,
1158
- ) -> Result<StorageBenchReport, LixError> {
1159
- let mut transaction = fixture.storage.begin_read_transaction().await?;
1160
- let keys = (0..reads)
1161
- .map(|index| storage_api_key(index % 100))
1162
- .collect::<Vec<_>>();
1163
- let started_at = Instant::now();
1164
- let result = transaction
1165
- .get_values(KvGetRequest {
1166
- groups: vec![KvGetGroup {
1167
- namespace: STORAGE_API_NAMESPACE.to_string(),
1168
- keys,
1169
- }],
1170
- })
1171
- .await?;
1172
- transaction.rollback().await?;
1173
- let verified_rows = result.groups[0]
1174
- .values_iter()
1175
- .filter(|value| value.is_some())
1176
- .count();
1177
- Ok(StorageBenchReport {
1178
- measured_rows: reads,
1179
- verified_rows,
1180
- elapsed: started_at.elapsed(),
1181
- })
1182
- }
1183
-
1184
- pub async fn storage_api_scan_keys_prefix_prepared(
1185
- fixture: &StorageApiFixture,
1186
- limit: usize,
1187
- ) -> Result<StorageBenchReport, LixError> {
1188
- let mut transaction = fixture.storage.begin_read_transaction().await?;
1189
- let started_at = Instant::now();
1190
- let result = transaction
1191
- .scan_keys(KvScanRequest {
1192
- namespace: STORAGE_API_NAMESPACE.to_string(),
1193
- range: KvScanRange::prefix(b"key/".to_vec()),
1194
- after: None,
1195
- limit,
1196
- })
1197
- .await?;
1198
- transaction.rollback().await?;
1199
- Ok(StorageBenchReport {
1200
- measured_rows: result.keys.len(),
1201
- verified_rows: limit.min(fixture.rows),
1202
- elapsed: started_at.elapsed(),
1203
- })
1204
- }
1205
-
1206
- pub async fn storage_api_scan_keys_after_pages_prepared(
1207
- fixture: &StorageApiFixture,
1208
- page_size: usize,
1209
- ) -> Result<StorageBenchReport, LixError> {
1210
- let mut transaction = fixture.storage.begin_read_transaction().await?;
1211
- let started_at = Instant::now();
1212
- let mut after = None;
1213
- let mut measured_rows = 0usize;
1214
- loop {
1215
- let result = transaction
1216
- .scan_keys(KvScanRequest {
1217
- namespace: STORAGE_API_NAMESPACE.to_string(),
1218
- range: KvScanRange::prefix(b"key/".to_vec()),
1219
- after,
1220
- limit: page_size,
1221
- })
1222
- .await?;
1223
- if result.keys.is_empty() {
1224
- break;
1225
- }
1226
- measured_rows += result.keys.len();
1227
- let Some(resume_after) = result.resume_after else {
1228
- break;
1229
- };
1230
- after = Some(resume_after);
1231
- }
1232
- transaction.rollback().await?;
1233
- Ok(StorageBenchReport {
1234
- measured_rows,
1235
- verified_rows: fixture.rows,
1236
- elapsed: started_at.elapsed(),
1237
- })
1238
- }
1239
-
1240
- pub async fn storage_api_scan_keys_empty_range_prepared(
1241
- fixture: &StorageApiFixture,
1242
- ) -> Result<StorageBenchReport, LixError> {
1243
- let mut transaction = fixture.storage.begin_read_transaction().await?;
1244
- let started_at = Instant::now();
1245
- let result = transaction
1246
- .scan_keys(KvScanRequest {
1247
- namespace: STORAGE_API_NAMESPACE.to_string(),
1248
- range: KvScanRange::prefix(b"absent/".to_vec()),
1249
- after: None,
1250
- limit: fixture.rows,
1251
- })
1252
- .await?;
1253
- transaction.rollback().await?;
1254
- Ok(StorageBenchReport {
1255
- measured_rows: result.keys.len(),
1256
- verified_rows: 0,
1257
- elapsed: started_at.elapsed(),
1258
- })
1259
- }
1260
-
1261
- pub async fn prepare_storage_api_selective_scan(
1262
- backend: Arc<dyn Backend + Send + Sync>,
1263
- rows: usize,
1264
- selectivity: StorageBenchSelectivity,
1265
- ) -> Result<StorageApiFixture, LixError> {
1266
- let storage = StorageContext::new(backend);
1267
- let mut transaction = storage.begin_write_transaction().await?;
1268
- let mut batch = KvWriteBatch::new();
1269
- for index in 0..rows {
1270
- let key = if selectivity.matches(index) {
1271
- storage_api_selective_key(index)
1272
- } else {
1273
- storage_api_key(index)
1274
- };
1275
- batch.put(STORAGE_API_NAMESPACE, key, storage_api_value(index));
1276
- }
1277
- transaction.write_kv_batch(batch).await?;
1278
- transaction.commit().await?;
1279
- Ok(StorageApiFixture { storage, rows })
1280
- }
1281
-
1282
- pub async fn storage_api_scan_keys_selective_prefix_prepared(
1283
- fixture: &StorageApiFixture,
1284
- selectivity: StorageBenchSelectivity,
1285
- ) -> Result<StorageBenchReport, LixError> {
1286
- let mut transaction = fixture.storage.begin_read_transaction().await?;
1287
- let started_at = Instant::now();
1288
- let result = transaction
1289
- .scan_keys(KvScanRequest {
1290
- namespace: STORAGE_API_NAMESPACE.to_string(),
1291
- range: KvScanRange::prefix(b"selective/".to_vec()),
1292
- after: None,
1293
- limit: fixture.rows,
1294
- })
1295
- .await?;
1296
- transaction.rollback().await?;
1297
- Ok(StorageBenchReport {
1298
- measured_rows: result.keys.len(),
1299
- verified_rows: selectivity.expected_rows(fixture.rows),
1300
- elapsed: started_at.elapsed(),
1301
- })
1302
- }
1303
-
1304
- pub async fn storage_api_transaction_commit_empty(
1305
- backend: Arc<dyn Backend + Send + Sync>,
1306
- ) -> Result<StorageBenchReport, LixError> {
1307
- let storage = StorageContext::new(backend);
1308
- let started_at = Instant::now();
1309
- let transaction = storage.begin_write_transaction().await?;
1310
- transaction.commit().await?;
1311
- Ok(StorageBenchReport {
1312
- measured_rows: 0,
1313
- verified_rows: 0,
1314
- elapsed: started_at.elapsed(),
1315
- })
1316
- }
1317
-
1318
- fn storage_api_key(index: usize) -> Vec<u8> {
1319
- format!("key/{index:08}").into_bytes()
1320
- }
1321
-
1322
- fn storage_api_selective_key(index: usize) -> Vec<u8> {
1323
- format!("selective/{index:08}").into_bytes()
1324
- }
1325
-
1326
- fn storage_api_missing_key(index: usize) -> Vec<u8> {
1327
- format!("missing/{index:08}").into_bytes()
1328
- }
1329
-
1330
- fn storage_api_value(index: usize) -> Vec<u8> {
1331
- format!("value/{index:08}/{}", "x".repeat(64)).into_bytes()
1332
- }
1333
-
1334
- fn storage_api_value_with_bytes(index: usize, value_bytes: usize) -> Vec<u8> {
1335
- let prefix = format!("value/{index:08}/");
1336
- if value_bytes <= prefix.len() {
1337
- return prefix.into_bytes();
1338
- }
1339
- let mut value = prefix.into_bytes();
1340
- value.extend(std::iter::repeat_n(b'x', value_bytes - value.len()));
1341
- value
1342
- }
1343
-
1344
- fn storage_api_updated_value(index: usize) -> Vec<u8> {
1345
- format!("updated/{index:08}/{}", "y".repeat(64)).into_bytes()
1346
- }
1347
-
1348
- pub struct TrackedStateWriteRootFixture {
1349
- context: TrackedStateContext,
1350
- rows: Vec<MaterializedTrackedStateRow>,
1351
- }
1352
-
1353
- pub struct TrackedStateReadFixture {
1354
- context: TrackedStateContext,
1355
- rows: usize,
1356
- commit_id: String,
1357
- key_pattern: StorageBenchKeyPattern,
1358
- selectivity: StorageBenchSelectivity,
1359
- }
1360
-
1361
- pub struct TrackedStateUpdateFixture {
1362
- context: TrackedStateContext,
1363
- rows: Vec<MaterializedTrackedStateRow>,
1364
- }
1365
-
1366
- pub struct TrackedStateDiffFixture {
1367
- context: TrackedStateContext,
1368
- left_commit_id: String,
1369
- right_commit_id: String,
1370
- expected_entries: usize,
1371
- }
1372
-
1373
- pub struct TrackedStateMaterializeFixture {
1374
- context: TrackedStateContext,
1375
- commit_id: String,
1376
- expected_rows: usize,
1377
- }
1378
-
1379
- #[derive(Clone)]
1380
- pub struct JsonPointerStorageRow {
1381
- pub path: String,
1382
- pub value_json: String,
1383
- pub updated_value_json: String,
1384
- }
1385
-
1386
- pub struct JsonPointerTrackedStateReadFixture {
1387
- context: TrackedStateContext,
1388
- rows: Vec<JsonPointerStorageRow>,
1389
- commit_id: String,
1390
- }
1391
-
1392
- pub struct JsonPointerTrackedStateDiffFixture {
1393
- context: TrackedStateContext,
1394
- left_commit_id: String,
1395
- right_commit_id: String,
1396
- expected_entries: usize,
1397
- }
1398
-
1399
- pub struct UntrackedStateWriteFixture {
1400
- context: UntrackedStateContext,
1401
- rows: Vec<MaterializedUntrackedStateRow>,
1402
- }
1403
-
1404
- pub struct UntrackedStateReadFixture {
1405
- context: UntrackedStateContext,
1406
- rows: usize,
1407
- key_pattern: StorageBenchKeyPattern,
1408
- selectivity: StorageBenchSelectivity,
1409
- }
1410
-
1411
- pub struct ChangelogAppendFixture {
1412
- context: CommitStoreContext,
1413
- changes: Vec<MaterializedChange>,
1414
- }
1415
-
1416
- pub struct ChangelogReadFixture {
1417
- context: CommitStoreContext,
1418
- rows: usize,
1419
- }
1420
-
1421
- pub struct ChangelogCodecFixture {
1422
- changes: Vec<Change>,
1423
- encoded_changes: Vec<Vec<u8>>,
1424
- }
1425
-
1426
- pub struct CommitGraphReadFixture {
1427
- head_commit_id: String,
1428
- rows: usize,
1429
- }
1430
-
1431
- pub struct BinaryCasWriteFixture {
1432
- context: BinaryCasContext,
1433
- file_ids: Vec<String>,
1434
- payloads: Vec<Vec<u8>>,
1435
- }
1436
-
1437
- pub struct BinaryCasReadFixture {
1438
- context: BinaryCasContext,
1439
- rows: usize,
1440
- hashes: Vec<BlobHash>,
1441
- }
1442
-
1443
- #[derive(Debug, Clone, Copy)]
1444
- pub enum JsonStorePayloadShape {
1445
- SmallRaw1k,
1446
- MediumStructured16k,
1447
- LargeStructured128k,
1448
- LargeArray128k,
1449
- }
1450
-
1451
- #[derive(Debug, Clone, Copy)]
1452
- pub enum JsonStoreProjectionShape {
1453
- TopLevelTarget,
1454
- TopLevelTenProps,
1455
- NestedTarget,
1456
- ArrayItem999,
1457
- Status,
1458
- }
1459
-
1460
- pub struct JsonStoreWriteFixture {
1461
- context: JsonStoreContext,
1462
- documents: Vec<Vec<u8>>,
1463
- }
1464
-
1465
- pub struct JsonStoreReadFixture {
1466
- context: JsonStoreContext,
1467
- refs: Vec<JsonRef>,
1468
- paths: Vec<JsonProjectionPath>,
1469
- }
1470
-
1471
- pub async fn prepare_tracked_state_write_root(
1472
- config: StorageBenchConfig,
1473
- ) -> Result<TrackedStateWriteRootFixture, LixError> {
1474
- Ok(TrackedStateWriteRootFixture {
1475
- context: TrackedStateContext::new(),
1476
- rows: tracked_rows(config, "bench-tracked-commit"),
1477
- })
1478
- }
1479
-
1480
- pub async fn tracked_state_write_root_prepared(
1481
- backend: &Arc<dyn Backend + Send + Sync>,
1482
- fixture: &TrackedStateWriteRootFixture,
1483
- ) -> Result<StorageBenchReport, LixError> {
1484
- write_tracked_root(
1485
- backend,
1486
- &fixture.context,
1487
- "bench-tracked-commit",
1488
- None,
1489
- &fixture.rows,
162
+ (
163
+ StorageKey(Bytes::from(
164
+ crate::untracked_state::storage::encode_untracked_state_row_key_ref(row.into())
165
+ .expect("encode untracked bench key"),
166
+ )),
167
+ StorageValue {
168
+ bytes: Bytes::from(value),
169
+ },
1490
170
  )
1491
- .await?;
1492
- Ok(report(
1493
- fixture.rows.len(),
1494
- fixture.rows.len(),
1495
- Duration::ZERO,
1496
- ))
1497
- }
1498
-
1499
- pub async fn prepare_tracked_state_read(
1500
- backend: &Arc<dyn Backend + Send + Sync>,
1501
- config: StorageBenchConfig,
1502
- ) -> Result<TrackedStateReadFixture, LixError> {
1503
- let context = TrackedStateContext::new();
1504
- let rows = tracked_rows(config, "bench-tracked-commit");
1505
- write_tracked_root(backend, &context, "bench-tracked-commit", None, &rows).await?;
1506
- Ok(TrackedStateReadFixture {
1507
- context,
1508
- rows: config.rows,
1509
- commit_id: "bench-tracked-commit".to_string(),
1510
- key_pattern: config.key_pattern,
1511
- selectivity: config.selectivity,
1512
- })
1513
- }
1514
-
1515
- pub async fn prepare_tracked_state_read_file_selective(
1516
- backend: &Arc<dyn Backend + Send + Sync>,
1517
- config: StorageBenchConfig,
1518
- ) -> Result<TrackedStateReadFixture, LixError> {
1519
- let context = TrackedStateContext::new();
1520
- let rows = tracked_rows_file_selective(config, "bench-tracked-commit");
1521
- write_tracked_root(backend, &context, "bench-tracked-commit", None, &rows).await?;
1522
- Ok(TrackedStateReadFixture {
1523
- context,
1524
- rows: config.rows,
1525
- commit_id: "bench-tracked-commit".to_string(),
1526
- key_pattern: config.key_pattern,
1527
- selectivity: config.selectivity,
1528
- })
1529
- }
1530
-
1531
- pub async fn prepare_tracked_state_read_after_update_rows(
1532
- backend: &Arc<dyn Backend + Send + Sync>,
1533
- config: StorageBenchConfig,
1534
- updated_rows: usize,
1535
- ) -> Result<TrackedStateReadFixture, LixError> {
1536
- let fixture = prepare_tracked_state_update_rows(backend, config, updated_rows).await?;
1537
- tracked_state_update_existing_prepared(backend, &fixture).await?;
1538
- Ok(TrackedStateReadFixture {
1539
- context: fixture.context,
1540
- rows: config.rows,
1541
- commit_id: "bench-tracked-child".to_string(),
1542
- key_pattern: config.key_pattern,
1543
- selectivity: config.selectivity,
1544
- })
1545
- }
1546
-
1547
- pub async fn prepare_tracked_state_read_delta_chain(
1548
- backend: &Arc<dyn Backend + Send + Sync>,
1549
- config: StorageBenchConfig,
1550
- delta_commits: usize,
1551
- updated_rows_per_commit: usize,
1552
- ) -> Result<TrackedStateReadFixture, LixError> {
1553
- let (context, final_commit_id) =
1554
- write_tracked_delta_chain(backend, config, delta_commits, updated_rows_per_commit).await?;
1555
- Ok(TrackedStateReadFixture {
1556
- context,
1557
- rows: config.rows,
1558
- commit_id: final_commit_id,
1559
- key_pattern: config.key_pattern,
1560
- selectivity: config.selectivity,
1561
- })
1562
- }
1563
-
1564
- pub async fn prepare_tracked_state_read_materialized_delta_chain(
1565
- backend: &Arc<dyn Backend + Send + Sync>,
1566
- config: StorageBenchConfig,
1567
- delta_commits: usize,
1568
- updated_rows_per_commit: usize,
1569
- ) -> Result<TrackedStateReadFixture, LixError> {
1570
- let (context, final_commit_id) =
1571
- write_tracked_delta_chain(backend, config, delta_commits, updated_rows_per_commit).await?;
1572
- materialize_tracked_root(backend, &context, &final_commit_id).await?;
1573
- Ok(TrackedStateReadFixture {
1574
- context,
1575
- rows: config.rows,
1576
- commit_id: final_commit_id,
1577
- key_pattern: config.key_pattern,
1578
- selectivity: config.selectivity,
1579
- })
1580
- }
1581
-
1582
- pub async fn prepare_tracked_state_materialize_delta_chain(
1583
- backend: &Arc<dyn Backend + Send + Sync>,
1584
- config: StorageBenchConfig,
1585
- delta_commits: usize,
1586
- updated_rows_per_commit: usize,
1587
- ) -> Result<TrackedStateMaterializeFixture, LixError> {
1588
- let (context, final_commit_id) =
1589
- write_tracked_delta_chain(backend, config, delta_commits, updated_rows_per_commit).await?;
1590
- Ok(TrackedStateMaterializeFixture {
1591
- context,
1592
- commit_id: final_commit_id,
1593
- expected_rows: config.rows,
1594
- })
1595
- }
1596
-
1597
- fn tracked_point_hit_requests(
1598
- rows: usize,
1599
- key_pattern: StorageBenchKeyPattern,
1600
- ) -> Vec<TrackedStateRowRequest> {
1601
- (0..rows)
1602
- .map(|index| TrackedStateRowRequest {
1603
- schema_key: tracked_schema_key(index, StorageBenchSelectivity::Percent100),
1604
- entity_id: EntityIdentity::single(entity_id("tracked", index, key_pattern)),
1605
- file_id: NullableKeyFilter::Value("bench.json".to_string()),
1606
- })
1607
- .collect()
1608
- }
1609
-
1610
- fn tracked_point_miss_requests(
1611
- rows: usize,
1612
- selectivity: StorageBenchSelectivity,
1613
- ) -> Vec<TrackedStateRowRequest> {
1614
- (0..rows)
1615
- .map(|index| TrackedStateRowRequest {
1616
- schema_key: tracked_schema_key(index, selectivity),
1617
- entity_id: EntityIdentity::single(format!("missing-{index}")),
1618
- file_id: NullableKeyFilter::Value("bench.json".to_string()),
1619
- })
1620
- .collect()
1621
- }
1622
-
1623
- fn tracked_point_miss_requests_for_schema(
1624
- rows: usize,
1625
- schema_key: &str,
1626
- ) -> Vec<TrackedStateRowRequest> {
1627
- (0..rows)
1628
- .map(|index| TrackedStateRowRequest {
1629
- schema_key: schema_key.to_string(),
1630
- entity_id: EntityIdentity::single(format!("missing-{index}")),
1631
- file_id: NullableKeyFilter::Value("bench.json".to_string()),
1632
- })
1633
- .collect()
1634
- }
1635
-
1636
- pub async fn tracked_state_read_point_hit_prepared(
1637
- backend: &Arc<dyn Backend + Send + Sync>,
1638
- fixture: &TrackedStateReadFixture,
1639
- ) -> Result<StorageBenchReport, LixError> {
1640
- let mut reader = fixture
1641
- .context
1642
- .reader(StorageContext::new(Arc::clone(backend)));
1643
- let requests = tracked_point_hit_requests(fixture.rows, fixture.key_pattern);
1644
- let verified_rows = reader
1645
- .load_rows_at_commit(&fixture.commit_id, &requests)
1646
- .await?
1647
- .into_iter()
1648
- .filter(Option::is_some)
1649
- .count();
1650
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
1651
- }
1652
-
1653
- pub async fn tracked_state_read_point_hit_constant_prepared(
1654
- backend: &Arc<dyn Backend + Send + Sync>,
1655
- fixture: &TrackedStateReadFixture,
1656
- measured_reads: usize,
1657
- ) -> Result<StorageBenchReport, LixError> {
1658
- let measured_rows = measured_reads.min(fixture.rows);
1659
- let mut reader = fixture
1660
- .context
1661
- .reader(StorageContext::new(Arc::clone(backend)));
1662
- let requests = tracked_point_hit_requests(measured_rows, fixture.key_pattern);
1663
- let verified_rows = reader
1664
- .load_rows_at_commit(&fixture.commit_id, &requests)
1665
- .await?
1666
- .into_iter()
1667
- .filter(Option::is_some)
1668
- .count();
1669
- Ok(report(measured_rows, verified_rows, Duration::ZERO))
1670
- }
1671
-
1672
- pub async fn tracked_state_read_point_miss_prepared(
1673
- backend: &Arc<dyn Backend + Send + Sync>,
1674
- fixture: &TrackedStateReadFixture,
1675
- ) -> Result<StorageBenchReport, LixError> {
1676
- let mut reader = fixture
1677
- .context
1678
- .reader(StorageContext::new(Arc::clone(backend)));
1679
- let requests = tracked_point_miss_requests_for_schema(fixture.rows, TRACKED_MATCH_SCHEMA_KEY);
1680
- let misses = reader
1681
- .load_rows_at_commit(&fixture.commit_id, &requests)
1682
- .await?
1683
- .into_iter()
1684
- .filter(Option::is_none)
1685
- .count();
1686
- Ok(report(fixture.rows, misses, Duration::ZERO))
1687
- }
1688
-
1689
- pub async fn tracked_state_scan_all_prepared(
1690
- backend: &Arc<dyn Backend + Send + Sync>,
1691
- fixture: &TrackedStateReadFixture,
1692
- ) -> Result<StorageBenchReport, LixError> {
1693
- let verified_rows = scan_tracked(backend, &fixture.context, &fixture.commit_id)
1694
- .await?
1695
- .len();
1696
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
1697
- }
1698
-
1699
- pub async fn tracked_state_scan_keys_only_prepared(
1700
- backend: &Arc<dyn Backend + Send + Sync>,
1701
- fixture: &TrackedStateReadFixture,
1702
- ) -> Result<StorageBenchReport, LixError> {
1703
- let mut reader = fixture
1704
- .context
1705
- .reader(StorageContext::new(Arc::clone(backend)));
1706
- let verified_rows = reader
1707
- .scan_rows_at_commit(
1708
- &fixture.commit_id,
1709
- &TrackedStateScanRequest {
1710
- projection: TrackedStateProjection {
1711
- columns: vec!["entity_id".to_string()],
1712
- },
1713
- ..Default::default()
1714
- },
1715
- )
1716
- .await?
1717
- .len();
1718
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
1719
- }
1720
-
1721
- pub async fn tracked_state_scan_headers_only_prepared(
1722
- backend: &Arc<dyn Backend + Send + Sync>,
1723
- fixture: &TrackedStateReadFixture,
1724
- ) -> Result<StorageBenchReport, LixError> {
1725
- let mut reader = fixture
1726
- .context
1727
- .reader(StorageContext::new(Arc::clone(backend)));
1728
- let verified_rows = reader
1729
- .scan_rows_at_commit(
1730
- &fixture.commit_id,
1731
- &TrackedStateScanRequest {
1732
- projection: TrackedStateProjection {
1733
- columns: tracked_state_header_columns(),
1734
- },
1735
- ..Default::default()
1736
- },
1737
- )
1738
- .await?
1739
- .len();
1740
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
1741
- }
1742
-
1743
- pub async fn tracked_state_scan_full_rows_prepared(
1744
- backend: &Arc<dyn Backend + Send + Sync>,
1745
- fixture: &TrackedStateReadFixture,
1746
- ) -> Result<StorageBenchReport, LixError> {
1747
- tracked_state_scan_all_prepared(backend, fixture).await
1748
- }
1749
-
1750
- pub async fn tracked_state_scan_schema_prepared(
1751
- backend: &Arc<dyn Backend + Send + Sync>,
1752
- fixture: &TrackedStateReadFixture,
1753
- ) -> Result<StorageBenchReport, LixError> {
1754
- let mut reader = fixture
1755
- .context
1756
- .reader(StorageContext::new(Arc::clone(backend)));
1757
- let verified_rows = reader
1758
- .scan_rows_at_commit(
1759
- &fixture.commit_id,
1760
- &TrackedStateScanRequest {
1761
- filter: TrackedStateFilter {
1762
- schema_keys: vec![tracked_schema_key(0, StorageBenchSelectivity::Percent100)],
1763
- ..Default::default()
1764
- },
1765
- ..Default::default()
1766
- },
1767
- )
1768
- .await?
1769
- .len();
1770
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
1771
- }
1772
-
1773
- pub async fn tracked_state_scan_schema_selective_prepared(
1774
- backend: &Arc<dyn Backend + Send + Sync>,
1775
- fixture: &TrackedStateReadFixture,
1776
- ) -> Result<StorageBenchReport, LixError> {
1777
- let mut reader = fixture
1778
- .context
1779
- .reader(StorageContext::new(Arc::clone(backend)));
1780
- let verified_rows = reader
1781
- .scan_rows_at_commit(
1782
- &fixture.commit_id,
1783
- &TrackedStateScanRequest {
1784
- filter: TrackedStateFilter {
1785
- schema_keys: vec![TRACKED_MATCH_SCHEMA_KEY.to_string()],
1786
- ..Default::default()
1787
- },
1788
- ..Default::default()
1789
- },
1790
- )
1791
- .await?
1792
- .len();
1793
- Ok(report(
1794
- fixture.selectivity.expected_rows(fixture.rows),
1795
- verified_rows,
1796
- Duration::ZERO,
1797
- ))
1798
- }
1799
-
1800
- pub async fn tracked_state_scan_file_prepared(
1801
- backend: &Arc<dyn Backend + Send + Sync>,
1802
- fixture: &TrackedStateReadFixture,
1803
- ) -> Result<StorageBenchReport, LixError> {
1804
- let mut reader = fixture
1805
- .context
1806
- .reader(StorageContext::new(Arc::clone(backend)));
1807
- let verified_rows = reader
1808
- .scan_rows_at_commit(
1809
- &fixture.commit_id,
1810
- &TrackedStateScanRequest {
1811
- filter: TrackedStateFilter {
1812
- file_ids: vec![NullableKeyFilter::Value("bench.json".to_string())],
1813
- ..Default::default()
1814
- },
1815
- ..Default::default()
1816
- },
1817
- )
1818
- .await?
1819
- .len();
1820
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
1821
- }
1822
-
1823
- pub async fn tracked_state_scan_file_selective_prepared(
1824
- backend: &Arc<dyn Backend + Send + Sync>,
1825
- fixture: &TrackedStateReadFixture,
1826
- ) -> Result<StorageBenchReport, LixError> {
1827
- let mut reader = fixture
1828
- .context
1829
- .reader(StorageContext::new(Arc::clone(backend)));
1830
- let verified_rows = reader
1831
- .scan_rows_at_commit(
1832
- &fixture.commit_id,
1833
- &TrackedStateScanRequest {
1834
- filter: TrackedStateFilter {
1835
- file_ids: vec![NullableKeyFilter::Value("bench-match.json".to_string())],
1836
- ..Default::default()
1837
- },
1838
- ..Default::default()
1839
- },
1840
- )
1841
- .await?
1842
- .len();
1843
- Ok(report(
1844
- fixture.selectivity.expected_rows(fixture.rows),
1845
- verified_rows,
1846
- Duration::ZERO,
1847
- ))
1848
- }
1849
-
1850
- pub async fn tracked_state_scan_file_header_selective_prepared(
1851
- backend: &Arc<dyn Backend + Send + Sync>,
1852
- fixture: &TrackedStateReadFixture,
1853
- ) -> Result<StorageBenchReport, LixError> {
1854
- let mut reader = fixture
1855
- .context
1856
- .reader(StorageContext::new(Arc::clone(backend)));
1857
- let verified_rows = reader
1858
- .scan_rows_at_commit(
1859
- &fixture.commit_id,
1860
- &TrackedStateScanRequest {
1861
- filter: TrackedStateFilter {
1862
- file_ids: vec![NullableKeyFilter::Value("bench-match.json".to_string())],
1863
- ..Default::default()
1864
- },
1865
- projection: TrackedStateProjection {
1866
- columns: tracked_state_header_columns(),
1867
- },
1868
- ..Default::default()
1869
- },
1870
- )
1871
- .await?
1872
- .len();
1873
- Ok(report(
1874
- fixture.selectivity.expected_rows(fixture.rows),
1875
- verified_rows,
1876
- Duration::ZERO,
1877
- ))
1878
- }
1879
-
1880
- pub async fn prepare_tracked_state_update(
1881
- backend: &Arc<dyn Backend + Send + Sync>,
1882
- config: StorageBenchConfig,
1883
- ) -> Result<TrackedStateUpdateFixture, LixError> {
1884
- prepare_tracked_state_update_rows(backend, config, config.update_fraction.rows(config.rows))
1885
- .await
1886
- }
1887
-
1888
- pub async fn prepare_tracked_state_update_rows(
1889
- backend: &Arc<dyn Backend + Send + Sync>,
1890
- config: StorageBenchConfig,
1891
- updated_rows: usize,
1892
- ) -> Result<TrackedStateUpdateFixture, LixError> {
1893
- let context = TrackedStateContext::new();
1894
- let rows = tracked_rows(config, "bench-tracked-parent");
1895
- write_tracked_root(backend, &context, "bench-tracked-parent", None, &rows).await?;
1896
- let mut updated_rows = tracked_rows(
1897
- config.with_rows(updated_rows.min(config.rows)),
1898
- "bench-tracked-child",
1899
- );
1900
- for (index, row) in updated_rows.iter_mut().enumerate() {
1901
- row.snapshot_content = Some(updated_snapshot_content(index, config.state_payload_bytes));
1902
- }
1903
- Ok(TrackedStateUpdateFixture {
1904
- context,
1905
- rows: updated_rows,
1906
- })
1907
- }
1908
-
1909
- pub async fn prepare_tracked_state_partial_snapshot_update_rows(
1910
- backend: &Arc<dyn Backend + Send + Sync>,
1911
- config: StorageBenchConfig,
1912
- updated_rows: usize,
1913
- ) -> Result<TrackedStateUpdateFixture, LixError> {
1914
- let context = TrackedStateContext::new();
1915
- let rows = tracked_rows(config, "bench-tracked-parent");
1916
- write_tracked_root(backend, &context, "bench-tracked-parent", None, &rows).await?;
1917
- let mut updated_rows = tracked_rows(
1918
- config.with_rows(updated_rows.min(config.rows)),
1919
- "bench-tracked-child",
1920
- );
1921
- for (index, row) in updated_rows.iter_mut().enumerate() {
1922
- row.snapshot_content = Some(partial_updated_snapshot_content(
1923
- index,
1924
- config.state_payload_bytes,
1925
- ));
1926
- }
1927
- Ok(TrackedStateUpdateFixture {
1928
- context,
1929
- rows: updated_rows,
1930
- })
1931
- }
1932
-
1933
- pub async fn prepare_tracked_state_append_child(
1934
- backend: &Arc<dyn Backend + Send + Sync>,
1935
- config: StorageBenchConfig,
1936
- ) -> Result<TrackedStateUpdateFixture, LixError> {
1937
- prepare_tracked_state_append_child_rows(backend, config, config.rows).await
1938
- }
1939
-
1940
- pub async fn prepare_tracked_state_append_child_rows(
1941
- backend: &Arc<dyn Backend + Send + Sync>,
1942
- config: StorageBenchConfig,
1943
- appended_rows: usize,
1944
- ) -> Result<TrackedStateUpdateFixture, LixError> {
1945
- let context = TrackedStateContext::new();
1946
- let rows = tracked_rows(config, "bench-tracked-parent");
1947
- write_tracked_root(backend, &context, "bench-tracked-parent", None, &rows).await?;
1948
- let mut appended_rows = tracked_rows(
1949
- config.with_rows(appended_rows.min(config.rows)),
1950
- "bench-tracked-child",
1951
- );
1952
- for (index, row) in appended_rows.iter_mut().enumerate() {
1953
- row.entity_id = EntityIdentity::single(entity_id("tracked-new", index, config.key_pattern));
1954
- row.change_id = format!("tracked-new-change-{index}");
1955
- }
1956
- Ok(TrackedStateUpdateFixture {
1957
- context,
1958
- rows: appended_rows,
1959
- })
1960
- }
1961
-
1962
- pub async fn prepare_tracked_state_tombstone_rows(
1963
- backend: &Arc<dyn Backend + Send + Sync>,
1964
- config: StorageBenchConfig,
1965
- tombstone_rows: usize,
1966
- ) -> Result<TrackedStateUpdateFixture, LixError> {
1967
- let context = TrackedStateContext::new();
1968
- let rows = tracked_rows(config, "bench-tracked-parent");
1969
- write_tracked_root(backend, &context, "bench-tracked-parent", None, &rows).await?;
1970
- let mut tombstones = tracked_rows(
1971
- config.with_rows(tombstone_rows.min(config.rows)),
1972
- "bench-tracked-child",
1973
- );
1974
- for row in &mut tombstones {
1975
- row.snapshot_content = None;
1976
- }
1977
- Ok(TrackedStateUpdateFixture {
1978
- context,
1979
- rows: tombstones,
1980
- })
1981
- }
1982
-
1983
- pub async fn tracked_state_update_existing_prepared(
1984
- backend: &Arc<dyn Backend + Send + Sync>,
1985
- fixture: &TrackedStateUpdateFixture,
1986
- ) -> Result<StorageBenchReport, LixError> {
1987
- write_tracked_root(
1988
- backend,
1989
- &fixture.context,
1990
- "bench-tracked-child",
1991
- Some("bench-tracked-parent"),
1992
- &fixture.rows,
1993
- )
1994
- .await?;
1995
- Ok(report(
1996
- fixture.rows.len(),
1997
- fixture.rows.len(),
1998
- Duration::ZERO,
1999
- ))
2000
- }
2001
-
2002
- pub async fn prepare_tracked_state_diff_update_rows(
2003
- backend: &Arc<dyn Backend + Send + Sync>,
2004
- config: StorageBenchConfig,
2005
- updated_rows: usize,
2006
- ) -> Result<TrackedStateDiffFixture, LixError> {
2007
- let fixture = prepare_tracked_state_update_rows(backend, config, updated_rows).await?;
2008
- tracked_state_update_existing_prepared(backend, &fixture).await?;
2009
- Ok(TrackedStateDiffFixture {
2010
- context: fixture.context,
2011
- left_commit_id: "bench-tracked-parent".to_string(),
2012
- right_commit_id: "bench-tracked-child".to_string(),
2013
- expected_entries: fixture.rows.len(),
2014
- })
2015
- }
2016
-
2017
- pub async fn prepare_tracked_state_diff_delta_chain(
2018
- backend: &Arc<dyn Backend + Send + Sync>,
2019
- config: StorageBenchConfig,
2020
- delta_commits: usize,
2021
- updated_rows_per_commit: usize,
2022
- ) -> Result<TrackedStateDiffFixture, LixError> {
2023
- let (context, final_commit_id) =
2024
- write_tracked_delta_chain(backend, config, delta_commits, updated_rows_per_commit).await?;
2025
- Ok(TrackedStateDiffFixture {
2026
- context,
2027
- left_commit_id: "bench-tracked-base".to_string(),
2028
- right_commit_id: final_commit_id,
2029
- expected_entries: updated_rows_per_commit.min(config.rows),
2030
- })
2031
- }
2032
-
2033
- pub async fn prepare_tracked_state_diff_tombstone_rows(
2034
- backend: &Arc<dyn Backend + Send + Sync>,
2035
- config: StorageBenchConfig,
2036
- tombstone_rows: usize,
2037
- ) -> Result<TrackedStateDiffFixture, LixError> {
2038
- let fixture = prepare_tracked_state_tombstone_rows(backend, config, tombstone_rows).await?;
2039
- tracked_state_update_existing_prepared(backend, &fixture).await?;
2040
- Ok(TrackedStateDiffFixture {
2041
- context: fixture.context,
2042
- left_commit_id: "bench-tracked-parent".to_string(),
2043
- right_commit_id: "bench-tracked-child".to_string(),
2044
- expected_entries: fixture.rows.len(),
2045
- })
2046
- }
2047
-
2048
- pub async fn prepare_tracked_state_diff_equal(
2049
- backend: &Arc<dyn Backend + Send + Sync>,
2050
- config: StorageBenchConfig,
2051
- ) -> Result<TrackedStateDiffFixture, LixError> {
2052
- let context = TrackedStateContext::new();
2053
- let rows = tracked_rows(config, "bench-tracked-parent");
2054
- write_tracked_root(backend, &context, "bench-tracked-parent", None, &rows).await?;
2055
- Ok(TrackedStateDiffFixture {
2056
- context,
2057
- left_commit_id: "bench-tracked-parent".to_string(),
2058
- right_commit_id: "bench-tracked-parent".to_string(),
2059
- expected_entries: 0,
2060
- })
2061
- }
2062
-
2063
- pub async fn tracked_state_diff_commits_prepared(
2064
- backend: &Arc<dyn Backend + Send + Sync>,
2065
- fixture: &TrackedStateDiffFixture,
2066
- ) -> Result<StorageBenchReport, LixError> {
2067
- let mut reader = fixture
2068
- .context
2069
- .reader(StorageContext::new(Arc::clone(backend)));
2070
- let diff = reader
2071
- .diff_commits(
2072
- &fixture.left_commit_id,
2073
- &fixture.right_commit_id,
2074
- &TrackedStateDiffRequest::default(),
2075
- )
2076
- .await?;
2077
- Ok(report(
2078
- fixture.expected_entries,
2079
- diff.entries.len(),
2080
- Duration::ZERO,
2081
- ))
2082
- }
2083
-
2084
- pub async fn tracked_state_materialize_root_prepared(
2085
- backend: &Arc<dyn Backend + Send + Sync>,
2086
- fixture: &TrackedStateMaterializeFixture,
2087
- ) -> Result<StorageBenchReport, LixError> {
2088
- materialize_tracked_root(backend, &fixture.context, &fixture.commit_id).await?;
2089
- Ok(report(
2090
- fixture.expected_rows,
2091
- fixture.expected_rows,
2092
- Duration::ZERO,
2093
- ))
2094
- }
2095
-
2096
- pub async fn prepare_json_pointer_tracked_state_write_root(
2097
- rows: &[JsonPointerStorageRow],
2098
- ) -> Result<TrackedStateWriteRootFixture, LixError> {
2099
- Ok(TrackedStateWriteRootFixture {
2100
- context: TrackedStateContext::new(),
2101
- rows: json_pointer_tracked_rows(rows, "json-pointer-base", false),
2102
- })
2103
- }
2104
-
2105
- pub async fn prepare_json_pointer_tracked_state_read(
2106
- backend: &Arc<dyn Backend + Send + Sync>,
2107
- rows: &[JsonPointerStorageRow],
2108
- ) -> Result<JsonPointerTrackedStateReadFixture, LixError> {
2109
- let context = TrackedStateContext::new();
2110
- let materialized_rows = json_pointer_tracked_rows(rows, "json-pointer-base", false);
2111
- write_tracked_root(
2112
- backend,
2113
- &context,
2114
- "json-pointer-base",
2115
- None,
2116
- &materialized_rows,
2117
- )
2118
- .await?;
2119
- Ok(JsonPointerTrackedStateReadFixture {
2120
- context,
2121
- rows: rows.to_vec(),
2122
- commit_id: "json-pointer-base".to_string(),
2123
- })
2124
- }
2125
-
2126
- pub async fn prepare_json_pointer_tracked_state_diff_update_rows(
2127
- backend: &Arc<dyn Backend + Send + Sync>,
2128
- rows: &[JsonPointerStorageRow],
2129
- updated_rows: usize,
2130
- ) -> Result<JsonPointerTrackedStateDiffFixture, LixError> {
2131
- let context = TrackedStateContext::new();
2132
- let base_rows = json_pointer_tracked_rows(rows, "json-pointer-base", false);
2133
- write_tracked_root(backend, &context, "json-pointer-base", None, &base_rows).await?;
2134
- let child_rows = json_pointer_tracked_rows(
2135
- &rows[..updated_rows.min(rows.len())],
2136
- "json-pointer-child",
2137
- true,
2138
- );
2139
- write_tracked_root(
2140
- backend,
2141
- &context,
2142
- "json-pointer-child",
2143
- Some("json-pointer-base"),
2144
- &child_rows,
2145
- )
2146
- .await?;
2147
- Ok(JsonPointerTrackedStateDiffFixture {
2148
- context,
2149
- left_commit_id: "json-pointer-base".to_string(),
2150
- right_commit_id: "json-pointer-child".to_string(),
2151
- expected_entries: child_rows.len(),
2152
- })
2153
- }
2154
-
2155
- pub async fn json_pointer_tracked_state_get_many_prepared(
2156
- backend: &Arc<dyn Backend + Send + Sync>,
2157
- fixture: &JsonPointerTrackedStateReadFixture,
2158
- ) -> Result<StorageBenchReport, LixError> {
2159
- let mut reader = fixture
2160
- .context
2161
- .reader(StorageContext::new(Arc::clone(backend)));
2162
- let requests = fixture
2163
- .rows
2164
- .iter()
2165
- .map(|row| TrackedStateRowRequest {
2166
- schema_key: "json_pointer".to_string(),
2167
- entity_id: EntityIdentity::single(row.path.as_str()),
2168
- file_id: NullableKeyFilter::Null,
2169
- })
2170
- .collect::<Vec<_>>();
2171
- let verified_rows = reader
2172
- .load_rows_at_commit(&fixture.commit_id, &requests)
2173
- .await?
2174
- .into_iter()
2175
- .filter(Option::is_some)
2176
- .count();
2177
- Ok(report(fixture.rows.len(), verified_rows, Duration::ZERO))
2178
- }
2179
-
2180
- pub async fn json_pointer_tracked_state_get_many_missing_prepared(
2181
- backend: &Arc<dyn Backend + Send + Sync>,
2182
- fixture: &JsonPointerTrackedStateReadFixture,
2183
- ) -> Result<StorageBenchReport, LixError> {
2184
- let mut reader = fixture
2185
- .context
2186
- .reader(StorageContext::new(Arc::clone(backend)));
2187
- let requests = fixture
2188
- .rows
2189
- .iter()
2190
- .map(|row| TrackedStateRowRequest {
2191
- schema_key: "json_pointer".to_string(),
2192
- entity_id: EntityIdentity::single(format!("missing{}", row.path)),
2193
- file_id: NullableKeyFilter::Null,
2194
- })
2195
- .collect::<Vec<_>>();
2196
- let verified_rows = reader
2197
- .load_rows_at_commit(&fixture.commit_id, &requests)
2198
- .await?
2199
- .into_iter()
2200
- .filter(Option::is_none)
2201
- .count();
2202
- Ok(report(fixture.rows.len(), verified_rows, Duration::ZERO))
2203
- }
2204
-
2205
- pub async fn json_pointer_tracked_state_scan_keys_only_prepared(
2206
- backend: &Arc<dyn Backend + Send + Sync>,
2207
- fixture: &JsonPointerTrackedStateReadFixture,
2208
- ) -> Result<StorageBenchReport, LixError> {
2209
- json_pointer_scan_with_projection(
2210
- backend,
2211
- fixture,
2212
- TrackedStateProjection {
2213
- columns: vec!["entity_id".to_string()],
2214
- },
2215
- )
2216
- .await
2217
- }
2218
-
2219
- pub async fn json_pointer_tracked_state_scan_headers_only_prepared(
2220
- backend: &Arc<dyn Backend + Send + Sync>,
2221
- fixture: &JsonPointerTrackedStateReadFixture,
2222
- ) -> Result<StorageBenchReport, LixError> {
2223
- json_pointer_scan_with_projection(
2224
- backend,
2225
- fixture,
2226
- TrackedStateProjection {
2227
- columns: tracked_state_header_columns(),
2228
- },
2229
- )
2230
- .await
2231
- }
2232
-
2233
- pub async fn json_pointer_tracked_state_scan_full_rows_prepared(
2234
- backend: &Arc<dyn Backend + Send + Sync>,
2235
- fixture: &JsonPointerTrackedStateReadFixture,
2236
- ) -> Result<StorageBenchReport, LixError> {
2237
- json_pointer_scan_with_projection(backend, fixture, TrackedStateProjection::default()).await
2238
- }
2239
-
2240
- pub async fn json_pointer_tracked_state_prefix_scan_schema_prepared(
2241
- backend: &Arc<dyn Backend + Send + Sync>,
2242
- fixture: &JsonPointerTrackedStateReadFixture,
2243
- ) -> Result<StorageBenchReport, LixError> {
2244
- json_pointer_scan_with_projection(backend, fixture, TrackedStateProjection::default()).await
2245
- }
2246
-
2247
- pub async fn json_pointer_tracked_state_prefix_scan_schema_file_null_prepared(
2248
- backend: &Arc<dyn Backend + Send + Sync>,
2249
- fixture: &JsonPointerTrackedStateReadFixture,
2250
- ) -> Result<StorageBenchReport, LixError> {
2251
- json_pointer_scan_with_projection(backend, fixture, TrackedStateProjection::default()).await
2252
- }
2253
-
2254
- async fn json_pointer_scan_with_projection(
2255
- backend: &Arc<dyn Backend + Send + Sync>,
2256
- fixture: &JsonPointerTrackedStateReadFixture,
2257
- projection: TrackedStateProjection,
2258
- ) -> Result<StorageBenchReport, LixError> {
2259
- let mut reader = fixture
2260
- .context
2261
- .reader(StorageContext::new(Arc::clone(backend)));
2262
- let verified_rows = reader
2263
- .scan_rows_at_commit(
2264
- &fixture.commit_id,
2265
- &TrackedStateScanRequest {
2266
- filter: TrackedStateFilter {
2267
- schema_keys: vec!["json_pointer".to_string()],
2268
- file_ids: vec![NullableKeyFilter::Null],
2269
- ..Default::default()
2270
- },
2271
- projection,
2272
- ..Default::default()
2273
- },
2274
- )
2275
- .await?
2276
- .len();
2277
- Ok(report(fixture.rows.len(), verified_rows, Duration::ZERO))
2278
- }
2279
-
2280
- pub async fn prepare_json_pointer_tracked_state_update_rows(
2281
- backend: &Arc<dyn Backend + Send + Sync>,
2282
- rows: &[JsonPointerStorageRow],
2283
- updated_rows: usize,
2284
- ) -> Result<TrackedStateUpdateFixture, LixError> {
2285
- let context = TrackedStateContext::new();
2286
- let base_rows = json_pointer_tracked_rows(rows, "json-pointer-base", false);
2287
- write_tracked_root(backend, &context, "json-pointer-base", None, &base_rows).await?;
2288
- let child_rows = json_pointer_tracked_rows(
2289
- &rows[..updated_rows.min(rows.len())],
2290
- "json-pointer-child",
2291
- true,
2292
- );
2293
- Ok(TrackedStateUpdateFixture {
2294
- context,
2295
- rows: child_rows,
2296
- })
2297
- }
2298
-
2299
- pub async fn prepare_json_pointer_tracked_state_tombstone_rows(
2300
- backend: &Arc<dyn Backend + Send + Sync>,
2301
- rows: &[JsonPointerStorageRow],
2302
- tombstone_rows: usize,
2303
- ) -> Result<TrackedStateUpdateFixture, LixError> {
2304
- let context = TrackedStateContext::new();
2305
- let base_rows = json_pointer_tracked_rows(rows, "json-pointer-base", false);
2306
- write_tracked_root(backend, &context, "json-pointer-base", None, &base_rows).await?;
2307
- let mut child_rows = json_pointer_tracked_rows(
2308
- &rows[..tombstone_rows.min(rows.len())],
2309
- "json-pointer-child",
2310
- true,
2311
- );
2312
- for row in &mut child_rows {
2313
- row.snapshot_content = None;
2314
- }
2315
- Ok(TrackedStateUpdateFixture {
2316
- context,
2317
- rows: child_rows,
2318
- })
2319
- }
2320
-
2321
- pub async fn prepare_json_pointer_tracked_state_diff_delta_chain(
2322
- backend: &Arc<dyn Backend + Send + Sync>,
2323
- rows: &[JsonPointerStorageRow],
2324
- delta_commits: usize,
2325
- updated_rows_per_commit: usize,
2326
- ) -> Result<JsonPointerTrackedStateDiffFixture, LixError> {
2327
- let (context, final_commit_id) =
2328
- write_json_pointer_delta_chain(backend, rows, delta_commits, updated_rows_per_commit)
2329
- .await?;
2330
- Ok(JsonPointerTrackedStateDiffFixture {
2331
- context,
2332
- left_commit_id: "json-pointer-base".to_string(),
2333
- right_commit_id: final_commit_id,
2334
- expected_entries: updated_rows_per_commit.min(rows.len()),
2335
- })
2336
- }
2337
-
2338
- pub async fn prepare_json_pointer_tracked_state_materialize_delta_chain(
2339
- backend: &Arc<dyn Backend + Send + Sync>,
2340
- rows: &[JsonPointerStorageRow],
2341
- delta_commits: usize,
2342
- updated_rows_per_commit: usize,
2343
- ) -> Result<TrackedStateMaterializeFixture, LixError> {
2344
- let (context, final_commit_id) =
2345
- write_json_pointer_delta_chain(backend, rows, delta_commits, updated_rows_per_commit)
2346
- .await?;
2347
- Ok(TrackedStateMaterializeFixture {
2348
- context,
2349
- commit_id: final_commit_id,
2350
- expected_rows: rows.len(),
2351
- })
2352
- }
2353
-
2354
- pub async fn json_pointer_tracked_state_changed_keys_prepared(
2355
- backend: &Arc<dyn Backend + Send + Sync>,
2356
- fixture: &JsonPointerTrackedStateDiffFixture,
2357
- ) -> Result<StorageBenchReport, LixError> {
2358
- let mut reader = fixture
2359
- .context
2360
- .reader(StorageContext::new(Arc::clone(backend)));
2361
- let diff = reader
2362
- .diff_commits(
2363
- &fixture.left_commit_id,
2364
- &fixture.right_commit_id,
2365
- &TrackedStateDiffRequest::default(),
2366
- )
2367
- .await?;
2368
- Ok(report(
2369
- fixture.expected_entries,
2370
- diff.entries.len(),
2371
- Duration::ZERO,
2372
- ))
2373
- }
2374
-
2375
- pub async fn prepare_untracked_state_write_rows(
2376
- config: StorageBenchConfig,
2377
- ) -> Result<UntrackedStateWriteFixture, LixError> {
2378
- Ok(UntrackedStateWriteFixture {
2379
- context: UntrackedStateContext::new(),
2380
- rows: untracked_rows(config),
2381
- })
2382
- }
2383
-
2384
- pub async fn untracked_state_write_rows_prepared(
2385
- backend: &Arc<dyn Backend + Send + Sync>,
2386
- fixture: &UntrackedStateWriteFixture,
2387
- ) -> Result<StorageBenchReport, LixError> {
2388
- write_untracked_rows(backend, &fixture.context, &fixture.rows).await?;
2389
- let verified_rows = scan_untracked(
2390
- backend,
2391
- &fixture.context,
2392
- UntrackedStateScanRequest::default(),
2393
- )
2394
- .await?
2395
- .len();
2396
- Ok(report(fixture.rows.len(), verified_rows, Duration::ZERO))
2397
- }
2398
-
2399
- pub async fn prepare_untracked_state_read(
2400
- backend: &Arc<dyn Backend + Send + Sync>,
2401
- config: StorageBenchConfig,
2402
- ) -> Result<UntrackedStateReadFixture, LixError> {
2403
- let context = UntrackedStateContext::new();
2404
- let rows = untracked_rows(config);
2405
- write_untracked_rows(backend, &context, &rows).await?;
2406
- Ok(UntrackedStateReadFixture {
2407
- context,
2408
- rows: config.rows,
2409
- key_pattern: config.key_pattern,
2410
- selectivity: config.selectivity,
2411
- })
2412
- }
2413
-
2414
- pub async fn untracked_state_read_point_hit_prepared(
2415
- backend: &Arc<dyn Backend + Send + Sync>,
2416
- fixture: &UntrackedStateReadFixture,
2417
- ) -> Result<StorageBenchReport, LixError> {
2418
- let mut verified_rows = 0;
2419
- let mut reader = fixture
2420
- .context
2421
- .reader(StorageContext::new(Arc::clone(backend)));
2422
- for index in 0..fixture.rows {
2423
- if reader
2424
- .load_row(&UntrackedStateRowRequest {
2425
- schema_key: untracked_schema_key(index, StorageBenchSelectivity::Percent100),
2426
- version_id: "bench-version".to_string(),
2427
- entity_id: EntityIdentity::single(entity_id(
2428
- "untracked",
2429
- index,
2430
- fixture.key_pattern,
2431
- )),
2432
- file_id: NullableKeyFilter::Value("bench.json".to_string()),
2433
- })
2434
- .await?
2435
- .is_some()
2436
- {
2437
- verified_rows += 1;
2438
- }
2439
- }
2440
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
2441
- }
2442
-
2443
- pub async fn untracked_state_read_point_hit_constant_prepared(
2444
- backend: &Arc<dyn Backend + Send + Sync>,
2445
- fixture: &UntrackedStateReadFixture,
2446
- measured_reads: usize,
2447
- ) -> Result<StorageBenchReport, LixError> {
2448
- let mut verified_rows = 0;
2449
- let mut reader = fixture
2450
- .context
2451
- .reader(StorageContext::new(Arc::clone(backend)));
2452
- for index in 0..measured_reads.min(fixture.rows) {
2453
- if reader
2454
- .load_row(&UntrackedStateRowRequest {
2455
- schema_key: untracked_schema_key(index, StorageBenchSelectivity::Percent100),
2456
- version_id: "bench-version".to_string(),
2457
- entity_id: EntityIdentity::single(entity_id(
2458
- "untracked",
2459
- index,
2460
- fixture.key_pattern,
2461
- )),
2462
- file_id: NullableKeyFilter::Value("bench.json".to_string()),
2463
- })
2464
- .await?
2465
- .is_some()
2466
- {
2467
- verified_rows += 1;
2468
- }
2469
- }
2470
- Ok(report(
2471
- measured_reads.min(fixture.rows),
2472
- verified_rows,
2473
- Duration::ZERO,
2474
- ))
2475
- }
2476
-
2477
- pub async fn untracked_state_read_point_miss_prepared(
2478
- backend: &Arc<dyn Backend + Send + Sync>,
2479
- fixture: &UntrackedStateReadFixture,
2480
- ) -> Result<StorageBenchReport, LixError> {
2481
- let mut misses = 0;
2482
- let mut reader = fixture
2483
- .context
2484
- .reader(StorageContext::new(Arc::clone(backend)));
2485
- for index in 0..fixture.rows {
2486
- if reader
2487
- .load_row(&UntrackedStateRowRequest {
2488
- schema_key: "bench_untracked_entity".to_string(),
2489
- version_id: "bench-version".to_string(),
2490
- entity_id: EntityIdentity::single(format!("missing-{index}")),
2491
- file_id: NullableKeyFilter::Value("bench.json".to_string()),
2492
- })
2493
- .await?
2494
- .is_none()
2495
- {
2496
- misses += 1;
2497
- }
2498
- }
2499
- Ok(report(fixture.rows, misses, Duration::ZERO))
2500
- }
2501
-
2502
- pub async fn untracked_state_scan_all_prepared(
2503
- backend: &Arc<dyn Backend + Send + Sync>,
2504
- fixture: &UntrackedStateReadFixture,
2505
- ) -> Result<StorageBenchReport, LixError> {
2506
- let verified_rows = scan_untracked(
2507
- backend,
2508
- &fixture.context,
2509
- UntrackedStateScanRequest::default(),
2510
- )
2511
- .await?
2512
- .len();
2513
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
2514
- }
2515
-
2516
- pub async fn untracked_state_scan_keys_only_prepared(
2517
- backend: &Arc<dyn Backend + Send + Sync>,
2518
- fixture: &UntrackedStateReadFixture,
2519
- ) -> Result<StorageBenchReport, LixError> {
2520
- let verified_rows = scan_untracked(
2521
- backend,
2522
- &fixture.context,
2523
- UntrackedStateScanRequest {
2524
- projection: UntrackedStateProjection {
2525
- columns: vec!["entity_id".to_string()],
2526
- },
2527
- ..Default::default()
2528
- },
2529
- )
2530
- .await?
2531
- .len();
2532
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
2533
- }
2534
-
2535
- pub async fn untracked_state_scan_headers_only_prepared(
2536
- backend: &Arc<dyn Backend + Send + Sync>,
2537
- fixture: &UntrackedStateReadFixture,
2538
- ) -> Result<StorageBenchReport, LixError> {
2539
- let verified_rows = scan_untracked(
2540
- backend,
2541
- &fixture.context,
2542
- UntrackedStateScanRequest {
2543
- projection: UntrackedStateProjection {
2544
- columns: untracked_state_header_columns(),
2545
- },
2546
- ..Default::default()
2547
- },
2548
- )
2549
- .await?
2550
- .len();
2551
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
2552
- }
2553
-
2554
- pub async fn untracked_state_scan_full_rows_prepared(
2555
- backend: &Arc<dyn Backend + Send + Sync>,
2556
- fixture: &UntrackedStateReadFixture,
2557
- ) -> Result<StorageBenchReport, LixError> {
2558
- untracked_state_scan_all_prepared(backend, fixture).await
2559
- }
2560
-
2561
- pub async fn untracked_state_scan_version_prepared(
2562
- backend: &Arc<dyn Backend + Send + Sync>,
2563
- fixture: &UntrackedStateReadFixture,
2564
- ) -> Result<StorageBenchReport, LixError> {
2565
- let verified_rows = scan_untracked(
2566
- backend,
2567
- &fixture.context,
2568
- UntrackedStateScanRequest {
2569
- filter: UntrackedStateFilter {
2570
- version_ids: vec!["bench-version".to_string()],
2571
- ..Default::default()
2572
- },
2573
- ..Default::default()
2574
- },
2575
- )
2576
- .await?
2577
- .len();
2578
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
2579
- }
2580
-
2581
- pub async fn untracked_state_scan_schema_prepared(
2582
- backend: &Arc<dyn Backend + Send + Sync>,
2583
- fixture: &UntrackedStateReadFixture,
2584
- ) -> Result<StorageBenchReport, LixError> {
2585
- let verified_rows = scan_untracked(
2586
- backend,
2587
- &fixture.context,
2588
- UntrackedStateScanRequest {
2589
- filter: UntrackedStateFilter {
2590
- schema_keys: vec![untracked_schema_key(0, StorageBenchSelectivity::Percent100)],
2591
- ..Default::default()
2592
- },
2593
- ..Default::default()
2594
- },
2595
- )
2596
- .await?
2597
- .len();
2598
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
2599
- }
2600
-
2601
- pub async fn untracked_state_scan_schema_selective_prepared(
2602
- backend: &Arc<dyn Backend + Send + Sync>,
2603
- fixture: &UntrackedStateReadFixture,
2604
- ) -> Result<StorageBenchReport, LixError> {
2605
- let verified_rows = scan_untracked(
2606
- backend,
2607
- &fixture.context,
2608
- UntrackedStateScanRequest {
2609
- filter: UntrackedStateFilter {
2610
- schema_keys: vec![UNTRACKED_MATCH_SCHEMA_KEY.to_string()],
2611
- ..Default::default()
2612
- },
2613
- ..Default::default()
2614
- },
2615
- )
2616
- .await?
2617
- .len();
2618
- Ok(report(
2619
- fixture.selectivity.expected_rows(fixture.rows),
2620
- verified_rows,
2621
- Duration::ZERO,
2622
- ))
2623
- }
2624
-
2625
- pub async fn prepare_untracked_state_overwrite(
2626
- backend: &Arc<dyn Backend + Send + Sync>,
2627
- config: StorageBenchConfig,
2628
- ) -> Result<UntrackedStateWriteFixture, LixError> {
2629
- let context = UntrackedStateContext::new();
2630
- let rows = untracked_rows(config);
2631
- write_untracked_rows(backend, &context, &rows).await?;
2632
- let mut updated_rows =
2633
- untracked_rows(config.with_rows(config.update_fraction.rows(config.rows)));
2634
- for (index, row) in updated_rows.iter_mut().enumerate() {
2635
- row.snapshot_content = Some(updated_snapshot_content(index, config.state_payload_bytes));
2636
- }
2637
- Ok(UntrackedStateWriteFixture {
2638
- context,
2639
- rows: updated_rows,
2640
- })
2641
- }
2642
-
2643
- pub async fn prepare_untracked_state_insert_new_keys(
2644
- backend: &Arc<dyn Backend + Send + Sync>,
2645
- config: StorageBenchConfig,
2646
- ) -> Result<UntrackedStateWriteFixture, LixError> {
2647
- let context = UntrackedStateContext::new();
2648
- let rows = untracked_rows(config);
2649
- write_untracked_rows(backend, &context, &rows).await?;
2650
- let mut new_rows = untracked_rows(config);
2651
- for (index, row) in new_rows.iter_mut().enumerate() {
2652
- row.entity_id =
2653
- EntityIdentity::single(entity_id("untracked-new", index, config.key_pattern));
2654
- }
2655
- Ok(UntrackedStateWriteFixture {
2656
- context,
2657
- rows: new_rows,
2658
- })
2659
- }
2660
-
2661
- pub async fn untracked_state_overwrite_existing_prepared(
2662
- backend: &Arc<dyn Backend + Send + Sync>,
2663
- fixture: &UntrackedStateWriteFixture,
2664
- ) -> Result<StorageBenchReport, LixError> {
2665
- write_untracked_rows(backend, &fixture.context, &fixture.rows).await?;
2666
- let verified_rows = scan_untracked(
2667
- backend,
2668
- &fixture.context,
2669
- UntrackedStateScanRequest::default(),
2670
- )
2671
- .await?
2672
- .len();
2673
- Ok(report(fixture.rows.len(), verified_rows, Duration::ZERO))
2674
- }
2675
-
2676
- pub async fn prepare_changelog_append_changes(
2677
- config: StorageBenchConfig,
2678
- ) -> Result<ChangelogAppendFixture, LixError> {
2679
- Ok(ChangelogAppendFixture {
2680
- context: CommitStoreContext::new(),
2681
- changes: changelog_materialized_changes(config),
2682
- })
2683
- }
2684
-
2685
- pub async fn prepare_changelog_append_tombstones(
2686
- config: StorageBenchConfig,
2687
- ) -> Result<ChangelogAppendFixture, LixError> {
2688
- Ok(ChangelogAppendFixture {
2689
- context: CommitStoreContext::new(),
2690
- changes: changelog_tombstone_changes(config),
2691
- })
2692
- }
2693
-
2694
- pub async fn prepare_changelog_append_metadata(
2695
- config: StorageBenchConfig,
2696
- ) -> Result<ChangelogAppendFixture, LixError> {
2697
- Ok(ChangelogAppendFixture {
2698
- context: CommitStoreContext::new(),
2699
- changes: changelog_metadata_changes(config),
2700
- })
2701
- }
2702
-
2703
- pub async fn prepare_changelog_append_shared_payload(
2704
- config: StorageBenchConfig,
2705
- ) -> Result<ChangelogAppendFixture, LixError> {
2706
- Ok(ChangelogAppendFixture {
2707
- context: CommitStoreContext::new(),
2708
- changes: changelog_shared_payload_changes(config),
2709
- })
2710
- }
2711
-
2712
- pub async fn prepare_changelog_append_shared_metadata(
2713
- config: StorageBenchConfig,
2714
- ) -> Result<ChangelogAppendFixture, LixError> {
2715
- Ok(ChangelogAppendFixture {
2716
- context: CommitStoreContext::new(),
2717
- changes: changelog_shared_metadata_changes(config),
2718
- })
2719
- }
2720
-
2721
- pub async fn prepare_changelog_append_shared_payload_and_metadata(
2722
- config: StorageBenchConfig,
2723
- ) -> Result<ChangelogAppendFixture, LixError> {
2724
- Ok(ChangelogAppendFixture {
2725
- context: CommitStoreContext::new(),
2726
- changes: changelog_shared_payload_and_metadata_changes(config),
2727
- })
2728
- }
2729
-
2730
- pub async fn prepare_changelog_append_composite_entity_ids(
2731
- config: StorageBenchConfig,
2732
- ) -> Result<ChangelogAppendFixture, LixError> {
2733
- Ok(ChangelogAppendFixture {
2734
- context: CommitStoreContext::new(),
2735
- changes: changelog_composite_entity_id_changes(config),
2736
- })
2737
- }
2738
-
2739
- pub async fn prepare_changelog_codec(
2740
- config: StorageBenchConfig,
2741
- ) -> Result<ChangelogCodecFixture, LixError> {
2742
- let changes = changelog_changes(config);
2743
- let encoded_changes = changes
2744
- .iter()
2745
- .map(|change| crate::commit_store::codec::encode_change_ref(change.as_ref()))
2746
- .collect::<Result<Vec<_>, _>>()?;
2747
- Ok(ChangelogCodecFixture {
2748
- changes,
2749
- encoded_changes,
2750
- })
2751
- }
2752
-
2753
- pub async fn changelog_append_changes_prepared(
2754
- backend: &Arc<dyn Backend + Send + Sync>,
2755
- fixture: &ChangelogAppendFixture,
2756
- ) -> Result<StorageBenchReport, LixError> {
2757
- append_changelog_changes(backend, &fixture.context, &fixture.changes).await?;
2758
- let reader = fixture
2759
- .context
2760
- .reader(StorageContext::new(Arc::clone(backend)));
2761
- let verified_rows = reader
2762
- .scan_changes(&ChangeScanRequest::default())
2763
- .await?
2764
- .len();
2765
- Ok(report(fixture.changes.len(), verified_rows, Duration::ZERO))
2766
- }
2767
-
2768
- pub async fn prepare_changelog_read(
2769
- backend: &Arc<dyn Backend + Send + Sync>,
2770
- config: StorageBenchConfig,
2771
- ) -> Result<ChangelogReadFixture, LixError> {
2772
- let context = CommitStoreContext::new();
2773
- let changes = changelog_materialized_changes(config);
2774
- append_changelog_changes(backend, &context, &changes).await?;
2775
- Ok(ChangelogReadFixture {
2776
- context,
2777
- rows: config.rows,
2778
- })
2779
- }
2780
-
2781
- pub async fn prepare_changelog_read_with_selectivity(
2782
- backend: &Arc<dyn Backend + Send + Sync>,
2783
- config: StorageBenchConfig,
2784
- ) -> Result<ChangelogReadFixture, LixError> {
2785
- let context = CommitStoreContext::new();
2786
- let changes = changelog_selective_changes(config);
2787
- append_changelog_changes(backend, &context, &changes).await?;
2788
- Ok(ChangelogReadFixture {
2789
- context,
2790
- rows: config.rows,
2791
- })
2792
- }
2793
-
2794
- pub async fn prepare_changelog_read_entity_history(
2795
- backend: &Arc<dyn Backend + Send + Sync>,
2796
- config: StorageBenchConfig,
2797
- ) -> Result<ChangelogReadFixture, LixError> {
2798
- let context = CommitStoreContext::new();
2799
- let changes = changelog_entity_history_changes(config);
2800
- append_changelog_changes(backend, &context, &changes).await?;
2801
- Ok(ChangelogReadFixture {
2802
- context,
2803
- rows: config.rows,
2804
- })
2805
- }
2806
-
2807
- pub async fn changelog_encode_only_prepared(
2808
- fixture: &ChangelogCodecFixture,
2809
- ) -> Result<StorageBenchReport, LixError> {
2810
- let mut verified_rows = 0;
2811
- let mut encoded_bytes = 0;
2812
- for change in &fixture.changes {
2813
- encoded_bytes += crate::commit_store::codec::encode_change_ref(change.as_ref())?.len();
2814
- verified_rows += 1;
2815
- }
2816
- Ok(report(
2817
- fixture.changes.len(),
2818
- verified_rows + usize::from(encoded_bytes == 0),
2819
- Duration::ZERO,
2820
- ))
2821
- }
2822
-
2823
- pub async fn changelog_decode_only_prepared(
2824
- fixture: &ChangelogCodecFixture,
2825
- ) -> Result<StorageBenchReport, LixError> {
2826
- let mut verified_rows = 0;
2827
- let mut decoded_bytes = 0;
2828
- for bytes in &fixture.encoded_changes {
2829
- let change = crate::commit_store::codec::decode_change(bytes)?;
2830
- decoded_bytes += change.schema_key.len();
2831
- verified_rows += 1;
2832
- }
2833
- Ok(report(
2834
- fixture.encoded_changes.len(),
2835
- verified_rows + usize::from(decoded_bytes == 0),
2836
- Duration::ZERO,
2837
- ))
2838
- }
2839
-
2840
- pub async fn changelog_load_changes_hit_prepared(
2841
- backend: &Arc<dyn Backend + Send + Sync>,
2842
- fixture: &ChangelogReadFixture,
2843
- ) -> Result<StorageBenchReport, LixError> {
2844
- let reader = fixture
2845
- .context
2846
- .reader(StorageContext::new(Arc::clone(backend)));
2847
- let change_ids = (0..fixture.rows)
2848
- .map(|index| format!("bench-change-{index}"))
2849
- .collect::<Vec<_>>();
2850
- let verified_rows = reader
2851
- .load_changes(&change_ids)
2852
- .await?
2853
- .into_iter()
2854
- .filter(Option::is_some)
2855
- .count();
2856
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
2857
- }
2858
-
2859
- pub async fn changelog_load_changes_miss_prepared(
2860
- backend: &Arc<dyn Backend + Send + Sync>,
2861
- fixture: &ChangelogReadFixture,
2862
- ) -> Result<StorageBenchReport, LixError> {
2863
- let reader = fixture
2864
- .context
2865
- .reader(StorageContext::new(Arc::clone(backend)));
2866
- let change_ids = (0..fixture.rows)
2867
- .map(|index| format!("missing-change-{index}"))
2868
- .collect::<Vec<_>>();
2869
- let misses = reader
2870
- .load_changes(&change_ids)
2871
- .await?
2872
- .into_iter()
2873
- .filter(Option::is_none)
2874
- .count();
2875
- Ok(report(fixture.rows, misses, Duration::ZERO))
2876
- }
2877
-
2878
- pub async fn changelog_scan_all_prepared(
2879
- backend: &Arc<dyn Backend + Send + Sync>,
2880
- fixture: &ChangelogReadFixture,
2881
- ) -> Result<StorageBenchReport, LixError> {
2882
- let reader = fixture
2883
- .context
2884
- .reader(StorageContext::new(Arc::clone(backend)));
2885
- let verified_rows = reader
2886
- .scan_changes(&ChangeScanRequest::default())
2887
- .await?
2888
- .len();
2889
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
2890
- }
2891
-
2892
- pub async fn changelog_scan_full_changes_prepared(
2893
- backend: &Arc<dyn Backend + Send + Sync>,
2894
- fixture: &ChangelogReadFixture,
2895
- ) -> Result<StorageBenchReport, LixError> {
2896
- changelog_scan_all_prepared(backend, fixture).await
2897
- }
2898
-
2899
- pub async fn changelog_scan_limit_100_prepared(
2900
- backend: &Arc<dyn Backend + Send + Sync>,
2901
- fixture: &ChangelogReadFixture,
2902
- ) -> Result<StorageBenchReport, LixError> {
2903
- let reader = fixture
2904
- .context
2905
- .reader(StorageContext::new(Arc::clone(backend)));
2906
- let expected = fixture.rows.min(100);
2907
- let verified_rows = reader
2908
- .scan_changes(&ChangeScanRequest {
2909
- limit: Some(expected),
2910
- })
2911
- .await?
2912
- .len();
2913
- Ok(report(expected, verified_rows, Duration::ZERO))
2914
- }
2915
-
2916
- pub async fn changelog_scan_change_set_prepared(
2917
- backend: &Arc<dyn Backend + Send + Sync>,
2918
- fixture: &ChangelogReadFixture,
2919
- ) -> Result<StorageBenchReport, LixError> {
2920
- let reader = fixture
2921
- .context
2922
- .reader(StorageContext::new(Arc::clone(backend)));
2923
- let change_ids = (0..fixture.rows)
2924
- .map(|index| format!("bench-change-{index}"))
2925
- .collect::<Vec<_>>();
2926
- let verified_rows = reader
2927
- .load_changes(&change_ids)
2928
- .await?
2929
- .into_iter()
2930
- .filter(Option::is_some)
2931
- .count();
2932
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
2933
- }
2934
-
2935
- pub async fn changelog_scan_schema_prepared(
2936
- backend: &Arc<dyn Backend + Send + Sync>,
2937
- fixture: &ChangelogReadFixture,
2938
- selectivity: StorageBenchSelectivity,
2939
- ) -> Result<StorageBenchReport, LixError> {
2940
- let reader = fixture
2941
- .context
2942
- .reader(StorageContext::new(Arc::clone(backend)));
2943
- let changes = reader.scan_changes(&ChangeScanRequest::default()).await?;
2944
- let verified_rows = changes
2945
- .iter()
2946
- .filter(|change| change.record.schema_key == CHANGELOG_MATCH_SCHEMA_KEY)
2947
- .count();
2948
- Ok(report(
2949
- selectivity.expected_rows(fixture.rows),
2950
- verified_rows,
2951
- Duration::ZERO,
2952
- ))
2953
- }
2954
-
2955
- pub async fn changelog_scan_entity_history_prepared(
2956
- backend: &Arc<dyn Backend + Send + Sync>,
2957
- fixture: &ChangelogReadFixture,
2958
- ) -> Result<StorageBenchReport, LixError> {
2959
- let reader = fixture
2960
- .context
2961
- .reader(StorageContext::new(Arc::clone(backend)));
2962
- let changes = reader.scan_changes(&ChangeScanRequest::default()).await?;
2963
- let target = EntityIdentity::single(CHANGELOG_HISTORY_ENTITY_ID);
2964
- let verified_rows = changes
2965
- .iter()
2966
- .filter(|change| change.record.entity_id == target)
2967
- .count();
2968
- Ok(report(
2969
- fixture.rows.div_ceil(10),
2970
- verified_rows,
2971
- Duration::ZERO,
2972
- ))
2973
- }
2974
-
2975
- pub async fn prepare_commit_graph_read(
2976
- backend: &Arc<dyn Backend + Send + Sync>,
2977
- config: StorageBenchConfig,
2978
- ) -> Result<CommitGraphReadFixture, LixError> {
2979
- let changelog = CommitStoreContext::new();
2980
- let mut changes = changelog_materialized_changes(config);
2981
- let head_commit_id = "bench-commit-head".to_string();
2982
- changes.push(commit_graph_materialized_commit_change(
2983
- &head_commit_id,
2984
- config.rows,
2985
- ));
2986
- append_changelog_changes(backend, &changelog, &changes).await?;
2987
-
2988
- Ok(CommitGraphReadFixture {
2989
- head_commit_id,
2990
- rows: config.rows,
2991
- })
2992
- }
2993
-
2994
- pub async fn commit_graph_change_history_from_commit_prepared(
2995
- backend: &Arc<dyn Backend + Send + Sync>,
2996
- fixture: &CommitGraphReadFixture,
2997
- ) -> Result<StorageBenchReport, LixError> {
2998
- let graph = crate::commit_graph::CommitGraphContext::new();
2999
- let mut reader = graph.reader(StorageContext::new(Arc::clone(backend)));
3000
- let verified_rows = reader
3001
- .change_history_from_commit(
3002
- &fixture.head_commit_id,
3003
- &CommitGraphChangeHistoryRequest::default(),
3004
- )
3005
- .await?
3006
- .len();
3007
- Ok(report(fixture.rows, verified_rows, Duration::ZERO))
3008
- }
3009
-
3010
- pub async fn prepare_binary_cas_write_blobs(
3011
- config: StorageBenchConfig,
3012
- ) -> Result<BinaryCasWriteFixture, LixError> {
3013
- Ok(BinaryCasWriteFixture {
3014
- context: BinaryCasContext::new(),
3015
- file_ids: binary_file_ids(config.rows),
3016
- payloads: binary_payloads(config.rows, config.blob_bytes),
3017
- })
3018
- }
3019
-
3020
- pub async fn prepare_binary_cas_write_duplicate_payload(
3021
- config: StorageBenchConfig,
3022
- ) -> Result<BinaryCasWriteFixture, LixError> {
3023
- let payload = binary_payload(0, config.blob_bytes);
3024
- Ok(BinaryCasWriteFixture {
3025
- context: BinaryCasContext::new(),
3026
- file_ids: binary_file_ids(config.rows),
3027
- payloads: (0..config.rows).map(|_| payload.clone()).collect(),
3028
- })
3029
- }
3030
-
3031
- pub async fn prepare_binary_cas_write_half_duplicate_payload(
3032
- config: StorageBenchConfig,
3033
- ) -> Result<BinaryCasWriteFixture, LixError> {
3034
- Ok(BinaryCasWriteFixture {
3035
- context: BinaryCasContext::new(),
3036
- file_ids: binary_file_ids(config.rows),
3037
- payloads: binary_half_duplicate_payloads(config.rows, config.blob_bytes),
3038
- })
3039
- }
3040
-
3041
- pub async fn binary_cas_write_blobs_prepared(
3042
- backend: &Arc<dyn Backend + Send + Sync>,
3043
- fixture: &BinaryCasWriteFixture,
3044
- ) -> Result<StorageBenchReport, LixError> {
3045
- let writes = binary_blob_writes(&fixture.file_ids, &fixture.payloads);
3046
- write_binary_blob_writes(backend, &fixture.context, &writes).await?;
3047
- let verified_rows = count_binary_cas_manifests(backend).await?;
3048
- Ok(report(writes.len(), verified_rows, Duration::ZERO))
3049
- }
3050
-
3051
- pub async fn prepare_binary_cas_read(
3052
- backend: &Arc<dyn Backend + Send + Sync>,
3053
- config: StorageBenchConfig,
3054
- ) -> Result<BinaryCasReadFixture, LixError> {
3055
- let context = BinaryCasContext::new();
3056
- let payloads = binary_payloads(config.rows, config.blob_bytes);
3057
- let file_ids = binary_file_ids(config.rows);
3058
- let writes = binary_blob_writes(&file_ids, &payloads);
3059
- write_binary_blob_writes(backend, &context, &writes).await?;
3060
- let hashes = payloads
3061
- .iter()
3062
- .map(|payload| BlobHash::from_content(payload))
3063
- .collect::<Vec<_>>();
3064
- Ok(BinaryCasReadFixture {
3065
- context,
3066
- rows: config.rows,
3067
- hashes,
3068
- })
3069
- }
3070
-
3071
- pub async fn binary_cas_read_blob_hit_prepared(
3072
- backend: &Arc<dyn Backend + Send + Sync>,
3073
- fixture: &BinaryCasReadFixture,
3074
- ) -> Result<StorageBenchReport, LixError> {
3075
- let mut reader = fixture
3076
- .context
3077
- .reader(StorageContext::new(Arc::clone(backend)));
3078
- let verified_rows = reader
3079
- .load_bytes_many(&fixture.hashes)
3080
- .await?
3081
- .into_vec()
3082
- .into_iter()
3083
- .filter(|row| row.is_some())
3084
- .count();
3085
- Ok(report(fixture.hashes.len(), verified_rows, Duration::ZERO))
3086
- }
3087
-
3088
- pub async fn binary_cas_read_blob_miss_prepared(
3089
- backend: &Arc<dyn Backend + Send + Sync>,
3090
- fixture: &BinaryCasReadFixture,
3091
- ) -> Result<StorageBenchReport, LixError> {
3092
- let mut misses = 0;
3093
- let mut reader = fixture
3094
- .context
3095
- .reader(StorageContext::new(Arc::clone(backend)));
3096
- for index in 0..fixture.rows {
3097
- let missing_hash = BlobHash::from_hex(&format!("{index:064x}"))?;
3098
- if reader
3099
- .load_bytes_many(&[missing_hash])
3100
- .await?
3101
- .get(0)
3102
- .is_none()
3103
- {
3104
- misses += 1;
3105
- }
3106
- }
3107
- Ok(report(fixture.rows, misses, Duration::ZERO))
3108
- }
3109
-
3110
- pub async fn prepare_json_store_write(
3111
- shape: JsonStorePayloadShape,
3112
- rows: usize,
3113
- ) -> Result<JsonStoreWriteFixture, LixError> {
3114
- Ok(JsonStoreWriteFixture {
3115
- context: JsonStoreContext::new(),
3116
- documents: json_documents(shape, rows),
3117
- })
3118
- }
3119
-
3120
- pub async fn prepare_json_store_write_dedupe(
3121
- shape: JsonStorePayloadShape,
3122
- rows: usize,
3123
- ) -> Result<JsonStoreWriteFixture, LixError> {
3124
- let document = json_document(shape, 0);
3125
- Ok(JsonStoreWriteFixture {
3126
- context: JsonStoreContext::new(),
3127
- documents: (0..rows).map(|_| document.clone()).collect(),
3128
- })
3129
- }
3130
-
3131
- pub async fn json_store_write_prepared(
3132
- backend: &Arc<dyn Backend + Send + Sync>,
3133
- fixture: &JsonStoreWriteFixture,
3134
- ) -> Result<StorageBenchReport, LixError> {
3135
- let storage = StorageContext::new(Arc::clone(backend));
3136
- let mut transaction = storage.begin_write_transaction().await?;
3137
- {
3138
- let mut writes = StorageWriteSet::new();
3139
- let mut writer = fixture.context.writer();
3140
- writer.stage_batch(
3141
- &mut writes,
3142
- JsonWritePlacementRef::OutOfBand,
3143
- fixture
3144
- .documents
3145
- .iter()
3146
- .map(|document| {
3147
- std::str::from_utf8(document)
3148
- .map(NormalizedJsonRef::new)
3149
- .map_err(|error| {
3150
- LixError::new(
3151
- LixError::CODE_UNKNOWN,
3152
- format!("benchmark JSON document is invalid UTF-8: {error}"),
3153
- )
3154
- })
3155
- })
3156
- .collect::<Result<Vec<_>, _>>()?,
3157
- )?;
3158
- writes.apply(&mut transaction.as_mut()).await?;
3159
- }
3160
- transaction.commit().await?;
3161
- Ok(report(
3162
- fixture.documents.len(),
3163
- fixture.documents.len(),
3164
- Duration::ZERO,
3165
- ))
3166
- }
3167
-
3168
- pub async fn prepare_json_store_read(
3169
- backend: &Arc<dyn Backend + Send + Sync>,
3170
- shape: JsonStorePayloadShape,
3171
- rows: usize,
3172
- ) -> Result<JsonStoreReadFixture, LixError> {
3173
- prepare_json_store_projection_read(
3174
- backend,
3175
- shape,
3176
- rows,
3177
- JsonStoreProjectionShape::TopLevelTarget,
3178
- )
3179
- .await
3180
- }
3181
-
3182
- pub async fn prepare_json_store_projection_read(
3183
- backend: &Arc<dyn Backend + Send + Sync>,
3184
- shape: JsonStorePayloadShape,
3185
- rows: usize,
3186
- projection: JsonStoreProjectionShape,
3187
- ) -> Result<JsonStoreReadFixture, LixError> {
3188
- let context = JsonStoreContext::new();
3189
- let documents = json_documents(shape, rows);
3190
- let mut refs = Vec::with_capacity(documents.len());
3191
- let storage = StorageContext::new(Arc::clone(backend));
3192
- let mut transaction = storage.begin_write_transaction().await?;
3193
- {
3194
- let mut writes = StorageWriteSet::new();
3195
- let mut writer = context.writer();
3196
- for document in &documents {
3197
- refs.push(prepare_json_ref(document)?);
3198
- }
3199
- writer.stage_batch(
3200
- &mut writes,
3201
- JsonWritePlacementRef::OutOfBand,
3202
- documents
3203
- .iter()
3204
- .map(|document| {
3205
- std::str::from_utf8(document)
3206
- .map(NormalizedJsonRef::new)
3207
- .map_err(|error| {
3208
- LixError::new(
3209
- LixError::CODE_UNKNOWN,
3210
- format!("benchmark JSON document is invalid UTF-8: {error}"),
3211
- )
3212
- })
3213
- })
3214
- .collect::<Result<Vec<_>, _>>()?,
3215
- )?;
3216
- writes.apply(&mut transaction.as_mut()).await?;
3217
- }
3218
- transaction.commit().await?;
3219
- Ok(JsonStoreReadFixture {
3220
- context,
3221
- refs,
3222
- paths: json_projection_paths(projection),
3223
- })
3224
- }
3225
-
3226
- pub async fn json_store_read_bytes_prepared(
3227
- backend: &Arc<dyn Backend + Send + Sync>,
3228
- fixture: &JsonStoreReadFixture,
3229
- ) -> Result<StorageBenchReport, LixError> {
3230
- let mut verified_rows = 0;
3231
- let mut reader = fixture
3232
- .context
3233
- .reader(StorageContext::new(Arc::clone(backend)));
3234
- let batch = reader
3235
- .load_bytes_many(JsonLoadRequestRef {
3236
- refs: &fixture.refs,
3237
- scope: JsonReadScopeRef::OutOfBand,
3238
- })
3239
- .await?;
3240
- for value in batch.values() {
3241
- if value.is_some() {
3242
- verified_rows += 1;
3243
- }
3244
- }
3245
- Ok(report(fixture.refs.len(), verified_rows, Duration::ZERO))
3246
- }
3247
-
3248
- pub async fn json_store_read_value_prepared(
3249
- backend: &Arc<dyn Backend + Send + Sync>,
3250
- fixture: &JsonStoreReadFixture,
3251
- ) -> Result<StorageBenchReport, LixError> {
3252
- let mut verified_rows = 0;
3253
- let mut reader = fixture
3254
- .context
3255
- .reader(StorageContext::new(Arc::clone(backend)));
3256
- let batch = reader
3257
- .load_values_many(JsonLoadRequestRef {
3258
- refs: &fixture.refs,
3259
- scope: JsonReadScopeRef::OutOfBand,
3260
- })
3261
- .await?;
3262
- for value in batch.values() {
3263
- if value.is_some() {
3264
- verified_rows += 1;
3265
- }
3266
- }
3267
- Ok(report(fixture.refs.len(), verified_rows, Duration::ZERO))
3268
- }
3269
-
3270
- pub async fn json_store_read_projection_prepared(
3271
- backend: &Arc<dyn Backend + Send + Sync>,
3272
- fixture: &JsonStoreReadFixture,
3273
- ) -> Result<StorageBenchReport, LixError> {
3274
- let mut verified_rows = 0;
3275
- let mut reader = fixture
3276
- .context
3277
- .reader(StorageContext::new(Arc::clone(backend)));
3278
- let batch = reader
3279
- .load_projections_many(JsonProjectionLoadRequestRef {
3280
- refs: &fixture.refs,
3281
- scope: JsonReadScopeRef::OutOfBand,
3282
- paths: &fixture.paths,
3283
- })
3284
- .await?;
3285
- for value in batch.values() {
3286
- if value.is_some() {
3287
- verified_rows += 1;
3288
- }
3289
- }
3290
- Ok(report(fixture.refs.len(), verified_rows, Duration::ZERO))
3291
- }
3292
-
3293
- pub async fn prepare_json_store_base_update_object(
3294
- backend: &Arc<dyn Backend + Send + Sync>,
3295
- rows: usize,
3296
- ) -> Result<JsonStoreReadFixture, LixError> {
3297
- prepare_json_store_base_update(backend, JsonStorePayloadShape::LargeStructured128k, rows).await
3298
- }
3299
-
3300
- pub async fn prepare_json_store_base_update_array(
3301
- backend: &Arc<dyn Backend + Send + Sync>,
3302
- rows: usize,
3303
- ) -> Result<JsonStoreReadFixture, LixError> {
3304
- prepare_json_store_base_update(backend, JsonStorePayloadShape::LargeArray128k, rows).await
3305
- }
3306
-
3307
- async fn prepare_json_store_base_update(
3308
- backend: &Arc<dyn Backend + Send + Sync>,
3309
- shape: JsonStorePayloadShape,
3310
- rows: usize,
3311
- ) -> Result<JsonStoreReadFixture, LixError> {
3312
- let context = JsonStoreContext::new();
3313
- let documents = json_documents(shape, rows);
3314
- let mut refs = Vec::with_capacity(documents.len());
3315
- let storage = StorageContext::new(Arc::clone(backend));
3316
- let mut transaction = storage.begin_write_transaction().await?;
3317
- {
3318
- let mut writes = StorageWriteSet::new();
3319
- let mut writer = context.writer();
3320
- for document in &documents {
3321
- refs.push(prepare_json_ref(document)?);
3322
- }
3323
- writer.stage_batch(
3324
- &mut writes,
3325
- JsonWritePlacementRef::OutOfBand,
3326
- documents
3327
- .iter()
3328
- .map(|document| {
3329
- std::str::from_utf8(document)
3330
- .map(NormalizedJsonRef::new)
3331
- .map_err(|error| {
3332
- LixError::new(
3333
- LixError::CODE_UNKNOWN,
3334
- format!("benchmark JSON document is invalid UTF-8: {error}"),
3335
- )
3336
- })
3337
- })
3338
- .collect::<Result<Vec<_>, _>>()?,
3339
- )?;
3340
- writes.apply(&mut transaction.as_mut()).await?;
3341
- }
3342
- transaction.commit().await?;
3343
- Ok(JsonStoreReadFixture {
3344
- context,
3345
- refs,
3346
- paths: json_projection_paths(JsonStoreProjectionShape::TopLevelTarget),
3347
- })
3348
- }
3349
-
3350
- pub async fn json_store_write_against_base_object_prepared(
3351
- backend: &Arc<dyn Backend + Send + Sync>,
3352
- fixture: &JsonStoreReadFixture,
3353
- ) -> Result<StorageBenchReport, LixError> {
3354
- json_store_write_against_base_prepared(
3355
- backend,
3356
- fixture,
3357
- JsonStorePayloadShape::LargeStructured128k,
3358
- )
3359
- .await
3360
- }
3361
-
3362
- pub async fn json_store_write_against_base_array_prepared(
3363
- backend: &Arc<dyn Backend + Send + Sync>,
3364
- fixture: &JsonStoreReadFixture,
3365
- ) -> Result<StorageBenchReport, LixError> {
3366
- json_store_write_against_base_prepared(backend, fixture, JsonStorePayloadShape::LargeArray128k)
3367
- .await
3368
- }
3369
-
3370
- async fn json_store_write_against_base_prepared(
3371
- backend: &Arc<dyn Backend + Send + Sync>,
3372
- fixture: &JsonStoreReadFixture,
3373
- shape: JsonStorePayloadShape,
3374
- ) -> Result<StorageBenchReport, LixError> {
3375
- let storage = StorageContext::new(Arc::clone(backend));
3376
- let mut transaction = storage.begin_write_transaction().await?;
3377
- {
3378
- let mut writes = StorageWriteSet::new();
3379
- let mut writer = fixture.context.writer();
3380
- let mut updated_documents = Vec::with_capacity(fixture.refs.len());
3381
- for (index, _json_ref) in fixture.refs.iter().enumerate() {
3382
- let updated = updated_json_document(shape, index);
3383
- prepare_json_ref(&updated)?;
3384
- updated_documents.push(updated);
3385
- }
3386
- writer.stage_batch(
3387
- &mut writes,
3388
- JsonWritePlacementRef::OutOfBand,
3389
- updated_documents
3390
- .iter()
3391
- .map(|document| {
3392
- std::str::from_utf8(document)
3393
- .map(NormalizedJsonRef::new)
3394
- .map_err(|error| {
3395
- LixError::new(
3396
- LixError::CODE_UNKNOWN,
3397
- format!("benchmark JSON document is invalid UTF-8: {error}"),
3398
- )
3399
- })
3400
- })
3401
- .collect::<Result<Vec<_>, _>>()?,
3402
- )?;
3403
- writes.apply(&mut transaction.as_mut()).await?;
3404
- }
3405
- transaction.commit().await?;
3406
- Ok(report(
3407
- fixture.refs.len(),
3408
- fixture.refs.len(),
3409
- Duration::ZERO,
3410
- ))
3411
- }
3412
-
3413
- pub async fn tracked_state_write_root(
3414
- backend: &Arc<dyn Backend + Send + Sync>,
3415
- config: StorageBenchConfig,
3416
- ) -> Result<StorageBenchReport, LixError> {
3417
- let rows = tracked_rows(config, "bench-tracked-commit");
3418
- let context = TrackedStateContext::new();
3419
- let started = Instant::now();
3420
- write_tracked_root(backend, &context, "bench-tracked-commit", None, &rows).await?;
3421
- let elapsed = started.elapsed();
3422
- let verified_rows = scan_tracked(backend, &context, "bench-tracked-commit")
3423
- .await?
3424
- .len();
3425
- Ok(report(rows.len(), verified_rows, elapsed))
3426
- }
3427
-
3428
- pub async fn tracked_state_read_point_hit(
3429
- backend: &Arc<dyn Backend + Send + Sync>,
3430
- config: StorageBenchConfig,
3431
- ) -> Result<StorageBenchReport, LixError> {
3432
- let context = TrackedStateContext::new();
3433
- let rows = tracked_rows(config, "bench-tracked-commit");
3434
- write_tracked_root(backend, &context, "bench-tracked-commit", None, &rows).await?;
3435
-
3436
- let started = Instant::now();
3437
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
3438
- let requests = tracked_point_hit_requests(config.rows, config.key_pattern);
3439
- let verified_rows = reader
3440
- .load_rows_at_commit("bench-tracked-commit", &requests)
3441
- .await?
3442
- .into_iter()
3443
- .filter(Option::is_some)
3444
- .count();
3445
- Ok(report(config.rows, verified_rows, started.elapsed()))
3446
- }
3447
-
3448
- pub async fn tracked_state_read_point_miss(
3449
- backend: &Arc<dyn Backend + Send + Sync>,
3450
- config: StorageBenchConfig,
3451
- ) -> Result<StorageBenchReport, LixError> {
3452
- let context = TrackedStateContext::new();
3453
- let rows = tracked_rows(config, "bench-tracked-commit");
3454
- write_tracked_root(backend, &context, "bench-tracked-commit", None, &rows).await?;
3455
-
3456
- let started = Instant::now();
3457
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
3458
- let requests = tracked_point_miss_requests(config.rows, StorageBenchSelectivity::Percent100);
3459
- let misses = reader
3460
- .load_rows_at_commit("bench-tracked-commit", &requests)
3461
- .await?
3462
- .into_iter()
3463
- .filter(Option::is_none)
3464
- .count();
3465
- Ok(report(config.rows, misses, started.elapsed()))
3466
- }
3467
-
3468
- pub async fn tracked_state_scan_all(
3469
- backend: &Arc<dyn Backend + Send + Sync>,
3470
- config: StorageBenchConfig,
3471
- ) -> Result<StorageBenchReport, LixError> {
3472
- let context = TrackedStateContext::new();
3473
- let rows = tracked_rows(config, "bench-tracked-commit");
3474
- write_tracked_root(backend, &context, "bench-tracked-commit", None, &rows).await?;
3475
-
3476
- let started = Instant::now();
3477
- let verified_rows = scan_tracked(backend, &context, "bench-tracked-commit")
3478
- .await?
3479
- .len();
3480
- Ok(report(config.rows, verified_rows, started.elapsed()))
3481
- }
3482
-
3483
- pub async fn tracked_state_scan_schema(
3484
- backend: &Arc<dyn Backend + Send + Sync>,
3485
- config: StorageBenchConfig,
3486
- ) -> Result<StorageBenchReport, LixError> {
3487
- let context = TrackedStateContext::new();
3488
- let rows = tracked_rows(config, "bench-tracked-commit");
3489
- write_tracked_root(backend, &context, "bench-tracked-commit", None, &rows).await?;
3490
-
3491
- let started = Instant::now();
3492
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
3493
- let verified_rows = reader
3494
- .scan_rows_at_commit(
3495
- "bench-tracked-commit",
3496
- &TrackedStateScanRequest {
3497
- filter: TrackedStateFilter {
3498
- schema_keys: vec![tracked_schema_key(0, StorageBenchSelectivity::Percent100)],
3499
- ..Default::default()
3500
- },
3501
- ..Default::default()
3502
- },
3503
- )
3504
- .await?
3505
- .len();
3506
- Ok(report(config.rows, verified_rows, started.elapsed()))
3507
- }
3508
-
3509
- pub async fn tracked_state_scan_file(
3510
- backend: &Arc<dyn Backend + Send + Sync>,
3511
- config: StorageBenchConfig,
3512
- ) -> Result<StorageBenchReport, LixError> {
3513
- let context = TrackedStateContext::new();
3514
- let rows = tracked_rows(config, "bench-tracked-commit");
3515
- write_tracked_root(backend, &context, "bench-tracked-commit", None, &rows).await?;
3516
-
3517
- let started = Instant::now();
3518
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
3519
- let verified_rows = reader
3520
- .scan_rows_at_commit(
3521
- "bench-tracked-commit",
3522
- &TrackedStateScanRequest {
3523
- filter: TrackedStateFilter {
3524
- file_ids: vec![NullableKeyFilter::Value("bench.json".to_string())],
3525
- ..Default::default()
3526
- },
3527
- ..Default::default()
3528
- },
3529
- )
3530
- .await?
3531
- .len();
3532
- Ok(report(config.rows, verified_rows, started.elapsed()))
3533
- }
3534
-
3535
- pub async fn tracked_state_update_existing(
3536
- backend: &Arc<dyn Backend + Send + Sync>,
3537
- config: StorageBenchConfig,
3538
- ) -> Result<StorageBenchReport, LixError> {
3539
- let context = TrackedStateContext::new();
3540
- let rows = tracked_rows(config, "bench-tracked-parent");
3541
- write_tracked_root(backend, &context, "bench-tracked-parent", None, &rows).await?;
3542
- let mut updated_rows = tracked_rows(config, "bench-tracked-child");
3543
- for (index, row) in updated_rows.iter_mut().enumerate() {
3544
- row.snapshot_content = Some(updated_snapshot_content(index, config.state_payload_bytes));
3545
- }
3546
-
3547
- let started = Instant::now();
3548
- write_tracked_root(
3549
- backend,
3550
- &context,
3551
- "bench-tracked-child",
3552
- Some("bench-tracked-parent"),
3553
- &updated_rows,
3554
- )
3555
- .await?;
3556
- let elapsed = started.elapsed();
3557
- let verified_rows = scan_tracked(backend, &context, "bench-tracked-child")
3558
- .await?
3559
- .len();
3560
- Ok(report(updated_rows.len(), verified_rows, elapsed))
3561
- }
3562
-
3563
- pub async fn untracked_state_write_rows(
3564
- backend: &Arc<dyn Backend + Send + Sync>,
3565
- config: StorageBenchConfig,
3566
- ) -> Result<StorageBenchReport, LixError> {
3567
- let rows = untracked_rows(config);
3568
- let context = UntrackedStateContext::new();
3569
- let started = Instant::now();
3570
- write_untracked_rows(backend, &context, &rows).await?;
3571
- let elapsed = started.elapsed();
3572
- let verified_rows = scan_untracked(backend, &context, UntrackedStateScanRequest::default())
3573
- .await?
3574
- .len();
3575
- Ok(report(rows.len(), verified_rows, elapsed))
3576
- }
3577
-
3578
- pub async fn untracked_state_read_point_hit(
3579
- backend: &Arc<dyn Backend + Send + Sync>,
3580
- config: StorageBenchConfig,
3581
- ) -> Result<StorageBenchReport, LixError> {
3582
- let context = UntrackedStateContext::new();
3583
- let rows = untracked_rows(config);
3584
- write_untracked_rows(backend, &context, &rows).await?;
3585
-
3586
- let started = Instant::now();
3587
- let mut verified_rows = 0;
3588
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
3589
- for index in 0..config.rows {
3590
- if reader
3591
- .load_row(&UntrackedStateRowRequest {
3592
- schema_key: untracked_schema_key(index, StorageBenchSelectivity::Percent100),
3593
- version_id: "bench-version".to_string(),
3594
- entity_id: EntityIdentity::single(entity_id(
3595
- "untracked",
3596
- index,
3597
- config.key_pattern,
3598
- )),
3599
- file_id: NullableKeyFilter::Value("bench.json".to_string()),
3600
- })
3601
- .await?
3602
- .is_some()
3603
- {
3604
- verified_rows += 1;
3605
- }
3606
- }
3607
- Ok(report(config.rows, verified_rows, started.elapsed()))
3608
- }
3609
-
3610
- pub async fn untracked_state_read_point_miss(
3611
- backend: &Arc<dyn Backend + Send + Sync>,
3612
- config: StorageBenchConfig,
3613
- ) -> Result<StorageBenchReport, LixError> {
3614
- let context = UntrackedStateContext::new();
3615
- let rows = untracked_rows(config);
3616
- write_untracked_rows(backend, &context, &rows).await?;
3617
-
3618
- let started = Instant::now();
3619
- let mut misses = 0;
3620
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
3621
- for index in 0..config.rows {
3622
- if reader
3623
- .load_row(&UntrackedStateRowRequest {
3624
- schema_key: "bench_untracked_entity".to_string(),
3625
- version_id: "bench-version".to_string(),
3626
- entity_id: EntityIdentity::single(format!("missing-{index}")),
3627
- file_id: NullableKeyFilter::Value("bench.json".to_string()),
3628
- })
3629
- .await?
3630
- .is_none()
3631
- {
3632
- misses += 1;
3633
- }
3634
- }
3635
- Ok(report(config.rows, misses, started.elapsed()))
3636
- }
3637
-
3638
- pub async fn untracked_state_scan_all(
3639
- backend: &Arc<dyn Backend + Send + Sync>,
3640
- config: StorageBenchConfig,
3641
- ) -> Result<StorageBenchReport, LixError> {
3642
- let context = UntrackedStateContext::new();
3643
- let rows = untracked_rows(config);
3644
- write_untracked_rows(backend, &context, &rows).await?;
3645
-
3646
- let started = Instant::now();
3647
- let verified_rows = scan_untracked(backend, &context, UntrackedStateScanRequest::default())
3648
- .await?
3649
- .len();
3650
- Ok(report(config.rows, verified_rows, started.elapsed()))
3651
- }
3652
-
3653
- pub async fn untracked_state_scan_version(
3654
- backend: &Arc<dyn Backend + Send + Sync>,
3655
- config: StorageBenchConfig,
3656
- ) -> Result<StorageBenchReport, LixError> {
3657
- let context = UntrackedStateContext::new();
3658
- let rows = untracked_rows(config);
3659
- write_untracked_rows(backend, &context, &rows).await?;
3660
-
3661
- let started = Instant::now();
3662
- let verified_rows = scan_untracked(
3663
- backend,
3664
- &context,
3665
- UntrackedStateScanRequest {
3666
- filter: UntrackedStateFilter {
3667
- version_ids: vec!["bench-version".to_string()],
3668
- ..Default::default()
3669
- },
3670
- ..Default::default()
3671
- },
3672
- )
3673
- .await?
3674
- .len();
3675
- Ok(report(config.rows, verified_rows, started.elapsed()))
3676
- }
3677
-
3678
- pub async fn untracked_state_scan_schema(
3679
- backend: &Arc<dyn Backend + Send + Sync>,
3680
- config: StorageBenchConfig,
3681
- ) -> Result<StorageBenchReport, LixError> {
3682
- let context = UntrackedStateContext::new();
3683
- let rows = untracked_rows(config);
3684
- write_untracked_rows(backend, &context, &rows).await?;
3685
-
3686
- let started = Instant::now();
3687
- let verified_rows = scan_untracked(
3688
- backend,
3689
- &context,
3690
- UntrackedStateScanRequest {
3691
- filter: UntrackedStateFilter {
3692
- schema_keys: vec![untracked_schema_key(0, StorageBenchSelectivity::Percent100)],
3693
- ..Default::default()
3694
- },
3695
- ..Default::default()
3696
- },
3697
- )
3698
- .await?
3699
- .len();
3700
- Ok(report(config.rows, verified_rows, started.elapsed()))
3701
- }
3702
-
3703
- pub async fn untracked_state_overwrite_existing(
3704
- backend: &Arc<dyn Backend + Send + Sync>,
3705
- config: StorageBenchConfig,
3706
- ) -> Result<StorageBenchReport, LixError> {
3707
- let context = UntrackedStateContext::new();
3708
- let rows = untracked_rows(config);
3709
- write_untracked_rows(backend, &context, &rows).await?;
3710
- let mut updated_rows = untracked_rows(config);
3711
- for (index, row) in updated_rows.iter_mut().enumerate() {
3712
- row.snapshot_content = Some(updated_snapshot_content(index, config.state_payload_bytes));
3713
- }
3714
-
3715
- let started = Instant::now();
3716
- write_untracked_rows(backend, &context, &updated_rows).await?;
3717
- let elapsed = started.elapsed();
3718
- let verified_rows = scan_untracked(backend, &context, UntrackedStateScanRequest::default())
3719
- .await?
3720
- .len();
3721
- Ok(report(updated_rows.len(), verified_rows, elapsed))
3722
- }
3723
-
3724
- pub async fn changelog_append_changes(
3725
- backend: &Arc<dyn Backend + Send + Sync>,
3726
- config: StorageBenchConfig,
3727
- ) -> Result<StorageBenchReport, LixError> {
3728
- let changes = changelog_materialized_changes(config);
3729
- let context = CommitStoreContext::new();
3730
- let started = Instant::now();
3731
- append_changelog_changes(backend, &context, &changes).await?;
3732
- let elapsed = started.elapsed();
3733
- let reader = context.reader(StorageContext::new(Arc::clone(backend)));
3734
- let verified_rows = reader
3735
- .scan_changes(&ChangeScanRequest::default())
3736
- .await?
3737
- .len();
3738
- Ok(report(changes.len(), verified_rows, elapsed))
3739
- }
3740
-
3741
- pub async fn changelog_load_changes_hit(
3742
- backend: &Arc<dyn Backend + Send + Sync>,
3743
- config: StorageBenchConfig,
3744
- ) -> Result<StorageBenchReport, LixError> {
3745
- let context = CommitStoreContext::new();
3746
- let changes = changelog_materialized_changes(config);
3747
- append_changelog_changes(backend, &context, &changes).await?;
3748
- let reader = context.reader(StorageContext::new(Arc::clone(backend)));
3749
-
3750
- let started = Instant::now();
3751
- let change_ids = (0..config.rows)
3752
- .map(|index| format!("bench-change-{index}"))
3753
- .collect::<Vec<_>>();
3754
- let verified_rows = reader
3755
- .load_changes(&change_ids)
3756
- .await?
3757
- .into_iter()
3758
- .filter(Option::is_some)
3759
- .count();
3760
- Ok(report(config.rows, verified_rows, started.elapsed()))
3761
- }
3762
-
3763
- pub async fn changelog_load_changes_miss(
3764
- backend: &Arc<dyn Backend + Send + Sync>,
3765
- config: StorageBenchConfig,
3766
- ) -> Result<StorageBenchReport, LixError> {
3767
- let context = CommitStoreContext::new();
3768
- let changes = changelog_materialized_changes(config);
3769
- append_changelog_changes(backend, &context, &changes).await?;
3770
- let reader = context.reader(StorageContext::new(Arc::clone(backend)));
3771
-
3772
- let started = Instant::now();
3773
- let change_ids = (0..config.rows)
3774
- .map(|index| format!("missing-change-{index}"))
3775
- .collect::<Vec<_>>();
3776
- let misses = reader
3777
- .load_changes(&change_ids)
3778
- .await?
3779
- .into_iter()
3780
- .filter(Option::is_none)
3781
- .count();
3782
- Ok(report(config.rows, misses, started.elapsed()))
3783
- }
3784
-
3785
- pub async fn changelog_scan_all(
3786
- backend: &Arc<dyn Backend + Send + Sync>,
3787
- config: StorageBenchConfig,
3788
- ) -> Result<StorageBenchReport, LixError> {
3789
- let context = CommitStoreContext::new();
3790
- let changes = changelog_materialized_changes(config);
3791
- append_changelog_changes(backend, &context, &changes).await?;
3792
- let reader = context.reader(StorageContext::new(Arc::clone(backend)));
3793
-
3794
- let started = Instant::now();
3795
- let verified_rows = reader
3796
- .scan_changes(&ChangeScanRequest::default())
3797
- .await?
3798
- .len();
3799
- Ok(report(config.rows, verified_rows, started.elapsed()))
3800
- }
3801
-
3802
- pub async fn changelog_scan_limit_100(
3803
- backend: &Arc<dyn Backend + Send + Sync>,
3804
- config: StorageBenchConfig,
3805
- ) -> Result<StorageBenchReport, LixError> {
3806
- let context = CommitStoreContext::new();
3807
- let changes = changelog_materialized_changes(config);
3808
- append_changelog_changes(backend, &context, &changes).await?;
3809
- let reader = context.reader(StorageContext::new(Arc::clone(backend)));
3810
- let expected = config.rows.min(100);
3811
-
3812
- let started = Instant::now();
3813
- let verified_rows = reader
3814
- .scan_changes(&ChangeScanRequest {
3815
- limit: Some(expected),
3816
- })
3817
- .await?
3818
- .len();
3819
- Ok(report(expected, verified_rows, started.elapsed()))
3820
- }
3821
-
3822
- pub async fn binary_cas_write_blobs(
3823
- backend: &Arc<dyn Backend + Send + Sync>,
3824
- config: StorageBenchConfig,
3825
- ) -> Result<StorageBenchReport, LixError> {
3826
- let payloads = binary_payloads(config.rows, config.blob_bytes);
3827
- let file_ids = binary_file_ids(config.rows);
3828
- let writes = binary_blob_writes(&file_ids, &payloads);
3829
- let context = BinaryCasContext::new();
3830
-
3831
- let started = Instant::now();
3832
- write_binary_blob_writes(backend, &context, &writes).await?;
3833
- let elapsed = started.elapsed();
3834
- let verified_rows = count_binary_cas_manifests(backend).await?;
3835
- Ok(report(writes.len(), verified_rows, elapsed))
3836
- }
3837
-
3838
- pub async fn binary_cas_read_blob_hit(
3839
- backend: &Arc<dyn Backend + Send + Sync>,
3840
- config: StorageBenchConfig,
3841
- ) -> Result<StorageBenchReport, LixError> {
3842
- let context = BinaryCasContext::new();
3843
- let payloads = binary_payloads(config.rows, config.blob_bytes);
3844
- let file_ids = binary_file_ids(config.rows);
3845
- let writes = binary_blob_writes(&file_ids, &payloads);
3846
- write_binary_blob_writes(backend, &context, &writes).await?;
3847
- let hashes = payloads
3848
- .iter()
3849
- .map(|payload| BlobHash::from_content(payload))
3850
- .collect::<Vec<_>>();
3851
-
3852
- let started = Instant::now();
3853
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
3854
- let verified_rows = reader
3855
- .load_bytes_many(&hashes)
3856
- .await?
3857
- .into_vec()
3858
- .into_iter()
3859
- .filter(|row| row.is_some())
3860
- .count();
3861
- Ok(report(hashes.len(), verified_rows, started.elapsed()))
3862
- }
3863
-
3864
- pub async fn binary_cas_read_blob_miss(
3865
- backend: &Arc<dyn Backend + Send + Sync>,
3866
- config: StorageBenchConfig,
3867
- ) -> Result<StorageBenchReport, LixError> {
3868
- let context = BinaryCasContext::new();
3869
- let payloads = binary_payloads(config.rows, config.blob_bytes);
3870
- let file_ids = binary_file_ids(config.rows);
3871
- let writes = binary_blob_writes(&file_ids, &payloads);
3872
- write_binary_blob_writes(backend, &context, &writes).await?;
3873
-
3874
- let started = Instant::now();
3875
- let mut misses = 0;
3876
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
3877
- for index in 0..config.rows {
3878
- let missing_hash = BlobHash::from_hex(&format!("{index:064x}"))?;
3879
- if reader
3880
- .load_bytes_many(&[missing_hash])
3881
- .await?
3882
- .get(0)
3883
- .is_none()
3884
- {
3885
- misses += 1;
3886
- }
3887
- }
3888
- Ok(report(config.rows, misses, started.elapsed()))
3889
- }
3890
-
3891
- pub async fn binary_cas_write_duplicate_payload(
3892
- backend: &Arc<dyn Backend + Send + Sync>,
3893
- config: StorageBenchConfig,
3894
- ) -> Result<StorageBenchReport, LixError> {
3895
- let payload = binary_payload(0, config.blob_bytes);
3896
- let payloads = (0..config.rows)
3897
- .map(|_| payload.clone())
3898
- .collect::<Vec<_>>();
3899
- let file_ids = binary_file_ids(config.rows);
3900
- let writes = binary_blob_writes(&file_ids, &payloads);
3901
- let context = BinaryCasContext::new();
3902
-
3903
- let started = Instant::now();
3904
- write_binary_blob_writes(backend, &context, &writes).await?;
3905
- let elapsed = started.elapsed();
3906
- let verified_rows = count_binary_cas_manifests(backend).await?;
3907
- Ok(report(writes.len(), verified_rows, elapsed))
3908
- }
3909
-
3910
- async fn write_tracked_root(
3911
- backend: &Arc<dyn Backend + Send + Sync>,
3912
- context: &TrackedStateContext,
3913
- commit_id: &str,
3914
- parent_commit_id: Option<&str>,
3915
- rows: &[MaterializedTrackedStateRow],
3916
- ) -> Result<(), LixError> {
3917
- let storage = StorageContext::new(Arc::clone(backend));
3918
- let mut transaction = storage.begin_write_transaction().await?;
3919
- let mut writes = StorageWriteSet::new();
3920
- let changes = rows
3921
- .iter()
3922
- .map(tracked_bench_change_from_materialized)
3923
- .collect::<Result<Vec<_>, _>>()?;
3924
- let payloads = tracked_bench_json_payloads(rows, &changes);
3925
- let json_report = JsonStoreContext::new().writer().stage_batch_report(
3926
- &mut writes,
3927
- JsonWritePlacementRef::CommitPack {
3928
- commit_id,
3929
- pack_id: 0,
3930
- },
3931
- payloads.iter().map(|(payload, json_ref)| match json_ref {
3932
- Some(json_ref) => NormalizedJsonRef::trusted_prehashed(payload.as_str(), *json_ref),
3933
- None => NormalizedJsonRef::new(payload.as_str()),
3934
- }),
3935
- )?;
3936
-
3937
- let parent_ids = parent_commit_id
3938
- .map(|parent| vec![parent.to_string()])
3939
- .unwrap_or_default();
3940
- let commit_change_id = format!("{commit_id}:commit");
3941
- let commit = CommitDraftRef {
3942
- id: commit_id,
3943
- change_id: &commit_change_id,
3944
- parent_ids: &parent_ids,
3945
- author_account_ids: &[],
3946
- created_at: rows
3947
- .first()
3948
- .map(|row| row.updated_at.as_str())
3949
- .unwrap_or("1970-01-01T00:00:00.000Z"),
3950
- };
3951
- let commit_store = CommitStoreContext::new();
3952
- let authored_changes = changes.iter().map(Change::as_ref).collect::<Vec<_>>();
3953
- let staged = commit_store
3954
- .writer(&mut transaction.as_mut(), &mut writes)
3955
- .stage_tracked_commit_draft(commit, authored_changes.clone(), Vec::new())
3956
- .await?;
3957
- let mut deltas = Vec::with_capacity(changes.len());
3958
- deltas.extend(
3959
- authored_changes
3960
- .iter()
3961
- .zip(&staged.authored_locators)
3962
- .zip(rows)
3963
- .map(|((change, locator), row)| TrackedStateDeltaRef {
3964
- change: *change,
3965
- locator: locator.as_ref(),
3966
- created_at: row.created_at.as_str(),
3967
- updated_at: row.updated_at.as_str(),
3968
- }),
3969
- );
3970
- context
3971
- .writer(&mut transaction.as_mut(), &mut writes)
3972
- .stage_delta_with_json_pack_indexes(
3973
- commit_id,
3974
- parent_commit_id,
3975
- &deltas,
3976
- crate::tracked_state::DeltaJsonPackIndexesRef {
3977
- commit_id,
3978
- pack_id: 0,
3979
- indexes: &json_report.pack_indexes,
3980
- },
3981
- )
3982
- .await?;
3983
- writes.apply(&mut transaction.as_mut()).await?;
3984
- transaction.commit().await
3985
- }
3986
-
3987
- async fn materialize_tracked_root(
3988
- backend: &Arc<dyn Backend + Send + Sync>,
3989
- context: &TrackedStateContext,
3990
- commit_id: &str,
3991
- ) -> Result<(), LixError> {
3992
- let storage = StorageContext::new(Arc::clone(backend));
3993
- let mut transaction = storage.begin_write_transaction().await?;
3994
- let mut writes = StorageWriteSet::new();
3995
- let commit_store = CommitStoreContext::new();
3996
- context
3997
- .materializer(&mut transaction.as_mut(), &mut writes, &commit_store)
3998
- .materialize_root_at(commit_id)
3999
- .await?;
4000
- writes.apply(&mut transaction.as_mut()).await?;
4001
- transaction.commit().await
4002
- }
4003
-
4004
- async fn write_tracked_delta_chain(
4005
- backend: &Arc<dyn Backend + Send + Sync>,
4006
- config: StorageBenchConfig,
4007
- delta_commits: usize,
4008
- updated_rows_per_commit: usize,
4009
- ) -> Result<(TrackedStateContext, String), LixError> {
4010
- let context = TrackedStateContext::new();
4011
- let base_commit_id = "bench-tracked-base";
4012
- let rows = tracked_rows(config, base_commit_id);
4013
- write_tracked_root(backend, &context, base_commit_id, None, &rows).await?;
4014
-
4015
- let mut parent_commit_id = base_commit_id.to_string();
4016
- for delta_index in 0..delta_commits {
4017
- let commit_id = format!("bench-tracked-delta-{delta_index}");
4018
- let mut updated_rows = tracked_rows(
4019
- config.with_rows(updated_rows_per_commit.min(config.rows)),
4020
- &commit_id,
4021
- );
4022
- for (row_index, row) in updated_rows.iter_mut().enumerate() {
4023
- row.snapshot_content = Some(delta_chain_snapshot_content(
4024
- delta_index,
4025
- row_index,
4026
- config.state_payload_bytes,
4027
- ));
4028
- row.updated_at = timestamp(config.rows + delta_index * config.rows + row_index);
4029
- }
4030
- write_tracked_root(
4031
- backend,
4032
- &context,
4033
- &commit_id,
4034
- Some(parent_commit_id.as_str()),
4035
- &updated_rows,
4036
- )
4037
- .await?;
4038
- parent_commit_id = commit_id;
4039
- }
4040
-
4041
- Ok((context, parent_commit_id))
4042
- }
4043
-
4044
- fn tracked_bench_change_from_materialized(
4045
- row: &MaterializedTrackedStateRow,
4046
- ) -> Result<Change, LixError> {
4047
- Ok(Change {
4048
- id: row.change_id.clone(),
4049
- entity_id: row.entity_id.clone(),
4050
- schema_key: row.schema_key.clone(),
4051
- file_id: row.file_id.clone(),
4052
- snapshot_ref: row
4053
- .snapshot_content
4054
- .as_deref()
4055
- .map(|value| prepare_json_ref(value.as_bytes()))
4056
- .transpose()?,
4057
- metadata_ref: row
4058
- .metadata
4059
- .as_ref()
4060
- .map(|value| {
4061
- let serialized = crate::serialize_row_metadata(value);
4062
- prepare_json_ref(serialized.as_bytes())
4063
- })
4064
- .transpose()?,
4065
- created_at: row.created_at.clone(),
4066
- })
4067
- }
4068
-
4069
- fn tracked_bench_json_payloads(
4070
- rows: &[MaterializedTrackedStateRow],
4071
- changes: &[Change],
4072
- ) -> Vec<(String, Option<JsonRef>)> {
4073
- let mut payloads = Vec::new();
4074
- for (row, change) in rows.iter().zip(changes) {
4075
- if let Some(snapshot) = row.snapshot_content.as_deref() {
4076
- payloads.push((snapshot.to_string(), change.snapshot_ref));
4077
- }
4078
- if let Some(metadata) = row.metadata.as_ref() {
4079
- payloads.push((crate::serialize_row_metadata(metadata), change.metadata_ref));
4080
- }
4081
- }
4082
- payloads
4083
- }
4084
-
4085
- async fn scan_tracked(
4086
- backend: &Arc<dyn Backend + Send + Sync>,
4087
- context: &TrackedStateContext,
4088
- commit_id: &str,
4089
- ) -> Result<Vec<MaterializedTrackedStateRow>, LixError> {
4090
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
4091
- reader
4092
- .scan_rows_at_commit(commit_id, &TrackedStateScanRequest::default())
4093
- .await
4094
- }
4095
-
4096
- async fn write_untracked_rows(
4097
- backend: &Arc<dyn Backend + Send + Sync>,
4098
- context: &UntrackedStateContext,
4099
- rows: &[MaterializedUntrackedStateRow],
4100
- ) -> Result<(), LixError> {
4101
- let storage = StorageContext::new(Arc::clone(backend));
4102
- let mut transaction = storage.begin_write_transaction().await?;
4103
- {
4104
- let mut writes = StorageWriteSet::new();
4105
- let canonical_rows = rows
4106
- .iter()
4107
- .map(|row| crate::test_support::untracked_state_row_from_materialized(&mut writes, row))
4108
- .collect::<Result<Vec<_>, _>>()?;
4109
- let mut writer = context.writer(&mut writes);
4110
- writer.stage_rows(canonical_rows.iter().map(|row| row.as_ref()))?;
4111
- writes.apply(&mut transaction.as_mut()).await?;
4112
- }
4113
- transaction.commit().await
4114
- }
4115
-
4116
- async fn scan_untracked(
4117
- backend: &Arc<dyn Backend + Send + Sync>,
4118
- context: &UntrackedStateContext,
4119
- request: UntrackedStateScanRequest,
4120
- ) -> Result<Vec<MaterializedUntrackedStateRow>, LixError> {
4121
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
4122
- reader.scan_rows(&request).await
4123
- }
4124
-
4125
- async fn append_changelog_changes(
4126
- backend: &Arc<dyn Backend + Send + Sync>,
4127
- context: &CommitStoreContext,
4128
- changes: &[MaterializedChange],
4129
- ) -> Result<(), LixError> {
4130
- let storage = StorageContext::new(Arc::clone(backend));
4131
- let mut transaction = storage.begin_write_transaction().await?;
4132
- {
4133
- let mut writes = StorageWriteSet::new();
4134
- let canonical_changes = changes
4135
- .iter()
4136
- .map(canonical_changelog_bench_change)
4137
- .collect::<Result<Vec<_>, _>>()?;
4138
- let payloads = changelog_bench_json_payloads(changes);
4139
- JsonStoreContext::new().writer().stage_batch(
4140
- &mut writes,
4141
- JsonWritePlacementRef::OutOfBand,
4142
- payloads
4143
- .iter()
4144
- .map(|payload| NormalizedJsonRef::new(payload.as_str())),
4145
- )?;
4146
- let parent_ids = Vec::new();
4147
- let author_account_ids = vec!["bench-author".to_string()];
4148
- {
4149
- let mut transaction_ref = transaction.as_mut();
4150
- let mut writer = context.writer(&mut transaction_ref, &mut writes);
4151
- writer
4152
- .stage_commit_draft(
4153
- CommitDraftRef {
4154
- id: "bench-changelog-commit-0",
4155
- change_id: "bench-changelog-header-change-0",
4156
- parent_ids: &parent_ids,
4157
- author_account_ids: &author_account_ids,
4158
- created_at: "2024-01-01T00:00:00.000Z",
4159
- },
4160
- canonical_changes
4161
- .iter()
4162
- .map(|change| change.as_ref())
4163
- .collect(),
4164
- Vec::new(),
4165
- )
4166
- .await?;
4167
- }
4168
- writes.apply(&mut transaction.as_mut()).await?;
4169
- }
4170
- transaction.commit().await
4171
- }
4172
-
4173
- async fn write_binary_blob_writes(
4174
- backend: &Arc<dyn Backend + Send + Sync>,
4175
- context: &BinaryCasContext,
4176
- writes: &[BlobWrite<'_>],
4177
- ) -> Result<(), LixError> {
4178
- let storage = StorageContext::new(Arc::clone(backend));
4179
- let mut transaction = storage.begin_write_transaction().await?;
4180
- {
4181
- let mut writeset = StorageWriteSet::new();
4182
- let mut writer = context.writer(&mut writeset);
4183
- writer.stage_many(writes)?;
4184
- writeset.apply(&mut transaction.as_mut()).await?;
4185
- }
4186
- transaction.commit().await
4187
- }
4188
-
4189
- async fn count_binary_cas_manifests(
4190
- backend: &Arc<dyn Backend + Send + Sync>,
4191
- ) -> Result<usize, LixError> {
4192
- let context = BinaryCasContext::new();
4193
- let mut reader = context.reader(StorageContext::new(Arc::clone(backend)));
4194
- reader.count_blob_manifests().await
4195
- }
4196
-
4197
- fn report(measured_rows: usize, verified_rows: usize, elapsed: Duration) -> StorageBenchReport {
4198
- StorageBenchReport {
4199
- measured_rows,
4200
- verified_rows,
4201
- elapsed,
4202
- }
4203
- }
4204
-
4205
- const TRACKED_MATCH_SCHEMA_KEY: &str = "bench_tracked_entity";
4206
- const TRACKED_OTHER_SCHEMA_KEY: &str = "bench_tracked_other_entity";
4207
- const UNTRACKED_MATCH_SCHEMA_KEY: &str = "bench_untracked_entity";
4208
- const UNTRACKED_OTHER_SCHEMA_KEY: &str = "bench_untracked_other_entity";
4209
- const CHANGELOG_MATCH_SCHEMA_KEY: &str = "bench_changelog_entity";
4210
- const CHANGELOG_OTHER_SCHEMA_KEY: &str = "bench_changelog_other_entity";
4211
- const CHANGELOG_HISTORY_ENTITY_ID: &str = "change-entity-history-target";
4212
-
4213
- fn tracked_rows(config: StorageBenchConfig, commit_id: &str) -> Vec<MaterializedTrackedStateRow> {
4214
- (0..config.rows)
4215
- .map(|index| MaterializedTrackedStateRow {
4216
- entity_id: EntityIdentity::single(entity_id("tracked", index, config.key_pattern)),
4217
- schema_key: tracked_schema_key(index, config.selectivity),
4218
- file_id: Some("bench.json".to_string()),
4219
- snapshot_content: Some(snapshot_content(index, config.state_payload_bytes)),
4220
- metadata: None,
4221
- deleted: false,
4222
- created_at: timestamp(index),
4223
- updated_at: timestamp(index),
4224
- change_id: tracked_change_id(commit_id, index),
4225
- commit_id: commit_id.to_string(),
4226
- })
4227
- .collect()
4228
- }
4229
-
4230
- fn json_pointer_tracked_rows(
4231
- rows: &[JsonPointerStorageRow],
4232
- commit_id: &str,
4233
- updated: bool,
4234
- ) -> Vec<MaterializedTrackedStateRow> {
4235
- rows.iter()
4236
- .enumerate()
4237
- .map(|(index, row)| {
4238
- let value_json = if updated {
4239
- row.updated_value_json.as_str()
4240
- } else {
4241
- row.value_json.as_str()
4242
- };
4243
- let value = serde_json::from_str::<serde_json::Value>(value_json)
4244
- .unwrap_or_else(|_| serde_json::Value::String(value_json.to_string()));
4245
- let snapshot = serde_json::json!({
4246
- "path": row.path,
4247
- "value": value,
4248
- })
4249
- .to_string();
4250
- MaterializedTrackedStateRow {
4251
- entity_id: EntityIdentity::single(row.path.as_str()),
4252
- schema_key: "json_pointer".to_string(),
4253
- file_id: None,
4254
- snapshot_content: Some(snapshot),
4255
- metadata: None,
4256
- deleted: false,
4257
- created_at: timestamp(index),
4258
- updated_at: timestamp(index),
4259
- change_id: tracked_change_id(commit_id, index),
4260
- commit_id: commit_id.to_string(),
4261
- }
4262
- })
4263
- .collect()
4264
- }
4265
-
4266
- async fn write_json_pointer_delta_chain(
4267
- backend: &Arc<dyn Backend + Send + Sync>,
4268
- rows: &[JsonPointerStorageRow],
4269
- delta_commits: usize,
4270
- updated_rows_per_commit: usize,
4271
- ) -> Result<(TrackedStateContext, String), LixError> {
4272
- let context = TrackedStateContext::new();
4273
- let base_commit_id = "json-pointer-base";
4274
- let base_rows = json_pointer_tracked_rows(rows, base_commit_id, false);
4275
- write_tracked_root(backend, &context, base_commit_id, None, &base_rows).await?;
4276
-
4277
- let mut parent_commit_id = base_commit_id.to_string();
4278
- for delta_index in 0..delta_commits {
4279
- let commit_id = format!("json-pointer-delta-{delta_index}");
4280
- let mut child_rows = json_pointer_tracked_rows(
4281
- &rows[..updated_rows_per_commit.min(rows.len())],
4282
- &commit_id,
4283
- true,
4284
- );
4285
- for row in &mut child_rows {
4286
- row.updated_at = timestamp(rows.len() + delta_index);
4287
- }
4288
- write_tracked_root(
4289
- backend,
4290
- &context,
4291
- &commit_id,
4292
- Some(parent_commit_id.as_str()),
4293
- &child_rows,
4294
- )
4295
- .await?;
4296
- parent_commit_id = commit_id;
4297
- }
4298
-
4299
- Ok((context, parent_commit_id))
4300
- }
4301
-
4302
- fn tracked_rows_file_selective(
4303
- config: StorageBenchConfig,
4304
- commit_id: &str,
4305
- ) -> Vec<MaterializedTrackedStateRow> {
4306
- (0..config.rows)
4307
- .map(|index| MaterializedTrackedStateRow {
4308
- entity_id: EntityIdentity::single(entity_id("tracked", index, config.key_pattern)),
4309
- schema_key: TRACKED_MATCH_SCHEMA_KEY.to_string(),
4310
- file_id: Some(
4311
- if config.selectivity.matches(index) {
4312
- "bench-match.json"
4313
- } else {
4314
- "bench-other.json"
4315
- }
4316
- .to_string(),
4317
- ),
4318
- snapshot_content: Some(snapshot_content(index, config.state_payload_bytes)),
4319
- metadata: None,
4320
- deleted: false,
4321
- created_at: timestamp(index),
4322
- updated_at: timestamp(index),
4323
- change_id: tracked_change_id(commit_id, index),
4324
- commit_id: commit_id.to_string(),
4325
- })
4326
- .collect()
4327
- }
4328
-
4329
- fn tracked_change_id(commit_id: &str, index: usize) -> String {
4330
- format!("{commit_id}:tracked-change-{index}")
4331
- }
4332
-
4333
- fn untracked_rows(config: StorageBenchConfig) -> Vec<MaterializedUntrackedStateRow> {
4334
- (0..config.rows)
4335
- .map(|index| MaterializedUntrackedStateRow {
4336
- entity_id: EntityIdentity::single(entity_id("untracked", index, config.key_pattern)),
4337
- schema_key: untracked_schema_key(index, config.selectivity),
4338
- file_id: Some("bench.json".to_string()),
4339
- snapshot_content: Some(snapshot_content(index, config.state_payload_bytes)),
4340
- metadata: None,
4341
- deleted: false,
4342
- created_at: timestamp(index),
4343
- updated_at: timestamp(index),
4344
- global: false,
4345
- version_id: "bench-version".to_string(),
4346
- })
4347
- .collect()
4348
- }
4349
-
4350
- fn changelog_changes(config: StorageBenchConfig) -> Vec<Change> {
4351
- changelog_materialized_changes(config)
4352
- .into_iter()
4353
- .map(changelog_bench_change_ref_only)
4354
- .collect()
4355
- }
4356
-
4357
- fn changelog_materialized_changes(config: StorageBenchConfig) -> Vec<MaterializedChange> {
4358
- (0..config.rows)
4359
- .map(|index| MaterializedChange {
4360
- id: format!("bench-change-{index}"),
4361
- entity_id: EntityIdentity::single(entity_id(
4362
- "change-entity",
4363
- index,
4364
- config.key_pattern,
4365
- )),
4366
- schema_key: "bench_changelog_entity".to_string(),
4367
- file_id: Some("bench.json".to_string()),
4368
- snapshot_content: Some(snapshot_content(index, config.state_payload_bytes)),
4369
- metadata: None,
4370
- created_at: timestamp(index),
4371
- })
4372
- .collect()
4373
- }
4374
-
4375
- fn commit_graph_materialized_commit_change(commit_id: &str, rows: usize) -> MaterializedChange {
4376
- let snapshot_content = serde_json::json!({
4377
- "id": commit_id,
4378
- })
4379
- .to_string();
4380
-
4381
- MaterializedChange {
4382
- id: format!("bench-commit-change-{commit_id}"),
4383
- entity_id: EntityIdentity::single(commit_id.to_string()),
4384
- schema_key: "lix_commit".to_string(),
4385
- file_id: None,
4386
- snapshot_content: Some(snapshot_content),
4387
- metadata: None,
4388
- created_at: timestamp(rows),
4389
- }
4390
- }
4391
-
4392
- fn canonical_changelog_bench_change(change: &MaterializedChange) -> Result<Change, LixError> {
4393
- let snapshot_ref = change
4394
- .snapshot_content
4395
- .as_ref()
4396
- .map(|value| prepare_json_ref(value.as_bytes()))
4397
- .transpose()?;
4398
- let metadata_ref = change
4399
- .metadata
4400
- .as_ref()
4401
- .map(|value| prepare_json_ref(value.as_bytes()))
4402
- .transpose()?;
4403
- Ok(Change {
4404
- id: change.id.clone(),
4405
- entity_id: change.entity_id.clone(),
4406
- schema_key: change.schema_key.clone(),
4407
- file_id: change.file_id.clone(),
4408
- snapshot_ref,
4409
- metadata_ref,
4410
- created_at: change.created_at.clone(),
4411
- })
4412
- }
4413
-
4414
- fn changelog_bench_json_payloads(changes: &[MaterializedChange]) -> Vec<String> {
4415
- changes
4416
- .iter()
4417
- .flat_map(|change| {
4418
- change
4419
- .snapshot_content
4420
- .iter()
4421
- .chain(change.metadata.iter())
4422
- .cloned()
4423
- .collect::<Vec<_>>()
4424
- })
4425
- .collect()
4426
- }
4427
-
4428
- fn changelog_bench_change_ref_only(change: MaterializedChange) -> Change {
4429
- let snapshot_ref = change
4430
- .snapshot_content
4431
- .as_ref()
4432
- .map(|value| JsonRef::from_hash(blake3::hash(value.as_bytes())));
4433
- let metadata_ref = change
4434
- .metadata
4435
- .as_ref()
4436
- .map(|value| JsonRef::from_hash(blake3::hash(value.as_bytes())));
4437
- Change {
4438
- id: change.id,
4439
- entity_id: change.entity_id,
4440
- schema_key: change.schema_key,
4441
- file_id: change.file_id,
4442
- snapshot_ref,
4443
- metadata_ref,
4444
- created_at: change.created_at,
4445
- }
4446
- }
4447
-
4448
- fn changelog_tombstone_changes(config: StorageBenchConfig) -> Vec<MaterializedChange> {
4449
- changelog_materialized_changes(config)
4450
- .into_iter()
4451
- .map(|mut change| {
4452
- change.snapshot_content = None;
4453
- change.metadata = None;
4454
- change
4455
- })
4456
- .collect()
4457
- }
4458
-
4459
- fn changelog_metadata_changes(config: StorageBenchConfig) -> Vec<MaterializedChange> {
4460
- changelog_materialized_changes(config)
4461
- .into_iter()
4462
- .enumerate()
4463
- .map(|(index, mut change)| {
4464
- change.metadata = Some(snapshot_metadata(index, config.state_payload_bytes));
4465
- change
4466
- })
4467
- .collect()
4468
- }
4469
-
4470
- fn changelog_shared_payload_changes(config: StorageBenchConfig) -> Vec<MaterializedChange> {
4471
- let shared_snapshot_content = snapshot_content(0, config.state_payload_bytes);
4472
- changelog_materialized_changes(config)
4473
- .into_iter()
4474
- .map(|mut change| {
4475
- change.snapshot_content = Some(shared_snapshot_content.clone());
4476
- change
4477
- })
4478
- .collect()
4479
- }
4480
-
4481
- fn changelog_shared_metadata_changes(config: StorageBenchConfig) -> Vec<MaterializedChange> {
4482
- let shared_metadata = snapshot_metadata(0, config.state_payload_bytes);
4483
- changelog_materialized_changes(config)
4484
- .into_iter()
4485
- .map(|mut change| {
4486
- change.snapshot_content = None;
4487
- change.metadata = Some(shared_metadata.clone());
4488
- change
4489
- })
4490
- .collect()
4491
- }
4492
-
4493
- fn changelog_shared_payload_and_metadata_changes(
4494
- config: StorageBenchConfig,
4495
- ) -> Vec<MaterializedChange> {
4496
- let shared_snapshot_content = snapshot_content(0, config.state_payload_bytes);
4497
- let shared_metadata = snapshot_metadata(1, config.state_payload_bytes);
4498
- changelog_materialized_changes(config)
4499
- .into_iter()
4500
- .map(|mut change| {
4501
- change.snapshot_content = Some(shared_snapshot_content.clone());
4502
- change.metadata = Some(shared_metadata.clone());
4503
- change
4504
- })
4505
- .collect()
4506
- }
4507
-
4508
- fn changelog_composite_entity_id_changes(config: StorageBenchConfig) -> Vec<MaterializedChange> {
4509
- changelog_materialized_changes(config)
4510
- .into_iter()
4511
- .enumerate()
4512
- .map(|(index, mut change)| {
4513
- change.entity_id = EntityIdentity {
4514
- parts: vec![
4515
- entity_id("change-composite", index, config.key_pattern),
4516
- index.to_string(),
4517
- (index % 2 == 0).to_string(),
4518
- ],
4519
- };
4520
- change
4521
- })
4522
- .collect()
4523
- }
4524
-
4525
- fn changelog_selective_changes(config: StorageBenchConfig) -> Vec<MaterializedChange> {
4526
- changelog_materialized_changes(config)
4527
- .into_iter()
4528
- .enumerate()
4529
- .map(|(index, mut change)| {
4530
- change.schema_key = changelog_schema_key(index, config.selectivity);
4531
- change
4532
- })
4533
- .collect()
4534
- }
4535
-
4536
- fn changelog_entity_history_changes(config: StorageBenchConfig) -> Vec<MaterializedChange> {
4537
- changelog_materialized_changes(config)
4538
- .into_iter()
4539
- .enumerate()
4540
- .map(|(index, mut change)| {
4541
- if index % 10 == 0 {
4542
- change.entity_id = EntityIdentity::single(CHANGELOG_HISTORY_ENTITY_ID);
4543
- }
4544
- change
4545
- })
4546
- .collect()
4547
- }
4548
-
4549
- fn tracked_schema_key(index: usize, selectivity: StorageBenchSelectivity) -> String {
4550
- if selectivity.matches(index) {
4551
- TRACKED_MATCH_SCHEMA_KEY
4552
- } else {
4553
- TRACKED_OTHER_SCHEMA_KEY
4554
- }
4555
- .to_string()
4556
- }
4557
-
4558
- fn untracked_schema_key(index: usize, selectivity: StorageBenchSelectivity) -> String {
4559
- if selectivity.matches(index) {
4560
- UNTRACKED_MATCH_SCHEMA_KEY
4561
- } else {
4562
- UNTRACKED_OTHER_SCHEMA_KEY
4563
- }
4564
- .to_string()
4565
- }
4566
-
4567
- fn changelog_schema_key(index: usize, selectivity: StorageBenchSelectivity) -> String {
4568
- if selectivity.matches(index) {
4569
- CHANGELOG_MATCH_SCHEMA_KEY
4570
- } else {
4571
- CHANGELOG_OTHER_SCHEMA_KEY
4572
- }
4573
- .to_string()
4574
- }
4575
-
4576
- fn entity_id(prefix: &str, index: usize, key_pattern: StorageBenchKeyPattern) -> String {
4577
- match key_pattern {
4578
- StorageBenchKeyPattern::Sequential => format!("{prefix}-{index}"),
4579
- StorageBenchKeyPattern::Random => format!("{prefix}-{:016x}", randomish_index(index)),
4580
- }
4581
- }
4582
-
4583
- fn randomish_index(index: usize) -> u64 {
4584
- let mut value = index as u64;
4585
- value ^= value >> 30;
4586
- value = value.wrapping_mul(0xbf58_476d_1ce4_e5b9);
4587
- value ^= value >> 27;
4588
- value = value.wrapping_mul(0x94d0_49bb_1331_11eb);
4589
- value ^ (value >> 31)
4590
- }
4591
-
4592
- fn binary_file_ids(rows: usize) -> Vec<String> {
4593
- (0..rows)
4594
- .map(|index| format!("bench-file-{index}"))
4595
- .collect()
4596
- }
4597
-
4598
- fn binary_payloads(rows: usize, blob_bytes: usize) -> Vec<Vec<u8>> {
4599
- (0..rows)
4600
- .map(|index| binary_payload(index, blob_bytes))
4601
- .collect()
4602
- }
4603
-
4604
- fn binary_half_duplicate_payloads(rows: usize, blob_bytes: usize) -> Vec<Vec<u8>> {
4605
- (0..rows)
4606
- .map(|index| {
4607
- if index % 2 == 0 {
4608
- binary_payload(0, blob_bytes)
4609
- } else {
4610
- binary_payload(index, blob_bytes)
4611
- }
4612
- })
4613
- .collect()
4614
- }
4615
-
4616
- fn binary_blob_writes<'a>(_file_ids: &'a [String], payloads: &'a [Vec<u8>]) -> Vec<BlobWrite<'a>> {
4617
- payloads
4618
- .iter()
4619
- .map(|payload| BlobWrite {
4620
- bytes: payload.as_slice(),
4621
- })
4622
- .collect()
4623
- }
4624
-
4625
- fn snapshot_content(index: usize, target_bytes: usize) -> String {
4626
- let mut value = serde_json::json!({
4627
- "id": format!("entity-{index}"),
4628
- "value": format!("value-{index}"),
4629
- "index": index
4630
- });
4631
- pad_snapshot_content(&mut value, target_bytes);
4632
- value.to_string()
4633
- }
4634
-
4635
- fn snapshot_metadata(index: usize, target_bytes: usize) -> String {
4636
- snapshot_content(index, target_bytes)
4637
- }
4638
-
4639
- fn tracked_state_header_columns() -> Vec<String> {
4640
- [
4641
- "entity_id",
4642
- "schema_key",
4643
- "file_id",
4644
- "metadata",
4645
- "created_at",
4646
- "updated_at",
4647
- "change_id",
4648
- "commit_id",
4649
- ]
4650
- .into_iter()
4651
- .map(str::to_string)
4652
- .collect()
4653
- }
4654
-
4655
- fn untracked_state_header_columns() -> Vec<String> {
4656
- [
4657
- "entity_id",
4658
- "schema_key",
4659
- "file_id",
4660
- "metadata",
4661
- "created_at",
4662
- "updated_at",
4663
- "global",
4664
- "version_id",
4665
- ]
4666
- .into_iter()
4667
- .map(str::to_string)
4668
- .collect()
4669
- }
4670
-
4671
- fn updated_snapshot_content(index: usize, target_bytes: usize) -> String {
4672
- let mut value = serde_json::json!({
4673
- "id": format!("entity-{index}"),
4674
- "value": format!("updated-{index}"),
4675
- "index": index
4676
- });
4677
- pad_snapshot_content(&mut value, target_bytes);
4678
- value.to_string()
4679
- }
4680
-
4681
- fn partial_updated_snapshot_content(index: usize, target_bytes: usize) -> String {
4682
- let mut value = serde_json::json!({
4683
- "id": format!("entity-{index}"),
4684
- "value": format!("value-{index}"),
4685
- "index": index,
4686
- "done": true
4687
- });
4688
- pad_snapshot_content(&mut value, target_bytes);
4689
- value.to_string()
4690
- }
4691
-
4692
- fn delta_chain_snapshot_content(
4693
- delta_index: usize,
4694
- row_index: usize,
4695
- target_bytes: usize,
4696
- ) -> String {
4697
- let mut value = serde_json::json!({
4698
- "id": format!("entity-{row_index}"),
4699
- "value": format!("delta-{delta_index}-{row_index}"),
4700
- "index": row_index,
4701
- "delta": delta_index
4702
- });
4703
- pad_snapshot_content(&mut value, target_bytes);
4704
- value.to_string()
4705
- }
4706
-
4707
- fn pad_snapshot_content(value: &mut serde_json::Value, target_bytes: usize) {
4708
- let current = value.to_string().len();
4709
- if target_bytes <= current {
4710
- return;
4711
- }
4712
- value["padding"] = serde_json::Value::String("x".repeat(target_bytes - current));
4713
- }
4714
-
4715
- fn timestamp(index: usize) -> String {
4716
- format!(
4717
- "2026-05-01T00:{:02}:{:02}.000Z",
4718
- (index / 60) % 60,
4719
- index % 60
4720
- )
4721
- }
4722
-
4723
- fn binary_payload(index: usize, len: usize) -> Vec<u8> {
4724
- let mut payload = (0..len)
4725
- .map(|offset| {
4726
- ((index as u64)
4727
- .wrapping_mul(31)
4728
- .wrapping_add((offset as u64).wrapping_mul(17))
4729
- & 0xff) as u8
4730
- })
4731
- .collect::<Vec<_>>();
4732
- for (offset, byte) in (index as u64).to_le_bytes().into_iter().enumerate() {
4733
- if offset < payload.len() {
4734
- payload[offset] = byte;
4735
- }
4736
- }
4737
- payload
4738
- }
4739
-
4740
- fn json_documents(shape: JsonStorePayloadShape, rows: usize) -> Vec<Vec<u8>> {
4741
- (0..rows).map(|index| json_document(shape, index)).collect()
4742
- }
4743
-
4744
- fn json_document(shape: JsonStorePayloadShape, index: usize) -> Vec<u8> {
4745
- match shape {
4746
- JsonStorePayloadShape::SmallRaw1k => json_object_document(index, 1_024, 8),
4747
- JsonStorePayloadShape::MediumStructured16k => json_object_document(index, 16 * 1024, 128),
4748
- JsonStorePayloadShape::LargeStructured128k => {
4749
- json_object_document(index, 128 * 1024, 1_000)
4750
- }
4751
- JsonStorePayloadShape::LargeArray128k => json_array_document(index, 128 * 1024, 1_000),
4752
- }
4753
- }
4754
-
4755
- fn updated_json_document(shape: JsonStorePayloadShape, index: usize) -> Vec<u8> {
4756
- let bytes = json_document(shape, index);
4757
- let mut value: serde_json::Value =
4758
- serde_json::from_slice(&bytes).expect("storage bench JSON document should parse");
4759
- match shape {
4760
- JsonStorePayloadShape::LargeArray128k => {
4761
- value["items"][999]["value"] =
4762
- serde_json::Value::String(format!("updated-array-value-{index}"));
4763
- }
4764
- JsonStorePayloadShape::SmallRaw1k
4765
- | JsonStorePayloadShape::MediumStructured16k
4766
- | JsonStorePayloadShape::LargeStructured128k => {
4767
- value["field_999"] = serde_json::Value::String(format!("updated-object-value-{index}"));
4768
- }
4769
- }
4770
- serde_json::to_vec(&value).expect("storage bench updated JSON should serialize")
4771
- }
4772
-
4773
- fn json_object_document(index: usize, target_bytes: usize, fields: usize) -> Vec<u8> {
4774
- let mut object = serde_json::Map::new();
4775
- object.insert(
4776
- "id".to_string(),
4777
- serde_json::Value::String(format!("json-{index}")),
4778
- );
4779
- object.insert(
4780
- "target".to_string(),
4781
- serde_json::Value::String(format!("target-{index}")),
4782
- );
4783
- object.insert(
4784
- "status".to_string(),
4785
- serde_json::Value::String(if index % 2 == 0 { "open" } else { "closed" }.to_string()),
4786
- );
4787
- object.insert(
4788
- "nested".to_string(),
4789
- serde_json::json!({
4790
- "target": format!("nested-target-{index}"),
4791
- "revision": index,
4792
- }),
4793
- );
4794
- for field_index in 0..fields {
4795
- object.insert(
4796
- format!("field_{field_index}"),
4797
- serde_json::Value::String(format!("value-{index}-{field_index}")),
4798
- );
4799
- }
4800
- pad_json_object(&mut object, target_bytes);
4801
- serde_json::to_vec(&serde_json::Value::Object(object))
4802
- .expect("storage bench object JSON should serialize")
4803
- }
4804
-
4805
- fn json_array_document(index: usize, target_bytes: usize, items: usize) -> Vec<u8> {
4806
- let mut object = serde_json::Map::new();
4807
- object.insert(
4808
- "id".to_string(),
4809
- serde_json::Value::String(format!("json-array-{index}")),
4810
- );
4811
- object.insert(
4812
- "target".to_string(),
4813
- serde_json::Value::String(format!("target-{index}")),
4814
- );
4815
- object.insert(
4816
- "status".to_string(),
4817
- serde_json::Value::String(if index % 2 == 0 { "open" } else { "closed" }.to_string()),
4818
- );
4819
- object.insert(
4820
- "items".to_string(),
4821
- serde_json::Value::Array(
4822
- (0..items)
4823
- .map(|item_index| {
4824
- serde_json::json!({
4825
- "index": item_index,
4826
- "status": if item_index % 2 == 0 { "ready" } else { "blocked" },
4827
- "value": format!("item-{index}-{item_index}"),
4828
- })
4829
- })
4830
- .collect(),
4831
- ),
4832
- );
4833
- pad_json_object(&mut object, target_bytes);
4834
- serde_json::to_vec(&serde_json::Value::Object(object))
4835
- .expect("storage bench array JSON should serialize")
4836
- }
4837
-
4838
- fn pad_json_object(object: &mut serde_json::Map<String, serde_json::Value>, target_bytes: usize) {
4839
- let current = serde_json::to_vec(&serde_json::Value::Object(object.clone()))
4840
- .expect("storage bench JSON should serialize")
4841
- .len();
4842
- if target_bytes <= current {
4843
- return;
4844
- }
4845
- object.insert(
4846
- "padding".to_string(),
4847
- serde_json::Value::String("x".repeat(target_bytes - current)),
4848
- );
4849
- }
4850
-
4851
- fn json_projection_paths(projection: JsonStoreProjectionShape) -> Vec<JsonProjectionPath> {
4852
- match projection {
4853
- JsonStoreProjectionShape::TopLevelTarget => vec![JsonProjectionPath::new("/target")],
4854
- JsonStoreProjectionShape::TopLevelTenProps => (0..10)
4855
- .map(|index| JsonProjectionPath::new(format!("/field_{index}")))
4856
- .collect(),
4857
- JsonStoreProjectionShape::NestedTarget => vec![JsonProjectionPath::new("/nested/target")],
4858
- JsonStoreProjectionShape::ArrayItem999 => {
4859
- vec![JsonProjectionPath::new("/items/999/value")]
4860
- }
4861
- JsonStoreProjectionShape::Status => vec![JsonProjectionPath::new("/status")],
4862
- }
4863
171
  }