---
description: Guidelines for creating OpenTelemetry metrics to avoid cardinality issues
globs:
  - "**/*.ts"
---

# OpenTelemetry Metrics Guidelines

When creating or editing OTEL metrics (counters, histograms, gauges), always ensure metric attributes have **low cardinality**.

## What is Cardinality?

Cardinality refers to the number of unique values an attribute can have. Each unique combination of attribute values creates a new time series, which consumes memory and storage in your metrics backend.

## Rules

### DO use low-cardinality attributes:
- **Enums**: `environment_type` (PRODUCTION, STAGING, DEVELOPMENT, PREVIEW)
- **Booleans**: `hasFailures`, `streaming`, `success`
- **Bounded error codes**: A finite, controlled set of error types
- **Shard IDs**: When sharding is bounded (e.g., 0-15)

### DO NOT use high-cardinality attributes:
- **UUIDs/IDs**: `envId`, `userId`, `runId`, `projectId`, `organizationId`
- **Unbounded integers**: `itemCount`, `batchSize`, `retryCount`
- **Timestamps**: `createdAt`, `startTime`
- **Free-form strings**: `errorMessage`, `taskName`, `queueName`

## Example

```typescript
// BAD - High cardinality
this.counter.add(1, {
  envId: options.environmentId,        // UUID - unbounded
  itemCount: options.runCount,         // Integer - unbounded
});

// GOOD - Low cardinality
this.counter.add(1, {
  environment_type: options.environmentType,  // Enum - 4 values
  streaming: true,                            // Boolean - 2 values
});
```

## Prometheus Metric Naming

When metrics are exported via OTLP to Prometheus, the exporter automatically adds unit suffixes to metric names:

| OTel Metric Name | Unit | Prometheus Name |
|------------------|------|-----------------|
| `my_duration_ms` | `ms` | `my_duration_ms_milliseconds` |
| `my_counter` | counter | `my_counter_total` |
| `items_inserted` | counter | `items_inserted_inserts_total` |
| `batch_size` | histogram | `batch_size_items_bucket` |

Keep this in mind when writing Grafana dashboards or Prometheus queries—the metric names in Prometheus will differ from the names defined in code.

## Reference

See the schedule engine (`internal-packages/schedule-engine/src/engine/index.ts`) for a good example of low-cardinality metric attributes.

High cardinality metrics can cause:
- Memory bloat in metrics backends (Axiom, Prometheus, etc.)
- Slow queries and dashboard timeouts
- Increased costs (many backends charge per time series)
- Potential data loss or crashes at scale
