Cuando una caché crece hacia el lado equivocado
En la carpeta de caché de este servicio hay cuatro archivos pequeños. El mayor tiene 35 bytes. Entre todos guardan tres números de contador, y se leen en cada petición de contador.
Existen para que el camino de conteo no tenga que hacerle a la base de datos cuatro preguntas que casi siempre responde con «no». ¿Este contador excluye las visitas de su propio dueño? ¿Cuenta clics salientes? ¿Sigue rutas por el sitio? ¿Se recarga solo? Para más del 99 % de los contadores todas las respuestas son no, y un archivo con el puñado de números donde son sí sale más barato de consultar que cuatro consultas con índice.
Sale más barato. También es la clase de barato que se da la vuelta.
Qué se midió
La misma pregunta — «¿está este contador en la lista?» — hecha de dos maneras, con seis tamaños de lista. La vía del archivo: leerlo, descodificar el JSON, buscar el número. La vía de la base: una consulta preparada contra una columna con índice. Dos mil repeticiones cada una, cinco rondas, mediana tomada.
Al tamaño de hoy el archivo gana por ocho veces: 0,0202 milisegundos frente a 0,1630. Con mil números van empatados. Con diez mil el archivo cuesta doce veces más, y con cien mil cuesta 135 veces más, porque para entonces son 578 kilobytes que hay que leer y analizar en cada visita.
La línea de la base de datos no se mueve. 0,16 milisegundos con dos filas y 0,16 milisegundos con cien mil: para eso está un índice, y es fácil olvidar cuánto trabajo se esconde detrás de lo aburrida que se ve esa línea.
La parte incómoda
El cruce está entre 100 y 1.000 números. Este servicio tiene 2.218 contadores que tuvieron actividad en los últimos treinta días.
Así que si la exclusión fuera un éxito — si todo el que tiene un contador activara «no cuentes mis propias visitas» — el archivo tendría 2.218 números, pesaría 11 kilobytes y costaría 0,43 milisegundos por visita en vez de 0,02. Con las 18.423 visitas de ayer, eso son ocho segundos de trabajo al día para evitar una consulta que habría tardado tres.
La optimización es más rápida cuanto menos se usa la función. No se degrada poco a poco con la carga, como una consulta lenta. Se degrada con la adopción, que es el único eje que nadie vigila, porque que la adopción suba se supone que es la buena noticia.
Por qué sigue ahí
Porque hoy es correcto, y «correcto hoy» puede ser la razón de algo, siempre que alguien haya escrito cuándo deja de serlo.
Tres números en tres archivos. Ocho veces más barato que la alternativa, en una máquina donde el camino de conteo es lo único que tiene que ser rápido. Reemplazarlo ahora por la consulta a la que gana sería un sistema peor justificado por una hipótesis.
Lo que falta es el hilo de aviso, no la reescritura. El archivo se escribe desde la base de todos modos, por el código que cambia un ajuste — ese es el sitio natural para notar que la lista ha pasado de unos cientos de entradas y decirlo. Una caché con un techo escrito es una decisión. Una sin él es una apuesta que nadie ha aceptado.
La forma general
Esto no es un argumento contra guardar caché en un archivo. Es un argumento a favor de saber hacia dónde crece tu caché.
La mayoría de las cachés mejoran con la carga: más peticiones, más aciertos, mejor proporción. Esta es de la otra clase. Su coste por petición depende de cuántos datos guarda, y lo que guarda crece con aquello que el servicio intenta fomentar. Cada lectura paga por cada entrada, incluidas las 2.215 que no tienen nada que ver con el visitante que se está contando ahora mismo.
La pregunta que hay que hacerle a cualquier tabla de búsqueda guardada en memoria o en un archivo no es «qué rápida es» sino «qué la hace crecer, y qué pasa cuando eso va bien». Si la respuesta es «se vuelve más lenta», el límite de tamaño va en el código, junto a lo que escribe el archivo, el día en que se construye.