Skip to content

go: store/nbs: reject a file manifest update that references a missing table file - #11430

Open
reltuk wants to merge 2 commits into
mainfrom
aaron/manifest-assert-specs-present
Open

go: store/nbs: reject a file manifest update that references a missing table file#11430
reltuk wants to merge 2 commits into
mainfrom
aaron/manifest-assert-specs-present

Conversation

@reltuk

@reltuk reltuk commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

A bare local store takes no cross-process lock while a table file is landing, so between the moment a writer opens or renames a table file and the moment it commits a manifest naming it, another process can unlink it. The writer does not notice, since its own file descriptor keeps working, and it publishes a manifest that is broken for everyone else.

Add a sanity check where we Stat the newly added files under the manifest LOCK. This allows other Dolt processes which want to safely unlink unreferenced files from the directory to do so under the same LOCK. While a write may transiently fail if an about-to-be-referenced file is deleted out from under it, it won't cause database corruption by writing a manifest which references a non-existant table file.

This PR adds the add-files sanity check. It does not add the machinery to safely remove any files yet.

Journaled stores are unaffected: ChunkJournal goes through journalManifest, not fileManifest.

…g table file

A bare local store takes no cross-process lock while a table file is
landing, so between the moment a writer opens or renames a table file
and the moment it commits a manifest naming it, another process can
unlink it.  The writer does not notice, since its own file descriptor
keeps working, and it publishes a manifest that is broken for everyone
else.

Add a sanity check where we Stat the newly added files under the
manifest LOCK. This allows other Dolt processes which want to safely
unlink unreferenced files from the directory to do so under the same
LOCK. While a write may transiently fail if an about-to-be-referenced
file is deleted out from under it, it won't cause database corruption
by writing a manifest which references a non-existant table file.

This PR adds the add-files sanity check. It does not add the machinery
to safely remove any files yet.

Journaled stores are unaffected: ChunkJournal goes through
journalManifest, not fileManifest.
@reltuk
reltuk requested a review from nicktobey August 6, 2026 14:54
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

This PR is being tested for SQL correctness. Please allow ~25 mins for this to complete. If this PR does not result in a SQL correctness regression, the correctness_approved label will be automatically added to this PR and the Check for correctness_approved workflow will succeed.

@coffeegoddd

Copy link
Copy Markdown
Contributor

@reltuk DOLT

read_tests from_latency to_latency percent_change
covering_index_scan 2.3 2.3 0.0
groupby_scan 64.47 63.32 -1.78
index_join 1.93 1.93 0.0
index_join_scan 1.32 1.34 1.52
index_scan 196.89 196.89 0.0
oltp_point_select 0.25 0.25 0.0
oltp_read_only 5.0 5.0 0.0
select_random_points 0.51 0.51 0.0
select_random_ranges 0.64 0.64 0.0
table_scan 200.47 196.89 -1.79
types_table_scan 458.96 450.77 -1.78
write_tests from_latency to_latency percent_change
oltp_delete_insert 6.09 6.09 0.0
oltp_insert 3.13 3.13 0.0
oltp_read_write 11.24 11.24 0.0
oltp_update_index 3.3 3.3 0.0
oltp_update_non_index 3.02 3.02 0.0
oltp_write_only 6.21 6.21 0.0
types_delete_insert 6.79 6.79 0.0

@coffeegoddd

Copy link
Copy Markdown
Contributor

@reltuk DOLT

test_name from_latency_p95 to_latency_p95 percent_change
tpcc-scale-factor-1 45.79 44.98 -1.77
test_name from_server_name from_server_version from_tps to_server_name to_server_version to_tps percent_change
tpcc-scale-factor-1 dolt a995f24 52.51 dolt 23ccbf9 52.71 0.38

Comment thread go/store/nbs/file_manifest.go Outdated
// into place, so a file that passes here cannot be removed by a lock-respecting
// process before the manifest naming it is committed.
//
// Nothing is expected to trip this: every path that adds a spec either renames

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.

"Nothing is expected to trip this" seems inaccurate, and the rest of the paragraph makes no sense.

Comment thread go/store/nbs/file_manifest.go Outdated
// missing in between, which our own open file descriptor would not notice.
//
// Only specs |upstream| does not already carry are checked; re-verifying the
// whole set would make every manifest write a directory-sized stat storm.

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.

"write a directory-sized stat storm" is too clever for its own good, please say it straight.

Comment thread go/store/nbs/file_manifest_test.go Outdated
}

// TestFileManifestUpdateAcceptsArchive asserts an archive satisfies a spec.
// A tableSpec records only the address, so both spellings must be tried.

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.

"Spellings?" This docstring is vague and unclear.

Comment thread go/store/nbs/file_manifest_test.go Outdated
}

// TestFileManifestUpdateAcceptsAppendix asserts appendix specs are checked too;
// they name table files the same way the main spec list does.

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.

This docstring should be written such that it doesn't require the previous test docstrings for context and avoid language like "they name table files the same way the main spec list does" which doesn't actually explain anything without additional context.

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.

This test is also almost identical to the previous one? Should it be a single parameterized test?

Comment thread go/store/nbs/file_manifest_test.go Outdated

// TestFileManifestUpdateSkipsExistingSpecs asserts only newly added specs are
// checked. Re-verifying the whole set on every write would cost a stat per
// table file, and those files were vouched for when they were added.

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.

"vouched for" is an unusual word choice here.

Comment thread go/store/nbs/file_manifest_test.go Outdated
}

// TestFileManifestUpdateGCGenRejectsMissingTableFile asserts the GC generation
// update path is checked as well; it publishes a wholly new spec list.

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.

Like the previous comment above, this docstring doesn't say much without the context of the previous docstrings. "it publishes a wholly new spec list" should use more precise language to say what's actually happening.

touchTableFile(t, fm.dir, present)
contents := manifestContents{
nbfVers: constants.FormatDoltString,
lock: computeAddr([]byte("lock 1")),

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.

Can the test make clear why two different locks are used?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants