Skip to main content
Documentação
CENTRO DE APRENDIZAGEM

Domine a moderação de conteúdo com guias completos, tutoriais e documentação da API

Links rápidos

Escalando a moderação de conteúdo

Uma configuração de moderação que funciona com 1.000 mensagens por dia não se sustenta em um milhão: os limites de taxa começam a pesar, a latência por chamada se acumula e os custos disparam. Este guia mostra como integrar a moderação da Discuse a pipelines de alto volume — quando bloquear, quando verificar em segundo plano, o que armazenar em cache e como contornar picos de carga.

O que deixa de funcionar quando a moderação escala?

Cada problema de escala tem uma causa específica e uma correção específica:

Desafio Impacto Solução
Crescimento do volume Limites de taxa da API, aumento de custos Processamento assíncrono, cache
Requisitos de latência Experiência ruim para o usuário Pré-moderação, filas
Gestão de custos Restrições de orçamento Roteamento inteligente, cache
Consistência Aplicação irregular das regras Configuração centralizada
Tratamento de picos Degradação do serviço Escalonamento automático, filas

Qual padrão de arquitetura devo usar?

Padrão 1: Pré-moderação síncrona

Ideal para: Conteúdo de baixo volume e alto impacto (pagamentos, jurídico)

User Input → API Call → Moderation → Decision → Response
                ↓
           (Blocking call)
// Simple synchronous flow
async function createPost(content) {
  // Check moderation before saving
  const result = await checkModeration(content);

  if (result.has_violations) {
    throw new ModerationError(result.message);
  }

  // Safe to publish
  return await savePost(content);
}

Prós: Simples, feedback imediato Contras: Aumenta a latência, limita a taxa de processamento

Padrão 2: Pós-moderação assíncrona

Ideal para: Conteúdo de alto volume e menor impacto (comentários, mensagens)

User Input → Save (Pending) → Publish → Background Check → Action
                                              ↓
                                    ┌────────┴────────┐
                                    ↓                 ↓
                                  Safe            Violation
                                    ↓                 ↓
                                  Keep             Remove
// Async flow with queue
async function createPost(content) {
  // Save immediately with pending status
  const post = await savePost(content, { status: 'pending' });

  // Queue for async moderation
  await moderationQueue.add('check-content', {
    postId: post.id,
    content: content
  });

  return post;
}

// Background worker
moderationQueue.process('check-content', async (job) => {
  const result = await checkModeration(job.data.content);

  if (result.has_violations) {
    await removePost(job.data.postId);
    await notifyUser(job.data.postId, 'content_removed');
  } else {
    await updatePost(job.data.postId, { status: 'approved' });
  }
});

Prós: Não bloqueia, lida bem com volume Contras: Conteúdo prejudicial fica visível por um curto período

Padrão 3: Moderação em camadas

Ideal para: Uma abordagem equilibrada com otimização de custos

User Input → Quick Check → High Confidence? ──Yes──► Auto-decision
                              │
                              No
                              ↓
                         Full Analysis ──► Decision
async function tieredModeration(content) {
  // Tier 1: Fast local checks (regex, blocklists)
  const localResult = await quickLocalCheck(content);
  if (localResult.definiteViolation) {
    return { action: 'block', source: 'local' };
  }

  // Tier 2: Cached API results
  const cacheKey = hashContent(content);
  const cached = await cache.get(cacheKey);
  if (cached) {
    return { action: cached.action, source: 'cache' };
  }

  // Tier 3: Full API check
  const apiResult = await checkModeration(content);
  await cache.set(cacheKey, apiResult, TTL);

  return { action: determineAction(apiResult), source: 'api' };
}

Padrão 4: Distribuição com agregação

Ideal para: Vários tipos de verificação com escalabilidade independente

                    ┌──► Text Check ──┐
                    │                  │
User Input ──► Router ──► Image Check ──► Aggregator ──► Decision
                    │                  │
                    └──► Link Check ──┘
async function parallelModeration(content) {
  const checks = [];

  if (content.text) {
    checks.push(checkText(content.text));
  }

  if (content.images?.length) {
    checks.push(checkImages(content.images));
  }

  if (content.links?.length) {
    checks.push(checkLinks(content.links));
  }

  // Run all checks in parallel
  const results = await Promise.all(checks);

  // Aggregate results
  return aggregateResults(results);
}

function aggregateResults(results) {
  // Most severe result wins
  const hasViolation = results.some(r => r.has_violations);
  const maxScore = Math.max(...results.map(r => r.max_score || 0));

  return {
    has_violations: hasViolation,
    max_score: maxScore,
    details: results
  };
}

Estratégias de cache

Cache com base no conteúdo

Armazene em cache os resultados para conteúdos idênticos:

const cache = new Redis();

async function checkWithCache(content) {
  const hash = crypto.createHash('sha256')
    .update(content.text || '')
    .update(JSON.stringify(content.images || []))
    .digest('hex');

  // Check cache first
  const cached = await cache.get(`mod:${hash}`);
  if (cached) {
    return { ...JSON.parse(cached), cached: true };
  }

  // API call
  const result = await callModerationAPI(content);

  // Cache for 24 hours
  await cache.setex(`mod:${hash}`, 86400, JSON.stringify(result));

  return { ...result, cached: false };
}

Cache com base no usuário

Armazene em cache as decisões para infratores reincidentes:

async function checkUserHistory(userId) {
  const recentViolations = await cache.get(`violations:${userId}`);

  if (recentViolations > 5) {
    // Flag for enhanced scrutiny
    return { enhancedModeration: true };
  }

  return { enhancedModeration: false };
}

async function recordViolation(userId) {
  await cache.incr(`violations:${userId}`);
  await cache.expire(`violations:${userId}`, 86400 * 7); // 7 days
}

Gerenciamento de filas

Filas de prioridade

Trate diferentes tipos de conteúdo com prioridades diferentes:

const queues = {
  critical: new Queue('moderation-critical'),  // Reports, appeals
  high: new Queue('moderation-high'),          // Public posts
  normal: new Queue('moderation-normal'),      // Comments, DMs
  low: new Queue('moderation-low')             // Profile updates
};

async function queueForModeration(content, priority = 'normal') {
  const queue = queues[priority] || queues.normal;

  return queue.add('check', {
    contentId: content.id,
    type: content.type,
    data: content
  }, {
    priority: getPriorityNumber(priority),
    timeout: getTimeout(priority)
  });
}

Respeitando limites de taxa

Cada chave de API tem um limite de requisições por minuto; ultrapassá-lo faz com que a API rejeite a chamada com um erro de "rate limit exceeded". Aplique limitação do seu lado para permanecer abaixo desse limite e evitar gastar cota com chamadas redundantes. Defina o teto por minuto do limitador para corresponder ao RPM configurado da sua chave:

const Bottleneck = require('bottleneck');

const limiter = new Bottleneck({
  maxConcurrent: 50,                   // Cap parallel requests
  minTime: 20,                         // Min 20ms between requests
  reservoir: YOUR_KEY_RPM,             // Match your API key's per-minute limit
  reservoirRefreshAmount: YOUR_KEY_RPM,
  reservoirRefreshInterval: 60 * 1000  // Refill each minute
});

async function rateLimitedCheck(content) {
  return limiter.schedule(() => checkModeration(content));
}

A cota é separada do limite de taxa: cada chamada conta para a franquia mensal de requisições de API do seu plano, e o objeto usage da resposta informa api_requests_used, api_requests_limit e api_requests_remaining para que você possa acompanhar a margem disponível.

Filas de mensagens mortas

Trate tentativas de moderação que falharam:

moderationQueue.process('check-content', async (job) => {
  try {
    const result = await checkModeration(job.data.content);
    await applyDecision(job.data.contentId, result);
  } catch (error) {
    if (job.attemptsMade < 3) {
      throw error; // Retry
    }

    // Move to dead letter queue after 3 attempts
    await deadLetterQueue.add('failed-moderation', {
      ...job.data,
      error: error.message,
      attempts: job.attemptsMade
    });

    // Apply safe default (hold for review)
    await holdForManualReview(job.data.contentId);
  }
});

Otimização de custos

Roteamento inteligente

Chame APIs pagas apenas quando necessário:

async function smartModeration(content) {
  // Step 1: Free local checks
  const localResult = runLocalFilters(content);
  if (localResult.isDefinitelySpam) {
    return { action: 'block', cost: 0 };
  }

  // Step 2: Check cache (free)
  const cached = await getCachedResult(content);
  if (cached) {
    return { action: cached.action, cost: 0 };
  }

  // Step 3: Risk-based API call
  const riskScore = calculateRiskScore(content, localResult);

  if (riskScore < 0.2) {
    // Low risk: approve without API call
    return { action: 'approve', cost: 0 };
  }

  // Step 4: API call for uncertain content
  const apiResult = await checkModeration(content);
  return {
    action: determineAction(apiResult),
    cost: calculateApiCost(content)
  };
}

Controle de concorrência

O endpoint /api/v2/check avalia um envio por chamada. Para processar em "lote", agrupe os itens no seu worker e distribua-os simultaneamente dentro de um pool limitado, em vez de esperar uma única solicitação com vários itens. Uma verificação que inclui várias URLs de mídia continua sendo uma única chamada que cobre todas elas — uma chamada com muitas imagens sai mais barata do que várias chamadas com uma única imagem.

// Bounded concurrency: many items, capped parallel calls.
async function processBatch(items, concurrency = 10) {
  const results = [];
  for (let i = 0; i < items.length; i += concurrency) {
    const slice = items.slice(i, i + concurrency);
    const settled = await Promise.allSettled(
      slice.map(item => checkModeration(item.content))
    );
    results.push(...settled);
  }
  return results;
}

Uma única solicitação pode conter até 10 URLs de imagens, 5 URLs de GIFs, 3 URLs de vídeos, 5 URLs de documentos e 20 links, além de texto com até 10.000 caracteres — portanto, consolide a mídia de um envio em uma única chamada em vez de dividi-la.

Monitoramento e alertas

Painel de métricas principais

const metrics = {
  // Volume metrics
  total_requests: new Counter('moderation_requests_total'),
  requests_per_second: new Gauge('moderation_rps'),

  // Latency metrics
  latency_p50: new Histogram('moderation_latency_p50'),
  latency_p99: new Histogram('moderation_latency_p99'),

  // Queue metrics
  queue_depth: new Gauge('moderation_queue_depth'),
  processing_time: new Histogram('moderation_processing_time'),

  // Cost metrics
  api_calls: new Counter('moderation_api_calls'),
  estimated_cost: new Counter('moderation_estimated_cost'),

  // Accuracy metrics
  violations_detected: new Counter('moderation_violations'),
  false_positives: new Counter('moderation_false_positives')
};

Regras de alerta

# Alert on queue backup
- alert: ModerationQueueBackup
  expr: moderation_queue_depth > 10000
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: Moderation queue backing up

# Alert on high latency
- alert: ModerationLatencyHigh
  expr: moderation_latency_p99 > 5000
  for: 2m
  labels:
    severity: critical
  annotations:
    summary: Moderation latency exceeding 5 seconds

# Alert on high error rate
- alert: ModerationErrorRate
  expr: rate(moderation_errors_total[5m]) / rate(moderation_requests_total[5m]) > 0.01
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: Moderation error rate above 1%

Escalabilidade da infraestrutura

Escalabilidade horizontal

# Kubernetes HPA for moderation workers
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: moderation-worker
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: moderation-worker
  minReplicas: 3
  maxReplicas: 50
  metrics:
  - type: External
    external:
      metric:
        name: queue_depth
      target:
        type: AverageValue
        averageValue: 100

Redução da latência para usuários globais

A API da Discuse é acessada por uma única URL base, https://api.discuse.com. Para manter a latência baixa no tráfego global, execute seus workers de moderação perto dos seus usuários e deixe que eles chamem a API, em vez de chamá-la de forma síncrona a partir de um fluxo de requisição voltado ao usuário em uma região distante. Combine isso com os padrões de pós-moderação assíncrona e cache acima para que uma ida e volta lenta nunca bloqueie um usuário.

Resumo das melhores práticas

  1. Use cache de forma agressiva: conteúdos idênticos retornam um resultado em cache. A API também elimina duplicidades de conteúdos idênticos por projeto em uma janela curta e retorna cached: true.
  2. Use filas: processamento assíncrono para grandes volumes.
  3. Implemente camadas: local → cache → API.
  4. Monitore tudo: profundidade da fila, latência e uso em relação à sua cota.
  5. Planeje-se para falhas: filas de mensagens não entregues e alternativas seguras.
  6. Escale horizontalmente: mais workers, não máquinas maiores.
  7. Otimize custos: roteamento inteligente, menos chamadas redundantes e mídia consolidada por solicitação.

Próximos passos

Escrito por Equipe Discuse · Última atualização em June 2026

Artigos relacionados

Guia de moderação de conteúdo com AI

Como o aprendizado de máquina impulsiona os sistemas modernos de moderação de conteúdo

Configuração de limites de detecção

Equilibre falsos positivos e negativos para seu caso de uso