Todos los artículos

Dejar que el worker de CreateRatio entre en reposo

Cómo una pequeña condición evita el sondeo innecesario sin perder actividad pendiente ni reanudar el seguimiento con ajustes obsoletos.

Leer How to be more ambitious: A tutorial for humans with models, de Felix Rieseberg, me inspiró a seguir mejorando CreateRatio. Un buen primer candidato era la memoria que seguía ocupando su worker en segundo plano cuando no había actividad que registrar.

A veces, mejorar el rendimiento no consiste en hacer una tarea más rápido. Consiste en reconocer cuándo no hay ninguna tarea que hacer.

El PR #2 de CreateRatio aplica esa idea al seguimiento de creación de una extensión de navegador. El cambio es pequeño: omitir el ciclo de actualización y envío de cada cinco segundos cuando una página está inactiva y no tiene mediciones pendientes. La consecuencia es más interesante: el sondeo vacío deja de mantener vivo al worker en segundo plano.

Ver el mecanismo

Así funciona el cambio: dejar de enviar solicitudes vacías sin perder la actividad que sí importa.

Antes: una cola vacía seguía generando trabajo

CreateRatio separa el rastreador que se ejecuta en una pestaña del worker de la extensión. El rastreador clasifica la actividad y envía mediciones. El worker realiza el trabajo del lado de la extensión.

Antes del cambio, cada rastreador ejecutaba esto cada cinco segundos:

const interval = setInterval(() => {
  refresh();
  flush();
}, 5000);

flush() prepara la actividad registrada para enviarla. Incluso cuando no hay nada que enviar, le pide al worker un snapshot de los ajustes actuales.

Es decir, «no hay nada que entregar» también produce un mensaje al worker. Una página inactiva puede seguir pidiendo una actualización que no necesita en ese momento.

La distinción importante es entre el script de contenido, que permanece en la pestaña, y el worker en segundo plano, cuyo proceso puede terminar cuando el navegador lo deja entrar en reposo. Reducir la actividad de uno no elimina el otro.

Después: el trabajo necesita un motivo

El PR conserva el temporizador, pero condiciona su trabajo:

const interval = setInterval(() => {
  if (
    outbox.length ||
    (pageActive &&
      document.visibilityState === 'visible' &&
      document.hasFocus())
  ) {
    refresh();
    flush();
  }
}, 5000);

El temporizador solo necesita actuar en dos casos:

  • Hay mediciones sin entregar. Intentar enviarlas, aunque la página esté inactiva.
  • La página está activa, visible y tiene el foco. Continuar con el ciclo normal de actualización y envío.

En cualquier otro caso, el temporizador sigue marcando el tiempo sin hacer ese trabajo. La pestaña permanece abierta y el navegador puede dejar que el worker descanse.

El silencio no debe perder mediciones

Dejar de trabajar es fácil. Hacerlo sin perder la actividad de nadie exige resolver tres situaciones cotidianas.

Enviar el último intervalo parcial

Imaginemos que alguien interactúa con la página y cambia de pestaña dos segundos después, antes del siguiente ciclo de cinco segundos. Esos dos segundos siguen contando.

Los manejadores existentes siguen guardando la actividad al salir de la página. La nueva condición no los sustituye, así que esos dos segundos no se pierden porque todavía no haya llegado el siguiente ciclo del temporizador.

Seguir reintentando un paquete pendiente

Una entrega puede fallar justo cuando la página pasa a estar inactiva. Si la condición comprobara únicamente la visibilidad, el paquete podría quedarse sin enviar.

Por eso importa la condición outbox.length. Mientras haya actividad sin entregar, el temporizador puede seguir intentando enviar la misma medición. Conserva los dos segundos originales sin sumar tiempo mientras estás fuera.

Cuando la cola queda vacía y la página sigue inactiva, el trabajo recurrente puede volver a detenerse.

Actualizar los ajustes al volver

Los ajustes pueden cambiar mientras estás fuera. CreateRatio los consulta de nuevo al volver, así que el seguimiento sigue desactivado si lo apagaste. Al activarlo otra vez, solo cuenta la actividad nueva, no el tiempo que pasaste fuera.

Qué cambió en la práctica

La comparación usó Chromium en Linux con veinte pestañas de prueba de X sin conexión y tres ejecuciones separadas por versión. Otra pestaña permanecía en primer plano mientras se observaba el worker durante 42 segundos. El depurador estaba desconectado para que no mantuviera despierto al worker.

Ver cómo desaparece el worker

Supervivencia registrada del worker: las tres ejecuciones originales conservan un worker durante toda la observación; las tres corregidas muestran por primera vez su ausencia unos 24 segundos después de desconectar el depurador

Azul es antes del cambio; verde azulado es después. Cada línea cuenta las ejecuciones separadas en las que el worker seguía despierto.

Antes del cambio, el worker seguía vivo al terminar la observación. Después, dejó de aparecer en las muestras hacia los 24 segundos en las tres ejecuciones. El tiempo varía según el navegador y lo que esté haciendo la extensión. Al volver a una pestaña compatible, un nuevo worker despertaba y recuperaba los ajustes guardados en cada caso.

Medición Antes Después
Mediana de PSS del proceso del worker en reposo 18,32 MiB 0; el proceso terminó
Mediana de memoria residente privada del proceso en ese punto 13,68 MiB 0; el proceso terminó
Mediana de PSS del proceso del worker despierto, con depurador 17,30 MiB 17,40 MiB
Ejecuciones con worker vivo en el punto de control en reposo 3/3 0/3
Ejecuciones con worker operativo al volver 3/3; ya despierto 3/3; despertó de nuevo

Lo importante no es que el worker ocupe menos mientras funciona: dejó de ejecutarse cuando no hacía falta.

Las cifras de memoria son dos formas de mirar el mismo proceso, no ahorros que se puedan sumar. PSS incluye una parte de la memoria compartida con otros procesos; la memoria residente privada cuenta las páginas que solo usa este proceso.

Estas mediciones siguieron el proceso del propio worker, no la memoria total del navegador.

Los scripts de contenido siguen costando memoria

La memoria JavaScript de las páginas apenas cambió. Una comparación separada con y sin los scripts de contenido estimó que añadían unos 324 KiB por pestaña, o 6,3 MiB en veinte pestañas de heap JS.

Esos scripts siguen cargados mientras el worker descansa. Cero memoria del proceso del worker no significa cero memoria de la extensión.

Seguir mejorando con IA sin perder lo ganado

Arreglarlo una vez no basta. También quiero que los agentes de IA con los que programo eviten reintroducir el problema con la siguiente funcionalidad o refactorización. Por eso convertí «mantén la extensión ligera» en reglas concretas para el proyecto:

  • Dejar que el reposo sea reposo. Un rastreador inactivo sin mediciones pendientes no debe seguir despertando al worker con solicitudes periódicas.
  • Limitar lo que permanece en memoria. Las colas y las cachés necesitan límites, sin descartar silenciosamente mediciones sin guardar. Los temporizadores, los listeners y los observadores necesitan un responsable y un ciclo de vida claros para que no se acumulen con cada visita.
  • Medir los cambios que afectan al consumo de recursos. Comparar el antes y el después en las mismas condiciones, incluyendo muchas pestañas o navegaciones repetidas cuando esos costes puedan cambiar.

Usar más memoria puede estar justificado por una funcionalidad útil, pero un aumento por encima de la tolerancia acordada necesita una explicación y mi aprobación.

También dejé AGENTS.md breve. Apunta a las normas de CONTRIBUTING.md y a los pasos de verificación de docs/testing.md, en lugar de repetirlos. Cada regla tiene un único lugar, así que los agentes no tienen que elegir entre instrucciones solapadas a medida que evoluciona el proyecto.

Las pruebas de regresión existentes se ejecutan en CI: comprueban el sondeo en pestañas inactivas, los reintentos de entrega y los ajustes al volver. No miden la RAM ni demuestran que el worker termine. Eso sigue necesitando mediciones en el navegador antes de que dé por terminado un cambio que afecte al consumo de recursos.

La idea principal

La mejora no consiste en hacer más eficiente una solicitud vacía. Consiste en no enviarla hasta que exista un motivo.

CreateRatio sigue guardando la actividad acumulada, reintentando si falla el envío y recuperando los ajustes actuales al volver. Simplemente no necesita mantener despierto al worker cuando no hay nada que hacer.