dgraph-io/dgraph · error

RestoreRequest must have a valid restoreTs

Error message

RestoreRequest must have a valid restoreTs

What it means

RunMapper validates the RestoreRequest before starting the map phase and refuses to run when req.RestoreTs is zero. RestoreTs is the timestamp (restore-to point) the restored keyspace must carry; without it the restore cannot construct valid postings, so the request is rejected immediately.

Solutions

  1. Set req.RestoreTs from the backup manifest's value (manifest.RestoreTs or BackupTs) before calling RunMapper.
  2. If using dgraph restore CLI/Alpha RPC, ensure the input manifest is loaded so RestoreTs is populated rather than built by hand.
  3. Verify with a nil/zero check on the request before invoking RunMapper to fail fast with a clearer message.

Example fix

// before
req := &pb.RestoreRequest{Location: loc}
res, err := worker.RunMapper(req, mapDir)
// after
req := &pb.RestoreRequest{Location: loc, RestoreTs: manifest.RestoreTs}
if req.RestoreTs == 0 {
    return errors.New("manifest has no RestoreTs; cannot restore")
}
res, err := worker.RunMapper(req, mapDir)
Defensive patterns

Strategy: validation

Validate before calling

if req == nil || req.RestoreTs == 0 {
    return errors.New("RestoreRequest.RestoreTs must be set from the backup manifest")
}

Prevention

When it happens

Trigger: Calling RunMapper (directly or via runExportBackup, handleRestoreProposal, RunOfflineRestore) with a pb.RestoreRequest whose RestoreTs field was never set (defaults to 0).

Common situations: Constructing a RestoreRequest programmatically for offline restore or export-backup tooling and forgetting to copy RestoreTs from the backup manifest; older clients or tooling that predate the RestoreTs field leaving it unset.

Related errors


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/d47520e44b9db11c. Report an issue: GitHub.

Appendix: source

Thrown at worker/restore_map.go:744

	dropAttr      map[string]struct{}
	dropNs        map[uint64]struct{}
}

// we create MAP files each of a limited size and write sorted data into it. We may end up
// creating many such files. Then we take all of these MAP files and read part of the data
// from each file, sort all of this data and then use streamwriter to write the sorted data
// into pstore badger. We store some sort of partition keys in the MAP file in the beginning
// of the file. The partition keys are just intermediate keys among the entries that we store
// in the map file. When we read data during reduce, we read in the chunks of these partition
// keys, meaning from one partition key to the next partition key. I am not sure if there is a
// value in having these partition keys. Maybe, we can live without them.
func RunMapper(req *pb.RestoreRequest, mapDir string) (*mapResult, error) {
	uri, err := url.Parse(req.Location)
	if err != nil {
		return nil, err
	}
	if req.RestoreTs == 0 {
		return nil, errors.New("RestoreRequest must have a valid restoreTs")
	}

	creds := getCredentialsFromRestoreRequest(req)
	h, err := NewUriHandler(uri, creds)
	if err != nil {
		return nil, err
	}

	manifests, err := getManifestsToRestore(h, uri, req)
	if err != nil {
		return nil, errors.Wrapf(err, "cannot retrieve manifests")
	}
	glog.Infof("Got %d backups to restore ", len(manifests))

	cfg, err := getEncConfig(req)
	if err != nil {
		return nil, errors.Wrapf(err, "unable to get encryption config")
	}

View on GitHub (pinned to 759e242be6)