← Back to home

Cache

By Pscodium · 8/4/2026 · 2 views

Cache — System Design

Cache é uma das técnicas mais importantes para melhorar a performance e a escalabilidade de sistemas.

A ideia é simples:

Se uma informação é acessada frequentemente, podemos armazená-la temporariamente em um local mais rápido para evitar recalcular ou buscar novamente a mesma informação.

Em vez de fazer:

Client
   │
   ▼
API
   │
   ▼
Database
   │
   ▼
Busca dados
   │
   ▼
Retorna resposta

Podemos utilizar um cache:

Client
   │
   ▼
API
   │
   ▼
Cache
   │
   ├── HIT ──► Retorna imediatamente
   │
   └── MISS
          │
          ▼
       Database
          │
          ▼
       Cache
          │
          ▼
       Resposta

O objetivo é reduzir:

  • Latência
  • Carga no banco
  • Número de operações custosas
  • Custos de infraestrutura

Ao mesmo tempo, o cache pode aumentar significativamente a capacidade de um sistema.


1. Por que precisamos de Cache?

Imagine uma aplicação com:

1.000.000 usuários

E cada usuário acessa:

GET /products/123

Se todas as requisições forem diretamente para o banco:

1.000.000 Requests
        │
        ▼
    Database

O banco pode se tornar um gargalo.

Agora adicionamos cache:

1.000.000 Requests
        │
        ▼
       Cache
        │
        ├── 950.000 → HIT
        │
        └── 50.000 → MISS
                    │
                    ▼
                 Database

Agora o banco recebe apenas uma pequena parte das requisições.

Isso pode melhorar significativamente a escalabilidade.


2. Cache Hit e Cache Miss

Existem dois resultados principais ao consultar um cache.

Cache Hit

O dado está disponível.

GET product:123
        │
        ▼
      Cache
        │
        ▼
      FOUND
        │
        ▼
      RETURN

Isso é um:

Cache Hit


Cache Miss

O dado não está no cache.

GET product:123
        │
        ▼
      Cache
        │
        ▼
     NOT FOUND
        │
        ▼
     Database

Isso é um:

Cache Miss

A aplicação então busca o dado na fonte original e pode armazená-lo no cache.

Database
    │
    ▼
Cache
    │
    ▼
Response

3. Cache Hit Rate

Uma métrica muito importante é o Cache Hit Rate.

Imagine:

1.000 requisições

Dessas:

900 → Cache Hit
100 → Cache Miss

Temos:

Hit Rate = 90%

Quanto maior o Hit Rate, maior a quantidade de requisições atendidas diretamente pelo cache.

Uma aplicação pode monitorar:

Cache Hit Rate
Cache Miss Rate
Cache Evictions
Cache Latency
Memory Usage

Um Hit Rate baixo pode indicar:

  • TTL muito curto
  • Chaves mal definidas
  • Cache pequeno
  • Dados pouco reutilizados
  • Estratégia de cache inadequada

4. Cache vs Database

Uma diferença fundamental:

DATABASE

é a fonte persistente dos dados.

Enquanto:

CACHE

é normalmente uma cópia temporária ou derivada.

Podemos pensar:

Database
   │
   │ Fonte original
   ▼
Cache
   │
   │ Cópia temporária
   ▼
Application

Se o cache desaparecer:

Cache
   X

o sistema ainda pode buscar os dados no banco.

Por isso, normalmente:

Não devemos tratar o cache como a única fonte de verdade.


5. Onde o Cache pode existir?

Existem diferentes níveis de cache.

Podemos ter:

Client
   │
   ▼
Browser Cache
   │
   ▼
CDN Cache
   │
   ▼
Application Cache
   │
   ▼
Distributed Cache
   │
   ▼
Database

Cada camada reduz a necessidade de acessar a próxima.


6. Browser Cache

O navegador pode armazenar recursos localmente.

Por exemplo:

HTML
CSS
JavaScript
Imagens
Fonts

Em vez de baixar novamente:

Browser
   │
   ▼
Cache local
   │
   ▼
Arquivo

Podemos utilizar headers HTTP como:

Cache-Control: max-age=3600

Isso informa que o recurso pode ser armazenado por determinado período.


7. CDN Cache

CDNs armazenam conteúdo próximo dos usuários.

Imagine:

Usuário Brasil
      │
      ▼
CDN São Paulo
      │
      ▼
Cache Hit

Sem cache:

Brasil
   │
   ▼
Servidor EUA

Com CDN:

Brasil
   │
   ▼
CDN
   │
   ▼
Resposta

Isso reduz:

  • Latência
  • Tráfego até o servidor de origem
  • Carga na aplicação

CDNs são especialmente úteis para:

  • Imagens
  • Vídeos
  • JavaScript
  • CSS
  • Arquivos estáticos

8. Application Cache

Podemos armazenar dados na memória da aplicação.

Por exemplo:

API Server
   │
   ├── Cache local
   │
   └── Database

Isso é extremamente rápido.

Porém, existe um problema.

Imagine:

        Load Balancer
              │
       ┌──────┼──────┐
       ▼      ▼      ▼
     App 1  App 2  App 3

Cada servidor possui seu próprio cache:

App 1 → Cache A
App 2 → Cache B
App 3 → Cache C

Agora podemos ter:

App 1 → product:123 = R$ 100

App 2 → product:123 = R$ 120

Os caches podem divergir.

Por isso, em sistemas distribuídos, muitas vezes utilizamos um cache compartilhado.


9. Distributed Cache

Uma solução comum é utilizar um sistema de cache distribuído.

Exemplos:

  • Redis
  • Memcached

Arquitetura:

                 LOAD BALANCER
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
        API 1       API 2       API 3
          │           │           │
          └───────────┼───────────┘
                      │
                      ▼
                   REDIS
                      │
                      ▼
                  DATABASE

Agora todas as instâncias utilizam o mesmo cache.

API 1 ──┐
API 2 ──┼──► Redis
API 3 ──┘

Isso é especialmente útil para aplicações Stateless.


10. Redis

O Redis é uma das soluções mais utilizadas para caching.

Ele armazena dados principalmente em memória.

Isso permite operações extremamente rápidas.

Podemos armazenar:

user:123
product:456
session:abc

Por exemplo:

SET product:123 "Produto A"

Depois:

GET product:123

Retorna:

Produto A

O Redis também oferece estruturas de dados como:

  • Strings
  • Lists
  • Sets
  • Sorted Sets
  • Hashes
  • Streams

Isso permite utilizar Redis para mais do que apenas cache.


11. Cache-Aside

Uma das estratégias mais comuns é:

Cache-Aside

Também chamada de:

Lazy Loading

O fluxo é:

Application
    │
    ▼
Check Cache
    │
    ├── HIT ──► Return
    │
    └── MISS
          │
          ▼
       Database
          │
          ▼
       Update Cache
          │
          ▼
       Return

Exemplo:

GET product:123

A aplicação verifica:

Redis

Se encontrar:

HIT

Retorna.

Se não encontrar:

MISS

Busca no banco:

Database
    │
    ▼
Product 123

Depois salva:

Redis
    │
    ▼
product:123

12. Vantagens do Cache-Aside

É simples de implementar.

O cache armazena apenas dados que realmente são utilizados.

Database
    │
    ▼
Request
    │
    ▼
Cache

Se um dado nunca for acessado:

Não entra no cache

Isso economiza memória.


13. Desvantagens do Cache-Aside

Existe uma pequena complexidade de sincronização.

Imagine:

Database
product:123 = R$ 100

Cache
product:123 = R$ 100

Atualizamos o banco:

Database
product:123 = R$ 120

Mas esquecemos de atualizar o cache:

Cache
product:123 = R$ 100

Agora temos:

Database → R$ 120
Cache    → R$ 100

O sistema pode retornar dados antigos.

Esse problema é conhecido como:

Stale Data


14. Write-Through Cache

No modelo Write-Through:

Application
     │
     ▼
Cache
     │
     ▼
Database

Quando escrevemos:

WRITE

O cache é atualizado e o banco também.

Fluxo:

Application
    │
    ▼
Cache
    │
    ▼
Database

A vantagem é manter o cache mais consistente com o banco.

Porém, cada escrita pode ter um custo maior.


15. Write-Back

No modelo Write-Back:

Application
    │
    ▼
Cache
    │
    │ assíncrono
    ▼
Database

A aplicação escreve primeiro no cache.

O banco é atualizado posteriormente.

Vantagem:

Escrita mais rápida

Porém:

Cache
   X

Se o cache falhar antes de persistir os dados:

Dados podem ser perdidos

Por isso, essa estratégia é mais arriscada quando os dados são críticos.


16. Cache Invalidation

Existe uma famosa frase atribuída a Phil Karlton:

"There are only two hard things in Computer Science: cache invalidation and naming things."

Invalidar cache significa remover ou atualizar dados antigos.

Imagine:

Cache
product:123 = R$ 100

Atualizamos:

Database
product:123 = R$ 120

Precisamos invalidar:

Cache
product:123

Podemos:

DEL product:123

Na próxima consulta:

Cache MISS
    │
    ▼
Database
    │
    ▼
Novo valor

17. TTL — Time To Live

Uma estratégia comum é definir um tempo de expiração.

Por exemplo:

product:123
TTL = 60 segundos

Depois de 60 segundos:

Cache
   │
   ▼
Expirado

A próxima requisição buscará novamente no banco.

Isso reduz o risco de dados antigos permanecerem indefinidamente.


18. TTL e Stale Data

Imagine:

Database = R$ 120

Cache = R$ 100
TTL = 60 segundos

Durante os próximos 60 segundos:

Usuário
   │
   ▼
Cache
   │
   ▼
R$ 100

Mesmo que o banco contenha:

R$ 120

O cache pode continuar retornando:

R$ 100

Portanto:

TTL reduz a duração do dado stale, mas não elimina completamente a inconsistência.


19. Cache Stampede

Imagine que uma chave popular expire:

product:123

Milhares de usuários fazem requisições simultaneamente:

Request 1 ──┐
Request 2 ──┤
Request 3 ──┤
Request 4 ──┼──► Cache MISS
Request 5 ──┤
Request 6 ──┘

Todos consultam o banco:

        DATABASE
       ▲ ▲ ▲ ▲ ▲ ▲
       │ │ │ │ │ │
       Requests

Isso pode sobrecarregar o banco.

Esse problema é conhecido como:

Cache Stampede

Ou:

Thundering Herd


20. Como evitar Cache Stampede?

Algumas estratégias:

Lock

Apenas uma requisição atualiza o cache.

Request 1 ──► LOCK ──► Database
Request 2 ──► WAIT
Request 3 ──► WAIT
Request 4 ──► WAIT

Depois:

Database
   │
   ▼
Cache atualizado
   │
   ▼
Requests recebem o resultado

Probabilistic Early Expiration

Podemos atualizar o cache antes de ele realmente expirar.

TTL = 60s

Após 50s
   │
   ▼
Refresh antecipado

Isso evita que milhares de requisições encontrem um cache vazio simultaneamente.


Background Refresh

Um worker atualiza o cache periodicamente:

Worker
  │
  ▼
Database
  │
  ▼
Cache

Assim, os usuários normalmente encontram os dados disponíveis.


21. Cache Penetration

Outro problema ocorre quando usuários consultam repetidamente dados que não existem.

Por exemplo:

GET product:999999999

O banco responde:

NOT FOUND

Como o dado não existe, não armazenamos nada.

Então:

Request
  │
  ▼
Cache MISS
  │
  ▼
Database
  │
  ▼
NOT FOUND

Se milhares de requisições fizerem isso:

Cache MISS
Cache MISS
Cache MISS
Cache MISS
Cache MISS

O banco pode ser sobrecarregado.

Uma solução é armazenar temporariamente o resultado negativo:

product:999999999 = NULL
TTL = 60s

Agora:

Request
  │
  ▼
Cache
  │
  ▼
NULL

Isso é conhecido como:

Negative Caching


22. Cache Avalanche

Imagine que temos milhares de chaves:

Cache A → TTL 60s
Cache B → TTL 60s
Cache C → TTL 60s
Cache D → TTL 60s

Se todas expirarem ao mesmo tempo:

60 segundos
    │
    ▼
TODOS EXPIRAM

Teremos:

Milhares de Cache Misses
          │
          ▼
       Database
          │
          ▼
      Sobrecarga

Isso é conhecido como:

Cache Avalanche

Uma estratégia comum é adicionar TTL aleatório:

TTL = 60s + Random(0-30s)

Assim, as expirações são distribuídas no tempo.


23. Eviction Policies

O cache possui memória limitada.

Imagine:

Cache
100 GB

Os dados continuam sendo adicionados:

100 GB
101 GB
102 GB

Em algum momento, precisamos remover dados.

Isso é chamado de:

Eviction

Algumas estratégias comuns:

LRU

Least Recently Used

Remove os dados que não são utilizados há mais tempo.

A → usado agora
B → usado recentemente
C → não usado há muito tempo

Remove:

C

LFU

Least Frequently Used

Remove os dados menos acessados.

A → 1.000 acessos
B → 500 acessos
C → 2 acessos

Remove:

C

FIFO

First In, First Out

Remove primeiro os dados mais antigos.

A → entrou primeiro
B
C → entrou por último

Remove:

A

24. Hot Keys

Imagine que temos:

product:123

Esse produto é extremamente popular.

Milhões de requisições:

Request
Request
Request
Request
Request
   │
   ▼
product:123

Essa chave é chamada de:

Hot Key

Ela pode sobrecarregar uma única instância do cache.

Uma solução pode ser replicar o valor:

product:123:A
product:123:B
product:123:C

E distribuir as leituras.

Outra opção é utilizar cache local na aplicação para dados extremamente populares.


25. Quando não devemos usar Cache?

Cache não é uma solução universal.

Ele pode não ser útil quando:

Dados raramente acessados

1 consulta por mês

O cache pode apenas consumir memória.


Dados mudam constantemente

Valor muda
100 vezes por segundo

Manter o cache atualizado pode ser mais complexo do que consultar diretamente a fonte.


Dados precisam de consistência imediata

Se o usuário exige:

Sempre o valor mais recente

o cache pode introduzir dados stale.

Nesse caso, precisamos avaliar cuidadosamente a estratégia.


26. Cache e Consistência

O cache adiciona uma nova cópia dos dados:

Database
   │
   ├── Valor A
   │
   ▼
Cache
   │
   └── Valor A

Agora temos duas fontes de informação.

Se o banco mudar:

Database = B
Cache = A

Temos inconsistência.

Por isso:

Cache é uma decisão de consistência, não apenas de performance.

Quanto mais agressivo o cache:

Performance ↑

maior pode ser:

Staleness ↑

Esse é um dos principais trade-offs.


27. Arquitetura Completa

Uma arquitetura web moderna pode utilizar múltiplas camadas de cache:

                         CLIENT
                            │
                            ▼
                    BROWSER CACHE
                            │
                            ▼
                           CDN
                            │
                            ▼
                     LOAD BALANCER
                            │
                ┌───────────┼───────────┐
                ▼           ▼           ▼
              API 1       API 2       API 3
                │           │           │
                └───────────┼───────────┘
                            │
                            ▼
                          REDIS
                            │
                            ▼
                         DATABASE

Cada camada reduz a carga da próxima.

Browser
   │
   │ Cache Hit
   ▼
CDN
   │
   │ Cache Hit
   ▼
Redis
   │
   │ Cache Hit
   ▼
Database

Idealmente:

Quanto mais próximo do usuário,
mais rápido o acesso.

28. Cache como ferramenta de escalabilidade

O cache não serve apenas para deixar o sistema mais rápido.

Ele também permite reduzir a carga em componentes mais caros.

Imagine:

Sem Cache

100.000 Requests
       │
       ▼
    Database

Com cache:

100.000 Requests
       │
       ▼
      Redis
       │
       ├── 95.000 HIT
       │
       └── 5.000 MISS
              │
              ▼
           Database

Agora o banco recebe:

5.000 requisições

em vez de:

100.000 requisições

Isso permite que o sistema suporte uma carga maior.


29. Cache e escalabilidade horizontal

Imagine:

           LOAD BALANCER
                 │
       ┌─────────┼─────────┐
       ▼         ▼         ▼
     API 1     API 2     API 3
       │         │         │
       └─────────┼─────────┘
                 │
                 ▼
               Redis
                 │
                 ▼
             Database

Podemos adicionar:

API 4
API 5
API 6

Sem precisar duplicar o cache.

Todas continuam utilizando:

Redis

Isso torna o cache distribuído uma peça importante em arquiteturas escaláveis.


30. Cache e o princípio de "source of truth"

Uma arquitetura saudável normalmente define:

Database
    │
    ▼
Source of Truth

E:

Cache
    │
    ▼
Cópia temporária

Isso significa que:

Cache pode desaparecer

Sem perder os dados permanentes.

Se o Redis cair:

Redis
  X

podemos temporariamente sofrer:

Cache Miss ↑
Database Load ↑
Latency ↑

Mas os dados continuam no banco.

Esse é um dos motivos pelos quais precisamos pensar em:

  • Cache fallback
  • Circuit breakers
  • Rate limiting
  • Replicação
  • Alta disponibilidade

31. Checklist para usar Cache

Antes de adicionar cache, pergunte:

O dado é acessado frequentemente?

Sim → Cache pode ajudar
Não → Talvez não valha a pena

O dado muda com frequência?

Muito → Cuidado com stale data
Pouco → Excelente candidato

Qual a tolerância a dados antigos?

Segundos?
Minutos?
Nunca?

Qual será o TTL?

10 segundos?
5 minutos?
1 hora?

O que acontece quando o cache falha?

Database fallback?
Erro?

Qual será o tamanho?

Quantas chaves?
Quantos GB?

Como invalidar?

TTL?
DELETE?
Write-through?
Eventos?

Existe risco de Cache Stampede?

Sim → Lock / Refresh antecipado

Existem Hot Keys?

Sim → Replicação / Cache local

32. Resumo

Cache

Armazena dados temporariamente para acelerar acessos.

Request
  │
  ▼
Cache
  │
  ├── HIT → Resposta
  │
  └── MISS → Database

Cache Hit

Dado encontrado.

Cache HIT

Cache Miss

Dado não encontrado.

Cache MISS

Cache-Aside

A aplicação controla o cache.

App
 │
 ▼
Cache
 │
 └── MISS
      │
      ▼
   Database
      │
      ▼
    Cache

Write-Through

Escreve no cache e no banco.

App
 │
 ▼
Cache
 │
 ▼
Database

Write-Back

Escreve primeiro no cache.

App
 │
 ▼
Cache
 │
 │ async
 ▼
Database

TTL

Define quando um item expira.

TTL
 │
 ▼
Expiração
 │
 ▼
Cache Miss

Cache Stampede

Muitas requisições encontram o cache vazio simultaneamente.

Cache MISS
Cache MISS
Cache MISS
Cache MISS
     │
     ▼
Database

Cache Penetration

Muitas consultas por dados inexistentes.

Solução:

Negative Caching

Cache Avalanche

Muitos itens expiram simultaneamente.

Solução:

TTL + Randomização

Conclusão

Cache é uma das ferramentas mais poderosas de System Design.

Ele pode transformar:

100.000 requisições
        │
        ▼
     Database

em:

100.000 requisições
        │
        ▼
       Cache
        │
        ├── 95.000 → HIT
        │
        └── 5.000 → Database

O resultado pode ser:

Latência ↓
Database Load ↓
Custo ↓
Throughput ↑
Escalabilidade ↑

Mas cache também introduz complexidade.

Agora precisamos lidar com:

Stale Data
Cache Invalidation
Cache Stampede
Cache Penetration
Cache Avalanche
Hot Keys
Eviction

Por isso, a pergunta não deve ser apenas:

"Posso colocar Redis aqui?"

A pergunta correta é:

"Qual dado vale a pena armazenar em cache, por quanto tempo, qual é a tolerância a dados desatualizados e o que acontece quando o cache falhar?"

Um bom sistema de cache é aquele que entende o comportamento dos dados e escolhe a estratégia adequada.

A regra mental mais importante é:

DADO FREQUENTE
      +
DADO QUE PODE SER REUTILIZADO
      +
TOLERÂNCIA A STALE DATA
      │
      ▼
    CACHE

E, principalmente:

Cache não é uma substituição do banco de dados. É uma camada de aceleração entre a aplicação e a fonte de verdade.

Quando bem utilizado, ele permite que sistemas suportem uma quantidade muito maior de usuários e requisições sem precisar aumentar proporcionalmente a capacidade do banco de dados.


Comments

No comments yet.