Skip to content

Reserve data file space in large steps - #74

Closed
martian56 wants to merge 1 commit into
mainfrom
prealloc-data
Closed

Reserve data file space in large steps#74
martian56 wants to merge 1 commit into
mainfrom
prealloc-data

Conversation

@martian56

@martian56 martian56 commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Found by counting rather than guessing. A probe confirmed we issue exactly 1.00 syncs per commit group, so the sync count was never the problem, but the same probe showed 18 ms per group under eight writers where a single threaded commit measures 3 ms. Fifteen milliseconds was going somewhere other than the flush.

Every fresh page extended the data file by one page, so each new page paid a file metadata update, and under load that is most of the group. The WAL has been preallocated for this reason since #40; the data file never was.

It now grows in eight MiB steps. The catch is that page_count drives both page id allocation and the recovery sweep, so the reserved tail must not be counted as pages: the file tracks how far content actually reaches and finds it at open by walking back over the zeroed tail. A test covers the reopen path.

Summary by CodeRabbit

  • Bug Fixes
    • Improved storage file handling so reserved space at the end of a file is no longer counted as written content.
    • Corrected page counts after reopening files, ensuring they reflect pages containing actual data.
    • Added safer handling for large page offsets and file growth during writes.

Every fresh page extended the file by eight KiB, so each one paid a
metadata update. The file now grows in eight MiB steps and tracks how
far content actually reaches, since the reserved tail must not be
counted as pages after a reopen.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

DataFile now tracks reserved physical bytes separately from logical written pages, scans reserved tails during reopen, grows files in chunks, updates page counts on writes, and tests that reserved space is not counted as content.

Changes

DataFile page tracking

Layer / File(s) Summary
Physical and logical size state
storage/src/data_file.rs
DataFile stores reserved physical size and logical page count, while reopening scans trailing zero pages to recover the last written page.
Chunked growth and logical page counting
storage/src/data_file.rs
Writes reserve chunked file space, use checked page-end offsets and positional writes, and report the stored logical page count; a reopen test verifies reserved tail space is excluded.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 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 accurately captures the main change: reserving data file space in larger allocation steps.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch prealloc-data

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
storage/src/data_file.rs (1)

202-213: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Synchronize before reopening in the durability test.

Without data.sync(), the test only verifies that a same-process reopen observes cached writes; it does not exercise the persisted file state used by recovery.

Proposed fix
             assert!(
                 data.file.metadata().expect("meta").len() > 2 * PAGE_SIZE as u64,
                 "the file reserves room beyond the pages written"
             );
+            data.sync().expect("sync");
🤖 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 `@storage/src/data_file.rs` around lines 202 - 213, Update the durability
test’s DataFile write block to call data.sync() after the page writes and before
reopening or validating persisted state, ensuring the test exercises
synchronized file contents rather than cached writes.
🤖 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 `@storage/src/data_file.rs`:
- Around line 75-78: Replace the saturating reservation arithmetic in the growth
branch of the data-file write logic with checked addition; when adding
GROW_BYTES to end overflows, return the existing CorruptDataFile error
consistently with the offset validation, and only call set_len and update
self.physical after a successful checked result.

---

Nitpick comments:
In `@storage/src/data_file.rs`:
- Around line 202-213: Update the durability test’s DataFile write block to call
data.sync() after the page writes and before reopening or validating persisted
state, ensuring the test exercises synchronized file contents rather than cached
writes.
🪄 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: 53e93c14-b72f-4a90-98d9-2af54a86a633

📥 Commits

Reviewing files that changed from the base of the PR and between ddbaaf8 and a0720e1.

📒 Files selected for processing (1)
  • storage/src/data_file.rs

Comment thread storage/src/data_file.rs
Comment on lines +75 to +78
if end > self.physical {
let grown = end.saturating_add(GROW_BYTES);
self.file.set_len(grown)?;
self.physical = grown;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Return an error when reservation arithmetic overflows.

end is checked, but end.saturating_add(GROW_BYTES) can become u64::MAX and then pass an unrepresentable or enormous length to set_len. Use checked_add and return CorruptDataFile on overflow, consistent with the existing offset check.

Proposed fix
-            let grown = end.saturating_add(GROW_BYTES);
+            let grown = end.checked_add(GROW_BYTES).ok_or_else(|| {
+                StorageError::CorruptDataFile("file growth overflow".to_owned())
+            })?;
📝 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
if end > self.physical {
let grown = end.saturating_add(GROW_BYTES);
self.file.set_len(grown)?;
self.physical = grown;
if end > self.physical {
let grown = end.checked_add(GROW_BYTES).ok_or_else(|| {
StorageError::CorruptDataFile("file growth overflow".to_owned())
})?;
self.file.set_len(grown)?;
self.physical = grown;
🤖 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 `@storage/src/data_file.rs` around lines 75 - 78, Replace the saturating
reservation arithmetic in the growth branch of the data-file write logic with
checked addition; when adding GROW_BYTES to end overflows, return the existing
CorruptDataFile error consistently with the offset validation, and only call
set_len and update self.physical after a successful checked result.

@github-actions

Copy link
Copy Markdown
Contributor

Bench report

⚠️ Worst regression: windows-x64-full writes at -26.6%.

Baseline: main @ same runn. Cells read baseline → this PR. Verdicts need more than ±15% to leave the noise band.

linux-arm64-full

workload ops/s Δ ops/s read p99 ms write p99 ms write p99.9 ms
reads 32179 → 32228 ⚪ +0.2% 0.22 → 0.22 - -
mixed 14210 → 13514 ⚪ -4.9% 0.44 → 0.42 11.54 → 19.04 36.45 → 36.72
writes 6225 → 5838 ⚪ -6.2% - 9.05 → 10.06 28.19 → 28.81

linux-x64-1cpu-1gb

workload ops/s Δ ops/s read p99 ms write p99 ms write p99.9 ms
reads 21225 new 0.28 - -
mixed 6363 new 0.39 59.15 110.08
writes 2528 new - 30.68 115.88

linux-x64-2cpu-2gb

workload ops/s Δ ops/s read p99 ms write p99 ms write p99.9 ms
reads 30077 new 0.21 - -
mixed 6793 new 0.32 53.06 171.37
writes 2279 new - 27.89 153.95

linux-x64-full

workload ops/s Δ ops/s read p99 ms write p99 ms write p99.9 ms
reads 31596 → 31686 ⚪ +0.3% 0.22 → 0.22 - -
mixed 17469 → 17600 ⚪ +0.7% 0.35 → 0.37 3.55 → 3.07 26.87 → 25.82
writes 7709 → 7654 ⚪ -0.7% - 1.45 → 1.75 8.37 → 8.55

windows-x64-full

workload ops/s Δ ops/s read p99 ms write p99 ms write p99.9 ms
reads 22166 → 22181 ⚪ +0.1% 0.40 → 0.35 - -
mixed 5663 → 5609 ⚪ -1.0% 0.35 → 0.35 30.55 → 30.59 39.30 → 40.49
writes 842 → 618 🔴 -26.6% - 16.23 → 31.69 26.59 → 32.55

Field reference

All engines in identical containers, same driver, measured 2026-07-24 @ ba0ec39. A different harness than the tables above, so read this as standing, not as this PR's delta.

linux-arm64-full

engine reads mixed writes
quantadb 17978 12420 (27ms wp99) 6449 (19ms wp99)
postgres18 15227 14723 (2ms wp99) 9923 (1ms wp99)
mariadb114 4221 4437 (8ms wp99) 6666 (3ms wp99)
mysql84 4258 4394 (9ms wp99) 4274 (4ms wp99)

linux-x64-1cpu-1gb

engine reads mixed writes
quantadb 19419 10854 (29ms wp99) 5170 (39ms wp99)
postgres18 10196 8580 (43ms wp99) 5912 (46ms wp99)
mariadb114 3455 3732 (4ms wp99) 5626 (8ms wp99)
mysql84 3497 3628 (4ms wp99) 3259 (36ms wp99)

linux-x64-2cpu-2gb

engine reads mixed writes
quantadb 22528 16774 (5ms wp99) 8374 (4ms wp99)
postgres18 17060 17006 (2ms wp99) 11404 (1ms wp99)
mariadb114 3735 4006 (3ms wp99) 6741 (3ms wp99)
mysql84 3804 3845 (4ms wp99) 4847 (3ms wp99)

linux-x64-full

engine reads mixed writes
quantadb 18144 15075 (6ms wp99) 8025 (5ms wp99)
postgres18 15402 15154 (2ms wp99) 10872 (1ms wp99)
mariadb114 3437 3699 (4ms wp99) 6138 (3ms wp99)
mysql84 3490 3615 (4ms wp99) 3996 (5ms wp99)

Shared CI runners: treat single-profile swings as suggestive, agreement across profiles as real.

@martian56

Copy link
Copy Markdown
Owner Author

Measured and rejected.

  • linux-x64-full: minus 0.7 percent
  • linux-arm64-full: minus 6.2 percent
  • windows-x64-full: minus 26.6 percent, write p99 16.23 to 31.69 ms
  • docker profiles had no paired baseline this run

So the eighteen milliseconds per group that motivated this is a Windows property, not a general one. NTFS extends a file by zeroing to the new valid data length, so reserving eight MiB costs an eight MiB write, which is why Windows gets materially worse rather than better. Linux extends a file cheaply and had nothing to gain.

Two things worth keeping from the attempt. The probe confirmed we issue exactly 1.00 syncs per commit group, so the sync count was never the problem and can stop being suspected. And local timings on this Windows machine cannot be used to reason about the Linux runners at all: eighteen milliseconds per group here versus roughly one there, from file extension alone.

@martian56 martian56 closed this Jul 25, 2026
@martian56
martian56 deleted the prealloc-data branch July 25, 2026 08:26
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.

1 participant