O que esta aplicação guarda em cache, e por quê
Um cache é mais uma aposta do que um truque de velocidade. Ele aposta que algum trabalho é caro de produzir, barato de guardar, pedido várias vezes e aceitável quando um pouco desatualizado. As quatro coisas precisam valer. Quando uma delas não vale, o cache piora o sistema de um jeito difícil de enxergar.
Para que serve cache, em geral
O padrão tem sempre o mesmo formato. Algo leva tempo para calcular: uma agregação no banco, um template renderizado, uma resposta de outro serviço. Alguém pede esse resultado de novo, o mesmo usuário ou outro, antes de os fatos por trás dele mudarem. Guardar a resposta transforma cálculo repetido numa única consulta.
A quarta condição é a que as pessoas pulam. Um cache sempre serve uma resposta calculada no passado. Alguns dados precisam estar certos no instante: um saldo, uma checagem de permissão, um estoque. Para esses dados, a única duração certa de cache é zero, e recorrer a um cache é recorrer a um bug.
flowchart TB
Q["este trabalho é caro de produzir?"] --> Q2["ele é pedido repetidamente?"]
Q2 --> Q3["uma resposta levemente antiga é aceitável?"]
Q3 -->|sim para as três| C["faça cache"]
Q3 -->|não| N["não faça cache. uma resposta obsoleta aqui é um bug"]
C --> K["aí a única parte difícil é saber quando ela não vale mais"]
O backend, numa linha
# config/environments/development.rb, production.rb, test.rb
config.cache_store = :solid_cache_store
A configuração é a mesma nos três ambientes, e essa é uma decisão que vale notar. A fila de jobs desta aplicação usa :async em desenvolvimento e :solid_queue no resto, mas o cache não tem essa divisão. O que você vê localmente é o que a produção faz.
O solid_cache é uma gem oficial do Rails 8. Cada membro da família "Solid" troca um serviço externo (um cache, uma fila de jobs, um broker de WebSocket) pelo banco que esta aplicação já roda. O resto do curso mostra quanto isso custa e como fica na prática.
O que esta aplicação guarda em cache
São dois padrões em sete lugares, e a fronteira entre eles vale a pena ver.
Seis caches de fragmento guardam HTML renderizado. O maior é o do roadmap. A chave dele sai de um método helper, e não de um bloco inline, para que um grupo inteiro de cards se resolva com uma só chamada read_multi em vez de uma leitura de cache por card:
# app/helpers/catalog/course_helper.rb
def card_cache_key(course, progress)
prog = progress[course.id]
[ course, I18n.locale, current_user.present?, prog&.percentage, prog&.enrolled ]
end
Esse partial renderiza uma vez por curso ativo, entre intern, junior, mid e senior. Neste banco de desenvolvimento são 361 cards para quem está logado; visitantes anônimos veem uma lista menor por nível. Cada card tem o estilo do nível, uma variante da imagem de capa, um título, uma contagem de aulas e uma barra de progresso. Sem o cache, cada visita à página mais movimentada do produto renderizaria centenas de templates.
Os outros cinco guardam os cards de cursos em andamento do painel, os cursos sugeridos, os resumos de cursos concluídos, cada linha de aula numa página de curso e a linha "Supported by" de parceiros no rodapé de todas as páginas.
Um cache de valor guarda um objeto Ruby em vez de markup:
Rails.cache.fetch("admin/stats/#{resolved}", expires_in: 5.minutes) do
O Admin::StatsQuery monta oito conjuntos de dados de gráfico e um conjunto de contadores. Com os dados de desenvolvimento desta máquina, levou cerca de 16 ms a frio e cerca de 1 ms vindo do cache, umas quinze vezes mais rápido.
Por que os dois têm chaves tão diferentes
Esta é a ideia que mais se aproveita fora do curso. Os caches de fragmento não têm expiração nenhuma, e o cache de estatísticas expira em cinco minutos. Isso é de propósito.
Uma chave de fragmento dá nome ao registro. Para [course, I18n.locale, prog&.percentage], o Rails guarda a entrada sob o cache_key do curso e registra o updated_at do curso como a versão da entrada. Quando o curso muda, a versão guardada deixa de bater, então a próxima leitura é um miss e a nova renderização substitui a entrada. Ela não precisa de expiração porque não consegue servir um curso desatualizado.
O cache de estatísticas não funciona assim, porque o resultado vem de milhares de linhas em muitas tabelas. Não existe um único updated_at para usar como versão, então a única saída é um limite de tempo: aceitar dados com até cinco minutos.
A regra: se você consegue dizer do que a resposta depende, use isso como versão e nunca expire. Se não consegue, escolha uma defasagem que você consiga defender em voz alta.
O que esta aplicação não guarda em cache
Todo o resto, e esse é o padrão certo. Não há checagens de permissão em cache, nem registros de usuário, nem resultados de quiz. Um cache é um risco em qualquer coisa que precisa estar certa. Neste código só a apresentação renderizada e um painel de admin ficam em cache, nunca um fato do qual uma decisão depende.
O mesmo store guarda uma coisa que não é cache: o rate_limit do Rails, usado por alguns controllers daqui, mantém seus contadores de requisições no Rails.cache. É um estado do qual uma decisão depende, e ele fica no Solid Cache porque o rate_limit usa o cache store configurado por padrão.
Pontos-chave
- Cache aposta que o trabalho é caro, barato de guardar, repetido e aceitável quando um pouco desatualizado: as quatro coisas precisam valer.
- Esta aplicação usa cache em sete lugares: seis pontos de fragmento HTML e um valor Ruby no
Admin::StatsQuery. - O roadmap renderiza um card por curso ativo numa única requisição, e é por isso que o cache de fragmento faz sentido ali.
- Fragmentos nunca expiram porque a versão deles é o
updated_atdo registro, então um registro alterado dá miss e renderiza de novo; o cache de estatísticas expira em cinco minutos porque não tem um registro único para usar como versão. - Nada do qual uma decisão depende fica em cache, embora os contadores do
rate_limitdividam o mesmo store.
Continue com a aula 2
Cada elemento das chaves de cache compostas deste app
Explica por que idioma, estado de login e progresso de cada aluno aparecem nas chaves de cache em array.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.