thanos-io/thanos · error

non-unique name for metric family

Error message

non-unique name for metric family: %q

What it means

NewMetricFamilyMap requires metric family names to be unique; a duplicate name in the input slice means the gatherer produced two families for the same metric, which violates the Prometheus data model. The helper fails fast with the offending name in the message.

Solutions

  1. Find the collector emitting duplicate family names (the error message quotes the name) and make it aggregate all samples into a single MetricFamily.
  2. Ensure each metric name is registered by exactly one collector in the registry.
  3. Deduplicate the input slice by name before calling NewMetricFamilyMap if you control its construction.
  4. Pin/upgrade client_golang to a version where Gather guarantees unique families.

Example fix

// before
for _, mf := range raw {
    out = append(out, mf) // may contain duplicate names
}
// after
seen := map[string]bool{}
for _, mf := range raw {
    if !seen[mf.GetName()] {
        seen[mf.GetName()] = true
        out = append(out, mf)
    }
}
Defensive patterns

Strategy: validation

Validate before calling

names := map[string]int{}
for _, mf := range families {
    names[mf.GetName()]++
}
for n, c := range names {
    if c > 1 { return fmt.Errorf("collector emits duplicate family %q", n) }
}

Try / catch

mfm, err := util.NewMetricFamilyMap(metrics)
if err != nil {
    level.Error(logger).Log("msg", "duplicate metric family", "err", err)
    return nil, err
}

Prevention

When it happens

Trigger: A []*dto.MetricFamily passed to NewMetricFamilyMap (via softRemoveUserRegistry or BuildMetricFamiliesPerUser) where two entries share the same GetName() value — usually a collector that emits one family per sample instead of aggregating.

Common situations: Custom collectors that call NewMetricFamily or send per-sample families instead of grouping by metric name; client_golang bugs where the same metric is registered under multiple collectors; dynamically re-registered collectors racing with Gather().

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/dcb9ade801f4f1c0. Report an issue: GitHub.

Appendix: source

Thrown at internal/cortex/util/metrics_helper.go:80

// MetricFamilyMap is a map of metric names to their family (metrics with same name, but different labels)
// Keeping map of metric name to its family makes it easier to do searches later.
type MetricFamilyMap map[string]*dto.MetricFamily

// NewMetricFamilyMap sorts output from Gatherer.Gather method into a map.
// Gatherer.Gather specifies that there metric families are uniquely named, and we use that fact here.
// If they are not, this method returns error.
func NewMetricFamilyMap(metrics []*dto.MetricFamily) (MetricFamilyMap, error) {
	perMetricName := MetricFamilyMap{}

	for _, m := range metrics {
		name := m.GetName()
		// these errors should never happen when passing Gatherer.Gather() output.
		if name == "" {
			return nil, errors.New("empty name for metric family")
		}
		if perMetricName[name] != nil {
			return nil, fmt.Errorf("non-unique name for metric family: %q", name)
		}

		perMetricName[name] = m
	}

	return perMetricName, nil
}

func (mfm MetricFamilyMap) SumCounters(name string) float64 {
	return sum(mfm[name], counterValue)
}

func (mfm MetricFamilyMap) SumGauges(name string) float64 {
	return sum(mfm[name], gaugeValue)
}

func (mfm MetricFamilyMap) MaxGauges(name string) float64 {
	return max(mfm[name], gaugeValue)

View on GitHub (pinned to 35b8b99117)