Repository navigation
Conversation
Remove nine newly added Deep storage tests (429 lines) at the user's request to reduce the review diff. This removes new coverage for resident/tiered storage, prepared inserts, page reuse, and blob pins. Existing tests from #6305 and all production code remain unchanged. Validation: 112 existing spacetimedb-table library tests pass; clippy for the library and tests passes with -D warnings.
Remove the local macOS benchmarking accommodations from the review diff. Restore the original unconditional Table=328 assertion and keep TxState=144, which accounts for the inline transaction blob map. Non-macOS compiled code is unchanged. Native macOS builds again fail the original size checks, as on #6305; a compatibility patch is saved outside the branch for future local profiling. Validation: rustfmt and git diff --check; exact diff is six deleted lines. Native clippy confirms the expected Table size assertion failure (328 versus 336). Linux checks were not rerun locally.
7625b27 to
b840802
Compare
| serialized.write_large_blobs(blob_store) | ||
| serialized.write_large_blobs(blobs)? |
There was a problem hiding this comment.
We may need to rollback previous changes to the page now that this is fallible, no?
There was a problem hiding this comment.
Ya possibly. I think it is actually infallible because we pinned all the blobs, but that is not expressible locally at the moment.
| // All page mutations have finished, so pinned blob frames no longer | ||
| // need to be pinned by the commit guard. | ||
| drop(prepared.pinned_blobs); |
There was a problem hiding this comment.
This releases the blob pins, but where do we decrement the ref count and eventually drop/remove the blob(s)?
There was a problem hiding this comment.
The refcounts are in the BlobSet that is owned by the transaction state, while the blob frames are shared via the ResidentObjects. The normal insertion path then updates the refcount in the committed state's BlobSet.
Just dropping the handles will leave dangling weak references in ResidentObjects, unless successful insertion created new strong references in the committed state BlobSet (they are guaranteed to become resident, because we pinned them).
| ptrs.extend(self.scan_rows(&NullBlobStore).map(|row| row.pointer())); | ||
| ptrs | ||
| for row in self.scan_rows(&BlobSet::new_for_test()) { |
There was a problem hiding this comment.
Is this equivalent to NullBlobStore?
There was a problem hiding this comment.
No it's not, the NullBlobStore panics on any method invocation. I'll have to find a way to emulate this, as BlobSet is not a trait object.
|
|
||
| // If we removed the last unique index, add a pointer map. | ||
| if index.is_unique() && !self.indexes.values().any(|idx| idx.is_unique()) { | ||
| self.pointer_map = Some(pointer_map.unwrap_or_else(|| self.rebuild_pointer_map(blob_store))); | ||
| self.pointer_map = Some(pointer_map.map_or_else(|| self.rebuild_pointer_map(blobs), Ok)?); |
There was a problem hiding this comment.
When do we record a pending schema change for a deleted index? Before or after this call? If after, we'll rollback to an invalid state if the pointer map rebuild fails.
There was a problem hiding this comment.
Normally before. I added rollback_add_index specifically to not rebuild the pointer map on rollback -- we should end up in the same state that we started with as pending schema changes get reverted in reverse order (and rebuilding on intermediate reverts was probably a bug to begin with). The TODO is to remind me that this needs a test.
| @@ -1377,33 +1610,34 @@ impl Table { | |||
| // SAFETY: | |||
| // - `self` trivially has the same `row_layout` as `self`. | |||
| // - We just inserted `temp_ptr`, so it's valid. | |||
| let (_, existing_row_ptr) = unsafe { Self::find_same_row(self, self, blob_store, temp_ptr, None) }; | |||
| let (_, existing_row_ptr) = unsafe { Self::find_same_row(self, self, blobs, temp_ptr, None) }?; | |||
There was a problem hiding this comment.
The temporary insert above needs to be rolled back in the case of a failure here as well as the other fallible calls below.
There was a problem hiding this comment.
I don't see at moment how this method could be made atomic, because deleting the temporary row can itself fail. But what should happen then?
joshua-spacetime
left a comment
There was a problem hiding this comment.
More rollback safety concerns. It would be good to add some rollback tests if they're indeed legitimate.
| let effects = UpdateEffects::after_migration(res, &tx); | ||
| let effects = UpdateEffects::after_migration(res, &tx)?; |
There was a problem hiding this comment.
Rollback safety: If this fails, do we undo the migration?
There was a problem hiding this comment.
Rollback safety: The revert is skipped if get_row_ref fails.
| commit_table.make_index_non_unique(index_id, commit_blob_store)?; | ||
| tx_table.make_index_non_unique(index_id, tx_blob_store)?; |
There was a problem hiding this comment.
Rollback safety: make_index_non_unique succeeds for commit_table but fails on tx_table. Is this a problem? It would appear so, since we haven't added the pending schema change at this point.
e1cd71a to
63d327f
Compare
8c3a919 to
4a229fb
Compare
f9e3421 to
89a9a07
Compare
63d327f to
490b308
Compare
89a9a07 to
002db2f
Compare
490b308 to
397e694
Compare
397e694 to
6905eab
Compare
Remove nine newly added Deep storage tests (429 lines) at the user's request to reduce the review diff. This removes new coverage for resident/tiered storage, prepared inserts, page reuse, and blob pins. Existing tests from #6305 and all production code remain unchanged. Validation: 112 existing spacetimedb-table library tests pass; clippy for the library and tests passes with -D warnings.
Remove the local macOS benchmarking accommodations from the review diff. Restore the original unconditional Table=328 assertion and keep TxState=144, which accounts for the inline transaction blob map. Non-macOS compiled code is unchanged. Native macOS builds again fail the original size checks, as on #6305; a compatibility patch is saved outside the branch for future local profiling. Validation: rustfmt and git diff --check; exact diff is six deleted lines. Native clippy confirms the expected Table size assertion failure (328 versus 336). Linux checks were not rerun locally.
1e739db to
bb400af
Compare
Introduces the datastructures for tiered storage / faultable objects.
Integrates tiered storage, replacing `Pages` with `PageSet` and `BlobStore` with `BlobSet`. Handles fallible object acquisition everywhere. Also implements two-phase transaction commit, which preflights the transaction and pins all affected objects. This allows the actual merge into the committed state to be infallible and therefore atomic.
Keep directly owned pages and borrowed access for resident tables while retaining tiered page eviction and the newer RowRef API. Stream eligible resident commits while their transaction state is alive, reuse resident pages and budget permits, and reduce row/blob bookkeeping overhead. Also fix shared blob registration and preserve logical blob entries on eviction, with coverage for both backends, budgets, snapshots and pins. Validation: 239 storage tests pass; Clippy passes with -D warnings. Three alternating 60-second TypeScript keynote runs with pipelining and confirmed reads enabled averaged 231,612 transfers/sec versus 241,902 on master (4.25% lower; paired gaps 3.26%, 3.49%, and 5.99%).
the page to be faulted in.
assert that an existing frame is actually the same allocation.
Commit changes only after potential pointer map rebuild succeeded.
If the pointer map needs to be rebuilt, rebuild it first before committing the changes.
This prevents unnecessarily faulting in blobs. With tiered storage, constructing a RowRef requires to load a BlobReadSet, so calling `scan_rows` with an empty BlobSet is not equivalent to the previous `NullBlobStore`.
`BlobReadSet` fails.
Fallibly collect blob references before mutating anything. Delete rows by marking all pages as absent.
`MutTxId::delete_by_row_value`, too
To make this possible, introduce a `PinnedRow` that works like an owned `RowRef`. When a pinned row is in scope, potentially faulting functions can be unwrapped, as they are guaranteed to not fault the pinned row's page.
instead of `RowPointer`.
bb400af to
8eb4f30
Compare
# Description of Changes Tiered storage makes loading rows fallible. Some cleanup, result-construction, and duplicate-lookup paths looked up rows again even though they already held the required page and blob pins. This PR reuses those existing pins, making the distinction between acquiring a row and using an already-loaded row clearer: - **Cleanup:** Expose the existing `Table::delete_unchecked` helper to the datastore and use it to delete rows whose pins are already held. Its deletion implementation is unchanged. - **Update results:** Retain the replacement row’s pins through confirmation and construct the returned `RowRef` from them, avoiding another lookup after mutation. - **Duplicate lookup:** Pass a borrowed `RowRef` constructed from existing pins into lookup and comparison helpers to avoid redundant row acquisition. # API and ABI breaking changes N/A # Rollback safety impact N/A # Expected complexity level and risk 3 # Testing Relies on existing coverage
# Description of Changes Tiered storage makes loading rows fallible. When mutation code already holds a `PinnedRow`, it can reuse the row’s page and blob pins instead of performing another storage lookup during cleanup. This PR uses those existing pins more consistently, keeping cleanup free of additional storage reads and making unsafe mutation error behavior easier to reason about: - Route insert/update comparison failures through existing cleanup, using the attempted row’s retained pins. - Restore the old row’s index entries without repeating duplicate-row comparisons. - During insertion, load the committed row being returned before removing its transaction-local deletion marker. The PR also extends commit preparation to pin pages needed for pointer-map hash-collision comparisons. This protects committed-state, since those comparisons must not encounter a storage failure after committed rows have already been changed. # API and ABI breaking changes N/A # Rollback safety impact N/A # Expected complexity level and risk 3 # Testing Existing coverage
## Description of Changes Fix two cases where rolling back a transaction could leave committed tables with different duplicate-detection or uniqueness behavior: - **Preserve the pointer map when undoing a non-unique index addition.** Adding that index leaves the existing pointer map untouched, but rollback previously replaced it with `None`. Subsequent duplicate checks could panic. Restore the saved map only when undoing a unique index addition. - **Preserve indexes that were already unique before adding a constraint.** Rollback previously made every matching index non-unique, including indexes enforcing an existing constraint. Convert and record only previously non-unique indexes, so undo reverses only changes made by this operation. Both fixes use the existing undo records. ## API and ABI breaking changes N/A ## Rollback safety impact N/A ## Expected complexity level and risk 2 ## Testing - Verify that rolling back unique and non-unique index additions restores the pointer map and preserves duplicate detection. - Verify that rolling back an additional unique constraint preserves the original constraint, its index uniqueness, and rejection of conflicting inserts.
Description of Changes
Integrates tiered storage into the rest of the codebase.
This is a big change, because every call site that may access a page or blob
becomes fallible due to the possibility of fault errors.
Much of this is mechanical, but note that transaction commit becomes a two-step
process with this patch: first, all pages and blobs affected by the transaction
are faulted in if necessary, and pinned so they can't be evicted until the
transaction completes. Then, the transaction is actually applied -- this remains
infallible and therefore atomic.
API and ABI breaking changes
Internal only.
Rollback safety impact
Currently rollback safe.
Expected complexity level and risk
5
Testing