Quando una cache cresce dalla parte sbagliata
Nella cartella di cache di questo servizio ci sono quattro file piccoli. Il più grande è di 35 byte. Insieme contengono tre numeri di contatore, e vengono letti a ogni singola richiesta di contatore.
Esistono perché il percorso di conteggio non debba porre al database quattro domande a cui quasi sempre risponde «no». Questo contatore esclude le visite del proprietario stesso? Conta i clic in uscita? Segue i percorsi nel sito? Si ricarica da solo? Per oltre il 99 % dei contatori ogni risposta è no, e un file con la manciata di numeri per cui è sì costa meno da consultare di quattro interrogazioni con indice.
Costa meno. È anche il tipo di convenienza che si rovescia.
Che cosa è stato misurato
La stessa domanda — «questo contatore è nell'elenco?» — posta in due modi, con sei lunghezze di elenco. La via del file: leggerlo, decodificare il JSON, cercare il numero. La via del database: un'interrogazione preparata su una colonna con indice. Duemila ripetizioni ciascuna, cinque giri, presa la mediana.
Alla dimensione di oggi il file vince di otto volte: 0,0202 millisecondi contro 0,1630. Con mille numeri sono pari. Con diecimila il file costa dodici volte tanto, e con centomila costa 135 volte tanto, perché a quel punto sono 578 kilobyte da leggere e analizzare a ogni visita.
La linea del database non si muove. 0,16 millisecondi con due righe e 0,16 millisecondi con centomila: a questo serve un indice, ed è facile dimenticare quanto lavoro si nasconda dietro una linea così noiosa.
La parte scomoda
L'incrocio sta fra 100 e 1.000 numeri. Questo servizio ha 2.218 contatori che hanno avuto traffico negli ultimi trenta giorni.
Quindi se l'esclusione fosse un successo — se chiunque abbia un contatore attivasse «non contare le mie visite» — il file conterrebbe 2.218 numeri, peserebbe 11 kilobyte e costerebbe 0,43 millisecondi a visita invece di 0,02. Sulle 18.423 visite di ieri fanno otto secondi di lavoro al giorno per evitare un'interrogazione che ne avrebbe presi tre.
L'ottimizzazione è più veloce quando la funzione è meno usata. Non peggiora gradualmente sotto carico, come un'interrogazione lenta. Peggiora con la diffusione, che è l'unico asse che nessuno sorveglia, perché una diffusione che cresce dovrebbe essere la buona notizia.
Perché resta lì
Perché oggi è giusto, e «giusto oggi» può essere la ragione di qualcosa, purché qualcuno abbia scritto quando smette di esserlo.
Tre numeri in tre file. Otto volte più a buon mercato dell'alternativa, su una macchina dove il percorso di conteggio è l'unica cosa che deve essere veloce. Sostituirlo ora con l'interrogazione che batte darebbe un sistema peggiore giustificato da un'ipotesi.
Quel che manca è il filo d'allarme, non la riscrittura. Il file viene comunque scritto dal database, dal codice che cambia un'impostazione — è il posto naturale per accorgersi che l'elenco ha superato qualche centinaio di voci, e dirlo. Una cache con un tetto scritto è una decisione. Una senza è una scommessa che nessuno ha accettato.
La forma generale
Questo non è un argomento contro la cache in un file. È un argomento a favore del sapere in che direzione cresce la propria cache.
Quasi tutte le cache migliorano sotto carico: più richieste, più successi, rapporto migliore. Questa è dell'altro tipo. Il suo costo per richiesta dipende da quanti dati contiene, e ciò che contiene cresce insieme alla cosa che il servizio vuole incoraggiare. Ogni lettura paga per ogni voce, comprese le 2.215 che non hanno nulla a che fare con il visitatore che si sta contando adesso.
La domanda da porre a qualunque tabella di ricerca tenuta in memoria o in un file non è «quanto è veloce» ma «che cosa la fa crescere, e che succede quando quella cosa va bene». Se la risposta è «rallenta», il limite di dimensione appartiene al codice, accanto a ciò che scrive il file, il giorno in cui lo si costruisce.