thanos-io/thanos · warning · ErrorSyncMetaNotFound
meta.json not found
Error message
meta.json not found
What it means
ErrorSyncMetaNotFound is returned by BaseFetcher.loadMeta when meta.json for a block disappears from object storage between the bucket Exists check and the read. The loadMeta code wraps the underlying bucket not-found error with this sentinel so callers can match it with errors.Is/As. It signals a benign race: the block was deleted concurrently (typically by a compactor/GC) while the syncer was fetching its metadata.
Solutions
- Treat it as benign: noMetas is counted and the block is skipped; retry the sync later — the block is gone from the bucket view.
- Increase block deletion delay (--store.grpc.series-sample-limit irrelevant; use --objstore.* / compactor deletion-delay) so concurrent syncers finish reading before GC deletes blocks.
- Check whether another process (compactor, manual cleanup, S3 lifecycle policy) is deleting blocks; pause or reconfigure it.
- If this occurs persistently for blocks that still exist, verify bucket credentials/permissions and that IsObjNotFoundErr is configured correctly for your object store.
Example fix
// before (reactive: block vanishes mid-sync) // compactor deletion-delay too small // after: give concurrent readers time // thanos compact --delete-delay=48h // (ensures blocks removed by compaction stay readable for in-flight syncs)
Defensive patterns
Strategy: try-catch
Validate before calling
exists, err := bkt.Exists(ctx, metaPath)
if err == nil && !exists {
// skip block before calling the fetcher; it's already gone
} Type guard
func isMetaNotFound(err error) bool {
return errors.Is(err, block.ErrorSyncMetaNotFound)
} Try / catch
m, err := metaFetcher.Fetch(ctx, metas)
switch {
case err == nil:
// use metas
case errors.Is(err, block.ErrorSyncMetaNotFound):
// benign: block deleted concurrently; log at debug, retry next sync
case errors.As(err, &e): // inspect wrapped cause
// unexpected: handle genuine storage error
} Prevention
- Keep compactor deletion-delay larger than the longest expected sync duration
- Avoid manual/lifecycle-based deletion while syncers are running
- Monitor resp.noMetas rate; occasional occurrences are expected, spikes indicate aggressive GC
- Use bucket indexes or consistent object stores to reduce stale Exists results
When it happens
Trigger: loadMeta: f.bkt.Exists returns true for a block's meta.json, then ReaderWithExpectedErrs(...).Get(ctx, metaFile) fails with an object-not-found error that satisfies f.bkt.IsObjNotFoundErr; the fetcher wraps it in ErrorSyncMetaNotFound. In fetchBlocks this causes the block to be counted in resp.noMetas and skipped.
Common situations: Compactor/GC deletes blocks concurrently with a Store/Querier syncing blocks; two Thanos instances racing where one removes blocks (deletion delay too short); manual block deletion or lifecycle rules in S3/GCS removing objects mid-sync; stale bucket view from eventually-consistent object stores.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/f7bdc98d4fab9925.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/block/fetcher.go:431
b, err := NewBaseFetcherWithMetrics(logger, concurrency, bkt, blockIDsFetcher, dir, baseFetcherMetrics)
if err != nil {
return nil, err
}
return b.NewMetaFetcherWithMetrics(fetcherMetrics, filters), nil
}
// NewMetaFetcher transforms BaseFetcher into actually usable *MetaFetcher.
func (f *BaseFetcher) NewMetaFetcher(reg prometheus.Registerer, filters []MetadataFilter, logTags ...any) *MetaFetcher {
return f.NewMetaFetcherWithMetrics(NewFetcherMetrics(reg, nil, nil), filters, logTags...)
}
// NewMetaFetcherWithMetrics transforms BaseFetcher into actually usable *MetaFetcher.
func (f *BaseFetcher) NewMetaFetcherWithMetrics(fetcherMetrics *FetcherMetrics, filters []MetadataFilter, logTags ...any) *MetaFetcher {
return &MetaFetcher{metrics: fetcherMetrics, wrapped: f, filters: filters, logger: log.With(f.logger, logTags...)}
}
var (
ErrorSyncMetaNotFound = errors.New("meta.json not found")
ErrorSyncMetaCorrupted = errors.New("meta.json corrupted")
)
func (f *BaseFetcher) metaUpdated(id ulid.ULID, modified time.Time) bool {
if f.modifiedTimestamps[id].IsZero() {
return false
}
return !f.modifiedTimestamps[id].Equal(modified)
}
func (f *BaseFetcher) bustCacheForID(id ulid.ULID) {
f.cacheBusts.Inc()
f.cached.Delete(id)
if err := os.RemoveAll(filepath.Join(f.cacheDir, id.String())); err != nil {
level.Warn(f.logger).Log("msg", "failed to remove cached meta.json dir", "dir", filepath.Join(f.cacheDir, id.String()), "err", err)
}
}View on GitHub (pinned to 35b8b99117)