thanos-io/thanos · error
postings offsets for
Error message
postings offsets for %s
What it means
optimizePostingsFetchByDownloadedBytes resolves posting list offsets via the block index header reader (PostingsOffsets) for each postings group it wants to fetch. This error wraps a failure of that index lookup for the given label name, meaning posting offsets for that name could not be read from the block's index (corrupt/partial index, IO error, or unsupported index format).
Solutions
- Inspect the wrapped underlying error to distinguish decode vs IO failure.
- Verify the block in object storage is complete (all index files, matching index-header version) and re-upload if truncated.
- Delete local cached/extracted copies of the block so the gateway re-downloads it.
- Check that Prometheus index format version is supported by your Thanos build (upgrade if needed).
Example fix
// before: stale index-header left from interrupted upload // fix by re-syncing/removing the bad block thanos tools bucket verify --objstore.config-file=bucket.yml --id=<block-id> thanos tools bucket mark --marker=deletion-mark.json --id=<corrupt-block-id> --objstore.config-file=bucket.yml
Defensive patterns
Strategy: retry
Validate before calling
// Pre-check before issuing lazy postings query
if err := q.BlockMeta(); err != nil {
return fmt.Errorf("block unreadable, skip postings optimization: %w", err)
} Try / catch
rngs, err := r.block.indexHeaderReader.PostingsOffsets(pg.name, pg.addKeys...)
if err != nil {
if isTransient(err) { // object-store timeout / 5xx
return nil, false, backoff.Retry(ctx, func() error {
_, err = r.block.indexHeaderReader.PostingsOffsets(pg.name, pg.addKeys...)
return err
})
}
logger.Warn("falling back to non-optimized postings fetch", "err", err)
} Prevention
- Run periodic 'thanos tools bucket verify' to catch incomplete/corrupt blocks.
- Use atomic block uploads so index and index-header always match.
- Configure retry/backoff on object-store clients.
- Mark and ignore blocks with corrupt indexes instead of failing queries repeatedly.
When it happens
Trigger: A store-gateway lazy postings fetch (fetchLazyExpandedPostings path) where r.block.indexHeaderReader.PostingsOffsets(name, vals...) fails for a label name — typically because the block's index is truncated/corrupted, the index-header file is missing/stale relative to the index, or a decode error occurs reading the postings offset table.
Common situations: Blocks uploaded incompletely (missing or mismatched .idx versus index-header), object-store flakiness during index read, Thanos version reading blocks written by an incompatible Prometheus index format, corrupted local cache of block index files.
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/a4f8a3b246e5b740.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/store/lazy_postings.go:63
seriesMaxSize int64,
seriesMatchRatio float64,
postingGroupMaxKeySeriesRatio float64,
lazyExpandedPostingSizeBytes prometheus.Counter,
lazyExpandedPostingGroupsByReason *prometheus.CounterVec,
) ([]*postingGroup, bool, error) {
if len(postingGroups) <= 1 {
return postingGroups, false, nil
}
// Collect posting cardinality of each posting group.
for _, pg := range postingGroups {
// A posting group can have either add keys or remove keys but not both the same time.
vals := pg.addKeys
if len(pg.removeKeys) > 0 {
vals = pg.removeKeys
}
rngs, err := r.block.indexHeaderReader.PostingsOffsets(pg.name, vals...)
if err != nil {
return nil, false, errors.Wrapf(err, "postings offsets for %s", pg.name)
}
existentKeys := 0
for _, rng := range rngs {
if rng == indexheader.NotFoundRange {
continue
}
if rng.End <= rng.Start {
level.Error(r.logger).Log("msg", "invalid index range, fallback to non lazy posting optimization")
return postingGroups, false, nil
}
existentKeys++
// Each range starts from the #entries field which is 4 bytes.
// Need to subtract it when calculating number of postings.
// https://github.com/prometheus/prometheus/blob/v2.46.0/tsdb/docs/format/index.md.
pg.cardinality += (rng.End - rng.Start - 4) / 4
}
pg.existentKeys = existentKeysView on GitHub (pinned to 35b8b99117)