¿Cuáles son los principales tipos de métricas —counters, gauges, histograms— y cuándo se usa cada uno?
Elegir mal el tipo de métrica es la razón por la que la mitad de los dashboards de producción mienten: un gauge que solo sube, o un counter al que le sacaste un promedio, producen sinsentidos en silencio.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cuáles son los principales tipos de métricas —counters, gauges, histograms— y cuándo se usa cada uno?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Tres formas de datos, una sola librería de instrumentación
Todo sistema de métricas moderno —Prometheus, y todo lo que habla su modelo de datos (Datadog, Grafana Mimir, Amazon Managed Prometheus, VictoriaMetrics)— está construido alrededor de un puñado de tipos de métrica, y elegir el correcto no es una decisión de estilo. Cada tipo trae garantías matemáticas distintas, y usar el tipo equivocado produce números que técnicamente se emiten pero que en la práctica no significan nada. Los tres sobre los que casi seguro te van a preguntar en cualquier entrevista de backend son counters, gauges e histograms.
#Counter — un número que solo sube
Un counter es un valor acumulativo que solo puede aumentar (o resetearse a cero cuando el proceso reinicia). Existe para responder "cuántas veces pasó X," nunca "cuántos X existen ahora mismo." http_requests_total, errors_total, bytes_sent_total son counters canónicos — nunca decrementás un counter, y si tu librería de instrumentación te lo permite, es una señal de que en realidad necesitabas un gauge.
Casi nunca consultás el valor crudo de un counter; consultás su tasa de cambio sobre una ventana de tiempo, porque el valor absoluto ("14.502.991 requests desde que el proceso arrancó hace tres semanas") casi nunca es interesante por sí solo. En PromQL eso es rate(http_requests_total[5m]) — requests por segundo, promediados sobre los últimos cinco minutos. El cálculo de tasa también es la razón por la que los counters están diseñados para sobrevivir reinicios de proceso con elegancia: un reset del counter (el valor cae porque el proceso reinició) es un caso conocido y manejado dentro de rate(), así que un deploy no produce una tasa negativa sin sentido.
#Gauge — un número que sube y baja
Un gauge es un valor instantáneo en el momento en que se lee: puede subir, bajar o quedarse plano, y representa "cuánto de X existe ahora mismo," no "cuántos X pasaron." queue_depth, active_connections, memory_usage_bytes, goroutines_running, temperature_celsius son gauges. No existe una "tasa de un gauge" con sentido como sí existe para un counter — lo leés directamente, podés promediarlo en el tiempo, y podés alertar sobre umbrales ("gauge por encima de 90 durante 5 minutos").
El bug de métricas más común en código real es usar un gauge para algo que debería ser un counter (o viceversa): un gauge de "errores totales" que algún path de código resetea a cero en cada deploy descarta el historial en silencio y hace imposibles los dashboards estilo rate(); un counter de "profundidad de la cola" que solo incrementa oculta el hecho de que la cola en realidad se está vaciando.
#Histogram — la forma de una distribución
Un histogram responde una pregunta que ni el counter ni el gauge pueden: no "cuántos" ni "cuál es el valor actual," sino "qué forma tiene la distribución de valores" — casi siempre usado para latencia, tamaño de request o tamaño de payload. Un histogram agrupa observaciones en rangos configurables (le="0.1", le="0.5", le="1", le="+Inf", es decir "menor o igual a") y, por debajo, expone cada bucket como un counter de cuántas observaciones cayeron en ese límite o por debajo, más un _sum (suma total de los valores observados) y un _count (cantidad total de observaciones).
La ganancia es que los histograms registrados en distintas instancias del mismo servicio se pueden sumar del lado del servidor y recién ahí calcular percentiles después de agregar — histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) te da un p99 de latencia a nivel de toda la flota. Esto es exactamente lo que un tipo summary (el otro tipo de latencia de Prometheus, que calcula percentiles del lado del cliente, por instancia) no puede hacer: los percentiles calculados del lado del cliente son matemáticamente imposibles de promediar correctamente entre instancias, porque el p99 de un promedio no es el promedio de los p99. Por eso los histograms, y no los summaries, son la recomendación por defecto en casi todos lados donde la latencia necesita agregarse a nivel de flota.
Los límites de los buckets importan muchísimo y suelen ser la parte donde la gente se equivoca: pocos buckets, o límites agrupados en el rango equivocado (por ejemplo, todos los buckets por debajo de 10ms para una API cuya latencia real vive entre 50ms y 2s) y histogram_quantile produce una estimación inexacta e interpolada linealmente sin importar cuánto tráfico tengas.
#Instrumentando los tres tipos
Acá va un ejemplo real y ejecutable usando prometheus_client de Python, la implementación de referencia de la API del lado del cliente de Prometheus — los mismos tres constructores existen, con la misma semántica, en la librería cliente de cada lenguaje (prometheus/client_golang, Micrometer para Java, prom-client para Node).
from prometheus_client import Counter, Gauge, Histogram, start_http_serverimport randomimport timehttp_requests_total = Counter("http_requests_total","Total de requests HTTP procesados",["method", "path", "status"],)inflight_requests = Gauge("http_requests_inflight","Cantidad de requests HTTP siendo procesados en este momento",)request_latency_seconds = Histogram("http_request_duration_seconds","Latencia de requests HTTP en segundos",["path"],buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5),)def handle(method: str, path: str) -> None:inflight_requests.inc()start = time.perf_counter()status = "200"try:time.sleep(random.uniform(0.01, 0.3)) # lógica de negocio simuladaexcept Exception:status = "500"raisefinally:elapsed = time.perf_counter() - startrequest_latency_seconds.labels(path=path).observe(elapsed)http_requests_total.labels(method=method, path=path, status=status).inc()inflight_requests.dec()if __name__ == "__main__":start_http_server(8000) # expone /metrics para que Prometheus haga scrapewhile True:handle("GET", "/checkout")
Fijate la forma de cada llamada: .inc() y nada más para el counter (nunca .dec()), .inc() y .dec() para el gauge (se mueve en ambos sentidos), y .observe(valor) para el histogram, que es el único de los tres que registra un valor medido real en lugar de simplemente "pasó una vez más."
#Eligiendo el tipo correcto, de un vistazo
| Tipo | Responde | ¿Puede bajar? | Consulta típica | Ejemplo |
|---|---|---|---|---|
| Counter | Cuántas veces pasó, en total | No (solo se resetea) | rate(x_total[5m]) | http_requests_total |
| Gauge | Cuál es el valor ahora mismo | Sí | avg_over_time(x[5m]), alertas por umbral | queue_depth, memory_usage_bytes |
| Histogram | Cuál es la distribución / percentil | N/A (los buckets son counters acumulativos) | histogram_quantile(0.99, ...) | http_request_duration_seconds |
🧠 Check your understanding
0/3 · 0/3 answered1. ¿Qué tipo de métrica es el correcto para 'cantidad de requests que este servicio está procesando en este momento'?
2. ¿Por qué se prefiere calcular histogram_quantile() del lado del servidor sobre buckets agregados, en vez de los percentiles de un summary del lado del cliente, para dashboards de latencia a nivel de flota?
3. ¿Cuál es el riesgo principal de agregar un valor crudo y sin límite, como user_id, como label de una métrica?