惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Google DeepMind News
Google DeepMind News
Martin Fowler
Martin Fowler
罗磊的独立博客
The GitHub Blog
The GitHub Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
P
Proofpoint News Feed
J
Java Code Geeks
小众软件
小众软件
Y
Y Combinator Blog
M
MIT News - Artificial intelligence
A
About on SuperTechFans
Last Week in AI
Last Week in AI
Jina AI
Jina AI
云风的 BLOG
云风的 BLOG
阮一峰的网络日志
阮一峰的网络日志
D
DataBreaches.Net
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Project Zero
Project Zero
美团技术团队
I
Intezer
Vercel News
Vercel News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
Netflix TechBlog - Medium
F
Fortinet All Blogs
T
Threatpost
P
Privacy International News Feed
Latest news
Latest news
K
Kaspersky official blog
博客园 - 聂微东
月光博客
月光博客
P
Palo Alto Networks Blog
T
The Exploit Database - CXSecurity.com
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
D
Docker
Cisco Talos Blog
Cisco Talos Blog
P
Privacy & Cybersecurity Law Blog
L
LINUX DO - 热门话题
Blog — PlanetScale
Blog — PlanetScale
Security Latest
Security Latest
Know Your Adversary
Know Your Adversary
Recent Announcements
Recent Announcements
T
Tenable Blog
B
Blog RSS Feed
Recorded Future
Recorded Future
雷峰网
雷峰网
SecWiki News
SecWiki News

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Redis: Big Keys Destroem o Desempenho Compartilhado
Lincoln Zocateli · 2026-05-25 · via DEV Community

Introdução

Se você usa Redis em um ambiente compartilhado — seja uma instância usada por múltiplos microserviços, múltiplos times ou múltiplos tenants — uma única chave grande pode degradar o desempenho de todos os outros consumidores ao mesmo tempo. Não é exagero: é como o Redis funciona internamente, e ignorar esse comportamento em produção custa caro.

O problema das chaves grandes (big keys) no Redis existe porque o servidor é single-threaded para operações de dados. Quando um comando acessa ou manipula uma chave que contém centenas de megabytes — seja uma lista com 500 mil elementos, um hash com 200 mil campos ou um string binário de 50 MB — o event loop inteiro fica bloqueado processando essa operação. Todos os outros clientes, de outros serviços completamente diferentes, ficam esperando na fila. Em ambientes compartilhados isso é fatal.

Neste artigo vou mostrar o que caracteriza uma chave grande no Redis, por que o impacto é amplificado em ambientes multi-tenant, como detectar big keys em produção, e as estratégias concretas para eliminar ou mitigar o problema — com exemplos em C# usando StackExchange.Redis.

O que é uma “Big Key” no Redis

O Redis não tem um limite fixo que define quando uma chave é “grande”. A comunidade e a documentação oficial usam o termo big key para descrever chaves cujo custo de processamento ou memória é desproporcional em relação ao workload geral da instância.

Na prática, os limites que costumam surgir como referência são:

Tipo de Dado Considerado “grande” a partir de
String / Binary > 5 MB
List > 10.000 elementos ou > 10 MB
Hash > 10.000 campos
Set > 10.000 membros
Sorted Set > 10.000 membros
Stream > 10.000 mensagens pendentes

Esses números não são absolutos — dependem do hardware, da versão do Redis e do padrão de acesso. Mas são referências sólidas para alertas de monitoramento.

ℹ️ Informação: O Redis 7.0+ introduziu melhorias nas estruturas de dados internas (como Listpack substituindo ZipList), o que mudou ligeiramente os limiares de quando estruturas “promovem” para representações maiores. O conceito de big key, no entanto, permanece o mesmo.

Diferença entre chave grande e chave quente

É comum confundir big key com hot key. São problemas diferentes:

  • Big key: chave com muito volume de dados — o problema é o custo de CPU/memória para processar o dado.
  • Hot key: chave acessada com altíssima frequência — o problema é a contenção no slot de hash (relevante em Redis Cluster). Um artigo pode ter ambos os problemas ao mesmo tempo, mas as soluções são distintas. Este artigo foca em big keys.

Como o Redis Processa Comandos (e por que isso importa)

Para entender o impacto de big keys, é preciso entender como o Redis executa comandos. O Redis usa um event loop single-threaded para processar operações de dados. Isso significa:

  • O servidor recebe um comando de um cliente.
  • O event loop executa o comando até o fim antes de processar qualquer outro.
  • Somente depois o próximo comando da fila é processado. Essa arquitetura é uma das razões pelo qual o Redis é tão rápido para operações simples: sem locks, sem context-switching entre threads. O custo é que uma operação lenta bloqueia tudo.
Cliente A: GET user:123          → resposta em 0,1ms ✅
Cliente B: LRANGE big-list 0 -1  → 2.3s processando 500k elementos ⚠️
Cliente C: SET config:timeout 30  → aguardando na fila... ⌛
Cliente D: GET session:abc123     → aguardando na fila... ⌛

⚠️ Atenção: Comandos como LRANGE key 0 -1, SMEMBERS key, HGETALL key e KEYS * são O(N) — o custo cresce linearmente com o tamanho da estrutura. Em uma chave com 500 mil elementos, isso pode levar segundos.

O modelo de I/O do Redis não é bloqueante — mas o processamento é

É importante separar dois conceitos:

  • I/O: o Redis usa multiplexação de I/O (epoll/kqueue), então aceitar conexões e ler dados de rede é não bloqueante.
  • Processamento de comandos: a execução do comando em si é single-threaded e bloqueante para os outros clientes. O Redis 6.0 introduziu threads de I/O para leitura e escrita de rede, mas a execução dos comandos ainda é single-threaded no thread principal. O problema das big keys não foi resolvido por essa mudança.

Impacto em Ambientes Compartilhados

Em um ambiente onde uma única instância Redis serve múltiplos microserviços ou múltiplos tenants de um SaaS, o impacto de uma big key é amplificado porque:

1. A latência afeta todos, não só o culpado

Se o serviço de relatórios lê uma chave com 300 mil registros toda hora, isso introduz picos de latência para o serviço de autenticação, o serviço de notificações e qualquer outro que compartilhe a mesma instância — mesmo que esses serviços estejam funcionando perfeitamente.

2. O consumo de memória é disputado

Redis mantém todos os dados em memória. Uma big key que ocupa 500 MB comprime o espaço disponível para todos os outros tenants. Quando o Redis atinge maxmemory, inicia a política de eviction (expulsão de chaves), que pode descartar dados críticos de outros serviços.

3. Operações de snapshot e replicação ficam mais lentas

Quando o Redis gera um RDB snapshot (persistência) ou replica dados para um replica, big keys aumentam o tempo do fork() e o tamanho do arquivo. Em instâncias cloud (AWS ElastiCache, Azure Cache for Redis), isso pode causar falhas de replicação ou penalidades de performance durante a sincronização.

4. Dificuldade de debug e rastreabilidade

Em um ambiente compartilhado, identificar quem criou a big key e qual serviço é o responsável exige instrumentação cuidadosa. Sem namespacing adequado nas chaves, rastrear a origem do problema pode levar horas.

Como Detectar Big Keys em Produção

O Redis oferece ferramentas nativas para detectar big keys. Veja as principais abordagens:

redis-cli –bigkeys

O comando mais simples para uma varredura inicial:

# Varredura completa — use com cuidado em produção (pode ser lenta)
redis-cli --bigkeys

# Com host e porta específicos
redis-cli -h redis.example.com -p 6379 --bigkeys

A saída mostra as maiores chaves por tipo de dado. O problema é que esse comando usa SCAN internamente e pode demorar em instâncias grandes. Em produção, prefira executá-lo em horários de menor carga.

MEMORY USAGE — verificação pontual

Para checar o tamanho de uma chave específica:

# Retorna o tamanho em bytes, incluindo overhead de metadados
MEMORY USAGE minha-chave

# Com nested sampling (para estruturas complexas)
MEMORY USAGE minha-chave SAMPLES 10

SCAN + MEMORY USAGE — varredura automatizada em C

Para monitoramento programático com StackExchange.Redis:

public class RedisBigKeyScanner
{
    private const long LimiarBigKeyBytes = 10 * 1024 * 1024; // 10 MB
    private readonly IDatabase _db;
    private readonly IServer _server;
    private readonly ILogger<RedisBigKeyScanner> _logger;

    public RedisBigKeyScanner(IConnectionMultiplexer redis, ILogger<RedisBigKeyScanner> logger)
    {
        _db = redis.GetDatabase();
        _server = redis.GetServer(redis.GetEndPoints().First());
        _logger = logger;
    }

    public async Task VarrerBigKeysAsync(string padrao = "*", int tamanhoPagina = 100,
        CancellationToken ct = default)
    {
        // SCAN é não bloqueante — itera em lotes (tamanhoPagina por vez)
        await foreach (var key in _server.KeysAsync(pattern: padrao, pageSize: tamanhoPagina)
                                         .WithCancellation(ct))
        {
            var sizeBytes = await _db.ExecuteAsync("MEMORY", "USAGE", key, "SAMPLES", "5");
            if (sizeBytes.IsNull) continue;

            var size = (long)sizeBytes;
            if (size >= LimiarBigKeyBytes)
                _logger.LogWarning("Big key: {Key} | {SizeMB:F2} MB", key, size / (1024.0 * 1024.0));
        }
    }
}

📂 Código Fonte: O exemplo completo está disponível no repositório de exemplos do blog:
BlogSamples/Cache/Redis/

📝 Exemplo: Em um ambiente com 2 milhões de chaves, uma varredura com pageSize = 500 e pattern = "relatorio:*" limita o escopo apenas ao namespace problemático, reduzindo o tempo de varredura de horas para minutos.

Redis Latency Monitoring

Para detectar o impacto em tempo real, ative o monitoramento de latência:

# Ativar no redis.conf ou via CONFIG SET
CONFIG SET latency-monitor-threshold 100

# Ver eventos de latência
LATENCY LATEST
LATENCY HISTORY command

Estratégias para Evitar Big Keys

Detectar o problema é o primeiro passo. Resolver é mais interessante. Existem quatro abordagens principais:

1. Fragmentação de dados (Sharding)

Em vez de uma única chave grande, distribuir os dados em múltiplas chaves menores usando um campo de particionamento:

public class RedisHashSharded
{
    private readonly IDatabase _db;
    private const int QuantidadeShards = 16; // potência de 2 para distribuição uniforme

    public RedisHashSharded(IConnectionMultiplexer redis) => _db = redis.GetDatabase();

    private string ObterChaveShard(string chaveBase, string campo)
    {
        var indiceShard = Math.Abs(campo.GetHashCode()) % QuantidadeShards;
        return $"{chaveBase}:shard:{indiceShard}";
    }

    public async Task GravarAsync(string chaveBase, string campo, string valor)
    {
        var chaveShard = ObterChaveShard(chaveBase, campo);
        await _db.HashSetAsync(chaveShard, campo, valor);
    }

    public async Task<string?> LerAsync(string chaveBase, string campo)
    {
        var chaveShard = ObterChaveShard(chaveBase, campo);
        var resultado = await _db.HashGetAsync(chaveShard, campo);
        return resultado.HasValue ? resultado.ToString() : null;
    }

    // LerTodosAsync paralleliza a leitura dos QuantidadeShards shards — código completo no repositório
}

2. Serialização eficiente com compressão

Um dos maiores culpados por strings grandes é a serialização ingênua de objetos complexos. Trocar JSON por um formato binário + compressão reduz dramaticamente o tamanho:

public static class RedisSerializerExtensions
{
    public static async Task GravarComprimidoAsync<T>(
        this IDatabase db, string chave, T valor, TimeSpan? expiracao = null)
    {
        using var stream = new MemoryStream();
        await using (var gzip = new GZipStream(stream, CompressionLevel.Optimal))
            await JsonSerializer.SerializeAsync(gzip, valor);

        await db.StringSetAsync(chave, stream.ToArray(), expiracao);
    }

    public static async Task<T?> LerComprimidoAsync<T>(this IDatabase db, string chave)
    {
        var valor = await db.StringGetAsync(chave);
        if (!valor.HasValue) return default;

        using var stream = new MemoryStream((byte[])valor!);
        await using var gzip = new GZipStream(stream, CompressionMode.Decompress);
        return await JsonSerializer.DeserializeAsync<T>(gzip);
    }
}

💡 Dica: Compressão GZip típica reduz payloads JSON em 60–80%. Um objeto de 5 MB pode cair para 800 KB — o que muda a classificação de “big key” para um tamanho gerenciável. Mas atenção: compressão tem custo de CPU, avalie o trade-off para dados acessados com alta frequência.

3. TTL obrigatório para chaves de cache

Chaves sem TTL em ambientes compartilhados tendem a acumular dados indefinidamente. Implemente uma política de TTL obrigatória:

public class RedisCacheComPoliticaTtl
{
    private readonly IDatabase _db;
    private static readonly TimeSpan TtlPadrao = TimeSpan.FromHours(1);
    private static readonly TimeSpan TtlMaximo = TimeSpan.FromHours(24);

    public RedisCacheComPoliticaTtl(IConnectionMultiplexer redis)
        => _db = redis.GetDatabase();

    public async Task GravarAsync(string chave, string valor, TimeSpan? ttl = null)
    {
        // Nunca permite TTL nulo — usa o padrão se não especificado
        var ttlEfetivo = ttl.HasValue
            ? TimeSpan.FromTicks(Math.Min(ttl.Value.Ticks, TtlMaximo.Ticks))
            : TtlPadrao;

        await _db.StringSetAsync(chave, valor, ttlEfetivo);
    }
}

4. Namespacing e segregação por tenant

Em ambientes multi-tenant, use prefixos obrigatórios para identificar a origem das chaves. Isso facilita o diagnóstico e permite políticas de limpeza seletivas:

// Padrão de chave: {ambiente}:{tenant}:{dominio}:{entidade}:{id}
// Exemplo: prod:acme-corp:relatorio:mensal:2026-05

public static class RedisKeyBuilder
{
    public static string Construir(
        string tenant,
        string dominio,
        string entidade,
        string id,
        string ambiente = "prod")
    {
        return $"{ambiente}:{Sanitizar(tenant)}:{Sanitizar(dominio)}:{Sanitizar(entidade)}:{id}";
    }

    private static string Sanitizar(string segmento) =>
        segmento.ToLowerInvariant().Replace(" ", "-").Replace(":", "");
}

Exemplo Prático

Vou mostrar um cenário real: um serviço de relatórios que carregava todos os registros de transações do mês em uma única chave Redis para “cache” — e estava derrubando o desempenho dos outros serviços no mesmo cluster.

O problema original

// ❌ CÓDIGO PROBLEMÁTICO — NÃO FAÇA ISSO
public async Task<List<Transacao>> ObterTransacoesMensaisAsync(
    int clienteId,
    int mes,
    int ano)
{
    var cacheKey = $"transacoes:{clienteId}:{mes}/{ano}";

    var cached = await _cache.StringGetAsync(cacheKey);
    if (cached.HasValue)
    {
        // Desserializa 300.000 transações em memória de uma vez
        return JsonSerializer.Deserialize<List<Transacao>>(cached!)!;
    }

    // Busca 300.000 registros do banco
    var transacoes = await _db.Transacoes
        .Where(t => t.ClienteId == clienteId
                 && t.Data.Month == mes
                 && t.Data.Year == ano)
        .ToListAsync();

    // Serializa 300.000 objetos para um JSON de ~80 MB e salva no Redis
    // Sem TTL → fica para sempre
    await _cache.StringSetAsync(
        cacheKey,
        JsonSerializer.Serialize(transacoes));

    return transacoes;
}

Esse código cria uma chave que pode ter 80 MB no Redis, sem TTL, e é lida inteira a cada acesso. Cada leitura bloqueia o event loop por centenas de milissegundos.

A solução refatorada

// ✅ SOLUÇÃO CORRETA — Cache paginado com TTL obrigatório

public class TransacaoCache
{
    private readonly IDatabase _redis;
    private const int TamanhoPagina = 100;
    private const string PrefixoChave = "tx";

    public TransacaoCache(IConnectionMultiplexer redis) => _redis = redis.GetDatabase();

    public async Task GravarPaginaAsync(int clienteId, int mes, int ano, int pagina,
        IEnumerable<Transacao> transacoes)
    {
        var chave = ConstruirChavePagina(clienteId, mes, ano, pagina);
        var json = JsonSerializer.Serialize(transacoes.Take(TamanhoPagina));
        // TTL de 30 minutos — dado de relatório, não precisa ser eterno
        await _redis.StringSetAsync(chave, json, TimeSpan.FromMinutes(30));
    }

    public async Task<List<Transacao>?> ObterPaginaAsync(int clienteId, int mes, int ano, int pagina)
    {
        var chave = ConstruirChavePagina(clienteId, mes, ano, pagina);
        var cache = await _redis.StringGetAsync(chave);
        return cache.HasValue ? JsonSerializer.Deserialize<List<Transacao>>((string)cache!) : null;
    }

    // Cada chave contém apenas TamanhoPagina transações (~30 KB)
    private static string ConstruirChavePagina(int clienteId, int mes, int ano, int pagina) =>
        $"{PrefixoChave}:{clienteId}:{mes}-{ano}:p{pagina}";
}

Com essa abordagem, cada chave passa de 80 MB para ~30 KB. O event loop é liberado em microssegundos, e os outros serviços compartilhados deixam de sofrer latência por conta do serviço de relatórios.

Dicas e Boas Práticas

  • Defina limiares de alerta no CI/CD: antes de um deploy, verifique se novos padrões de chave respeitam os limites de tamanho. Uma pipeline de testes de carga com redis-cli --bigkeys pode prevenir o problema antes de chegar à produção.

  • Use TTL em absolutamente todas as chaves: em ambientes compartilhados, uma chave sem TTL é uma chave que vai crescer indefinidamente. Configure maxmemory-policy allkeys-lru no Redis para que, mesmo que alguma chave escape sem TTL, o Redis possa descartá-la quando necessário.

  • Nunca use KEYS * em produção: o comando KEYS bloqueia o Redis enquanto varre todo o keyspace. Use sempre SCAN com MATCH e COUNT para varreduras iterativas e não bloqueantes. O StackExchange.Redis expõe esse comportamento através de IServer.KeysAsync().

  • Monitore redis-cli --latency-history: latências acima de 50ms recorrentes são sinal de big keys ou hot keys. Configure alertas de latência no Redis e correlacione com os padrões de chave que estão sendo acessados no momento.

  • Separe workloads em instâncias distintas quando possível: se um serviço de relatórios inevitavelmente precisa de chaves grandes, isole-o em uma instância Redis dedicada. O custo de uma instância extra é muito menor que o impacto de latência nos serviços críticos de um ambiente compartilhado.

  • Prefira estruturas nativas do Redis ao invés de strings JSON grandes: um HASH com 1.000 campos é mais eficiente que um STRING com um JSON de 1.000 propriedades. O Redis pode acessar campos individuais do hash sem deserializar a estrutura toda, economizando CPU e memória.

  • Revise o design ao usar Redis como banco de dados: Redis é excelente como cache de curta duração, pub/sub e contadores. Quando começa a receber coleções completas de entidades para “persistência temporária”, é um sinal de que o design precisa ser revisado — provavelmente um banco relacional ou documento é a ferramenta certa para aquele dado.

Resumo Objetivo

  • Big key no Redis — chave cujo volume de dados é desproporcional ao workload; limites práticos são 5 MB para strings, 10.000 elementos para listas/hashes/sets. A partir desses valores, operações O(N) bloqueiam o event loop por centenas de milissegundos.
  • Event loop single-threaded — o Redis processa um comando por vez no thread principal (mesmo no Redis 6.0+ com I/O threads). Uma big key bloqueia todos os outros clientes da instância enquanto é processada.
  • Ambientes compartilhados — em instâncias Redis usadas por múltiplos microserviços ou tenants, uma big key criada por um único serviço introduz latência para todos os outros, independentemente do seu comportamento.
  • Comandos O(N) perigososLRANGE key 0 -1, SMEMBERS key, HGETALL key, KEYS * e SUNIONSTORE são os principais vetores de bloqueio quando aplicados a chaves grandes. Prefira variantes paginadas como HSCAN, SSCAN e ZSCAN.
  • Detecção em produçãoredis-cli --bigkeys realiza varredura por tipo; MEMORY USAGE key SAMPLES N retorna o tamanho exato em bytes incluindo overhead; LATENCY LATEST identifica comandos com latência anômala.
  • Fragmentação como solução — dividir uma big key em múltiplas chaves menores usando sharding por campo elimina o bloqueio e permite paralelismo na leitura. Um hash com 100.000 campos pode ser dividido em 16 shards de ~6.250 campos cada.
  • TTL obrigatório — toda chave em ambiente compartilhado deve ter expiração. A ausência de TTL é a principal causa de acúmulo de big keys em produção. Configure maxmemory-policy allkeys-lru como rede de segurança.

Leia Também

  • Gargalo em Banco de Dados: Mensageria e Paginação — estratégias para resolver gargalos de escrita e leitura com mensageria e paginação eficiente no EF Core 8+.
  • .NET Worker e Background Service: Processamento de Alto Volume — como processar grandes volumes de dados em background sem sobrecarregar os serviços principais.
  • Programação Assíncrona em C#: async/await e Threads — fundamentos de programação assíncrona em C# para evitar bloqueios em operações de I/O, incluindo acesso a caches.
  • Paginação em APIs REST com C# e EF Core: Guia Prático — padrões de paginação para evitar retornar grandes volumes de dados de uma vez.

Referências

  • Redis Big Keys — Documentação Oficial — especificações de tipos de dados e comportamento interno do Redis por versão.
  • Redis Memory Optimization — Best Practices — guia oficial de otimização de memória no Redis, cobrindo encoding interno de estruturas de dados.
  • StackExchange.Redis — Documentação — documentação da biblioteca cliente .NET para Redis, incluindo uso de SCAN e MEMORY USAGE.
  • Redis Latency Problems — Troubleshooting Guide — guia oficial de diagnóstico de latência no Redis, com foco em comandos bloqueantes.
  • Anti-Patterns in Redis Usage — Redis Labs Blog — anti-padrões comuns de uso do Redis identificados pela equipe da Redis Ltd., incluindo big keys e hot keys.
  • Redis SCAN command — documentação do comando SCAN com exemplos de varredura não bloqueante para uso em produção. 📬

👉 Artigo completo com todos os exemplos de código: Redis: Big Keys Destroem o Desempenho Compartilhado