python-micrometer-cardinality-control

Prevent OutOfMemoryError from unbounded metric tags by implementing cardinality limits. Use when adding tags to Micrometer metrics, normalizing URIs or IDs to bounded categories, monitoring metric explosion, or avoiding high-cardinality fields like user IDs and request IDs. Critical for production microservices in GKE with cost-sensitive monitoring backends.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "python-micrometer-cardinality-control" with this command: npx skills add dawiddutoit/custom-claude/dawiddutoit-custom-claude-python-micrometer-cardinality-control

Micrometer Cardinality Control

Quick Start

For any metric with dynamic tags, apply this pattern:

// ❌ Dangerous: unbounded cardinality
.tag("supplier.id", supplierId) // 10,000+ unique values

// ✅ Safe: normalized to bounded categories
.tag("supplier.category", normalizeSupplier(supplier)) // 5-10 values

private String normalizeSupplier(Supplier s) {
    if (s.isTopTier()) return "tier1";
    if (s.isDirectSupplier()) return "direct";
    return "standard";
}

When to Use

  • Add tags to metrics (ensure bounded cardinality)
  • Normalize high-cardinality data (URIs, IDs)
  • Prevent OutOfMemoryError from metric explosion
  • Control monitoring costs
  • Debug metric growth

When NOT to use:

  • Truly unbounded data (use distributed tracing)
  • Per-request details (use structured logging)

Cardinality Rules

Safe Tags (Low Cardinality)

// ✅ HTTP method (4-10 values)
.tag("method", "GET")

// ✅ Status class (5 values)
.tag("status.class", "2xx")

// ✅ Environment (3-5 values)
.tag("env", "production")

Dangerous Tags (High Cardinality)

// ❌ User ID (millions) → Use tracing
.tag("user.id", userId)

// ❌ Request ID (infinite) → Use tracing
.tag("request.id", requestId)

// ❌ Full URI → Normalize!
.tag("uri", "/api/charges?supplier=123")

Rule of Thumb:

  • Safe per metric: < 1,000 combinations
  • Safe application-wide: < 10,000 active metrics

Normalization Patterns

URI Normalization

@Bean
public MeterFilter uriNormalization() {
    return MeterFilter.replaceTagValues("uri", uri -> {
        // Strip query parameters
        int queryIndex = uri.indexOf('?');
        if (queryIndex > 0) uri = uri.substring(0, queryIndex);

        // Replace IDs: /charges/123 → /charges/{id}
        return uri.replaceAll("/\\d+", "/{id}")
                  .replaceAll("/[a-f0-9-]{36}", "/{uuid}");
    });
}

Business Category Normalization

private String normalizeSupplier(String supplierId) {
    Supplier supplier = supplierRepository.findById(supplierId);
    
    if (supplier.getAnnualVolume() > 1_000_000) return "enterprise";
    if (supplier.getAnnualVolume() > 100_000) return "mid-market";
    if (supplier.isDirect()) return "direct";
    return "standard";
}

Cardinality Limits

@Bean
public MeterFilter cardinalityLimiter() {
    // Limit unique URIs to 100
    return MeterFilter.maximumAllowableTags(
        "http.server.requests",
        "uri",
        100,
        MeterFilter.deny()  // Deny new meters after limit
    );
}

Monitor Cardinality

@Component
public class CardinalityMonitor {

    private final MeterRegistry registry;

    @Scheduled(fixedRate = 60_000)
    public void monitorMetricCount() {
        int meterCount = registry.getMeters().size();

        if (meterCount > 8000) {
            log.error("CRITICAL: {} metrics (threshold 8000)", meterCount);
        }

        Gauge.builder("micrometer.meter.count", () -> meterCount)
             .register(registry);
    }
}

Alternatives to High-Cardinality Tags

Use Distributed Tracing

// Store user ID in span, NOT metrics
span.setAttribute("user.id", userId);

// Metrics use only bounded tags
Timer.builder("charge.processing")
    .tag("status", "processing")  // bounded
    .register(registry);

Use Structured Logging

// Add to MDC for logging, NOT metrics
MDC.put("user.id", userId);

Requirements

  • Spring Boot 2.1+
  • spring-boot-starter-actuator
  • Java 11+
  • For tracing: micrometer-tracing-bridge-otel

Anti-Patterns

// ❌ NEVER add unbounded tags
.tag("user.id", userId)
.tag("request.id", requestId)
.tag("timestamp", Instant.now())

// ✅ DO normalize to bounded categories
.tag("customer.tier", normalizeCustomer(customer))
.tag("request.type", normalizeRequest(request))

See Also

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

java-best-practices-code-review

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

textual-widget-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

uv-python-version-management

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

python-best-practices-async-context-manager

No summary provided by upstream source.

Repository SourceNeeds Review