\n\n\n\n Padrões de Design de Banco de Dados Práticos para Bots de Produção - BotClaw Padrões de Design de Banco de Dados Práticos para Bots de Produção - BotClaw \n

Padrões de Design de Banco de Dados Práticos para Bots de Produção

📖 9 min read1,632 wordsUpdated Apr 5, 2026


Por que o banco de dados do seu bot secretamente controla tudo

O primeiro bot “bem-sucedido” que lancei em 2018 falhou não porque o modelo era ruim, ou o código era lento, mas porque o banco de dados estava uma bagunça.
Os usuários recebiam respostas destinadas a outra pessoa. As sessões “esqueciam” aleatoriamente quem você era. As análises eram inúteis.
Tudo porque tratei o DB como uma reflexão tardia.

Se você está criando bots para usuários reais, o banco de dados é a espinha dorsal. Não o modelo. Não o gateway. O modelo de dados.
Em 2026, temos LLMs mais legais e frameworks mais brilhantes, mas a mesma dor de antes:

  • “Onde armazeno o estado da conversa?”
  • “Como mantenho as sessões separadas?”
  • “O que eu registro sem estourar o armazenamento?”

Deixe-me explicar como eu projeto bancos de dados para bots que realmente funcionam em produção, e não apenas em demonstrações.

Entidades principais: usuários, sessões, mensagens

Todo bot que eu lanço começa com três tabelas. Os nomes mudam, as ideias não:

  • users — quem está falando com você
  • sessions — um período de conversa relacionada
  • messages — o que foi dito, em ordem

users

Eu trato os usuários como “quem esta plataforma acha que você é”, não “o conceito filosófico de uma pessoa”.

users
- id (pk)
- external_id (único, por exemplo, slack_user_id, telefone)
- platform (slack | web | whatsapp | etc.)
- created_at
- deleted_at (nulo)
 

Um usuário do Slack e um usuário do WhatsApp podem ser a mesma pessoa, mas eu não complico “identidade global” até realmente precisar.
Se você tentar resolver a identidade perfeitamente no dia um, vai ficar paralisado.

sessions

Uma sessão é “um fio de conversa coerente.” Para um bot de suporte, pode começar quando alguém faz uma pergunta e terminar após 30 minutos de silêncio ou quando um agente fecha o ticket.

sessions
- id (pk)
- user_id (fk users.id)
- platform (novamente, slack/web/etc.)
- status (ativo | fechado)
- started_at
- ended_at (nulo)
- metadata (jsonb) -- por exemplo, tópico, channel_id, ticket_id
 

Eu sempre armazeno metadata como JSON para informações específicas da plataforma que não quero que vazem para o esquema central.
Exemplo: channel_id do Slack, browser_session_id da web, o que for.

messages

Mensagens são a única fonte de verdade para “o que realmente aconteceu.”

messages
- id (pk)
- session_id (fk sessions.id)
- sender_type (user | bot | system)
- sender_id (nulo, fk users.id para usuário, talvez bot_id mais tarde)
- external_message_id (nulo, por exemplo, slack_ts)
- content (texto)
- content_type (texto | json | rico)
- created_at
- raw_payload (jsonb, nulo)
 

Duas razões pelas quais esta tabela é importante:

  • Depuração: quando alguém diz “o bot ficou louco”, você precisa de uma transcrição exata.
  • Treinamento: você vai querer conversas reais para ajustes finos ou alterações de prompt.

Para um bot que lida com ~50 mil mensagens/dia (um que rodamos no Postgres 15 na AWS RDS),
essa estrutura se manteve boa com indexação adequada em session_id e created_at.
Consultar as últimas 50 mensagens de uma sessão ainda é barato.

Onde colocar o “estado” do bot para não te prejudicar

O maior erro de design que vejo: entulhar o estado em todo lugar. Redis. JWTs. Colunas JSON aleatórias. Oculto em strings de prompt.
Então, ninguém sabe qual estado é o verdadeiro.

Estado de curto prazo: armazene em cache

Coisas que você precisa apenas durante a interação atual, e que pode reconstruir do zero, vão para um armazenamento rápido:

  • Redis, KeyDB, ou armazenamento em memória para chaves efêmeras
  • Expiração em minutos, não dias

Exemplos de chaves que realmente uso:

session_state:{session_id} -> json, ttl=30m
rate_limit:{user_id} -> contadores, ttl=1h
otp:{phone} -> código, ttl=5m
 

Se o Redis falhar, seu bot pode esquecer onde estava em um formulário. Irritante, mas não catastrófico.
Esse é o tipo certo de coisa para colocar ali.

Estado persistente: use banco de dados

Qualquer coisa que você se arrepender de perder vai para um banco de dados real:

  • Progresso de integração
  • Flags de recursos por usuário
  • Fluxos de trabalho de longo prazo (“aplicação de empréstimo #1234 etapa 3/7”)

Eu geralmente mantenho isso em uma tabela separada em vez de inchar sessions:

conversation_state
- id (pk)
- session_id (fk sessions.id, único)
- state_name (texto) -- por exemplo, "verificar_email", "coletar_endereço"
- data (jsonb) -- estado estruturado arbitrário
- updated_at
 

Sim, JSON. Não, não para tudo. Mas para o estado do fluxo do bot, JSON é bom. Ele muda com frequência, e você é o único consumidor.

Não esconda o estado em JWTs

“`html

Armazenar a lógica da sessão em declarações JWT é tentador. Também é assim que você obtém bugs onde vários clientes discordam sobre a realidade.
Eu mantenho JWTs para autenticação, e só isso. O estado vive no banco de dados ou no cache.

Registro, análise e não se afogar em dados

Bots geram uma quantidade absurda de dados. Se você registrar cada token para cada requisição e mantê-lo para sempre, sua conta de infraestrutura vai te lembrar mensalmente.

Separe “logs de runtime” e “dados de análise”

Eu trato-os como duas preocupações diferentes:

  • Logs de runtime: para depuração. Retenção curta (7–30 dias).
    Envie-os para algo como Loki, Elasticsearch ou CloudWatch.
  • Dados de análise: estruturados, consultáveis, a longo prazo. Vivem em SQL ou em um armazém.

Para análise, gosto de um esquema ou banco de dados separado:

conversation_metrics
- id (pk)
- session_id
- user_id
- messages_user_count
- messages_bot_count
- started_at
- ended_at
- first_response_ms
- resolved (bool)
 

Você computa isso através de um trabalho que escaneia messages todas as noites ou via streaming.
Em um bot de cliente, ativamos a agregação noturna em março de 2025 e cortamos pela metade as perguntas de suporte “o que está acontecendo?” porque conseguimos ver realmente os pontos de desistência.

Logs de prompt e modelo

Se o seu bot usa LLMs, mantenha um registro do que você enviou e do que recebeu de volta, com um apontador para a mensagem/sessão.

llm_calls
- id (pk)
- session_id
- message_id (fk messages.id, nullable)
- provider (openai | anthropic | local)
- model
- prompt_tokens
- completion_tokens
- cost_usd
- request_payload (jsonb) -- redacted
- response_payload (jsonb) -- redacted
- created_at
 

Duas notas importantes:

  • Redija segredos e PII antes de persistir logs.
  • Defina uma política de retenção. Você não precisa de 3 anos de prompts brutos para 99% dos bots.

SQL vs NoSQL vs armazéns vetoriais (e como eu os misturo)

Versão curta: Eu padrão para Postgres. Vou adicionar outros armazéns quando a dor for real, não hipotética.

Relacional (Postgres, MySQL)

Perfeito para:

  • Usuários, sessões, mensagens, estado
  • Relatórios, joins, migrações
  • Consistência forte para “quem disse o quê e quando”

Postgres com jsonb lhe dá flexibilidade suficiente para evitar criar cinco bancos de dados diferentes muito cedo.

Cache (Redis)

Use isso para:

  • Limites de taxa
  • Estado de sessão efêmero
  • Tabelas de pesquisa pequenas e quentes (flags de configuração, toggles de recurso)

Armazém vetorial

Se o seu bot faz geração aumentada por recuperação (RAG), você vai querer embeddings e busca por similaridade.
Na prática, vejo três padrões:

  • Postgres + pgvector
  • Serviços dedicados como Pinecone, Weaviate, Qdrant
  • Opções específicas de nuvem como Aurora + pgvector, AlloyDB, etc.

Eu me apoio no pgvector até:

  • Embeddings > ~5–10 milhões de linhas ou
  • Requisitos de latência de consulta se tornarem apertados (< 50ms) com tráfego intenso

Esboço do esquema:

documents
- id (pk)
- source (kb | ticket | faq)
- source_id
- content
- metadata (jsonb)
- created_at

document_embeddings
- id (pk)
- document_id (fk documents.id)
- embedding vector(1536)
- created_at
 

Mantenha o “o que” (documentos) separado do “como buscamos isso” (embeddings).
Você vai mudar de modelo; não quer que isso esteja misturado com seu conteúdo central.

FAQ: perguntas sobre esquema que continuo recebendo

P: Devo armazenar mensagens completas no DB ou apenas resumos?

Armazene mensagens completas, pelo menos por 30–90 dias. Resumos são ótimos para busca e análise, mas quando algo quebra, você quer o texto bruto.
Se você está preocupado com armazenamento, defina uma política de retenção e archive mensagens antigas para um armazenamento mais barato.

P: Como eu lido com bots multi-tenant (muitos clientes)?

Adicione um tenant_id em todos os lugares que importam: users, sessions, messages, conversation_state.
Indexe. Cada consulta que toca em dados de usuário deve filtrar por tenant_id.
Se você precisar de isolamento mais rigoroso depois, pode dividir inquilinos entre esquemas ou bancos de dados, mas comece com uma coluna de inquilino sólida.

P: Onde devo armazenar arquivos (imagens, PDFs) que os usuários enviam?

Não no banco de dados. Armazene-os no S3/GCS/Blob storage e mantenha referências:

attachments
- id (pk)
- message_id (fk messages.id)
- file_url
- file_type
- size_bytes
- created_at
 

Se você precisar executar OCR ou embedding neles, armazene essa saída em tabelas separadas vinculadas de volta via attachment_id.


“`

🕒 Published:

🛠️
Written by Jake Chen

Full-stack developer specializing in bot frameworks and APIs. Open-source contributor with 2000+ GitHub stars.

Learn more →
Browse Topics: Bot Architecture | Business | Development | Open Source | Operations

More AI Agent Resources

AidebugClawgoAgntdevAgnthq
Scroll to Top