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

  1. Re-upload or re-compact the affected block to repair the index file
  2. Delete and rebuild the index-header file if the offsets are stale
  3. Check the object storage for partial uploads and restore a complete block
  4. 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

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)