Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions pkg/backup/restore_online.go
Original file line number Diff line number Diff line change
Expand Up @@ -816,18 +816,23 @@ func (r *restoreResumer) waitForDownloadToComplete(
return ctx.Err()
}

func unstickRestoreSpans(
ctx context.Context, execCfg *sql.ExecutorConfig, spans roachpb.Spans,
) error {
func unstickRestoreSpans(ctx context.Context, execCfg *sql.ExecutorConfig, spans roachpb.Spans) {
for _, sp := range spans {
if err := execCfg.DB.AdminUnsplit(ctx, sp.Key); err != nil {
return errors.Wrapf(err, "failed to unsplit %s", sp)
}
if err := execCfg.DB.AdminUnsplit(ctx, sp.EndKey); err != nil {
return errors.Wrapf(err, "failed to unsplit %s", sp.EndKey)
}
besteffort.Warning(ctx, "or-unsplit", func(ctx context.Context) error {
if err := execCfg.DB.AdminUnsplit(ctx, sp.Key); err != nil &&
!kvpb.IsKeyNotStartOfRange(err) {
return errors.Wrapf(err, "failed to unsplit %s", sp)
}
return nil
})
besteffort.Warning(ctx, "or-unsplit", func(ctx context.Context) error {
if err := execCfg.DB.AdminUnsplit(ctx, sp.EndKey); err != nil &&
!kvpb.IsKeyNotStartOfRange(err) {
return errors.Wrapf(err, "failed to unsplit %s", sp.EndKey)
}
return nil
})
}
return nil
}

func getExternalBytesOverSpans(
Expand Down Expand Up @@ -985,7 +990,8 @@ func (r *restoreResumer) cleanupAfterDownload(
log.Dev.Warningf(ctx, "failed to re-enable auto stats on table %d", id)
}
}
return unstickRestoreSpans(ctx, r.execCfg, details.DownloadSpans)
unstickRestoreSpans(ctx, r.execCfg, details.DownloadSpans)
return nil
}

func createImportRollbackJob(
Expand Down Expand Up @@ -1115,7 +1121,8 @@ func (r *restoreResumer) maybeCleanupFailedOnlineRestore(
return errors.Wrapf(err, "failed to get external data after excise")
}

return unstickRestoreSpans(ctx, p.ExecCfg(), details.DownloadSpans)
unstickRestoreSpans(ctx, p.ExecCfg(), details.DownloadSpans)
return nil
}

// getNumOnlineRestoreLinkWorkers returns the total number of workers to use for
Expand Down
1 change: 1 addition & 0 deletions pkg/backup/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,4 +612,5 @@ func getFullBackupPaths(t *testing.T, sqlDB *sqlutils.SQLRunner, uri string) []s
func AllowORDownloadBestEffortFailures(t *testing.T) {
t.Helper()
t.Cleanup(besteffort.TestAllowFailure("or-download-failed-log"))
t.Cleanup(besteffort.TestAllowFailure("or-unsplit"))
}
19 changes: 19 additions & 0 deletions pkg/kv/kvpb/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"
"reflect"
"slices"
"strings"

"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
Expand All @@ -29,6 +30,24 @@ import (
"github.com/gogo/protobuf/proto"
)

// ErrKeyNotStartOfRange is returned by AdminUnsplit when the target key is not
// the start key of any range, typically because the range was merged
// concurrently.
//
// NB: don't change the string here; this will cause cross-version issues since
// this singleton is used as a marker.
var ErrKeyNotStartOfRange = errors.New("key is not the start of a range")

// IsKeyNotStartOfRange returns true if the error indicates that an AdminUnsplit
// request targeted a key that is not the start of a range. It falls back to
// string matching for compatibility with older nodes that produce the untyped
// error.
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.

Should we leave a TODO here to signal that the string matching can be removed in 27.1?

func IsKeyNotStartOfRange(err error) bool {
// TODO(msbutler): remove string matching fallback in 27.1.
return errors.Is(err, ErrKeyNotStartOfRange) ||
strings.Contains(err.Error(), "is not the start of a range")
}

// Printer is an interface that lets us use what's common between the
// errors.Printer interface and redact.SafePrinter so we can write functions
// that both SafeFormatError and SafeFormat can share.
Expand Down
5 changes: 4 additions & 1 deletion pkg/kv/kvserver/replica_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,10 @@ func (r *Replica) adminUnsplitWithDescriptor(
) (kvpb.AdminUnsplitResponse, error) {
var reply kvpb.AdminUnsplitResponse
if !bytes.Equal(desc.StartKey.AsRawKey(), args.Header().Key) {
return reply, errors.Errorf("key %s is not the start of a range", args.Header().Key)
return reply, errors.Mark(
errors.Newf("key %s is not the start of a range", args.Header().Key),
kvpb.ErrKeyNotStartOfRange,
)
}

// If the range's sticky bit is already hlc.Timestamp{}, we treat the unsplit
Expand Down
5 changes: 2 additions & 3 deletions pkg/sql/gcjob/gc_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package gcjob

import (
"context"
"strings"
"time"

"github.com/cockroachdb/cockroach/pkg/jobs"
Expand Down Expand Up @@ -189,7 +188,7 @@ func unsplitRangesInSpan(
// that the sticky bit was removed and merged concurrently. DROP TABLE
// should not fail because of this.
if err := kvDB.AdminUnsplit(ctx, desc.StartKey); err != nil &&
!strings.Contains(err.Error(), "is not the start of a range") {
!kvpb.IsKeyNotStartOfRange(err) {
return err
}
}
Expand Down Expand Up @@ -222,7 +221,7 @@ func unsplitRangesInSpanForSecondaryTenant(
// Swallow "key is not the start of a range" errors because it would mean
// that the sticky bit was removed and merged concurrently. DROP TABLE
// should not fail because of this.
if strings.Contains(err.Error(), "is not the start of a range") {
if kvpb.IsKeyNotStartOfRange(err) {
continue
}
// If we are in a secondary tenant and get an auth
Expand Down
Loading