envio 3.1.0-rc.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,20 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Env from "./Env.res.mjs";
3
4
  import * as Utils from "./Utils.res.mjs";
4
5
  import * as Config from "./Config.res.mjs";
6
+ import * as Logging from "./Logging.res.mjs";
5
7
  import * as Internal from "./Internal.res.mjs";
8
+ import * as Throttler from "./Throttler.res.mjs";
6
9
  import * as Prometheus from "./Prometheus.res.mjs";
7
10
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
8
11
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
9
12
  import * as InMemoryTable from "./InMemoryTable.res.mjs";
13
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
14
+ import * as RollbackCommit from "./RollbackCommit.res.mjs";
10
15
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
16
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
17
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
11
18
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
12
19
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
13
20
 
@@ -39,15 +46,30 @@ let EntityTables = {
39
46
  get: get
40
47
  };
41
48
 
42
- function make$1(entities, committedCheckpointIdOpt) {
49
+ function make$1(entities, committedCheckpointIdOpt, persistence, config, onError) {
43
50
  let committedCheckpointId = committedCheckpointIdOpt !== undefined ? committedCheckpointIdOpt : Internal.initialCheckpointId;
51
+ let intervalMillis = Env.ThrottleWrites.chainMetadataIntervalMillis;
52
+ let chainMetaThrottler = Throttler.make(intervalMillis, Logging.createChild({
53
+ context: "Throttler for chain metadata writes",
54
+ intervalMillis: intervalMillis
55
+ }));
44
56
  return {
45
57
  allEntities: entities,
46
- rawEvents: [],
47
58
  entities: make(entities),
48
59
  effects: {},
49
60
  rollback: undefined,
50
- committedCheckpointId: committedCheckpointId
61
+ committedCheckpointId: committedCheckpointId,
62
+ processedCheckpointId: committedCheckpointId,
63
+ processedBatches: [],
64
+ writeFiber: undefined,
65
+ hasFailedWrite: false,
66
+ onError: onError,
67
+ commitWaiters: [],
68
+ persistence: persistence,
69
+ config: config,
70
+ chainMeta: {},
71
+ chainMetaDirty: false,
72
+ chainMetaThrottler: chainMetaThrottler
51
73
  };
52
74
  }
53
75
 
@@ -61,12 +83,65 @@ function getEffectInMemTable(inMemoryStore, effect) {
61
83
  idsToStore: [],
62
84
  invalidationsCount: 0,
63
85
  dict: {},
86
+ changesCount: 0,
64
87
  effect: effect
65
88
  };
66
89
  inMemoryStore.effects[key] = table$1;
67
90
  return table$1;
68
91
  }
69
92
 
93
+ function getEffectOutput(inMemTable, key) {
94
+ let match = inMemTable.dict[key];
95
+ if (match !== undefined && match.type === "SET") {
96
+ return Primitive_option.some(match.entity);
97
+ }
98
+ }
99
+
100
+ function setEffectOutput(inMemTable, checkpointId, cacheKey, output, shouldCache) {
101
+ let match = inMemTable.dict[cacheKey];
102
+ if (match !== undefined) {
103
+
104
+ } else {
105
+ inMemTable.changesCount = inMemTable.changesCount + 1;
106
+ }
107
+ inMemTable.dict[cacheKey] = {
108
+ type: "SET",
109
+ entityId: cacheKey,
110
+ entity: output,
111
+ checkpointId: checkpointId
112
+ };
113
+ if (shouldCache) {
114
+ inMemTable.idsToStore.push(cacheKey);
115
+ return;
116
+ }
117
+ }
118
+
119
+ function initEffectOutputFromDb(inMemTable, cacheKey, output) {
120
+ if (Stdlib_Option.isNone(inMemTable.dict[cacheKey])) {
121
+ inMemTable.changesCount = inMemTable.changesCount + 1;
122
+ inMemTable.dict[cacheKey] = {
123
+ type: "SET",
124
+ entityId: cacheKey,
125
+ entity: output,
126
+ checkpointId: Internal.loadedFromDbCheckpointId
127
+ };
128
+ return;
129
+ }
130
+ }
131
+
132
+ function dropCommittedEffects(inMemTable, committedCheckpointId, keepLoadedFromDb) {
133
+ let keysToDelete = [];
134
+ Utils.Dict.forEachWithKey(inMemTable.dict, (change, key) => {
135
+ let checkpointId = change.checkpointId;
136
+ if (checkpointId <= committedCheckpointId && !(keepLoadedFromDb && checkpointId === Internal.loadedFromDbCheckpointId)) {
137
+ keysToDelete.push(key);
138
+ return;
139
+ }
140
+ });
141
+ keysToDelete.forEach(key => Utils.Dict.deleteInPlace(inMemTable.dict, key));
142
+ inMemTable.changesCount = inMemTable.changesCount - keysToDelete.length;
143
+ }
144
+
70
145
  function getInMemTable(inMemoryStore, entityConfig) {
71
146
  return get(inMemoryStore.entities, entityConfig.name);
72
147
  }
@@ -75,33 +150,145 @@ function isRollingBack(inMemoryStore) {
75
150
  return inMemoryStore.rollback !== undefined;
76
151
  }
77
152
 
78
- async function writeBatch(inMemoryStore, persistence, batch, config, isInReorgThreshold) {
79
- let match = persistence.storageStatus;
80
- if (typeof match !== "object") {
81
- return Stdlib_JsError.throwWithMessage(`Failed to access the indexer storage. The Persistence layer is not initialized.`);
82
- }
83
- if (match.TAG === "Initializing") {
84
- return Stdlib_JsError.throwWithMessage(`Failed to access the indexer storage. The Persistence layer is not initialized.`);
85
- }
86
- let cache = match._0.cache;
87
- let committedCheckpointId = inMemoryStore.committedCheckpointId;
88
- let totalChanges = {
153
+ function getChangesCount(inMemoryStore) {
154
+ let total = {
155
+ contents: 0
156
+ };
157
+ inMemoryStore.allEntities.forEach(entityConfig => {
158
+ total.contents = total.contents + getInMemTable(inMemoryStore, entityConfig).changesCount;
159
+ });
160
+ Utils.Dict.forEach(inMemoryStore.effects, inMemTable => {
161
+ total.contents = total.contents + inMemTable.changesCount;
162
+ });
163
+ inMemoryStore.processedBatches.forEach(batch => {
164
+ total.contents = total.contents + batch.totalBatchSize;
165
+ });
166
+ return total.contents;
167
+ }
168
+
169
+ function wakeCommitWaiters(inMemoryStore) {
170
+ let waiters = inMemoryStore.commitWaiters;
171
+ inMemoryStore.commitWaiters = [];
172
+ waiters.forEach(resolve => resolve());
173
+ }
174
+
175
+ function waitForCommit(inMemoryStore) {
176
+ return new Promise((resolve, param) => {
177
+ inMemoryStore.commitWaiters.push(resolve);
178
+ });
179
+ }
180
+
181
+ function drainBatchRun(inMemoryStore) {
182
+ let all = inMemoryStore.processedBatches;
183
+ let isInReorgThreshold = all[0].isInReorgThreshold;
184
+ let rest = [];
185
+ let progressedChainsById = {};
186
+ let totalBatchSize = {
89
187
  contents: 0
90
188
  };
91
- persistence.allEntities.forEach(entityConfig => {
92
- totalChanges.contents = totalChanges.contents + getInMemTable(inMemoryStore, entityConfig).changesCount;
189
+ let items = [];
190
+ let checkpointIds = [];
191
+ let checkpointChainIds = [];
192
+ let checkpointBlockNumbers = [];
193
+ let checkpointBlockHashes = [];
194
+ let checkpointEventsProcessed = [];
195
+ all.forEach(batch => {
196
+ if (Utils.$$Array.isEmpty(rest) && batch.isInReorgThreshold === isInReorgThreshold) {
197
+ Utils.Dict.forEachWithKey(batch.progressedChainsById, (chainAfterBatch, key) => {
198
+ progressedChainsById[key] = chainAfterBatch;
199
+ });
200
+ totalBatchSize.contents = totalBatchSize.contents + batch.totalBatchSize | 0;
201
+ items.push(...batch.items);
202
+ checkpointIds.push(...batch.checkpointIds);
203
+ checkpointChainIds.push(...batch.checkpointChainIds);
204
+ checkpointBlockNumbers.push(...batch.checkpointBlockNumbers);
205
+ checkpointBlockHashes.push(...batch.checkpointBlockHashes);
206
+ checkpointEventsProcessed.push(...batch.checkpointEventsProcessed);
207
+ } else {
208
+ rest.push(batch);
209
+ }
210
+ });
211
+ inMemoryStore.processedBatches = rest;
212
+ return {
213
+ totalBatchSize: totalBatchSize.contents,
214
+ items: items,
215
+ progressedChainsById: progressedChainsById,
216
+ isInReorgThreshold: isInReorgThreshold,
217
+ checkpointIds: checkpointIds,
218
+ checkpointChainIds: checkpointChainIds,
219
+ checkpointBlockNumbers: checkpointBlockNumbers,
220
+ checkpointBlockHashes: checkpointBlockHashes,
221
+ checkpointEventsProcessed: checkpointEventsProcessed
222
+ };
223
+ }
224
+
225
+ function snapshotEffects(inMemoryStore, cache) {
226
+ let acc = [];
227
+ Utils.Dict.forEach(inMemoryStore.effects, inMemTable => {
228
+ let idsToStore = inMemTable.idsToStore;
229
+ let invalidationsCount = inMemTable.invalidationsCount;
230
+ let dict = inMemTable.dict;
231
+ let effect = inMemTable.effect;
232
+ if (idsToStore.length !== 0) {
233
+ let items = Stdlib_Array.filterMap(idsToStore, id => {
234
+ let match = dict[id];
235
+ if (match.type === "SET") {
236
+ return {
237
+ id: id,
238
+ output: match.entity
239
+ };
240
+ }
241
+ });
242
+ let effectName = effect.name;
243
+ let c = cache[effectName];
244
+ let effectCacheRecord;
245
+ if (c !== undefined) {
246
+ effectCacheRecord = c;
247
+ } else {
248
+ let c$1 = {
249
+ effectName: effectName,
250
+ count: 0
251
+ };
252
+ cache[effectName] = c$1;
253
+ effectCacheRecord = c$1;
254
+ }
255
+ let shouldInitialize = effectCacheRecord.count === 0;
256
+ effectCacheRecord.count = (effectCacheRecord.count + items.length | 0) - invalidationsCount | 0;
257
+ Prometheus.EffectCacheCount.set(effectCacheRecord.count, effectName);
258
+ acc.push({
259
+ effect: effect,
260
+ items: items,
261
+ shouldInitialize: shouldInitialize
262
+ });
263
+ }
264
+ inMemTable.idsToStore = [];
265
+ inMemTable.invalidationsCount = 0;
93
266
  });
94
- let keepLatestChanges = totalChanges.contents < 50000;
267
+ return acc;
268
+ }
269
+
270
+ async function runOneWrite(inMemoryStore, persistence, config) {
271
+ let match = persistence.storageStatus;
272
+ let cache;
273
+ cache = typeof match !== "object" || match.TAG === "Initializing" ? Stdlib_JsError.throwWithMessage(`Failed to access the indexer storage. The Persistence layer is not initialized.`) : match._0.cache;
274
+ let chainMetaData = inMemoryStore.chainMetaDirty ? (inMemoryStore.chainMetaDirty = false, Utils.Dict.shallowCopy(inMemoryStore.chainMeta)) : undefined;
275
+ let match$1 = inMemoryStore.processedBatches;
276
+ if (match$1.length === 0) {
277
+ if (chainMetaData !== undefined) {
278
+ return await persistence.storage.setChainMeta(chainMetaData);
279
+ } else {
280
+ return;
281
+ }
282
+ }
283
+ let committedCheckpointId = inMemoryStore.committedCheckpointId;
284
+ let batch = drainBatchRun(inMemoryStore);
285
+ let checkpointId = Utils.$$Array.last(batch.checkpointIds);
286
+ let upToCheckpointId = checkpointId !== undefined ? checkpointId : committedCheckpointId;
287
+ let rollback = inMemoryStore.rollback;
288
+ inMemoryStore.rollback = undefined;
95
289
  let updatedEntities = Stdlib_Array.filterMap(persistence.allEntities, entityConfig => {
96
290
  let table = getInMemTable(inMemoryStore, entityConfig);
97
- table.changesCount = table.changesCount - table.prevEntityChanges.length;
98
- let changes = table.prevEntityChanges;
99
- Utils.Dict.forEach(table.latestEntityChangeById, change => {
100
- if (change.checkpointId > committedCheckpointId) {
101
- changes.push(change);
102
- return;
103
- }
104
- });
291
+ let changes = InMemoryTable.Entity.snapshotChanges(table, committedCheckpointId, upToCheckpointId);
105
292
  if (Utils.$$Array.isEmpty(changes)) {
106
293
  return;
107
294
  } else {
@@ -111,74 +298,120 @@ async function writeBatch(inMemoryStore, persistence, batch, config, isInReorgTh
111
298
  };
112
299
  }
113
300
  });
114
- let acc = [];
115
- await persistence.storage.writeBatch(batch, inMemoryStore.rawEvents, inMemoryStore.rollback, isInReorgThreshold, config, persistence.allEntities, (Utils.Dict.forEach(inMemoryStore.effects, inMemTable => {
116
- let idsToStore = inMemTable.idsToStore;
117
- let invalidationsCount = inMemTable.invalidationsCount;
118
- let effect = inMemTable.effect;
119
- let dict = inMemTable.dict;
120
- if (idsToStore.length === 0) {
121
- return;
301
+ let updatedEffectsCache = snapshotEffects(inMemoryStore, cache);
302
+ await persistence.storage.writeBatch(batch, rollback, batch.isInReorgThreshold, config, persistence.allEntities, updatedEffectsCache, updatedEntities, chainMetaData);
303
+ inMemoryStore.committedCheckpointId = upToCheckpointId;
304
+ if (rollback !== undefined && Utils.$$Array.notEmpty(RollbackCommit.callbacks)) {
305
+ return await RollbackCommit.fire(rollback.progressBlockNumberByChainId);
306
+ }
307
+ }
308
+
309
+ function hasPendingWrite(inMemoryStore) {
310
+ if (Utils.$$Array.notEmpty(inMemoryStore.processedBatches)) {
311
+ return true;
312
+ } else {
313
+ return inMemoryStore.chainMetaDirty;
314
+ }
315
+ }
316
+
317
+ async function runWriteLoop(inMemoryStore) {
318
+ while (hasPendingWrite(inMemoryStore) && !inMemoryStore.hasFailedWrite) {
319
+ try {
320
+ await runOneWrite(inMemoryStore, inMemoryStore.persistence, inMemoryStore.config);
321
+ wakeCommitWaiters(inMemoryStore);
322
+ } catch (raw_exn) {
323
+ let exn = Primitive_exceptions.internalToException(raw_exn);
324
+ inMemoryStore.hasFailedWrite = true;
325
+ inMemoryStore.onError(exn);
122
326
  }
123
- let items = idsToStore.map(id => ({
124
- id: id,
125
- output: dict[id]
126
- }));
127
- let effectName = effect.name;
128
- let c = cache[effectName];
129
- let effectCacheRecord;
130
- if (c !== undefined) {
131
- effectCacheRecord = c;
132
- } else {
133
- let c$1 = {
134
- effectName: effectName,
135
- count: 0
136
- };
137
- cache[effectName] = c$1;
138
- effectCacheRecord = c$1;
327
+ };
328
+ inMemoryStore.writeFiber = undefined;
329
+ return wakeCommitWaiters(inMemoryStore);
330
+ }
331
+
332
+ function kick(inMemoryStore) {
333
+ if (Stdlib_Option.isNone(inMemoryStore.writeFiber) && !inMemoryStore.hasFailedWrite && hasPendingWrite(inMemoryStore)) {
334
+ inMemoryStore.writeFiber = runWriteLoop(inMemoryStore);
335
+ return;
336
+ }
337
+ }
338
+
339
+ function metaFieldsEqual(a, b) {
340
+ if (Primitive_object.equal(a.first_event_block, b.first_event_block) && a.buffer_block === b.buffer_block && a._is_hyper_sync === b._is_hyper_sync) {
341
+ return Primitive_object.equal(Stdlib_Option.map(Primitive_option.fromNull(a.ready_at), prim => prim.getTime()), Stdlib_Option.map(Primitive_option.fromNull(b.ready_at), prim => prim.getTime()));
342
+ } else {
343
+ return false;
344
+ }
345
+ }
346
+
347
+ function setChainMeta(inMemoryStore, chainsData) {
348
+ Utils.Dict.forEachWithKey(chainsData, (meta, chainId) => {
349
+ let prev = inMemoryStore.chainMeta[chainId];
350
+ let changed = prev !== undefined ? !metaFieldsEqual(meta, prev) : true;
351
+ if (changed) {
352
+ inMemoryStore.chainMeta[chainId] = meta;
353
+ inMemoryStore.chainMetaDirty = true;
354
+ return;
139
355
  }
140
- let shouldInitialize = effectCacheRecord.count === 0;
141
- effectCacheRecord.count = (effectCacheRecord.count + items.length | 0) - invalidationsCount | 0;
142
- Prometheus.EffectCacheCount.set(effectCacheRecord.count, effectName);
143
- acc.push({
144
- effect: effect,
145
- items: items,
146
- shouldInitialize: shouldInitialize
356
+ });
357
+ if (inMemoryStore.chainMetaDirty) {
358
+ return Throttler.schedule(inMemoryStore.chainMetaThrottler, () => {
359
+ kick(inMemoryStore);
360
+ return Promise.resolve();
147
361
  });
148
- }), acc), updatedEntities);
149
- inMemoryStore.rawEvents = [];
150
- inMemoryStore.effects = {};
151
- inMemoryStore.rollback = undefined;
362
+ }
363
+ }
364
+
365
+ function commitBatch(inMemoryStore, batch) {
366
+ inMemoryStore.processedBatches.push(batch);
152
367
  let checkpointId = Utils.$$Array.last(batch.checkpointIds);
153
- inMemoryStore.committedCheckpointId = checkpointId !== undefined ? checkpointId : committedCheckpointId;
154
- if (keepLatestChanges) {
155
- persistence.allEntities.forEach(entityConfig => {
156
- let table = getInMemTable(inMemoryStore, entityConfig);
157
- inMemoryStore.entities[entityConfig.name] = InMemoryTable.Entity.resetButKeepLatestChanges(table);
158
- });
368
+ if (checkpointId !== undefined) {
369
+ inMemoryStore.processedCheckpointId = checkpointId;
370
+ }
371
+ kick(inMemoryStore);
372
+ }
373
+
374
+ function dropCommitted(inMemoryStore, keepLoadedFromDb) {
375
+ let committedCheckpointId = inMemoryStore.committedCheckpointId;
376
+ inMemoryStore.allEntities.forEach(entityConfig => InMemoryTable.Entity.dropCommittedChanges(getInMemTable(inMemoryStore, entityConfig), committedCheckpointId, keepLoadedFromDb));
377
+ Utils.Dict.forEach(inMemoryStore.effects, inMemTable => dropCommittedEffects(inMemTable, committedCheckpointId, keepLoadedFromDb));
378
+ }
379
+
380
+ async function awaitCapacity(inMemoryStore) {
381
+ if (!inMemoryStore.hasFailedWrite && getChangesCount(inMemoryStore) >= Env.inMemoryObjectsTarget) {
382
+ dropCommitted(inMemoryStore, true);
383
+ if (getChangesCount(inMemoryStore) >= Env.inMemoryObjectsTarget) {
384
+ dropCommitted(inMemoryStore, false);
385
+ }
386
+ if (getChangesCount(inMemoryStore) >= Env.inMemoryObjectsTarget && Utils.$$Array.notEmpty(inMemoryStore.processedBatches)) {
387
+ kick(inMemoryStore);
388
+ await waitForCommit(inMemoryStore);
389
+ return await awaitCapacity(inMemoryStore);
390
+ } else {
391
+ return;
392
+ }
393
+ }
394
+ }
395
+
396
+ async function flush(inMemoryStore) {
397
+ if (inMemoryStore.hasFailedWrite) {
159
398
  return;
160
399
  }
161
- let loadedFromDbCount = {
162
- contents: 0
163
- };
164
- let resetTables = persistence.allEntities.map(entityConfig => {
165
- let resetTable = InMemoryTable.Entity.resetButKeepLoadedFromDbChanges(getInMemTable(inMemoryStore, entityConfig));
166
- loadedFromDbCount.contents = loadedFromDbCount.contents + resetTable.changesCount;
167
- return resetTable;
168
- });
169
- let dropEverything = loadedFromDbCount.contents >= 50000;
170
- persistence.allEntities.forEach((entityConfig, idx) => {
171
- inMemoryStore.entities[entityConfig.name] = dropEverything ? InMemoryTable.Entity.make() : resetTables[idx];
172
- });
400
+ kick(inMemoryStore);
401
+ let fiber = inMemoryStore.writeFiber;
402
+ if (fiber !== undefined) {
403
+ await fiber;
404
+ return await flush(inMemoryStore);
405
+ }
173
406
  }
174
407
 
175
- async function prepareRollbackDiff(inMemoryStore, persistence, rollbackTargetCheckpointId, rollbackDiffCheckpointId) {
176
- inMemoryStore.rawEvents = [];
408
+ async function prepareRollbackDiff(inMemoryStore, persistence, rollbackTargetCheckpointId, rollbackDiffCheckpointId, progressBlockNumberByChainId) {
177
409
  inMemoryStore.entities = make(inMemoryStore.allEntities);
178
410
  inMemoryStore.effects = {};
179
411
  inMemoryStore.rollback = {
180
412
  targetCheckpointId: rollbackTargetCheckpointId,
181
- diffCheckpointId: rollbackDiffCheckpointId
413
+ diffCheckpointId: rollbackDiffCheckpointId,
414
+ progressBlockNumberByChainId: progressBlockNumberByChainId
182
415
  };
183
416
  let deletedEntities = {};
184
417
  let setEntities = {};
@@ -247,17 +480,35 @@ function setBatchDcs(inMemoryStore, batch) {
247
480
  }
248
481
  }
249
482
 
250
- let keepLatestChangesLimit = 50000;
483
+ let keepLatestChangesLimit = Env.inMemoryObjectsTarget;
251
484
 
252
485
  export {
253
486
  EntityTables,
254
487
  make$1 as make,
255
488
  keepLatestChangesLimit,
256
489
  getEffectInMemTable,
490
+ getEffectOutput,
491
+ setEffectOutput,
492
+ initEffectOutputFromDb,
493
+ dropCommittedEffects,
257
494
  getInMemTable,
258
495
  isRollingBack,
259
- writeBatch,
496
+ getChangesCount,
497
+ wakeCommitWaiters,
498
+ waitForCommit,
499
+ drainBatchRun,
500
+ snapshotEffects,
501
+ runOneWrite,
502
+ hasPendingWrite,
503
+ runWriteLoop,
504
+ kick,
505
+ metaFieldsEqual,
506
+ setChainMeta,
507
+ commitBatch,
508
+ dropCommitted,
509
+ awaitCapacity,
510
+ flush,
260
511
  prepareRollbackDiff,
261
512
  setBatchDcs,
262
513
  }
263
- /* Utils Not a pure module */
514
+ /* Env Not a pure module */
@@ -9,13 +9,14 @@ module Entity = {
9
9
  type entityIndices = Utils.Set.t<TableIndices.Index.t>
10
10
  type t = {
11
11
  latestEntityChangeById: dict<Change.t<Internal.entity>>,
12
- // Counts every recorded change (new latest ids and pushes to
13
- // prevEntityChanges), kept in sync manually so InMemoryStore can gauge the
14
- // store size without scanning every dict.
12
+ // Recorded changes (new latest ids + prevEntityChanges pushes), tracked
13
+ // manually so InMemoryStore can gauge size without scanning every dict.
15
14
  mutable changesCount: float,
16
- prevEntityChanges: array<Change.t<Internal.entity>>,
17
- indicesByEntityId: dict<entityIndices>,
18
- fieldNameIndices: indexFieldNameToIndices,
15
+ // Swapped out when a write starts so processing keeps appending while the
16
+ // previous changes persist in the background.
17
+ mutable prevEntityChanges: array<Change.t<Internal.entity>>,
18
+ mutable indicesByEntityId: dict<entityIndices>,
19
+ mutable fieldNameIndices: indexFieldNameToIndices,
19
20
  }
20
21
 
21
22
  // Helper to extract entity ID from any entity
@@ -55,33 +56,51 @@ module Entity = {
55
56
  fieldNameIndices: Dict.make(),
56
57
  }
57
58
 
58
- // Drops the per-batch index state and rollback history, but keeps the
59
- // already committed entities so the next batch can read them without
60
- // hitting the database.
61
- let resetButKeepLatestChanges = (self: t): t => {
62
- ...make(),
63
- latestEntityChangeById: self.latestEntityChangeById,
64
- // writeBatch already mutated this to subtract the dropped prevEntityChanges.
65
- changesCount: self.changesCount,
59
+ // Changes to persist for checkpoints in (committedCheckpointId, upToCheckpointId].
60
+ // Those above upToCheckpointId stay in the table for a later write, while
61
+ // concurrent processing keeps accumulating.
62
+ let snapshotChanges = (self: t, ~committedCheckpointId, ~upToCheckpointId): array<
63
+ Change.t<Internal.entity>,
64
+ > => {
65
+ let changes = []
66
+ let keptPrev = []
67
+ self.prevEntityChanges->Array.forEach(change =>
68
+ if change->Change.getCheckpointId > upToCheckpointId {
69
+ keptPrev->Array.push(change)
70
+ } else {
71
+ changes->Array.push(change)
72
+ }
73
+ )
74
+ self.prevEntityChanges = keptPrev
75
+ self.changesCount = self.changesCount -. changes->Array.length->Int.toFloat
76
+ self.latestEntityChangeById->Utils.Dict.forEach(change => {
77
+ let checkpointId = change->Change.getCheckpointId
78
+ if checkpointId > committedCheckpointId && !(checkpointId > upToCheckpointId) {
79
+ changes->Array.push(change)
80
+ }
81
+ })
82
+ changes
66
83
  }
67
84
 
68
- // Like resetButKeepLatestChanges, but only keeps entities loaded from the db
69
- // (changes carrying loadedFromDbCheckpointId), dropping everything written in
70
- // a batch. The kept count is exposed through the table's changesCount.
71
- let resetButKeepLoadedFromDbChanges = (self: t): t => {
72
- let latestEntityChangeById = Dict.make()
73
- let keptCount = ref(0.)
74
- self.latestEntityChangeById->Utils.Dict.forEachWithKey((change, key) =>
75
- if change->Change.getCheckpointId === Internal.loadedFromDbCheckpointId {
76
- latestEntityChangeById->Dict.set(key, change)
77
- keptCount := keptCount.contents +. 1.
85
+ // Frees committed changes: drops latest entries at or below committedCheckpointId
86
+ // (re-readable from the db) and clears the per-batch indices (rebuilt on the next
87
+ // getWhere). Uncommitted changes are kept. With keepLoadedFromDb, entries seeded
88
+ // from a db read are spared so the cheaper-to-re-derive writes are dropped first.
89
+ let dropCommittedChanges = (self: t, ~committedCheckpointId, ~keepLoadedFromDb) => {
90
+ let keysToDelete = []
91
+ self.latestEntityChangeById->Utils.Dict.forEachWithKey((change, key) => {
92
+ let checkpointId = change->Change.getCheckpointId
93
+ if (
94
+ !(checkpointId > committedCheckpointId) &&
95
+ !(keepLoadedFromDb && checkpointId == Internal.loadedFromDbCheckpointId)
96
+ ) {
97
+ keysToDelete->Array.push(key)
78
98
  }
79
- )
80
- {
81
- ...make(),
82
- latestEntityChangeById,
83
- changesCount: keptCount.contents,
84
- }
99
+ })
100
+ keysToDelete->Array.forEach(key => self.latestEntityChangeById->Utils.Dict.deleteInPlace(key))
101
+ self.changesCount = self.changesCount -. keysToDelete->Array.length->Int.toFloat
102
+ self.indicesByEntityId = Dict.make()
103
+ self.fieldNameIndices = Dict.make()
85
104
  }
86
105
 
87
106
  let updateIndices = (self: t, ~entity: Internal.entity) => {