Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/t13275-page-clock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
id: t13275-page-clock
tasks: [T13275]
kind: fix
summary: A sync apply page's time budget counts applying, not its rewind
---

The page time bound (`REBASE_PAGE_MS`) used to be measured from before the page's rewind. When the local scope was large, the rewind
alone could use up the budget. Every page then took a single transaction, and each page rewound and replayed the same scope again,
all under the write lock. The bound now starts once the rewind is done.
17 changes: 17 additions & 0 deletions packages/core/src/store/sync/apply/__tests__/rebase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,23 @@ describe('pages (搂3.5 Rule 3)', () => {
expect(last2[0]?.f).not.toBe(last2[1]?.f);
});

it("the time bound counts applying, not the page's rewind (T13275)", async () => {
const [a, b] = await threeReplicas();
write(a, "UPDATE tasks_tasks SET priority = 'high' WHERE uid = 'x'");
for (const t of ['t1', 't2', 't3']) {
publish(b, write(b, `UPDATE tasks_tasks SET title = '${t}' WHERE uid = 'x'`));
}
// Opening the page (its rewind) takes 100 ms; applying takes no time.
const base = Date.now();
const clock = () => (a.db.isTransaction ? base + 100 : base);
expect(sync(a, published.length, { pageMs: 50, now: clock })).toMatchObject({
applied: 3,
rebased: 3,
});
const last3 = frames(a).slice(-3);
expect(new Set(last3.map((x) => x.f)).size, 'the rewind used up the page budget').toBe(1);
});

it('a page breaks on an actor change and keeps a larger transaction whole', async () => {
const [a, b] = await threeReplicas();
publish(
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/store/sync/apply/applier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1426,10 +1426,13 @@ export function applyStagedTxns(db: DatabaseSync, opts: ApplyStagedOptions): App
const rw: Rewound = rebase ? rewindTxns(c0, rebase) : { kept: new Map(), after: new Map() };
const rewound = new Set(rebase?.rewind.map((l) => l.txn) ?? []);
const decided = new Set<string>(); // rewound locals whose echo this page applied
// The time bound covers applying, not the rewind: a large scope must
// not shrink every page to one transaction that rewinds it again.
const applyStart = now();
let taken = 0;
for (const { st } of page) {
// The time bound cuts a page between transactions (never inside one).
if (taken > 0 && now() - pageStart > (opts.pageMs ?? REBASE_PAGE_MS)) break;
if (taken > 0 && now() - applyStart > (opts.pageMs ?? REBASE_PAGE_MS)) break;
taken += 1;
const nowMs = now();
const nowIso = new Date(nowMs).toISOString();
Expand Down
Loading