Che cos’è il caching?

Sto sentendo costantemente di persona e ho avuto problemi di prestazioni x che hanno risolto con il caching.

Oppure, come fare x, y, z nel codice del tuo programma può danneggiare la tua capacità di caching.

Anche in uno degli ultimi podcast, Jeff Atwood parla di come nascondono determinati valori per un rapido recupero.

Sembra esserci qualche ambiguità nei termini “cache” e “cache” e mi ha portato a confondermi sul suo significato in diversi casi. Sia che tu stia facendo riferimento a cache di applicazioni o database, cpu, ecc. E cosa significa.

Che cos’è il caching e quali sono i diversi tipi?

Dal contesto, posso averne un’idea, archiviare un valore spesso recuperato nella memoria principale e avere accesso rapido ad esso. Tuttavia, cos’è veramente ?

Questa parola sembra essere usata in molti contesti diversi con un significato leggermente diverso (cpu, database, applicazione, ecc.) E sto davvero cercando di chiarirlo.

Esiste una distinzione tra il modo in cui la memorizzazione nella cache funziona nelle applicazioni e il caching del database?

Quando qualcuno dice di aver trovato un pezzo di codice che avrebbe danneggiato il caching e dopo averlo risolto, ha migliorato la velocità della loro app, di cosa stanno parlando?

Il caching del programma è qualcosa che viene fatto automaticamente? Come si consentono ai valori di essere memorizzati nella cache nei programmi? Ho letto spesso che gli utenti di questo sito dicono di aver memorizzato nella loro applicazione un valore, mi siedo qui e mi chiedo cosa significano.

Inoltre, cosa significa veramente quando qualcuno parla del caching del database? È semplicemente una funzionalità che accendono nel loro database? Devi memorizzare in modo esplicito i valori o il database sceglie quelli da memorizzare nella cache?

Come posso iniziare a memorizzare gli elementi nella cache per migliorare le prestazioni?

Puoi darmi alcuni esempi di come posso iniziare a memorizzare i valori nella cache delle mie applicazioni ? O ancora, questo è qualcosa che è già stato fatto, sotto la cappa e devo semplicemente scrivere il mio codice in un modo particolare per consentire il “caching”?

Che dire del caching del database, come posso iniziare? Ho sentito parlare di cose come memcache. Questo tipo di utilità è necessario per memorizzare nella cache i database?

Sto cercando di ottenere una buona distinzione tra il caching nelle applicazioni vs i database, come vengono utilizzati e come viene implementato in entrambi i casi.

Il caching è solo la pratica di archiviare dati e recuperare dati da un archivio ad alte prestazioni (di solito la memoria) in modo esplicito o implicito.

Lasciatemi spiegare. La memoria è più veloce da accedere di un file, un URL remoto (di solito), un database o qualsiasi altro archivio esterno di informazioni che ti piace. Pertanto, se l’utilizzo di una di queste risorse esterne è significativo, è ansible trarre vantaggio dalla memorizzazione nella cache per migliorare le prestazioni.

Knuth una volta disse che l’ottimizzazione prematura è la radice di tutto il male. Bene, il caching prematuro è la radice di tutti i mal di testa, per quanto mi riguarda. Non risolvere un problema finché non hai un problema. Ogni decisione che prendi ha un costo che dovrai pagare per implementarlo ora e pagare di nuovo per cambiarlo più tardi in modo che più a lungo puoi rimandare a fare un deionion e cambiare il tuo sistema, meglio è.

Quindi per prima cosa identifica che hai effettivamente un problema e dove si trova . Profilazione, registrazione e altre forms di test delle prestazioni ti aiuteranno qui. Non posso sottolineare abbastanza quanto sia importante questo passaggio. Il numero di volte in cui ho visto persone “ottimizzare” qualcosa che non è un problema è sbalorditivo.

Ok, quindi hai un problema di prestazioni. Supponi che le tue pagine stiano eseguendo una query che richiede molto tempo. Se si tratta di una lettura, hai una serie di opzioni:

  • Esegui la query come processo separato e inserisci il risultato in una cache. Tutte le pagine accedono semplicemente alla cache. È ansible aggiornare la versione memorizzata nella cache tutte le volte che è opportuno (una volta al giorno, una volta alla settimana, una ogni 5 secondi, qualunque sia il caso);
  • Cache in modo trasparente attraverso il tuo provider di persistenza, ORM o qualsiasi altra cosa. Ovviamente questo dipende dalla tecnologia che stai usando. Hibernate e Ibatis, ad esempio, supportano il caching dei risultati delle query;
  • Le tue pagine eseguono la query se il risultato non è nella cache (o è “obsoleto”, il che significa che è calcolato più tempo fa rispetto all’età specificata) e lo inserisce nella cache. Questo ha problemi di concorrenza se due (o più) processi separati decidono tutti di aver bisogno di aggiornare il risultato, così si finisce per eseguire la stessa (costosa) query otto volte alla volta. È ansible gestire questo blocco della cache ma questo crea un altro problema di prestazioni. È inoltre ansible ricorrere ai metodi di concorrenza nella propria lingua (ad esempio API di concorrenza Java 5).

Se si tratta di un aggiornamento (o di aggiornamenti che devono essere riflessi nella cache di lettura), è un po ‘più complicato perché non è utile avere un vecchio valore nella cache e un valore più recente nel database tale da fornire quindi le pagine con una visione incoerente dei dati. Ma in generale ci sono quattro approcci a questo:

  • Aggiornare la cache e quindi accodare una richiesta per aggiornare il negozio pertinente;
  • Scrittura tramite cache: il provider cache può fornire un meccanismo per mantenere l’aggiornamento e bloccare il chiamante fino a quando non viene apportata tale modifica; e
  • Memorizzazione nella cache write-behind: come nella cache write-through ma non blocca il chiamante. L’aggiornamento avviene in modo asincrono e separatamente; e
  • Persistenza come modelli di servizio: questo presuppone che il meccanismo di memorizzazione nella cache supporti un qualche tipo di osservabilità (ad esempio, gli ascoltatori di eventi di cache). Fondamentalmente un processo completamente separato – sconosciuto al chiamante – ascolta gli aggiornamenti della cache e li persiste se necessario.

Quale dei metodi sopra indicati dipende molto dalle tue esigenze, dalle tecnologie che stai utilizzando e da tutta una serie di altri fattori (ad esempio, sono necessari il clustering e il supporto per il failover?).

È difficile essere più specifici di questo e darti una guida su cosa fare senza conoscere molti più dettagli sul tuo problema (come se tu abbia o meno un problema).

Molto probabilmente leggerai sulla memorizzazione nella cache nel contesto delle applicazioni web. A causa della natura del Web, il caching può fare una grande differenza di prestazioni.

Considera quanto segue:

Una richiesta di pagina web arriva al server Web, che passa la richiesta al server delle applicazioni, che esegue un codice che esegue il rendering della pagina, che deve passare al database per recuperare in modo dinamico i dati.

Questo modello non si adatta bene, poiché quando il numero di richieste per la pagina aumenta, il server deve ripetere la stessa operazione più e più volte, per ogni richiesta.

Questo diventa ancora più un problema se server Web, server applicazioni e database si trovano su hardware diversi e comunicano tra loro in rete.

Se hai un numero elevato di utenti che hanno colpito questa pagina, ha senso non andare fino in fondo al database per ogni richiesta. Invece, si ricorre al caching a diversi livelli.

Cache di risultati

La memorizzazione nella cache del gruppo di risultati memorizza i risultati di una query di database insieme alla query nell’applicazione. Ogni volta che una pagina Web genera una query, le applicazioni controllano se i risultati sono già memorizzati nella cache e, in caso affermativo, li estraggono invece da un set di dati in memoria. L’applicazione deve ancora eseguire il rendering della pagina.

Cache Componente

Una pagina web è composta da diversi componenti: pagelets o qualsiasi altra cosa si voglia chiamare. Una strategia di caching dei componenti deve sapere quali parametri sono stati utilizzati per richiedere il componente. Ad esempio, una piccola barra “Ultime notizie” sul sito utilizza la posizione geografica dell’utente o la preferenza per mostrare notizie locali. Di conseguenza, se le notizie relative a una posizione vengono memorizzate nella cache, il componente non deve essere sottoposto a rendering e può essere estratto da una cache.

Cache di pagina

Una strategia per la memorizzazione nella cache di intere pagine consiste nel memorizzare la stringa di query e / oi parametri di intestazione insieme al codice HTML completamente renderizzato. Il file system è abbastanza veloce per questo: è comunque molto meno costoso per un server Web leggere un file piuttosto che effettuare una chiamata al server delle applicazioni per renderizzare la pagina. In questo caso, ogni utente che invia la stessa stringa di query otterrà lo stesso contenuto memorizzato nella cache.

Combinare in modo intelligente queste strategie di memorizzazione nella cache è l’unico modo per creare app Web scalabili per un numero elevato di utenti simultanei. Come puoi facilmente vedere, il rischio potenziale qui è che se un pezzo di contenuto nella cache non può essere identificato in modo univoco dalla sua chiave, la gente inizierà a vedere il contenuto sbagliato. Questo può diventare piuttosto complicato, in particolare quando gli utenti hanno sessioni e c’è un contesto di sicurezza.

Ci sono due significati che io conosco.


Uno è il caching dell’applicazione . Questo è il momento in cui, se i dati sono lenti da raggiungere da qualche parte (ad es. Dalla rete) o lenti da calcolare, l’applicazione memorizza nella cache una copia dei dati (in modo che non sia necessario recuperarli o ricalcolare: è già memorizzato nella cache). L’implementazione di una cache richiede un po ‘di software applicativo supplementare (logica per utilizzare la cache) e memoria extra (in cui archiviare i dati memorizzati nella cache).

Questo è il “caching” usato come stai citando qui:

Dal contesto, posso averne un’idea, archiviare un valore spesso recuperato nella memoria principale e avere accesso rapido ad esso.


Un altro è il caching della CPU , che è descritto in questo articolo di Wikipedia . Il caching della CPU avviene automaticamente. Se si esegue molta lettura da una piccola quantità di memoria, la CPU può eseguire la maggior parte di tali letture dalla sua cache. OTOH se si legge da una grande quantità di memoria, non può adattarsi alla cache e la CPU deve impiegare più tempo a lavorare con la memoria più lenta.

Questo è il “caching” usato come stai citando qui:

Quando qualcuno dice di aver trovato un pezzo di codice che avrebbe danneggiato il caching e dopo averlo risolto, ha migliorato la velocità della loro app, di cosa stanno parlando?

Significa che hanno trovato un modo per riorganizzare il loro codice per causare meno errori di cache .


Per quanto riguarda la memorizzazione nella cache del database , non lo so.

Ci sono un paio di problemi.

Uno, è granularità. L’applicazione può avere livelli molto fini di memorizzazione nella cache rispetto a ciò che fa il database. Ad esempio, è probabile che il database torni semplicemente nella cache di pagine di dati, non necessariamente in righe specifiche.

Un’altra cosa è che l’applicazione può memorizzare dati nel suo formato “nativo”, mentre il DB ovviamente memorizza solo nella cache nel suo formato interno.

Semplice esempio

Supponiamo che tu abbia un utente nel database, che è composto dalle colonne: USERID , FIRSTNAME , LASTNAME . Molto semplice.

Desideri caricare un utente, USERID=123 , nella tua applicazione. Quali sono i passaggi coinvolti?

  1. Emissione della chiamata al database
  2. Analisi della richiesta ( SELECT * FROM USER WHERE USERID = ? )
  3. Pianificazione della richiesta (es. Come va il sistema a recuperare i dati)
  4. Recupero dei dati dal disco
  5. Streaming dei dati dal database all’applicazione
  6. Conversione dei dati del database in dati dell’applicazione (es. USERID in un intero, ad esempio, i nomi in Stringhe.

La cache del database, probabilmente, memorizzerà nella cache i passaggi 2 e 3 (che è una cache di istruzioni, quindi non analizzerà o sostituirà la query) e memorizzerà nella cache i blocchi effettivi del disco.

Quindi, ecco la chiave. Il tuo utente, USER ID 123 , chiama JESSE JAMES . Puoi vedere che non si tratta di molti dati. Ma il database sta memorizzando i blocchi del disco nella cache. Avete il blocco indice (con 123 su di esso), quindi il blocco dati (con i dati effettivi e tutte le altre righe che si adattano a quel blocco). Quindi, ciò che è nominalmente, diciamo, 60-70 byte di dati ha effettivamente un effetto di memorizzazione nella cache e dati sul DB di, probabilmente, 4K-16K (dipende dalla dimensione del blocco).

Il lato positivo? Se hai bisogno di un’altra riga che si trova nelle vicinanze (ad esempio USER ID = 124 ), le probabilità sono alte e i dati sono già memorizzati nella cache.

Ma anche con quella memorizzazione nella cache, devi comunque pagare il costo per spostare i dati sul filo (ed è sempre sul filo, a meno che tu non stia usando un DB locale, quindi è il loopback), e stai “smantellando” i dati . Ovvero, convertendolo dai bit del database ai bit del linguaggio, ai bit dell’applicazione.

Ora, una volta che l’applicazione ottiene il suo USER ID 123 , inserisce il valore in una mappa hash longeva.

Se l’applicazione lo desidera di nuovo, cercherà nella mappa locale, nella cache dell’applicazione e salverà la ricerca, il trasporto del filo e i costi di marshalling.

Il lato oscuro della memorizzazione nella cache dell’applicazione è la sincronizzazione. Se qualcuno entra e fa un UPDATE USER SET LASTNAME="SMITH" WHERE USERID=123 , la tua applicazione non “lo sa”, e quindi la cache è sporca.

Quindi, ci sono un sacco di dettagli nella gestione di tale relazione per mantenere l’applicazione in sincronia con il DB.

Avere un sacco di cache del database è molto bello per le query di grandi dimensioni su un set di dati “hot”. Più memoria hai, più dati “caldi” puoi avere. Fino al punto in cui è ansible memorizzare in cache l’intero DB in RAM, si elimina il ritardo di I / O (almeno per le letture) del trasferimento dei dati dal disco a un buffer RAM. Ma hai ancora i costi di trasporto e di smistamento.

L’applicazione può essere molto più selettiva, come la memorizzazione nella cache di sottoinsiemi di dati più limitati (i blocchi DB si limitano a memorizzare nella cache) e il fatto di avere i dati “più vicini” all’applicazione migliora le prestazioni.

Il lato negativo è che non tutto è memorizzato nella cache dell’applicazione. Il database tende a memorizzare i dati in modo più efficiente, in generale, rispetto all’applicazione. Manca anche una lingua di “query” rispetto ai dati memorizzati nella cache dell’app. La maggior parte delle persone semplicemente memorizza la cache tramite una semplice chiave e passa da lì. Facile da trovare USER ID 123 , più difficile per “TUTTI GLI UTENTI NOMINATI JESSE”.

Il caching del database tende ad essere “libero”, si imposta un numero di buffer e il DBMS gestisce il resto. Basso impatto, riduce i tempi di I / O e ritardi del disco.

Il caching dell’applicazione è, beh, specifico dell’applicazione.

Funziona molto bene per dati “statici” isolati. È molto facile. Carica un sacco di cose per cercare le tabelle all’avvio e riavvia l’app se cambiano. È facile da fare.

Dopo che la complessità inizia ad aumentare quando aggiungi la logica “sporca”, ecc.

Ciò a cui tutto si riferisce è che, purché si disponga di un’API dati, è ansible eseguire la cache in modo incrementale.

Quindi, se chiami getUser(123) ovunque anziché colpire il DB, puoi successivamente tornare indietro e aggiungere la cache a getUser senza getUser sul codice.

Quindi, suggerisco sempre una sorta di livello di accesso ai dati nel codice di tutti, per fornire quel livello di astrazione e livello di intercettazione.

il caching sta prendendo il risultato di un algoritmo intensivo lungo o cpu e salvando la risposta in modo che non sia necessario eseguire nuovamente l’algoritmo, è sufficiente riutilizzare il risultato.

Il concetto di cache è un termine sovraccarico qui. Non ho familiarità con i dadi e bulloni del caching del database.

Nelle applicazioni ci sono due usi del termine.

Quando qualcuno dice di aver trovato un pezzo di codice che avrebbe danneggiato il caching e dopo averlo risolto, ha migliorato la velocità della loro app, di cosa stanno parlando?

In questo caso stanno facendo riferimento alla cache della CPU.

La cache della CPU è nella memoria della CPU che è molto più veloce della RAM, ma non ha accesso casuale. Ciò che la CPU decide di caricare nella cache può essere un po ‘complicato. Vedi Ulrich Dreppers Quello che ogni programmatore dovrebbe sapere sulla memoria per molti dettagli.

Essere consapevoli della cache della CPU può velocizzare le cose abbastanza bene – devi solo prestare un po ‘più di attenzione a dove le cose si metteranno l’una rispetto all’altra nella memoria fisica e quando saranno probabilmente usate.

Un esempio (probabilmente anche un anti-pattern per la manutenibilità) è che si dispone di una serie di strutture e si esegue molto il looping sui membri della struttura che si potrebbe meglio servire con una struttura in cui i campi sono tutti gli array. Se i dati su cui stai eseguendo il ciclo sono contigui nella memoria, hai maggiori probabilità di non sconvolgere la cache.

Tutti i tipi di cose possono influire sull’efficienza dell’utilizzo della cache: previsione delle branche per il codice caricato nella cache, dimensioni e allineamento delle strutture dati e dei pattern di accesso, dove e quando dichiarare le variabili locali che verranno messe in pila.

L’altro uso comune del termine per la programmazione delle applicazioni può essere fatto da qualcosa chiamato memoization . L’esempio fattoriale su quella pagina di wikipedia spiega le cose meglio di quanto avrei fatto.

La memorizzazione nella cache dei database è in genere una funzione del database ed è gestita automaticamente dal database. La memorizzazione nella cache delle applicazioni varierà da una piattaforma all’altra.

Una cache di oggetti è un meccanismo che è ansible utilizzare per inserire oggetti di uso comune nella memoria in modo che non sia necessario pagare il costo per recuperare i dati e ricrearli. Questo è generalmente gestito tramite codice e varia su quale soluzione di caching stai usando.

Esistono soluzioni di cache distribuita che implicano l’impostazione di servizi su più server per fornire una sorta di farm di cache. Ciò fornisce scalabilità e ridondanza. I client possono richiedere le informazioni memorizzate nella cache attraverso la rete. Anche in questo caso si tratta di una procedura manuale nel codice. Un esempio di un provider di cache distribuita è memcached:

http://www.danga.com/memcached/

Un esempio di un tipo specifico di memorizzazione nella cache potrebbe essere il caching di asp.net. Asp.net supporta diversi tipi di cache. Esiste la tradizionale cache degli oggetti (che può essere utilizzata in tutti i tipi di app .net, non solo nei siti Web). Sono inoltre disponibili funzionalità di memorizzazione nella cache che consentono di configurare pagine e controlli utente per memorizzare automaticamente il loro output. Ciò non memorizza nella cache i dati, memorizza nella cache il risultato finale (il codice HTML della pagina) e lo offre quando l’utente richiede la stessa pagina con le stesse parme di stringhe di query di un utente precedente.

Probabilmente è più facile di quanto tu possa immaginare – ed è per questo che le persone stanno cercando di chiuderlo.

Significa semplicemente memorizzare i valori nella memoria piuttosto che tornare al database per loro ogni volta.

Ci sono molti modi per farlo, ma il concetto stesso è banale.

Modifica: Può essere fatto anche a QUALSIASI livello – tutto ciò che richiede molto tempo può essere memorizzato in cache da qualche parte che è ansible ottenere più rapidamente.

Il caching non si applica necessariamente solo ai valori “spesso recuperati” ma a qualsiasi cosa su cui è ansible risparmiare tempo riducendo il numero di volte in cui lo si ricomincia. Un semplice esempio che viene in mente è il calcolo della sequenza di Fibonacci . L’implementazione ricorsiva più semplice ha questo aspetto (in psuedo-code):

 function f(n) if n < 2 then return n; return f(n - 1) + f(n - 2) 

Questo può essere migliorato con la memorizzazione nella cache per impedire il ricalcolo di valori già noti:

 fib_cache = {} function f(n) if n < 2 then return n; if fib_cache.contains(n) then return fib_cache[n] fib_cache[n] = f(n - 1) + f(n - 2) return fib_cache[n]