pingcap/tidb · critical

yet restoring a SST with two adjacent tables not supported,

Error message

yet restoring a SST with two adjacent tables not supported, they are %d and %d (start key = %s; end key = %s)

What it means

Thrown by CopiedSST.TableID() in BR's log-backup restore client. It decodes the table ID from both the SST file's StartKey and EndKey (via tablecodec.DecodeTableID) and asserts they match, because a restorable SST must sit inside exactly one table's key range. The panic fires when one backup SST spans two adjacent tables, which the restore path (ingest/split by table) cannot handle.

Source

Thrown at br/pkg/restore/log_client/ssts.go:101

	cachedTableID atomic.Int64
}

func (s *CopiedSST) String() string {
	return fmt.Sprintf("CopiedSSTs: %s", s.File)
}

func (s *CopiedSST) Type() int {
	return CopiedSSTsType
}

func (s *CopiedSST) TableID() int64 {
	cached := s.cachedTableID.Load()
	if cached == 0 {
		id := tablecodec.DecodeTableID(s.File.StartKey)
		id2 := tablecodec.DecodeTableID(s.File.EndKey)
		if id != id2 {
			panic(fmt.Sprintf(
				"yet restoring a SST with two adjacent tables not supported, they are %d and %d (start key = %s; end key = %s)",
				id,
				id2,
				hex.EncodeToString(s.File.StartKey),
				hex.EncodeToString(s.File.EndKey),
			))
		}
		s.cachedTableID.Store(id)
		return id
	}

	return cached
}

func (s *CopiedSST) GetSSTs() []*backuppb.File {
	if s.File == nil {
		return nil
	}

View on GitHub (pinned to d01f9615c1)

Solutions

  1. Re-run the restore with a BR version that matches (ideally the same version as) the one that took the backup, so range grouping logic is identical.
  2. If the source cluster is available, retake the backup with tiny adjacent tables excluded or pre-split, so no SST covers two tables.
  3. Inspect the reported start/end keys (hex in the message) with tablecodec.DecodeTableID to confirm which two tables are involved; if the keys are not valid table-prefixed keys, suspect backup corruption and re-generate the backup.
  4. If the keys are valid and the SST legitimately covers one table only (end key is the next table's prefix sentinel), report a BR bug at github.com/pingcap/br with the panic message and backup metadata.

Example fix

// before: relying on restore to accept any SST layout
br restore full --backup ts.sst ...

// after: pre-validate that each SST maps to a single table before restoring
for _, f := range backupMeta.Files {
    id1 := tablecodec.DecodeTableID(f.StartKey)
    id2 := tablecodec.DecodeTableID(f.EndKey)
    if id1 != id2 {
        log.Fatalf("backup SST %s spans tables %d and %d; retake backup", f.Name, id1, id2)
    }
}
Defensive patterns

Strategy: validation

Validate before calling

import "github.com/pingcap/tidb/pkg/tablecodec"

// run over backupMeta.Files before starting a restore
func sstSpansSingleTable(startKey, endKey []byte) bool {
    return tablecodec.DecodeTableID(startKey) == tablecodec.DecodeTableID(endKey)
}

Prevention

When it happens

Trigger: Calling restore on a backup whose SST file keys decode to two different table IDs: s.File.StartKey maps to table N and s.File.EndKey to table N+1. Typically produced by backups that merged small ranges across table boundaries (e.g. tiny partitioned tables or index/data ranges merged into one SST), or by a PITR/log-restore copy step that groups files crossing a table boundary.

Common situations: Restoring a log backup or incremental backup of a schema with many tiny tables/partitions; restoring a backup made by a newer BR whose range-grouping differs from the restoring BR; a corrupted or mis-generated SST meta where keys are not row/index keys of a single table.

Related errors


AI-assisted analysis of pingcap/tidb@d01f9615c1 (2026-08-15). Data as JSON: /api/errors/5cce2efe779fe1b4. Report an issue: GitHub.