Skip to content

Move in-memory history to per-entity-table storage - #1249

Closed
DZakh wants to merge 3 commits into
mainfrom
claude/sleepy-maxwell-XTQUN
Closed

Move in-memory history to per-entity-table storage#1249
DZakh wants to merge 3 commits into
mainfrom
claude/sleepy-maxwell-XTQUN

Conversation

@DZakh

@DZakh DZakh commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

  • Move the in-memory history array off inMemoryStoreEntityUpdate (per-row) and onto InMemoryTable.Entity.t (per entity table). Each row tracks its slot via historyIndex so a second write at the same checkpoint replaces in place — same dedup invariant as before, just keyed against a shared array.
  • Drop the shouldSaveHistory gate at write time; history is always populated in memory. The flag is still respected at the Postgres flush (DB behavior unchanged) and removed from the context-param threading where it was no longer used.
  • ClickHouse now writes the full table-level history, not just each row's latestChange, regardless of the flag.
  • Rollback-diff replays skip the history buffer (historyIndex = -1) since they restore from existing history rows.

Test plan

  • pnpm rescript clean in packages/envio and scenarios/test_codegen
  • pnpm vitest run test/rollback/Rollback_test.res.mjs — 15/15
  • pnpm vitest run for E2E_test, WriteRead_test, lib_tests/EntityHistory_test, lib_tests/PgStorage_test, lib_tests/ClickHouse_test, EventOrigin_test — 65 passed, 2 skipped
  • Full suite

https://claude.ai/code/session_01Xo1n3gN3az2PSH81vVa3Va


Generated by Claude Code

Summary by CodeRabbit

  • Refactor

    • Reworked entity history to use a shared per-entity history buffer referenced by index, removing the per-handler save-history flag and simplifying handler context.
    • Moved history to top-level batch updates, streamlining how sinks and persistence receive and persist history.
  • Bug Fixes

    • Improved rollback replay correctness and checkpoint handling for history writes.
  • Tests

    • Updated tests and mocks to match the new history and rollback behavior.

Review Change Stack

Replace `inMemoryStoreEntityUpdate.history` with a single
`InMemoryTable.Entity.t.history` array shared by all rows of an entity.
Each row tracks its slot via `historyIndex` so a same-checkpoint write
replaces in place, preserving the existing dedup invariant.

History is now always populated in memory; the `shouldSaveHistory` flag
only gates the Postgres flush (DB behavior unchanged) and is dropped
from the context-param threading where it was no longer used.
ClickHouse now writes the full table-level history, not only each row's
`latestChange`, regardless of the flag.

Rollback-diff replays skip the history buffer (`historyIndex = -1`)
since they restore from existing history rows.
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30a7330c-e81c-4bab-a3d1-6678066a1307

📥 Commits

Reviewing files that changed from the base of the PR and between 6c6559e and 4f32fd4.

📒 Files selected for processing (1)
  • packages/envio/src/InMemoryTable.res
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/envio/src/InMemoryTable.res

📝 Walkthrough

Walkthrough

Removes the threaded shouldSaveHistory flag and migrates per-update history arrays to a shared table-level history buffer with per-row historyIndex; updates handler APIs, in-memory store, persistence writers, ClickHouse binding, serialization, and tests.

Changes

History Parameter Removal and Index-Based Tracking Refactoring

Layer / File(s) Summary
Remove shouldSaveHistory from handler execution and context
packages/envio/src/EventProcessing.res, packages/envio/src/UserContext.res
Handler functions (runEventHandlerOrThrow, runHandlerOrThrow, runBatchHandlersOrThrow) and preload paths no longer accept or forward ~shouldSaveHistory; UserContext proxy traps stop including the flag.
Refactor in-memory history to index-based model
packages/envio/src/InMemoryTable.res, packages/envio/src/Internal.res, packages/envio/src/Persistence.res
Add table-level history: array<Change.t<Internal.entity>>; Entity.set computes/appends history and returns a historyIndex; inMemoryStoreEntityUpdate replaces per-update history with historyIndex; Persistence.updatedEntity gains top-level history.
Update batch processing and setBatchDcs
packages/envio/src/GlobalState.res, packages/envio/src/InMemoryStore.res
setBatchDcs signature drops ~shouldSaveHistory; writeBatch forwards per-entity table-level history; rollback-diff ops embed isRollbackDiff and remove prior ~shouldSaveHistory=false options.
Update storage writers to handle new history format
packages/envio/src/PgStorage.res, packages/envio/src/Sink.res, packages/envio/src/bindings/ClickHouse.res
Storage writers destructure history from updatedEntities; PgStorage iterates entity-level history once to build delete/upsert batches; ClickHouse binding signature changes to accept ~history.
Update test indexer and proxy storage serialization
packages/envio/src/TestIndexer.res, packages/envio/src/TestIndexerProxyStorage.res
TestIndexer processes entity-level history (fallback to updates.latestChange when empty); proxy serialization moves history to entity level and includes optional isRollbackDiff on serialized changes.
Update test expectations and mocks
scenarios/test_codegen/test/EventOrigin_test.res, scenarios/test_codegen/test/helpers/MockIndexer.res, scenarios/test_codegen/test/rollback/Rollback_test.res
Remove shouldSaveHistory from test persistence/mocks; adjust rollback tests to reflect new history ordering and checkpointId values.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • JonoPrest
  • JasoonS
  • moose-code
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Move in-memory history to per-entity-table storage' clearly and concisely summarizes the main architectural change in the changeset—relocating in-memory history from per-row to per-entity-table storage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/envio/src/TestIndexer.res`:
- Line 162: The expression pushes parsedEntity->Utils.magic without an explicit
cast; update the call in the entityChange.sets->Array.push(...) expression so
Utils.magic is used with an explicit annotation, e.g. change
parsedEntity->Utils.magic to parsedEntity->(Utils.magic: inputType =>
outputType) using the correct inputType and outputType for parsedEntity and the
target set element type in TestIndexer.res; ensure the annotated cast matches
the expected types so the push compiles.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 779f58c9-093a-41d1-89f8-9620d5d2bf99

📥 Commits

Reviewing files that changed from the base of the PR and between d134e46 and cd44aaf.

📒 Files selected for processing (15)
  • packages/envio/src/EventProcessing.res
  • packages/envio/src/GlobalState.res
  • packages/envio/src/InMemoryStore.res
  • packages/envio/src/InMemoryTable.res
  • packages/envio/src/Internal.res
  • packages/envio/src/Persistence.res
  • packages/envio/src/PgStorage.res
  • packages/envio/src/Sink.res
  • packages/envio/src/TestIndexer.res
  • packages/envio/src/TestIndexerProxyStorage.res
  • packages/envio/src/UserContext.res
  • packages/envio/src/bindings/ClickHouse.res
  • scenarios/test_codegen/test/EventOrigin_test.res
  • scenarios/test_codegen/test/helpers/MockIndexer.res
  • scenarios/test_codegen/test/rollback/Rollback_test.res
💤 Files with no reviewable changes (3)
  • scenarios/test_codegen/test/EventOrigin_test.res
  • scenarios/test_codegen/test/helpers/MockIndexer.res
  • packages/envio/src/UserContext.res

entityChanges->Dict.set(entityName, change)
change
}
entityChange.sets->Array.push(parsedEntity->Utils.magic)->ignore

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add explicit cast annotation for Utils.magic.

Line 162 uses Utils.magic without the required input/output type annotation.

♻️ Suggested fix
-        entityChange.sets->Array.push(parsedEntity->Utils.magic)->ignore
+        entityChange.sets->Array.push(parsedEntity->(Utils.magic: Internal.entity => unknown))->ignore

As per coding guidelines, "When using Utils.magic for type casting in ReScript, always add explicit type annotations: value->(Utils.magic: inputType => outputType)".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
entityChange.sets->Array.push(parsedEntity->Utils.magic)->ignore
entityChange.sets->Array.push(parsedEntity->(Utils.magic: Internal.entity => unknown))->ignore
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/envio/src/TestIndexer.res` at line 162, The expression pushes
parsedEntity->Utils.magic without an explicit cast; update the call in the
entityChange.sets->Array.push(...) expression so Utils.magic is used with an
explicit annotation, e.g. change parsedEntity->Utils.magic to
parsedEntity->(Utils.magic: inputType => outputType) using the correct inputType
and outputType for parsedEntity and the target set element type in
TestIndexer.res; ensure the annotated cast matches the expected types so the
push compiles.

Add optional `isRollbackDiff: bool` to `Change.Set` and `Change.Delete`.
`InMemoryTable.Entity.set` reads the flag directly from the change to
decide whether to record it in the table-level history buffer, and the
Postgres flush reads it from `update.latestChange` to gate backfill.

Drops the previous `containsRollbackDiffChange` row-level flag and the
`~containsRollbackDiffChange` argument that propagated it forward.
A user write following a rollback-diff replay no longer inherits the
flag, so backfill is computed for that entity; the backfill query is
idempotent so this is a no-op when history rows already exist, and
records the pre-write state when they don't.

Rollback test snapshots updated for the extra checkpoint=0 backfill
rows that now appear after rollback + user write.
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Actionable comments posted: 0

…-XTQUN

# Conflicts:
#	packages/envio/src/InMemoryTable.res
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Actionable comments posted: 0

@DZakh DZakh closed this Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants