· 6 min read
Speeding Up Telegram Moderation Without Sacrificing Accuracy
Telegram moderation can become a bottleneck as groups scale, especially when every message triggers multiple checks. This post explains how to reduce latency by restructuring moderation into fast-path decisions, deferred work, and smaller hot data structures.
Moderation logic in Telegram bots and services often starts simple: inspect every incoming message, run a few rules, maybe consult a database, and decide whether to delete, warn, mute, or ignore. That works fine in small groups. In large communities, it becomes expensive fast.
The performance problem is rarely the moderation rules themselves. It is usually the shape of the work:
- every message takes the same slow path
- state lookups happen more often than needed
- expensive checks run before cheap ones
- repeated moderation decisions are recalculated instead of cached
- message handling waits on I/O that could happen later
If your moderation pipeline is slowing down updates, the fix is not to remove checks. The fix is to make the common case cheap.
Where moderation time goes
A typical Telegram moderation flow might look like this:
- Receive an update
- Load chat config from storage
- Load user state or history
- Apply spam, link, and profanity rules
- Check rate limits and prior warnings
- Take action
- Write audit logs
The issue is that steps 2, 3, and 7 often dominate latency. If you do all of them for every incoming message, your bot spends most of its time waiting on storage rather than moderating.
The first optimization is to separate decision latency from recording latency.
Use a fast path for obvious cases
Most messages do not need a full moderation pass. Build a fast path that handles the majority of traffic with a few cheap checks:
- is moderation enabled in this chat?
- is the sender exempt?
- is the message clearly safe?
- does it match a trivial block rule?
These checks should live in memory and be ordered from cheapest to most expensive. For example:
if moderation_disabled(chat_id): return allow
if user_is_whitelisted(user_id, chat_id): return allow
if message_is_empty_or_service_update(update): return allow
if contains_banned_pattern(text): action = delete
else: action = allow
This lets you skip full policy evaluation for the common case. The key is to ensure the fast path does not require a database round trip.
Cache chat policy and user exemptions
Chat-level moderation settings usually change far less often than messages arrive. Keep them in memory and refresh them asynchronously.
Useful cache entries include:
- moderation enabled or disabled
- link restriction mode
- spam sensitivity tier
- admin and moderator exemptions
- temporary mute and ban expiration timestamps
If you need strong consistency, you do not need to fetch from storage on every update. A practical approach is:
- store the current policy snapshot in memory
- invalidate or refresh it when an admin changes settings
- attach a short TTL for safety
This reduces read load and makes moderation decisions predictable. For bots running across multiple workers, use a shared cache or push policy updates to each worker rather than pulling from the database repeatedly.
Precompute rule sets instead of rebuilding them
A common mistake is rebuilding moderation rules on every message. Examples include:
- compiling regexes repeatedly
- reconstructing allow/block lists from storage
- recalculating rate-limit thresholds from config
Instead, precompute once per policy change and reuse the result.
For example, if your bot blocks a list of keywords, normalize them into a structure optimized for lookup:
- lowercase all entries
- deduplicate
- store in a hash set or trie if matching large vocabularies
- compile regex patterns once, not per message
This matters a lot when you support many chats with different policies. The goal is to make moderation a pure lookup problem wherever possible.
Defer slow side effects
Moderation actions often trigger extra work:
- logging to a database
- writing analytics events
- sending moderator notifications
- updating user history
- syncing state to another service
Not all of that needs to happen before Telegram receives the moderation response.
For message deletion, warning counters, and mute actions, the critical part is to decide and act quickly. Audit logging and analytics can be queued for background processing.
A good split is:
- synchronous: decide, delete, mute, warn
- asynchronous: log, aggregate, notify, index
If you are using workers, push side effects into a queue after the moderation result is finalized. That keeps update handling responsive even when logging spikes.
Make rate limiting O(1) in the hot path
Rate limiting is often the heaviest moderation rule because it depends on recent history. If you implement it by scanning message logs, performance will suffer.
Instead, maintain compact counters per user per chat:
- message count in the last N seconds
- warning count
- recent deletion count
- recent mention or link count
Update these counters incrementally, using timestamps and rolling windows. For many bots, an in-memory token bucket or sliding window counter is enough.
A good pattern is:
- keep the last reset time
- store the current bucket count
- update in constant time
- expire inactive users automatically
This avoids expensive history scans and keeps moderation decisions stable under load.
Short-circuit by message type
Not every Telegram update should enter the same moderation logic. If your bot processes all update types through one path, you are wasting time.
Filter early:
- ignore service messages that do not require moderation
- skip media-only updates if your rules are text-only
- bypass edits when moderation applies only to new messages
- handle channel posts separately from group messages
The more precisely you route updates, the less work each worker does. This is especially helpful if your bot supports large supergroups where the update stream includes many event types.
Reduce lock contention
If your moderation state is shared across goroutines, threads, or workers, contention can become a hidden bottleneck.
Common symptoms:
- one global lock around the policy cache
- a shared map for every chat’s counters
- serialized writes for all moderation events
To improve throughput:
- shard state by chat_id
- use per-chat locks or lock-free reads where possible
- keep hot counters separate from cold metadata
- batch writes instead of writing on every message
The best structure depends on your runtime, but the principle is the same: keep unrelated chats from blocking one another.
Measure the real bottleneck
You should not optimize moderation blindly. Measure:
- update-to-decision latency
- cache hit rate
- database reads per message
- time spent in rule evaluation
- queue delay for async logging
If your system is already spending most of its time waiting on the database, optimizing regex matching will not help much. If rule evaluation is the bottleneck, then precomputing patterns and reordering checks may produce a real win.
In practice, the biggest improvement usually comes from combining three changes:
- keep policy data in memory
- make the common case a fast path
- defer non-critical work
A practical target architecture
A performant moderation pipeline for Telegram usually looks like this:
- Ingress layer: filters irrelevant update types immediately
- Policy cache: stores per-chat moderation settings in memory
- Decision engine: applies cheap checks first, expensive checks only when needed
- Action executor: performs delete, mute, warn, or ban
- Async workers: handle logging, analytics, and long-tail bookkeeping
This architecture keeps the hot path small and makes scaling easier as chat volume grows.
Final takeaway
Telegram moderation does not have to be slow. The biggest gains come from treating moderation like a latency-sensitive system, not a sequence of isolated rules.
If you move policy into memory, precompute your rule data, short-circuit obvious cases, and defer non-critical work, you can keep moderation responsive even in very active groups. That means fewer dropped updates, faster user-facing actions, and more headroom for real moderation logic instead of infrastructure overhead.