Pipelining do Redis
Pipelining permite enviar comandos em lote, reduzindo viagens de ida e volta na rede e melhorando drasticamente o desempenho.
O que é Pipelining?
Abordagem Tradicional
Cada comando requer uma viagem de ida e volta na rede:
Cliente Servidor Redis
| |
|----- SET key1 -------->|
|<----- OK --------------|
| |
|----- SET key2 -------->|
|<----- OK --------------|
| |
|----- SET key3 -------->|
|<----- OK --------------|
3 comandos = 6 transferências de rede (3 requisições + 3 respostas)
Abordagem com Pipelining
Múltiplos comandos enviados de uma vez:
Cliente Servidor Redis
| |
|----- SET key1 -------->|
|----- SET key2 -------->|
|----- SET key3 -------->|
| |
|<----- OK --------------|
|<----- OK --------------|
|<----- OK --------------|
3 comandos = 2 transferências de rede (1 requisição em lote + 1 resposta em lote)
💡 Ganho de desempenho: Pipelining reduz viagens de ida e volta na rede, melhorando drasticamente o desempenho — especialmente quando a latência da rede é alta.
Como o Pipelining Funciona
Efeito da Latência de Rede
Assumindo uma viagem de ida e volta de 1ms na rede:
Abordagem tradicional:
- 100 comandos = 100 viagens = 100ms de latência de rede
- Mais tempo de processamento Redis (assumir 0.1ms/comando) = 10ms
- Tempo total = 100ms + 10ms = 110ms
Pipelining:
- 100 comandos = 1 viagem = 1ms de latência de rede
- Mais tempo de processamento Redis = 10ms
- Tempo total = 1ms + 10ms = 11ms
Melhoria de desempenho: 110ms → 11ms, um aumento de 10x!
Características do Pipeline
- Envio em lote: múltiplos comandos enviados de uma vez
- Execução sequencial: Redis executa comandos em ordem
- Resposta em lote: todos os resultados retornados de uma vez
- Não atômico: não é uma transação — comandos podem ser interrompidos por outros clientes
⚠️ Observação: Pipelining não é uma transação e não garante atomicidade. Use MULTI/EXEC para atomicidade.
Usando Pipelining
Modo Pipeline do redis-cli
BASH
# Usar modo pipeline do redis-cli
echo -e "SET key1 value1\nSET key2 value2\nSET key3 value3" | redis-cli
# Ou ler de um arquivo
cat commands.txt | redis-cli
Pipelining em Python
PYTHON
import redis
r = redis.Redis(host='localhost', port=6379)
# Criar pipeline
pipe = r.pipeline()
# Adicionar comandos ao pipeline
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.set('key3', 'value3')
# Executar pipeline
resultados = pipe.execute()
print(resultados) # [True, True, True]
Pipelining em Java (Jedis)
JAVA
Jedis jedis = new Jedis("localhost", 6379);
Pipeline pipeline = jedis.pipelined();
pipeline.set("key1", "value1");
pipeline.set("key2", "value2");
pipeline.set("key3", "value3");
List<Object> results = pipeline.syncAndReturnAll();
Pipelining em Node.js (ioredis)
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
const pipeline = redis.pipeline();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.set('key3', 'value3');
const results = await pipeline.exec();
Pipeline vs Transações
Pipeline + Transações
Pipelining pode ser combinado com transações:
PYTHON
import redis
r = redis.Redis()
# Criar pipeline com transação habilitada
pipe = r.pipeline(transaction=True)
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.incr('counter')
# Executar transação
resultados = pipe.execute()
Pipeline vs Transação
| Aspecto | Pipeline | Transação |
|---|---|---|
| Atomicidade | ❌ Não | ✅ Sim |
| Isolamento | ❌ Não | ✅ Sim |
| Ganho de desempenho | ✅ Significativo | ✅ Significativo |
| Envio em lote | ✅ Sim | ✅ Sim |
| Caso de uso | Operações em lote, desempenho | Operações em lote que precisam de atomicidade |
💡 Escolha: Use pipeline quando a atomicidade não for necessária. Use transações ou pipeline+transação quando a atomicidade for necessária.
Casos de Uso do Pipeline
Caso de Uso 1: Configuração em Lote
PYTHON
import redis
r = redis.Redis()
pipe = r.pipeline()
# Definir 1000 chaves em lote
for i in range(1000):
pipe.set(f'key:{i}', f'value:{i}')
resultados = pipe.execute()
print(f'Definidas {len(resultados)} chaves')
Caso de Uso 2: Obtenção em Lote
PYTHON
import redis
r = redis.Redis()
pipe = r.pipeline()
# Obter 1000 chaves em lote
chaves = [f'key:{i}' for i in range(1000)]
for chave in chaves:
pipe.get(chave)
valores = pipe.execute()
print(f'Obtidos {len(valores)} valores')
Caso de Uso 3: Exclusão em Lote
PYTHON
import redis
r = redis.Redis()
pipe = r.pipeline()
# Exclusão em lote
chaves = ['key1', 'key2', 'key3', 'key4', 'key5']
for chave in chaves:
pipe.delete(chave)
resultados = pipe.execute()
print(f'Deletadas {sum(resultados)} chaves')
Caso de Uso 4: Importação de Dados
PYTHON
import redis
import json
r = redis.Redis()
pipe = r.pipeline()
# Importar dados de arquivo JSON
with open('data.json', 'r') as f:
dados = json.load(f)
for item in dados:
chave = f"user:{item['id']}"
valor = json.dumps(item)
pipe.set(chave, valor)
pipe.execute()
print('Importação concluída')
Caso de Uso 5: Atualização em Lote de Contadores
PYTHON
import redis
r = redis.Redis()
pipe = r.pipeline()
# Atualizar contadores em lote
contadores = {
'article:1:views': 10,
'article:2:views': 20,
'article:3:views': 30,
}
for chave, incremento in contadores.items():
pipe.incrby(chave, incremento)
pipe.execute()
print('Contadores atualizados')
Comparação de Desempenho do Pipeline
Código de Teste
PYTHON
import redis
import time
r = redis.Redis()
# Dados de teste
n = 10000
# Método 1: abordagem regular
inicio = time.time()
for i in range(n):
r.set(f'key:{i}', f'value:{i}')
fim = time.time()
print(f'Regular: {fim - inicio:.2f} segundos')
# Método 2: pipelining
inicio = time.time()
pipe = r.pipeline()
for i in range(n):
pipe.set(f'key:{i}', f'value:{i}')
pipe.execute()
fim = time.time()
print(f'Pipeline: {fim - inicio:.2f} segundos')
Resultados de Exemplo
Regular: 5.23 segundos
Pipeline: 0.51 segundos
Melhoria de desempenho: cerca de 10x
Melhores Práticas do Pipeline
1. Definir um Tamanho de Lote Razoável
PYTHON
# ❌ Enviar muitos comandos de uma vez
pipe = r.pipeline()
for i in range(100000):
pipe.set(f'key:{i}', f'value:{i}')
pipe.execute() # Pode usar muita memória
# ✅ Executar em lotes
tamanho_lote = 1000
for lote in range(0, 100000, tamanho_lote):
pipe = r.pipeline()
for i in range(lote, lote + tamanho_lote):
pipe.set(f'key:{i}', f'value:{i}')
pipe.execute()
2. Tratamento de Erros
PYTHON
import redis
r = redis.Redis()
pipe = r.pipeline()
pipe.set('key1', 'value1')
pipe.incr('key1') # Isso vai falhar (key1 não é um número)
pipe.set('key2', 'value2')
try:
resultados = pipe.execute()
for i, resultado in enumerate(resultados):
if isinstance(resultado, Exception):
print(f'Comando {i} falhou: {resultado}')
except Exception as e:
print(f'Execução do pipeline falhou: {e}')
3. Usar Transações para Atomicidade
PYTHON
import redis
r = redis.Redis()
# Usar transação quando a atomicidade for necessária
pipe = r.pipeline(transaction=True)
pipe.set('account:a', '100')
pipe.set('account:b', '50')
try:
resultados = pipe.execute()
print('Transação executada com sucesso')
except Exception as e:
print(f'Transação falhou: {e}')
4. Monitorar Desempenho do Pipeline
PYTHON
import redis
import time
r = redis.Redis()
inicio = time.time()
pipe = r.pipeline()
for i in range(10000):
pipe.set(f'key:{i}', f'value:{i}')
resultados = pipe.execute()
fim = time.time()
print(f'Tempo de execução: {fim - inicio:.2f} segundos')
print(f'QPS: {10000 / (fim - inicio):.0f}')
Limitações do Pipeline
1. Uso de Memória
PYTHON
# Pipeline armazena em cache todos os comandos e resultados
# Um lote muito grande usa muita memória
# Solução: executar em lotes
2. Não Atômico
PYTHON
# Comandos em um pipeline podem ser interrompidos por outros clientes
# Use transações quando a atomicidade for necessária
3. Não Pode Usar Resultados Intermediários
PYTHON
# Não pode usar o resultado de um comando anterior no pipeline
# ❌ Exemplo incorreto
pipe = r.pipeline()
pipe.incr('counter')
# Não pode obter o resultado de incr para comandos subsequentes
pipe.set('result', ???) # Não pode usar o resultado de incr
pipe.execute()
# ✅ Solução: usar scripts Lua
❓ Perguntas Frequentes
P Qual é a diferença entre pipeline e transação?
R Pipeline reduz viagens de rede, mas não garante atomicidade. Transações garantem atomicidade e também reduzem viagens de rede.
P Quanta melhoria de desempenho o pipelining pode proporcionar?
R Depende da latência da rede. Maior latência significa maior melhoria. Tipicamente 5-10x.
P Qual tamanho de lote devo usar?
R Recomendado 100-1000 comandos por lote. Muito grande usa muita memória; muito pequeno dá melhoria mínima.
P O que acontece se um comando no pipeline falhar?
R Uma falha de comando único não afeta outros comandos. Verifique os resultados retornados para erros.
P Quando devo usar pipelining?
R Operações em lote, importação/exportação de dados, e qualquer cenário onde a melhoria de desempenho seja necessária.
📖 Resumo
- Pipelining envia comandos em lote, reduzindo viagens de rede
- Melhoria significativa de desempenho, especialmente com alta latência de rede
- Pipelining não garante atomicidade — use transações quando necessário
- Casos de uso: configuração em lote, obtenção em lote, importação de dados
- Melhores práticas: tamanho de lote razoável, tratamento de erros, execução em lotes
- Limitações do pipeline: uso de memória, não atômico, não pode usar resultados intermediários
📝 Atividades
- Pipeline básico: Use pipelining para definir 100 chaves em lote, compare o desempenho com a abordagem regular
- Operações em lote: Implemente obtenção em lote e exclusão em lote com pipelining
- Comparação de desempenho: Teste o desempenho com diferentes tamanhos de lote (10, 100, 1000)
- Pipeline + transação: Implemente operações em lote atômicas usando pipeline com transações
Próxima Lição
Na próxima lição, nós aprenderemos sobre Python com Redis, abordando operações Redis com Python.



