Implement aggregate snapshot cache storage - #41
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR refactors the snapshot architecture to enrich snapshot metadata, update database schemas, refactor hydration logic, introduce async-capable snapshot repositories, and remove the legacy ChangesSnapshot Schema and Hydration Refactor
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src/snapshot/repository.rs (1)
123-126: ⚡ Quick winAvoid cloning the full stream before every snapshot hydrate.
entity.clone()duplicates the event list on every cache hit, so snapshot-backed reads still pay O(n) copy cost. If codec validation and snapshot deserialization happen before consumingentity, the fallback path can reuse the original stream without cloning.🤖 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 `@src/snapshot/repository.rs` around lines 123 - 126, The code is cloning the full event stream via entity.clone() before calling try_hydrate_from_snapshot::<A>, causing O(n) copies on every cache hit; instead change the call to pass a reference (e.g., &entity or entity.as_slice()) so try_hydrate_from_snapshot::<A> operates on borrowed data and only consumes/moves the original entity for the fallback hydrate::<A>(entity) path; update try_hydrate_from_snapshot::<A> signature (and its internal logic) to accept a borrowed stream (or slice) and return SnapshotHydrationError::Cache without requiring ownership so the Err(SnapshotHydrationError::Cache(_)) branch can call hydrate::<A>(entity) without prior cloning.
🤖 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 `@README.md`:
- Line 1262: The sentence "Distributed keeps aggregate events as the durable
source of truth..." has an unclear subject; replace "Distributed" with the
correct project or component name (e.g., "The framework" or "sourced_rust") so
the subject reads naturally and unambiguously; update the sentence that begins
with "As aggregates accumulate events, replaying from scratch gets expensive."
and specifically change the phrase "Distributed keeps aggregate events..." to a
clear subject (keep the rest of the sentence about snapshots as the rebuildable
hydration cache intact).
In `@src/snapshot/repository.rs`:
- Around line 281-283: Replace the overflow-unsafe comparison "if version >=
snap_version + self.frequency" with a saturating comparison using
version.saturating_sub(snap_version) >= self.frequency so high sequence numbers
can't overflow; do this in both snapshot writer branches (the current check that
calls snapshot_record_for(aggregate).map(Some) and the other occurrence around
lines 390-392) and ensure you reference the same fields (version, snap_version,
self.frequency) in each fix so both async and sync paths behave identically.
- Around line 21-29: The public helper hydrate_from_snapshot currently delegates
to try_hydrate_from_snapshot without validating aggregate identity or version,
so add the same guards used in hydrate_with_optional_snapshot: verify
snapshot.aggregate_id == entity.id (or equivalent), snapshot.aggregate_type ==
entity.type, and that snapshot.version > entity.version() (i.e. reject stale
snapshots where snapshot.version <= entity.version()) and convert those failures
into the same SnapshotHydrationError variants you already handle, or
alternatively restrict hydrate_from_snapshot to pub(crate) if callers are
expected to pre-validate; update hydrate_from_snapshot to perform these checks
before calling try_hydrate_from_snapshot (or change its visibility) and preserve
the existing map_err handling.
- Around line 113-126: The current early-return when snapshot.version >
entity.version() treats a future snapshot as a hard Model error; instead treat
that as an invalid/out-of-date cache and fall back to full hydration. Replace
the Err(RepositoryError::Model(...)) path so that when snapshot.version >
entity.version() you skip using the snapshot and call hydrate::<A>(entity)
(consistent with how SnapshotHydrationError::Cache is handled) so reads degrade
to full replay rather than failing. Ensure you still preserve the original
metadata (snapshot.aggregate_type / aggregate_id) in logs if you add any.
---
Nitpick comments:
In `@src/snapshot/repository.rs`:
- Around line 123-126: The code is cloning the full event stream via
entity.clone() before calling try_hydrate_from_snapshot::<A>, causing O(n)
copies on every cache hit; instead change the call to pass a reference (e.g.,
&entity or entity.as_slice()) so try_hydrate_from_snapshot::<A> operates on
borrowed data and only consumes/moves the original entity for the fallback
hydrate::<A>(entity) path; update try_hydrate_from_snapshot::<A> signature (and
its internal logic) to accept a borrowed stream (or slice) and return
SnapshotHydrationError::Cache without requiring ownership so the
Err(SnapshotHydrationError::Cache(_)) branch can call hydrate::<A>(entity)
without prior cloning.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 304cb320-4760-4f9c-9928-a423a9f09122
📒 Files selected for processing (22)
README.mddocs/async-repositories.mddocs/postgres-event-store.mdmigrations/postgres/0001_initial.sqlmigrations/sqlite/0001_initial.sqlsrc/entity/entity.rssrc/hashmap_repo/repository.rssrc/lib.rssrc/postgres_repo/mod.rssrc/snapshot/in_memory.rssrc/snapshot/mod.rssrc/snapshot/repository.rssrc/snapshot/snapshottable.rssrc/snapshot/store.rssrc/sqlite_repo/mod.rssrc/sqlx_repo/mod.rstests/async_repository/main.rstests/persistent_repository_conformance/scenario.rstests/postgres_repository/main.rstests/snapshots/main.rstests/sqlite_repository/main.rstests/upcasting/main.rs
💤 Files with no reviewable changes (1)
- src/entity/entity.rs
|
@coderabbitai I reviewed the snapshot comments and addressed the valid items in Changes made:
Verification run:
I intentionally did not address the broad docstring-coverage warning here because it is repo-wide documentation debt rather than one of the actionable review findings for this PR. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# List open review comments by coderabbitai on PR `#41` to resolve the addressed ones
gh pr view 41 --repo patrickleet/sourced_rust --json reviews,comments
gh api repos/patrickleet/sourced_rust/pulls/41/comments --jq '.[] | select(.user.login == "coderabbitai") | {id: .id, body: .body[:80], line: .line}'Length of output: 47160 🏁 Script executed: #!/bin/bash
# Get review threads for PR `#41` to find thread IDs for resolution
gh api graphql -f query='
{
repository(owner: "patrickleet", name: "sourced_rust") {
pullRequest(number: 41) {
reviewThreads(first: 20) {
nodes {
id
isResolved
comments(first: 1) {
nodes {
body
author { login }
}
}
}
}
}
}
}'Length of output: 10311 🏁 Script executed: #!/bin/bash
# Resolve all 4 addressed review threads
for thread_id in \
"PRRT_kwDOM_tGVs6E_zcy" \
"PRRT_kwDOM_tGVs6E_zc4" \
"PRRT_kwDOM_tGVs6E_zc8" \
"PRRT_kwDOM_tGVs6E_zdC"; do
echo "Resolving thread: $thread_id"
gh api graphql -f query="
mutation {
resolveReviewThread(input: {threadId: \"$thread_id\"}) {
thread {
id
isResolved
}
}
}"
echo "---"
doneLength of output: 997
The docstring coverage warning remains as the only open pre-merge check — happy to open a follow-up issue to track that repo-wide documentation debt if you'd like. 🐇 ✨ |
Summary
Verification
Summary by CodeRabbit
Release Notes
New Features
AsyncSnapshotAggregateRepositoryfor async aggregate operations with snapshot support.Improvements
Documentation