Agent skill

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.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/python-micrometer-cardinality-control

SKILL.md

Micrometer Cardinality Control

Quick Start

For any metric with dynamic tags, apply this pattern:

java
// ❌ 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)

java
// ✅ 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)

java
// ❌ 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

java
@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

java
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

java
@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

java
@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

java
// 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

java
// 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

java
// ❌ 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

Didn't find tool you were looking for?

Be as detailed as possible for better results