thanos-io/thanos · error
read postings table
Error message
read postings table
What it means
Wrapped error when index.ReadPostingsOffsetTable fails while loading the entire postings offset table for index FormatV1 blocks. The postings table section cannot be parsed, usually due to corruption or an offset pointing outside the valid region of the index file.
Solutions
- Re-upload or re-compact the affected block to repair the index file
- Delete and rebuild the index-header file if the offsets are stale
- Check the object storage for partial uploads and restore a complete block
- Verify the block's meta.json and file sizes match what storage reports
Defensive patterns
Strategy: validation
Validate before calling
// pre-check: ensure index file size covers toc.PostingsOffsetTable range before reading
Try / catch
if err != nil && strings.Contains(err.Error(), "read postings table") { delete index-header; retry with rebuilt header; else re-upload block } Prevention
- Use atomic block uploads (upload index last)
- Run block verification (thanos tools bucket verify) periodically
- Keep backups or object versioning for blocks
When it happens
Trigger: Reading a FormatV1 index (which loads the whole postings offset table into memory) when the table bytes at toc.PostingsOffsetTable are malformed, truncated, or fail CRC checks.
Common situations: Legacy index-version-1 blocks read by newer Thanos; partially uploaded/downloaded index files; block corrupted during compaction or object-storage transfer.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/c29f4e78310c7cdf.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/block/indexheader/binary_reader.go:719
var prevRng index.Range
if err := index.ReadPostingsOffsetTable(r.b, r.toc.PostingsOffsetTable, func(name, value []byte, postingsOffset uint64, _ int) error {
if lastName != nil {
prevRng.End = int64(postingsOffset - crc32.Size)
r.postingsV1[string(lastName)][string(lastValue)] = prevRng
}
if _, ok := r.postingsV1[string(name)]; !ok {
r.postingsV1[string(name)] = map[string]index.Range{}
r.postings[string(name)] = nil // Used to get a list of labelnames in places.
}
lastName = name
lastValue = value
prevRng = index.Range{Start: int64(postingsOffset + postingLengthFieldSize)}
return nil
}); err != nil {
return errors.Wrap(err, "read postings table")
}
if string(lastName) != "" {
prevRng.End = r.indexLastPostingEnd - crc32.Size
r.postingsV1[string(lastName)][string(lastValue)] = prevRng
}
} else {
lastTableOff := 0
valueCount := 0
// For the postings offset table we keep every label name but only every nth
// label value (plus the first and last one), to save memory.
if err := index.ReadPostingsOffsetTable(r.b, r.toc.PostingsOffsetTable, func(name, value []byte, postingsOffset uint64, labelOffset int) error {
if _, ok := r.postings[string(name)]; !ok {
// Not seen before label name.
r.postings[string(name)] = &postingValueOffsets{}
if lastName != nil {
// Always include last value for each label name, unless it was just added in previous iteration based
// on valueCount.View on GitHub (pinned to 35b8b99117)