Skip to main content
Documentación
CENTRO DE APRENDIZAJE

Domina la moderación de contenido con guías completas, tutoriales y documentación de API

Enlaces rápidos

Escalar la moderación de contenido

Una configuración de moderación que funciona con 1.000 mensajes al día se rompe al llegar al millón: los límites de frecuencia empiezan a afectar, la latencia por llamada se acumula y los costes se disparan. Esta guía muestra cómo integrar la moderación de Discuse en canalizaciones de alto rendimiento: cuándo bloquear, cuándo comprobar en segundo plano, qué almacenar en caché y cómo desviar la carga en los picos.

¿Qué falla cuando la moderación escala?

Cada problema de escalado tiene una causa concreta y una solución específica:

Desafío Impacto Solución
Crecimiento del volumen Límites de tasa de la API, aumento de costes Procesamiento asíncrono, caché
Requisitos de latencia Mala experiencia de usuario Premoderación, colas
Gestión de costes Restricciones presupuestarias Enrutamiento inteligente, caché
Consistencia Aplicación desigual Configuración centralizada
Gestión de picos Degradación del servicio Autoescalado, colas

¿Qué patrón de arquitectura debería usar?

Patrón 1: Premoderación síncrona

Ideal para: Contenido de bajo volumen y alto riesgo (pagos, legal)

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);
}

Ventajas: Sencillo, respuesta inmediata Desventajas: Añade latencia, limita el rendimiento

Patrón 2: Posmoderación asíncrona

Ideal para: Contenido de alto volumen y menor riesgo (comentarios, mensajes)

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' });
  }
});

Ventajas: No bloqueante, soporta mucho volumen Desventajas: El contenido dañino puede quedar visible brevemente

Patrón 3: Moderación por niveles

Ideal para: Un enfoque equilibrado con optimización de costes

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' };
}

Patrón 4: Distribución con agregación

Ideal para: Varios tipos de comprobación con escalado independiente

                    ┌──► 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
  };
}

Estrategias de caché

Caché basada en el contenido

Almacena en caché los resultados para contenido idéntico:

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 };
}

Caché basada en el usuario

Almacena en caché las decisiones para 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
}

Gestión de colas

Colas de prioridad

Gestiona distintos tipos de contenido con diferentes prioridades:

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)
  });
}

Respeto de los límites de tasa

Cada clave de API tiene un límite de solicitudes por minuto; si lo superas, la API rechazará la llamada con un error de "rate limit exceeded". Aplica control de velocidad en tu lado para mantenerte por debajo de ese límite y evitar consumir cuota en llamadas redundantes. Configura el límite por minuto del limitador para que coincida con las RPM configuradas para tu clave:

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));
}

La cuota es independiente del límite de tasa: cada llamada cuenta para el cupo mensual de solicitudes de API de tu plan, y el objeto usage de la respuesta informa api_requests_used, api_requests_limit y api_requests_remaining para que puedas controlar el margen disponible.

Colas de mensajes fallidos

Gestiona los intentos de moderación fallidos:

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);
  }
});

Optimización de costos

Enrutamiento inteligente

Llama a las APIs de pago solo cuando sea necesario:

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)
  };
}

Control de concurrencia

El endpoint /api/v2/check puntúa un envío por llamada. Para "procesar en lote", agrupa los elementos en tu worker y distribúyelos en paralelo con un pool limitado, en lugar de esperar una única solicitud con varios elementos. Una comprobación que incluye varias URL de medios cuenta como una sola llamada que las cubre todas: una llamada con muchas imágenes resulta más barata que muchas llamadas con una sola imagen.

// 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;
}

Una sola solicitud puede incluir hasta 10 URL de imágenes, 5 URL de GIF, 3 URL de vídeos, 5 URL de documentos y 20 enlaces, además de texto de hasta 10.000 caracteres; por tanto, consolida los medios de un envío en una sola llamada en lugar de dividirlos.

Monitoreo y alertas

Panel de métricas clave

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')
};

Reglas 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%

Escalado de la infraestructura

Escalado 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

Reducción de la latencia para usuarios de todo el mundo

Se accede a la API de Discuse desde una única URL base, https://api.discuse.com. Para mantener baja la latencia del tráfico global, ejecuta tus workers de moderación cerca de tus usuarios y deja que ellos llamen a la API, en lugar de invocarla de forma síncrona desde una ruta de solicitud orientada al usuario en una región lejana. Combina esto con los patrones de posmoderación asíncrona y almacenamiento en caché anteriores para que un recorrido de ida y vuelta lento nunca bloquee a un usuario.

Resumen de mejores prácticas

  1. Almacena en caché de forma agresiva: el contenido idéntico devuelve un resultado almacenado en caché. La API también deduplica el contenido idéntico por proyecto dentro de una ventana breve y devuelve cached: true.
  2. Usa colas: procesamiento asíncrono para grandes volúmenes.
  3. Implementa niveles: local → caché → API.
  4. Supervísalo todo: profundidad de la cola, latencia y uso respecto a tu cuota.
  5. Prepárate para fallos: colas de mensajes no entregados y alternativas seguras.
  6. Escala horizontalmente: más trabajadores, no máquinas más grandes.
  7. Optimiza el coste: enrutamiento inteligente, menos llamadas redundantes y medios consolidados por solicitud.

Próximos pasos

Escrito por Equipo de Discuse · Última actualización June 2026

Artículos relacionados

Guía de moderación de contenido con AI

Cómo el aprendizaje automático impulsa los sistemas modernos de moderación de contenido

Configuración de umbrales de detección

Equilibra falsos positivos y negativos para tu caso de uso