python-micrometer-cardinality-control

📁 dawiddutoit/custom-claude 📅 Jan 23, 2026
4
总安装量
4
周安装量
#52041
全站排名
安装命令
npx skills add https://github.com/dawiddutoit/custom-claude --skill python-micrometer-cardinality-control

Agent 安装分布

opencode 4
gemini-cli 4
claude-code 4
codex 4
mcpjam 3
kilo 3

Skill 文档

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