Skip to content

Commit 6508c1d

Browse files
alvrsfrolic
andauthored
feat(store-sync): add flag to define chunking behavior during initial hydration (#3745)
Co-authored-by: Kevin Ingersoll <kingersoll@gmail.com>
1 parent cd146eb commit 6508c1d

3 files changed

Lines changed: 42 additions & 20 deletions

File tree

.changeset/rotten-windows-dance.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@latticexyz/store-sync": patch
3+
---
4+
5+
The sync stack now supports defining the chunking behavior during initial hydration. Chunking remains enabled by default.
6+
7+
Chunking is useful to avoid blocking the main thread for too long, but it can lead to updates that happened in the same block being split across multiple chunks.
8+
If chunking is enabled, clients should account for this by waiting for full hydration before using the update stream.
9+
If atomicity of updates is important and blocking the main thread is not an issue, set this to `false`.

packages/store-sync/src/common.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ export type SyncOptions = GetRpcClientOptions & {
110110
blockNumber: bigint;
111111
logs: readonly StorageAdapterLog[];
112112
};
113+
/**
114+
* Optional flag to define the chunking behavior during the initial hydration. Defaults to `true`.
115+
* Chunking is useful to avoid blocking the main thread for too long, but it can lead to updates that happened in the same block being split across multiple chunks.
116+
* If chunking is enabled, clients should account for this by waiting for full hydration before using the update stream.
117+
* If atomicity of updates is important and blocking the main thread is not an issue, set this to `false`.
118+
*/
119+
enableHydrationChunking?: boolean;
113120
};
114121

115122
export type WaitForTransactionResult = Pick<TransactionReceipt, "blockNumber" | "status" | "transactionHash">;

packages/store-sync/src/createStoreSync.ts

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export async function createStoreSync({
7272
maxBlockRange,
7373
initialState,
7474
initialBlockLogs,
75+
enableHydrationChunking = true,
7576
...opts
7677
}: CreateStoreSyncOptions): Promise<SyncResult> {
7778
const filters: SyncFilter[] =
@@ -155,26 +156,31 @@ export async function createStoreSync({
155156
message: "Hydrating from snapshot",
156157
});
157158

158-
// Split snapshot operations into chunks so we can update the progress callback (and ultimately render visual progress for the user).
159-
// This isn't ideal if we want to e.g. batch load these into a DB in a single DB tx, but we'll take it.
160-
//
161-
// Split into 50 equal chunks (for better `onProgress` updates) but only if we have 100+ items per chunk
162-
const chunkSize = Math.max(100, Math.floor(logs.length / 50));
163-
const chunks = Array.from(chunk(logs, chunkSize));
164-
for (const [i, chunk] of chunks.entries()) {
165-
await storageAdapter({ blockNumber, logs: chunk });
166-
onProgress?.({
167-
step: SyncStep.SNAPSHOT,
168-
percentage: ((i + 1) / chunks.length) * 100,
169-
latestBlockNumber: 0n,
170-
lastBlockNumberProcessed: blockNumber,
171-
message: "Hydrating from snapshot",
172-
});
173-
174-
// RECS is a synchronous API so hydrating in a loop like this blocks downstream render cycles
175-
// that would display the percentage climbing up to 100.
176-
// We wait for idle callback here to give rendering a chance to complete.
177-
await waitForIdle();
159+
if (enableHydrationChunking) {
160+
// Split snapshot operations into chunks so we can update the progress callback (and ultimately render visual progress for the user).
161+
// This isn't ideal if we want to e.g. batch load these into a DB in a single DB tx, but we'll take it.
162+
//
163+
// Split into 50 equal chunks (for better `onProgress` updates) but only if we have 100+ items per chunk
164+
const chunkSize = Math.max(100, Math.floor(logs.length / 50));
165+
const chunks = Array.from(chunk(logs, chunkSize));
166+
for (const [i, chunk] of chunks.entries()) {
167+
debug(`hydrating chunk ${i}/${chunks.length}`);
168+
await storageAdapter({ blockNumber, logs: chunk });
169+
onProgress?.({
170+
step: SyncStep.SNAPSHOT,
171+
percentage: ((i + 1) / chunks.length) * 100,
172+
latestBlockNumber: 0n,
173+
lastBlockNumberProcessed: blockNumber,
174+
message: "Hydrating from snapshot",
175+
});
176+
// RECS is a synchronous API so hydrating in a loop like this blocks downstream render cycles
177+
// that would display the percentage climbing up to 100.
178+
// We wait for idle callback here to give rendering a chance to complete.
179+
await waitForIdle();
180+
}
181+
} else {
182+
debug("hydrating all logs without chunking");
183+
await storageAdapter({ blockNumber, logs });
178184
}
179185

180186
onProgress?.({

0 commit comments

Comments
 (0)