Skip to main content
Dokumentasi
Pusat Pembelajaran

Kuasai moderasi konten dengan panduan lengkap, tutorial, dan dokumentasi API

Tautan Cepat

Menskalakan Moderasi Konten

Pengaturan moderasi yang berjalan baik untuk 1.000 pesan per hari bisa kewalahan saat mencapai satu juta: batas laju mulai terasa, latensi per panggilan menumpuk, dan biaya membengkak. Panduan ini menunjukkan cara mengintegrasikan moderasi Discuse ke dalam pipeline ber-throughput tinggi — kapan harus memblokir, kapan memeriksa di latar belakang, apa yang perlu di-cache, dan cara mengalihkan beban saat trafik puncak.

Apa yang terganggu saat moderasi meningkat skalanya?

Setiap masalah penskalaan memiliki penyebab dan solusi yang spesifik:

Tantangan Dampak Solusi
Pertumbuhan volume Batas laju API, biaya meningkat Pemrosesan asinkron, caching
Kebutuhan latensi Pengalaman pengguna yang buruk Pra-moderasi, antrean
Pengelolaan biaya Keterbatasan anggaran Routing cerdas, caching
Konsistensi Penegakan aturan tidak merata Konfigurasi terpusat
Penanganan puncak Penurunan kualitas layanan Auto-scaling, antrean

Pola arsitektur mana yang sebaiknya saya gunakan?

Pola 1: Pra-moderasi sinkron

Paling cocok untuk: Konten bervolume rendah dengan risiko tinggi (pembayaran, hukum)

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

Kelebihan: Sederhana, umpan balik langsung Kekurangan: Menambah latensi, membatasi throughput

Pola 2: Pasca-moderasi asinkron

Paling cocok untuk: Konten bervolume tinggi dengan risiko lebih rendah (komentar, pesan)

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

Kelebihan: Tidak memblokir, mampu menangani volume besar Kekurangan: Konten berbahaya sempat terlihat sebentar

Pola 3: Moderasi bertingkat

Paling cocok untuk: Pendekatan seimbang dengan optimasi biaya

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

Pola 4: Fan-out dengan agregasi

Paling cocok untuk: Beberapa jenis pemeriksaan dengan penskalaan independen

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

Strategi caching

Caching berbasis konten

Cache hasil untuk konten yang identik:

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

Caching berbasis pengguna

Cache keputusan untuk pelanggar berulang:

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
}

Manajemen antrean

Antrean prioritas

Tangani berbagai jenis konten dengan prioritas yang berbeda:

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

Mematuhi batas laju

Setiap kunci API memiliki batas permintaan per menit; jika melampauinya, API akan menolak panggilan dengan error "rate limit exceeded". Lakukan pembatasan di sisi Anda agar tetap berada di bawah batas tersebut dan menghindari pemborosan kuota untuk panggilan yang redundan. Atur batas per menit pada limiter agar sesuai dengan RPM yang dikonfigurasi untuk kunci Anda:

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

Kuota terpisah dari batas laju: setiap panggilan dihitung terhadap jatah permintaan API bulanan pada paket Anda, dan objek usage dalam respons melaporkan api_requests_used, api_requests_limit, dan api_requests_remaining sehingga Anda dapat memantau sisa kapasitas.

Antrean dead-letter

Tangani upaya moderasi yang gagal:

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

Optimalisasi biaya

Perutean cerdas

Panggil API berbayar hanya saat diperlukan:

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

Kontrol konkurensi

Endpoint /api/v2/check menilai satu kiriman per panggilan. Untuk melakukan "batch" pekerjaan, kelompokkan item di worker Anda dan sebarkan secara bersamaan dalam pool terbatas, alih-alih mengharapkan satu permintaan berisi banyak item. Pemeriksaan yang mencakup beberapa URL media tetap dihitung sebagai satu panggilan yang mencakup semuanya — satu panggilan dengan banyak gambar lebih murah daripada banyak panggilan satu gambar.

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

Satu permintaan dapat memuat hingga 10 URL gambar, 5 URL GIF, 3 URL video, 5 URL dokumen, dan 20 tautan, ditambah teks hingga 10.000 karakter — jadi gabungkan media dari satu kiriman ke dalam satu panggilan, bukan memecahnya.

Pemantauan dan peringatan

Dasbor metrik utama

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

Aturan peringatan

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

Penskalaan infrastruktur

Penskalaan 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

Mengurangi latensi untuk pengguna global

Discuse API dapat diakses melalui satu URL dasar, https://api.discuse.com. Agar latensi tetap rendah untuk trafik global, jalankan worker moderasi Anda di lokasi yang dekat dengan pengguna dan biarkan worker tersebut memanggil API, alih-alih memanggilnya secara sinkron dari jalur permintaan yang langsung berhadapan dengan pengguna di region yang jauh. Gabungkan pendekatan ini dengan pola pascamoderasi asinkron dan caching di atas agar perjalanan pulang-pergi yang lambat tidak pernah memblokir pengguna.

Ringkasan praktik terbaik

  1. Cache secara agresif: konten yang identik akan mengembalikan hasil dari cache. API juga melakukan deduplikasi konten identik per proyek dalam jendela waktu singkat dan mengembalikan cached: true.
  2. Gunakan antrean: pemrosesan asinkron untuk volume tinggi.
  3. Terapkan tingkatan: lokal → cache → API.
  4. Pantau semuanya: kedalaman antrean, latensi, dan penggunaan terhadap kuota Anda.
  5. Rencanakan kegagalan: dead-letter queue dan fallback yang aman.
  6. Skalakan secara horizontal: lebih banyak worker, bukan mesin yang lebih besar.
  7. Optimalkan biaya: perutean cerdas, lebih sedikit panggilan berulang yang tidak perlu, dan media yang digabungkan per permintaan.

Langkah berikutnya

Ditulis oleh Tim Discuse · Terakhir diperbarui June 2026

Artikel Terkait

Panduan Moderasi Konten AI

Bagaimana machine learning mendukung sistem moderasi konten modern

Mengonfigurasi Ambang Deteksi

Seimbangkan positif palsu dan negatif palsu untuk kasus penggunaan Anda