Skip to content

Keep mapped object disk restores from sharing keys with the source table - #1570

Merged
Slach merged 2 commits into
masterfrom
issue-1568
Sep 18, 2026
Merged

Slach merged 2 commits into
masterfrom
issue-1568

Conversation

@Slach

@Slach Slach commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1568

Problem

restore --restore-database-mapping=source:completed of a table on an S3 disk next to the still existing source, followed by DROP DATABASE completed SYNC, made the source table fail with Code: 499 ... The specified key does not exist (S3_ERROR). The same happened when an interrupted mapped restore was dropped.

Root causes

  1. The key rewrite guard strings.Contains(objectPath, backupName) examined the whole key, so a disk endpoint prefix containing the backup name (backup regression, disk .../regression-test/live/) silently disabled the rewrite and the copy referenced the source objects. This matches the completed-restore case of the report.
  2. HardlinkBackupPartsToStorage ran before the objects were copied and the keys rewritten, so an interrupted restore left detached (or the table directory with restore_as_attach) pointing at the source keys. This matches the SIGINT case of the report.
  3. Shadow metadata files are hardlinked into the restored table and WriteMetadataToFile wrote them in place, so a second mapped restore of the same local backup rewrote the parts of the first copy; the suffix carried only the backup name, so two copies of one backup shared their keys.

Fix

  • splitRestoreObjectKeys: source key always derived from the backup key (a suffix left by a previous restore is stripped), destination key gets _<backup>_<crc32(db.table)>.
  • Object copy and key rewrite happen before the parts are linked into the table.
  • WriteMetadataToFile writes via temporary file and rename, so hardlinked parts of an already restored table are never modified.

Tests

  • TestSplitRestoreObjectKeys unit test.
  • TestRestoreMappingObjectDiskDropDst: plain s3_only, cache-wrapped tiered policy with MOVE PARTITION (as in the report), backup name contained in the disk key prefix, interrupted restore (object copy fails after schema is created, emulated with a wrong S3_OBJECT_DISK_PATH), restore of the same local backup twice. The last three fail on master with the exact error from the issue.
  • Verified on ClickHouse 26.8: TestRestoreMappingObjectDiskDropDst, TestRestoreMapping, TestCacheDiskBackup, TestS3.

🤖 Generated with Claude Code

Slach and others added 2 commits September 18, 2026 09:44
A table restored next to its source with --restore-database-mapping on
the same S3 disk lost the source data on DROP DATABASE ... SYNC of the
copy (Code: 499 The specified key does not exist). Three defects in
downloadObjectDiskParts and its callers combined into that:

The idempotency guard strings.Contains(objectPath, backupName) looked
at the whole key, so a disk endpoint prefix which contains the backup
name (backup `regression` on `.../regression-test/live/`) silently
disabled the key rewrite and the copy referenced the source objects.

HardlinkBackupPartsToStorage ran before the objects were copied and the
keys rewritten, so an interrupted restore left `detached` (or the table
directory with restore_as_attach) pointing at the source keys.

The shadow metadata files are hardlinked into the restored table and
WriteMetadataToFile wrote them in place, so a second mapped restore of
the same local backup rewrote the parts of the first copy, and since
the suffix carried only the backup name both copies shared their keys.

splitRestoreObjectKeys now derives the source key from the backup key
(a suffix left by a previous restore is stripped), the destination key
carries `_<backup>_<crc32(db.table)>`, objects are copied before the
parts are linked into the table and metadata files are written via a
temporary file plus rename.

TestRestoreMappingObjectDiskDropDst covers the plain, cache-wrapped
tiered, prefix-contains-backup-name, interrupted and restore-twice
cases; all but the first two fail on master.

Verified on 26.8: TestRestoreMappingObjectDiskDropDst,
TestRestoreMapping, TestCacheDiskBackup, TestS3.

fix #1568

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CI of the previous commit failed in two ways.

On ClickHouse 22.8 the object keys of an S3 disk are a bare random
name without the `xxx/` directory component, and splitRestoreObjectKeys
(like the code before it) skipped such keys, so the mapped copy still
shared its objects with the source and TestRestoreMappingObjectDiskDropDst
failed with the very error it guards against. The suffix now goes onto
the only component when there is no directory.

On 26.8 every embedded backup restore failed with `Cannot open file
.../metadata/<db>/<table>.sql ... Permission denied` and its remote
leftovers cascaded into the other tests of the same pooled environment.
WriteMetadataToFile now creates the file via CreateTemp, which as root
produces a root owned file, while the in-place os.Create kept the
clickhouse owner of the existing file and its 0640 mode made it
unreadable for the server. The owner of the replaced file is now copied
onto the new one together with its mode.

Verified on 22.8 (TestRestoreMappingObjectDiskDropDst) and 26.8
(TestRestoreMappingObjectDiskDropDst, TestEmbeddedS3,
TestBwLimitEmbeddedS3).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 35323856779

Coverage increased (+0.06%) to 67.514%

Details

  • Coverage increased (+0.06%) from the base build.
  • Patch coverage: 20 uncovered changes across 2 files (69 of 89 lines covered, 77.53%).
  • 22 coverage regressions across 5 files.

Uncovered Changes

File Changed Covered %
pkg/storage/object_disk/object_disk.go 26 10 38.46%
pkg/backup/restore.go 63 59 93.65%

Coverage Regressions

22 previously-covered lines in 5 files lost coverage.

File Lines Losing Coverage Coverage
pkg/clickhouse/clickhouse.go 8 80.56%
pkg/backup/upload.go 6 72.81%
pkg/backup/delete.go 3 69.97%
pkg/storage/object_disk/object_disk.go 3 70.14%
pkg/backup/create_remote.go 2 65.81%

Coverage Stats

Coverage Status
Relevant Lines: 27076
Covered Lines: 18280
Line Coverage: 67.51%
Coverage Strength: 35513.84 hits per line

💛 - Coveralls

@Slach
Slach merged commit ba970aa into master Sep 18, 2026
30 checks passed
@Slach Slach added this to the 2.8.2 milestone Sep 18, 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.

Dropping a mapped restore deletes objects needed by the original S3-backed table (reproduced on 2.8.1)

2 participants