thanos-io/thanos · error

incomplete view

Error message

incomplete view

What it means

Returned by the block metadata sync fetcher when some block metadata could not be fetched from the object store, so the returned metadata list is an 'incomplete view' of the blocks in the bucket. It wraps the aggregated per-block errors (resp.metaErrs), while any successfully fetched metas are still returned alongside the error.

Solutions

  1. Inspect the wrapped metaErrs (errors.Unwrap / errors.Is on the inner errors) to find which block IDs failed and why
  2. Retry the sync; transient bucket/network errors usually resolve on a subsequent attempt
  3. Check bucket permissions and availability; ensure meta.json files exist and are fully written
  4. Reduce metadata concurrency if the bucket is rate-limited, or increase timeouts
  5. If blocks were deleted mid-sync, ignore benign NotFound errors for blocks removed by the compactor
Defensive patterns

Strategy: retry

Try / catch

metas, partial, err := fetcher.FetchMetas(ctx)
if err != nil {
    var agg interface{ Unwrap() []error }
    if errors.As(err, &agg) {
        for _, e := range agg.Unwrap() { log.Printf("block meta failed: %v", e) }
    }
    // decide: fail hard vs continue with partial + partial list
}

Prevention

When it happens

Trigger: Calling FetchMetas (concurrent metadata sync) when one or more per-block metadata reads fail (Get of meta.json fails, corrupt/unparsable meta JSON, bucket errors, context cancellation), and syncMetadata's concurrency is > 1 so partial results are tolerated.

Common situations: Bucket throttling or transient network errors during a query/store startup; blocks deleted mid-sync by compactor (meta.json 404s); corrupted or partially written meta.json; very large buckets where slow object stores time out.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/f9c0a92d3aca1828. Report an issue: GitHub.

Appendix: source

Thrown at pkg/block/fetcher.go:694

	// Copy as same response might be reused by different goroutines.
	metas := make(map[ulid.ULID]*metadata.Meta, len(resp.metas))
	maps.Copy(metas, resp.metas)

	metrics.Synced.WithLabelValues(FailedMeta).Set(float64(len(resp.metaErrs)))
	metrics.Synced.WithLabelValues(NoMeta).Set(resp.noMetas)
	metrics.Synced.WithLabelValues(CorruptedMeta).Set(resp.corruptedMetas)

	for _, filter := range filters {
		// NOTE: filter can update synced metric accordingly to the reason of the exclude.
		if err := filter.Filter(ctx, metas, metrics.Synced, metrics.Modified); err != nil {
			return nil, nil, errors.Wrap(err, "filter metas")
		}
	}

	metrics.Synced.WithLabelValues(LoadedMeta).Set(float64(len(metas)))

	if len(resp.metaErrs) > 0 {
		return metas, resp.partial, errors.Wrap(resp.metaErrs.Err(), "incomplete view")
	}

	level.Info(f.logger).Log("msg", "successfully synchronized block metadata", "duration", time.Since(start).String(), "duration_ms", time.Since(start).Milliseconds(), "cached", f.countCached(), "returned", len(metas), "partial", len(resp.partial))
	return metas, resp.partial, nil
}

func (f *BaseFetcher) countCached() int {
	f.mtx.Lock()
	defer f.mtx.Unlock()
	var i int
	f.cached.Range(func(_, _ any) bool {
		i++
		return true
	})
	return i
}

type MetaFetcher struct {

View on GitHub (pinned to 35b8b99117)