Quando uma cache cresce para o lado errado
Na pasta de cache deste serviço há quatro ficheiros pequenos. O maior tem 35 bytes. Juntos guardam três números de contador, e são lidos em cada pedido de contador.
Existem para que o caminho de contagem não tenha de fazer à base de dados quatro perguntas a que quase sempre responde «não». Este contador exclui as visitas do próprio dono? Conta cliques de saída? Segue percursos pelo sítio? Recarrega-se sozinho? Para mais de 99 % dos contadores todas as respostas são não, e um ficheiro com o punhado de números onde são sim sai mais barato de consultar do que quatro consultas com índice.
Sai mais barato. É também o género de barato que se vira ao contrário.
O que foi medido
A mesma pergunta — «este contador está na lista?» — feita de duas maneiras, com seis tamanhos de lista. A via do ficheiro: lê-lo, descodificar o JSON, procurar o número. A via da base: uma consulta preparada contra uma coluna com índice. Duas mil repetições cada, cinco voltas, mediana tomada.
No tamanho de hoje o ficheiro ganha por oito vezes: 0,0202 milissegundos contra 0,1630. Com mil números ficam empatados. Com dez mil o ficheiro custa doze vezes mais, e com cem mil custa 135 vezes mais, porque nessa altura são 578 kilobytes a ler e a analisar em cada visita.
A linha da base de dados não se mexe. 0,16 milissegundos com duas linhas e 0,16 milissegundos com cem mil: é para isso que serve um índice, e esquece-se facilmente quanto trabalho se esconde por trás de uma linha tão aborrecida.
A parte incómoda
O cruzamento está entre 100 e 1.000 números. Este serviço tem 2.218 contadores que tiveram tráfego nos últimos trinta dias.
Portanto, se a exclusão fosse um êxito — se toda a gente com um contador ligasse «não contes as minhas próprias visitas» — o ficheiro teria 2.218 números, pesaria 11 kilobytes e custaria 0,43 milissegundos por visita em vez de 0,02. Nas 18.423 visitas de ontem isso dá oito segundos de trabalho por dia para evitar uma consulta que teria demorado três.
A optimização é mais rápida quanto menos a funcionalidade for usada. Não se degrada aos poucos com a carga, como uma consulta lenta. Degrada-se com a adopção, que é o único eixo que ninguém vigia, porque a adopção a subir é suposto ser a boa notícia.
Porque é que continua lá
Porque hoje está certo, e «certo hoje» pode ser a razão de alguma coisa, desde que alguém tenha escrito quando deixa de ser verdade.
Três números em três ficheiros. Oito vezes mais barato do que a alternativa, numa máquina onde o caminho de contagem é a única coisa que tem de ser rápida. Substituí-lo agora pela consulta que vence daria um sistema pior justificado por uma hipótese.
O que falta é o fio de aviso, não a reescrita. O ficheiro é escrito a partir da base de qualquer maneira, pelo código que muda uma definição — é o sítio natural para reparar que a lista passou umas centenas de entradas, e dizê-lo. Uma cache com um tecto escrito é uma decisão. Uma sem ele é uma aposta que ninguém aceitou.
A forma geral
Isto não é um argumento contra guardar cache num ficheiro. É um argumento a favor de saber para que lado a cache cresce.
A maioria das caches melhora com a carga: mais pedidos, mais acertos, melhor proporção. Esta é da outra espécie. O seu custo por pedido depende da quantidade que guarda, e o que guarda cresce com aquilo que o serviço quer incentivar. Cada leitura paga por cada entrada, incluindo as 2.215 que nada têm a ver com o visitante que está a ser contado neste momento.
A pergunta a fazer a qualquer tabela de consulta guardada em memória ou num ficheiro não é «quão rápida é» mas «o que a faz crescer, e o que acontece quando isso corre bem». Se a resposta for «fica mais lenta», o limite de tamanho pertence ao código, ao lado daquilo que escreve o ficheiro, no dia em que se constrói.