speed 18. Cache e Performance - Otimização Avançada
Performance é essencial. Usuários abandonam sites que demoram mais de 3 segundos para carregar. Cache é a técnica mais eficaz para acelerar sua aplicação: em vez de processar a mesma requisição várias vezes, você armazena o resultado e retorna instantaneamente.
layers Tipos de Cache
storage Database Cache
Cacheia resultados de queries SQL. Ideal para dados que mudam pouco (listagem de cursos).
api API Response Cache
Cacheia respostas completas de endpoints. Perfeito para dados públicos (home, sobre).
web Browser Cache
CSS, JS e imagens são cacheados no navegador. Reduz tráfego e acelera carregamento.
code Backend PHP - Cache Service (Redis)
apt-get install redis-server (Linux) ou
brew install redis (Mac). Windows: use Docker.
// services/CacheService.php
prefix = $prefix;
$this->redis = new Redis();
$this->redis->connect(
$_ENV['REDIS_HOST'] ?? '127.0.0.1',
$_ENV['REDIS_PORT'] ?? 6379
);
if (!empty($_ENV['REDIS_PASSWORD'])) {
$this->redis->auth($_ENV['REDIS_PASSWORD']);
}
}
/**
* Obter valor do cache
*/
public function get(string $key) {
$value = $this->redis->get($this->makeKey($key));
if ($value === false) {
return null;
}
return json_decode($value, true);
}
/**
* Armazenar no cache
*/
public function set(string $key, $value, ?int $ttl = null): bool {
$ttl = $ttl ?? $this->defaultTtl;
return $this->redis->setex(
$this->makeKey($key),
$ttl,
json_encode($value)
);
}
/**
* Deletar do cache
*/
public function delete(string $key): bool {
return $this->redis->del($this->makeKey($key)) > 0;
}
/**
* Limpar cache por padrão
*/
public function flush(string $pattern = '*'): void {
$keys = $this->redis->keys($this->prefix . ':' . $pattern);
if (!empty($keys)) {
$this->redis->del(...$keys);
}
}
/**
* Remember: busca do cache ou executa callback
*/
public function remember(string $key, callable $callback, ?int $ttl = null) {
$value = $this->get($key);
if ($value !== null) {
return $value;
}
$value = $callback();
$this->set($key, $value, $ttl);
return $value;
}
/**
* Incrementar contador
*/
public function increment(string $key, int $by = 1): int {
return $this->redis->incrBy($this->makeKey($key), $by);
}
/**
* Gerar chave com prefixo
*/
private function makeKey(string $key): string {
return $this->prefix . ':' . $key;
}
}
// ===== EXEMPLOS DE USO =====
$cache = new CacheService();
// Cachear lista de cursos
$courses = $cache->remember('courses:all', function() use ($db) {
$stmt = $db->query("SELECT * FROM courses WHERE status = 'published'");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}, 3600); // Cache por 1 hora
// Quando criar/editar curso, limpar cache
$cache->delete('courses:all');
// Cachear dados de um curso específico
$courseId = 123;
$course = $cache->remember("course:{$courseId}", function() use ($db, $courseId) {
$stmt = $db->prepare("SELECT * FROM courses WHERE id = ?");
$stmt->execute([$courseId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}, 7200); // 2 horas
optimization Otimização de Queries SQL
// ❌ LENTO - Query sem índice
SELECT * FROM users WHERE email = 'joao@email.com';
// Tempo: 500ms (tabela com 100k registros)
// ✅ RÁPIDO - Adicionar índice
CREATE INDEX idx_email ON users(email);
// Tempo: 5ms
// ❌ LENTO - N+1 Problem
$courses = $db->query("SELECT * FROM courses")->fetchAll();
foreach ($courses as $course) {
// 1 query por curso!
$stmt = $db->prepare("SELECT * FROM course_lessons WHERE course_id = ?");
$stmt->execute([$course['id']]);
$lessons = $stmt->fetchAll();
}
// Total: 1 + N queries (se 100 cursos = 101 queries)
// ✅ RÁPIDO - Eager Loading com JOIN
$sql = "
SELECT
c.*,
JSON_ARRAYAGG(
JSON_OBJECT(
'id', l.id,
'title', l.title,
'duration', l.duration_minutes
)
) as lessons
FROM courses c
LEFT JOIN course_lessons l ON c.id = l.course_id
GROUP BY c.id
";
// Total: 1 query apenas!
// ===== PAGINAÇÃO EFICIENTE =====
// ❌ LENTO - OFFSET alto
SELECT * FROM courses LIMIT 100 OFFSET 50000;
// Banco precisa processar 50.100 linhas
// ✅ RÁPIDO - Cursor-based pagination
SELECT * FROM courses WHERE id > ? LIMIT 100;
// Usa índice primary key
// ===== QUERY OPTIMIZATION TIPS =====
// 1. Use EXPLAIN para analisar queries
EXPLAIN SELECT * FROM courses WHERE category_id = 5;
// 2. Sempre tenha índices em foreign keys
CREATE INDEX idx_category_id ON courses(category_id);
// 3. Evite SELECT * quando possível
SELECT id, title, price FROM courses; // Mais rápido
// 4. Use COUNT(*) com WHERE otimizado
SELECT COUNT(*) FROM courses WHERE status = 'published';
CREATE INDEX idx_status ON courses(status);
// 5. Limite resultados quando possível
SELECT * FROM courses LIMIT 100; // Não retorne milhares de registros
web Frontend React - Cache e Memoization
// React Query - Cache automático de requisições
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
const API_URL = import.meta.env.VITE_API_URL;
// Hook com cache
export const useCourses = () => {
return useQuery({
queryKey: ['courses'],
queryFn: async () => {
const { data } = await axios.get(`${API_URL}/courses`);
return data.courses;
},
staleTime: 5 * 60 * 1000, // 5 minutos
cacheTime: 10 * 60 * 1000, // 10 minutos
});
};
// Mutação com invalidação de cache
export const useCreateCourse = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (courseData) => {
const { data } = await axios.post(`${API_URL}/courses`, courseData);
return data;
},
onSuccess: () => {
// Invalida cache para refetch
queryClient.invalidateQueries(['courses']);
}
});
};
// Uso no componente:
const CoursesList = () => {
const { data: courses, isLoading, error } = useCourses();
if (isLoading) return Carregando...;
if (error) return Erro: {error.message};
return (
{courses.map(course => (
))}
);
};
// ===== MEMOIZATION - useMemo e useCallback =====
import { useMemo, useCallback } from 'react';
const ExpensiveComponent = ({ items, searchTerm }) => {
// Cacheia resultado de computação pesada
const filteredItems = useMemo(() => {
console.log('Filtrando items...');
return items.filter(item =>
item.title.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [items, searchTerm]); // Só recalcula se items ou searchTerm mudar
// Cacheia função para não recriar a cada render
const handleClick = useCallback((id) => {
console.log('Clicked:', id);
}, []); // Função nunca muda
return (
{filteredItems.map(item => (
handleClick(item.id)}>
{item.title}
))}
);
};
// ===== VIRTUALIZAÇÃO - React Window =====
import { FixedSizeList } from 'react-window';
const VirtualizedList = ({ items }) => {
// Renderiza apenas itens visíveis
const Row = ({ index, style }) => (
{items[index].title}
);
return (
{Row}
);
};
check_circle Checklist de Performance
✅ Backend
- Implementar cache com Redis/Memcached
- Adicionar índices em colunas pesquisadas
- Evitar N+1 queries (usar JOINs)
- Paginar resultados grandes
- Usar prepared statements (evita parsing)
- Habilitar OPcache do PHP
- Comprimir respostas (gzip)
✅ Frontend
- Usar React Query para cache de API
- Lazy loading de componentes
- Code splitting (vite build)
- Otimizar imagens (WebP, lazy load)
- useMemo para computações pesadas
- useCallback para funções estáveis
- Virtualização para listas grandes