thanos-io/thanos · error
no tsdb statistics
Error message
no tsdb statistics
What it means
The gRPC streaming handler Send() in pkg/status/status.go:78 processes a TSDBStatisticsResponse and requires a non-nil Statistics payload. If a response arrives with neither a warning string nor a Statistics map, the server aborts the stream with 'no tsdb statistics'. This guards the aggregation logic that merges per-tenant TSDB stats from receiving an empty protobuf message.
Solutions
- Upgrade all Thanos components so peers send a populated TSDBStatisticsResponse (or a Warning instead of an empty message)
- Check the responding component's implementation/mock: it must set either Warning or Statistics before calling Send
- Capture gRPC client/server logs to identify which peer produced the empty response
- If the error is transient for one unreachable store, treat it as a warning for that store and continue streaming from others
Example fix
// before (peer handler)
res := &statuspb.TSDBStatisticsResponse{}
if err := stream.Send(res); err != nil { return err }
// after
res := &statuspb.TSDBStatisticsResponse{Statistics: stats}
if len(stats.Statistics) == 0 {
res = &statuspb.TSDBStatisticsResponse{Warning: "no statistics available"}
}
if err := stream.Send(res); err != nil { return err } Defensive patterns
Strategy: type-guard
Validate before calling
if res.GetWarning() == "" && res.GetStatistics() == nil {
return fmt.Errorf("peer %s returned empty TSDB statistics response", peerAddr)
} Type guard
func hasStatistics(res *statuspb.TSDBStatisticsResponse) bool {
return res.GetWarning() != "" || res.GetStatistics() != nil
} Try / catch
if err := srv.Send(res); err != nil {
if err.Error() == "no tsdb statistics" {
logger.Warn("skipping peer with empty statistics response", "peer", peerAddr)
return nil
}
return err
} Prevention
- Always populate either Warning or Statistics before sending a TSDBStatisticsResponse
- Run integration tests across all supported component versions
- Add a peer-side guard that rejects building empty responses
- Log peer identity on stream errors to ease triage
When it happens
Trigger: Calling Status.TSDBStatistics when the remote side returns a TSDBStatisticsResponse that has an empty Warning field and a nil Statistics field — i.e. a zero-value or partially populated response message from the peer.
Common situations: Version skew between Thanos components where an older peer does not populate Statistics; a gRPC server implementation (mock, proxy, or third-party) that returns an empty response; proto serialization where the Statistics oneof/field was dropped by an intermediary.
Understand the failure class
Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.
Related errors
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/8ba09d6c5574d1e8.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/status/status.go:88
statuspb.Status_TSDBStatisticsServer
ctx context.Context
mtx sync.Mutex
warnings annotations.Annotations
tsdbStatistics map[string][]*statuspb.TSDBStatisticsEntry
}
func (srv *tsdbStatisticsServer) Send(res *statuspb.TSDBStatisticsResponse) error {
if res.GetWarning() != "" {
srv.mtx.Lock()
defer srv.mtx.Unlock()
srv.warnings.Add(errors.New(res.GetWarning()))
return nil
}
stats := res.GetStatistics()
if stats == nil {
return errors.New("no tsdb statistics")
}
srv.mtx.Lock()
defer srv.mtx.Unlock()
for tenant, tenantStats := range stats.Statistics {
if _, found := srv.tsdbStatistics[tenant]; !found {
srv.tsdbStatistics[tenant] = nil
}
srv.tsdbStatistics[tenant] = append(srv.tsdbStatistics[tenant], tenantStats)
}
return nil
}
func (srv *tsdbStatisticsServer) Context() context.Context {
return srv.ctx
}
View on GitHub (pinned to 35b8b99117)