wandres.dev
LA PERCEPCIÓN DEL TIEMPO · Qué siente el usuario

El catálogo de latencias

Las cifras de referencia de cada operación, desde un acceso a la caché L1 hasta un viaje transatlántico, y qué cabe dentro de cada presupuesto de tiempo.

⏱ 16 min

Para juzgar si una operación es cara hace falta una escala interiorizada. Sin ella, todas las cifras parecen igual de grandes y no se distingue lo que cuesta microsegundos de lo que cuesta cientos de milisegundos. Esta lección construye ese catálogo, desde las operaciones que caben en un fotograma hasta las que rompen el umbral de los diez segundos.

🎯 Al terminar esta lección sabrás
  • Situar en una escala común las latencias de memoria, disco, red y renderizado.
  • Estimar de cabeza cuántas operaciones de cada tipo caben en un presupuesto dado.
  • Reconocer las tres discontinuidades donde la escala salta un orden de magnitud.
  • Aplicar la escala para descartar hipótesis de diagnóstico antes de medir.

La escala completa

Las cifras siguientes son órdenes de magnitud, no valores exactos: varían con el hardware, la implementación y las condiciones. Lo que importa es la distancia relativa entre filas, que es estable.

Operación Coste típico En unidades de fotograma a 60 Hz
Acceso a caché L1 ~1 ns 0,00000006
Acceso a memoria principal ~100 ns 0,000006
Una llamada a función JavaScript sencilla decenas de ns despreciable
Lectura de localStorage de una clave pequeña decenas de µs despreciable pero síncrona
Lectura de 1 MB secuencial de memoria ~100 µs 0,006
Lectura de 1 MB de SSD NVMe ~200-500 µs ~0,02
Decodificar un JPEG de 1 megapíxel 5-15 ms ~0,5
Layout de un DOM de 2.000 nodos 5-20 ms ~1
Parsear y compilar 100 KB de JavaScript en gama media 50-150 ms 3-9
RTT dentro del mismo centro de datos ~0,5 ms 0,03
RTT en una red local 1-5 ms 0,1-0,3
RTT a una CDN cercana por fibra 10-30 ms 0,6-1,8
RTT en 4G en buenas condiciones 40-80 ms 2,5-5
RTT móvil de referencia de Lighthouse 150 ms 9
RTT transatlántico por fibra 70-100 ms 4-6
RTT en 3G o red móvil congestionada 200-400 ms 12-24
Apertura completa de conexión HTTPS nueva, con DNS 3 a 4 RTT según red

La lectura útil de esta tabla no es memorizar valores sino grabar las distancias. Entre la memoria y el disco hay tres órdenes de magnitud. Entre el disco y la red local hay uno. Entre la red local y la red móvil hay dos. Y entre cualquier cosa que ocurra en el dispositivo y cualquier cosa que exija un viaje de ida y vuelta hay un abismo que ninguna optimización de código cruza.

Las tres discontinuidades

En la escala hay tres saltos donde el coste se multiplica y donde, por tanto, conviene poner las fronteras de diseño.

Primera discontinuidad: del dispositivo a la red. Cualquier operación que no salga del dispositivo cuesta como mucho unos pocos milisegundos. En cuanto tiene que salir, el suelo se convierte en un RTT, y en móvil eso son decenas o cientos de milisegundos. Consecuencia de diseño: una interfaz que hace una petición de red por pulsación de tecla es un problema estructural, no de afinado. Se resuelve con debounce, con datos locales o con predicción, no acelerando el servidor.

Segunda discontinuidad: de una conexión abierta a una conexión nueva. Reutilizar una conexión existente cuesta un RTT. Abrir una nueva a un origen distinto cuesta DNS más TCP más TLS, que son tres o cuatro RTT. En la red de referencia, esa diferencia es de unos 450 ms. Consecuencia: el número de orígenes distintos que toca tu página es una métrica de arquitectura, no un detalle.

Tercera discontinuidad: de la caché a la red. Un recurso servido desde la caché HTTP del navegador cuesta el tiempo de leerlo del disco y decodificarlo, es decir, del orden de un milisegundo para archivos pequeños. El mismo recurso pedido por red cuesta al menos un RTT. Consecuencia: la política de caché no es una optimización marginal, es la diferencia entre dos regímenes de coste separados por dos órdenes de magnitud.

💡
Un cálculo de servilleta que acierta sorprendentemente

Para estimar el tiempo de carga de una página sin medir nada: cuenta los orígenes distintos que toca, multiplica por 3 y suma el número de recursos en la ruta crítica. Eso te da un número aproximado de viajes de ida y vuelta. Multiplícalo por el RTT de tus usuarios y tendrás el suelo del tiempo de carga, sin contar transferencia ni CPU. Cuatro orígenes y seis recursos críticos con 150 ms de RTT dan 4*3 + 6 = 18 viajes, es decir, 2,7 segundos de suelo. Si tu LCP medido está cerca de ese número, tu problema es de viajes; si está muy por encima, hay CPU o bloqueo de por medio.

Qué cabe en cada presupuesto

Le damos la vuelta a la tabla: dado un presupuesto de tiempo, qué operaciones caben dentro.

En 10 ms, el presupuesto de un fotograma. Cabe recalcular estilos y hacer layout de un árbol moderado. Cabe una animación que solo toque transform y opacity, porque esas se resuelven en el compositor. Cabe recorrer un array de unos pocos miles de elementos. No cabe parsear un JSON grande, ni decodificar una imagen grande de forma síncrona, ni tocar el DOM en un bucle que fuerce layout en cada iteración.

En 100 ms, el presupuesto de la respuesta inmediata. Cabe todo lo anterior varias veces. Cabe ejecutar un manejador de evento con lógica de verdad. Cabe una consulta a IndexedDB. No cabe una petición de red, ni siquiera a un servidor cercano, si el usuario está en móvil.

En 1 segundo, el presupuesto de la continuidad. Caben unos pocos viajes de ida y vuelta en red móvil, o bastantes en fibra. Cabe cargar y ejecutar un módulo de JavaScript de tamaño moderado. Cabe una transición de vista completa en una aplicación de una sola página con los datos ya en memoria. No cabe una carga en frío que abra conexiones a varios orígenes.

En 2,5 segundos, el objetivo del LCP. En la red de referencia móvil de 150 ms de RTT, caben unos dieciséis viajes de ida y vuelta si no hubiese nada más. Descontando transferencia, parseo y renderizado, el presupuesto realista está en torno a ocho o diez viajes secuenciales. Ese es el número que hay que respetar cuando diseñas la ruta crítica.

Usar la escala para descartar hipótesis

La utilidad práctica del catálogo es que permite eliminar sospechosos sin medir. Algunos ejemplos de razonamiento que ahorra horas.

“El INP es de 500 ms y sospecho de la consulta a localStorage que hace el manejador.” Descartado. Una lectura de localStorage de una clave pequeña cuesta del orden de decenas de microsegundos. Para llegar a 500 ms harían falta decenas de miles de lecturas. Si el manejador las hace, el problema es el bucle, no localStorage.

“El LCP es de 4 segundos y creo que es porque la imagen pesa 300 KB.” Comprueba antes. A 1,6 Mbps, 300 KB son unos 1,5 segundos de transferencia. Explican parte, no el total. Los otros 2,5 segundos están en otro sitio y probablemente en esperas, no en bytes.

“He cambiado de gzip a Brotli y el TTFB no ha bajado.” Correcto y esperable. La compresión reduce bytes transferidos, no el tiempo hasta el primer byte. El TTFB lo dominan los viajes de ida y vuelta y el proceso del servidor. Lo que debería moverse es el tiempo entre el primer byte y el final de la transferencia.

“En mi portátil el sitio tarda 800 ms y los usuarios se quejan.” Espera lo esperable: tu portátil tiene la caché caliente, conexión de baja latencia y una CPU en el percentil alto. La diferencia entre 800 ms y los cuatro segundos de un móvil de gama media en 4G no es un misterio, es exactamente lo que predicen las tres discontinuidades.

La latencia que nadie mide es la del propio dispositivo bajo presión térmica

Todas las cifras de CPU de la tabla asumen un dispositivo en reposo térmico. Un teléfono que lleva diez minutos con la pantalla encendida, cargando y con el navegador trabajando, reduce su frecuencia de reloj de forma agresiva para no quemarse, y el mismo trabajo que costaba 80 ms pasa a costar 200 o 300. Esto no aparece en ninguna herramienta de laboratorio, porque las herramientas miden en el primer segundo de una sesión limpia. Sí aparece en los datos de campo, y explica una anomalía que desconcierta a mucha gente: por qué el INP de tus usuarios empeora conforme avanza la sesión, aunque la página no haya cambiado. Si ves esa forma en los datos, segmentados por tiempo desde la carga, no busques una fuga de memoria antes de descartar el estrangulamiento térmico. Y si tu aplicación mantiene trabajo periódico en segundo plano, ese trabajo no solo cuesta lo que cuesta: empeora todo lo demás durante el resto de la sesión.