مقیاسپذیر کردن نظارت بر محتوا
راهاندازی نظارتی که برای ۱٬۰۰۰ پیام در روز جواب میدهد، در مقیاس یک میلیون پیام از کار میافتد: محدودیتهای نرخ درخواست دردسرساز میشوند، تأخیر هر فراخوانی روی هم جمع میشود و هزینهها سر به فلک میکشند. این راهنما نشان میدهد چگونه نظارت Discuse را به پایپلاینهای پرترافیک وصل کنید — چه زمانی مسدود کنید، چه زمانی بررسی را در پسزمینه انجام دهید، چه چیزهایی را کش کنید و چطور در زمان اوج بار مسیر جایگزین انتخاب کنید.
وقتی نظارت در مقیاس بزرگتر دچار مشکل میشود، چه چیزی از کار میافتد؟
هر مشکل مقیاسپذیری یک علت مشخص و یک راهحل مشخص دارد:
| چالش | تأثیر | راهحل |
|---|---|---|
| رشد حجم | محدودیت نرخ API، افزایش هزینهها | پردازش ناهمگام، کشکردن |
| الزامات تأخیر | تجربه کاربری ضعیف | نظارت پیش از انتشار، صفبندی |
| مدیریت هزینه | محدودیتهای بودجه | مسیریابی هوشمند، کشکردن |
| یکنواختی | اعمال نامتوازن قوانین | پیکربندی متمرکز |
| مدیریت اوج بار | افت کیفیت سرویس | مقیاسپذیری خودکار، صفها |
از کدام الگوی معماری باید استفاده کنم؟
الگوی ۱: پیشنظارتی همگام
مناسب برای: محتوای کمحجم و پرریسک (پرداختها، مسائل حقوقی)
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);
}
مزایا: ساده، بازخورد فوری معایب: تأخیر را افزایش میدهد، توان پردازش را محدود میکند
الگوی ۲: پسنظارتی ناهمگام
مناسب برای: محتوای پرحجم و کمریسکتر (نظرها، پیامها)
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' });
}
});
مزایا: غیرمسدودکننده، مناسب برای حجم بالا معایب: محتوای زیانبار ممکن است برای مدت کوتاهی قابل مشاهده باشد
الگوی ۳: نظارت چندلایه
مناسب برای: رویکردی متعادل همراه با بهینهسازی هزینه
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' };
}
الگوی ۴: پخش به چند مسیر همراه با تجمیع
مناسب برای: چند نوع بررسی با مقیاسپذیری مستقل
┌──► 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
};
}
راهبردهای کشکردن
کشکردن بر اساس محتوا
نتایج را برای محتوای یکسان کش کنید:
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 };
}
کشکردن بر اساس کاربر
تصمیمها را برای متخلفان تکراری کش کنید:
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
}
مدیریت صف
صفهای اولویتدار
انواع مختلف محتوا را با اولویتهای متفاوت مدیریت کنید:
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)
});
}
رعایت محدودیت نرخ
هر کلید API یک محدودیت تعداد درخواست در دقیقه دارد؛ عبور از آن باعث میشود API درخواست را با خطای "rate limit exceeded" رد کند. از سمت خودتان درخواستها را محدود کنید تا زیر این سقف بمانید و سهمیه را برای فراخوانیهای تکراری هدر ندهید. سقف دقیقهای محدودکننده را مطابق RPM پیکربندیشده برای کلیدتان تنظیم کنید:
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));
}
سهمیه از محدودیت نرخ جداست: هر فراخوانی از سقف ماهانه درخواستهای API در پلن شما کم میکند، و شیء usage در پاسخ، مقادیر api_requests_used، api_requests_limit و api_requests_remaining را گزارش میدهد تا بتوانید ظرفیت باقیمانده را زیر نظر داشته باشید.
صفهای نامههای مرده
تلاشهای ناموفق برای بررسی محتوا را مدیریت کنید:
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);
}
});
بهینهسازی هزینه
مسیریابی هوشمند
فقط زمانی از APIهای پولی استفاده کنید که واقعاً لازم است:
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)
};
}
کنترل همزمانی
نقطهٔ پایانی /api/v2/check در هر فراخوانی، یک ارسال را امتیازدهی میکند. برای «دستهبندی» کارها، آیتمها را در ورکر خود گروهبندی کنید و بهجای انتظار برای یک درخواست تکیِ چندآیتمی، آنها را بهصورت همزمان و در قالب یک pool با ظرفیت محدود پخش کنید. بررسیای که شامل چند URL رسانهای باشد، خودش یک فراخوانی محسوب میشود و همهٔ آنها را پوشش میدهد — یک فراخوانی سنگین از نظر تصویر، ارزانتر از چندین فراخوانی تکتصویری است.
// 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;
}
یک درخواست تکی میتواند تا 10 URL تصویر، 5 URL GIF، 3 URL ویدئو، 5 URL سند و 20 لینک را بههمراه متن تا سقف 10,000 نویسه در خود داشته باشد — بنابراین رسانههای یک ارسال را بهجای تقسیمکردن، در یک فراخوانی واحد تجمیع کنید.
پایش و هشداردهی
داشبورد معیارهای کلیدی
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')
};
قوانین هشداردهی
# 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%
مقیاسپذیری زیرساخت
مقیاسپذیری افقی
# 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
کاهش تأخیر برای کاربران جهانی
Discuse API از طریق یک URL پایه واحد در دسترس است: https://api.discuse.com. برای پایین نگهداشتن تأخیر در ترافیک جهانی، workerهای مدیریت محتوای خودتان را نزدیک کاربران خود اجرا کنید و اجازه دهید آنها API را فراخوانی کنند، بهجای اینکه این فراخوانی را بهصورت همزمان از مسیر درخواستِ روبهروی کاربر در منطقهای دور انجام دهید. این کار را با الگوهای مدیریت محتوای پس از انتشار بهصورت ناهمگام و کشکردن که در بالا آمدهاند ترکیب کنید تا یک رفتوبرگشت کند هرگز کاربر را معطل نکند.
خلاصهٔ بهترین روشها
- با رویکردی تهاجمی کش کنید: محتوای یکسان، نتیجهٔ کششده برمیگرداند. API همچنین محتوای یکسان را برای هر پروژه در یک بازهٔ زمانی کوتاه حذف تکراری میکند و
cached: trueرا برمیگرداند. - از صفها استفاده کنید: پردازش ناهمگام برای حجم بالا.
- لایهبندی را پیادهسازی کنید: محلی → کش → API.
- همهچیز را پایش کنید: عمق صف، تأخیر، و میزان مصرف نسبت به سهمیهتان.
- برای خرابی برنامه داشته باشید: صفهای نامهٔ مرده و جایگزینهای امن.
- بهصورت افقی مقیاسپذیر شوید: کارگرهای بیشتر، نه ماشینهای بزرگتر.
- هزینه را بهینه کنید: مسیریابی هوشمند، فراخوانیهای تکراری کمتر، و رسانههای تجمیعشده در هر درخواست.
گامهای بعدی
- راهنمای تعدیل محتوای AI - آشنایی با تعدیل مبتنی بر AI
- پیکربندی آستانهها - تنظیم دقیق تشخیص
- راهنمای شروع سریع - پیادهسازی اولیه