Skip to content

A failed write to a non-staging backup destination leaves whatever the backend left behind #1101

Description

@xe-nvdk

⚠️ Internal work only. Community contributions are not being accepted for this issue.

Basekick Labs is implementing this internally. Please do not open PRs against it; they will be closed without review. Discussion and questions in the comments are welcome.## Summary

cleanupPartialWrite (internal/backup/backup.go:1243-1259) is the compensation a backup runs when a copy fails part-way. It only works on a backend that stages writes:

si, ok := backend.(storage.StagingInspector)
if !ok {
    return
}

LocalBackend stages, so the local backup destination is covered and the comment is right that a backend which never stages leaves no .part file. But it does not follow that nothing needs cleaning up: S3 and Azure do not stage, and a failed WriteReader to either can still leave a committed object (a complete small object whose surrounding operation then failed, or an aborted multipart upload holding storage the operator pays for). The function returns before considering that.

Called from three places, all of which pass a backend that may not stage: backup.go:911 (data file), internal/backup/cluster.go:404 (the manifest-files.json sidecar) and internal/backup/warehouse.go:343 (an Iceberg warehouse file). A fourth write, the SQLite metadata copy at backup.go:1322, has no compensation at all.

Why now

Not reachable today: the only backup destination is backup.local_path, always a LocalBackend, which always stages. It becomes reachable the moment a backup destination can be S3 or Azure, which is what named remote targets add (#1085).

Expected

For a destination that does not stage, compensate with a Delete of the destination key, and treat a failure to compensate the way the staging path does: log it at debug with the path, since the next run overwrites the key anyway and a failed cleanup must not fail the backup. Multipart uploads need the SDK's abort rather than a delete; worth confirming what WriteReader leaves behind on each backend before choosing.

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggoPull requests that update go codepriority: mediumCorrectness or operability gap with a workaround

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions