· 7 min read

Improving Telegram Message Delivery Performance Without Changing Product Behavior

If your Telegram infrastructure is already correct but still feels slow under load, the biggest gains usually come from reducing cross-service chatter, tightening storage access patterns, and making delivery paths more predictable. This article focuses on practical changes Telegram developers can make to improve throughput and tail latency without altering user-facing behavior.

Telegram infrastructure has a reputation for being fast, but at scale, speed is mostly the result of disciplined engineering rather than magic. If you are working on Telegram-like systems, the most valuable performance work is usually not adding more machines. It is reducing the amount of work each message forces the system to do.

This article focuses on one concrete improvement: shaving latency and increasing throughput in the message delivery path by making the hot path narrower, more cache-friendly, and less dependent on synchronous cross-service calls.

Start by defining the actual hot path

Before changing code, identify the exact sequence that happens when a message is sent:

  1. The sender authenticates and submits the message.
  2. The message is validated and assigned an identifier.
  3. Metadata is written durably.
  4. The recipient’s delivery state is checked.
  5. Push or websocket delivery is attempted.
  6. Acknowledgements and retries are scheduled.

In many systems, the slowest part is not the write itself. It is the combination of:

  • synchronous lookups for recipient state,
  • repeated serialization and deserialization,
  • fan-out to multiple internal services,
  • and contention on shared storage or locks.

If your p95 is acceptable but p99 is bad, the cause is often extra work on this path that only appears under fanout, cache misses, or retry storms.

Performance principle: make delivery decisions from local state

A common optimization is to move as much delivery decision-making as possible into local, precomputed state.

Instead of asking several services whether a message should be delivered now, keep enough indexed state close to the worker handling the message. For example:

  • online/offline status snapshots,
  • last-seen delivery cursor,
  • unread counter deltas,
  • shard-local recipient routing data,
  • and per-device delivery eligibility flags.

The goal is not perfect freshness. The goal is to avoid blocking the send path on remote coordination when the answer can be approximated locally and corrected asynchronously.

Why this helps

Every network round trip in the hot path adds variability. Even if the average latency is small, tail latency grows quickly when a message must wait on several dependent calls. Local state reduces that dependency chain.

What to watch out for

This only works if stale state is safe. You need explicit fallback rules for cases such as:

  • newly connected devices,
  • revoked sessions,
  • rate-limited recipients,
  • and migration between shards.

If the local view is uncertain, do the minimal safe action and repair state asynchronously.

Use append-first writes for message intake

If the product permits it, the intake path should be designed around a single durable append before anything else. That means the system stores the message envelope and routing metadata first, then performs secondary work afterward.

A good intake path often looks like this:

  1. Validate request format.
  2. Assign IDs and deduplication keys.
  3. Append to a partitioned log or write-optimized store.
  4. Return success as soon as durability is confirmed.
  5. Fan out delivery, indexing, and notifications asynchronously.

This structure keeps user-visible send latency predictable. It also allows downstream consumers to retry independently without making the sender wait.

Best practice

Keep the durable write record compact. Store only what is needed to reconstruct downstream work:

  • sender ID,
  • conversation ID,
  • message ID,
  • device scope,
  • timestamp,
  • and a pointer to payload data if the payload is stored separately.

Do not bloat the hot record with derived fields that can be recomputed later.

Reduce serialization overhead in the core loop

At scale, serialization cost matters. If every message is encoded multiple times between services, your throughput drops even if CPU usage appears moderate.

Practical improvements

  • Use one canonical internal representation for the message envelope.
  • Avoid converting between JSON and binary formats inside the hot path.
  • Keep field ordering stable so decoders can use faster parsing paths.
  • Batch small internal control messages instead of sending them one by one.

If your infrastructure uses both application-facing and internal protocols, draw a hard line between them. The external API can remain flexible, but internal service-to-service communication should prioritize compactness and decode speed.

Make fanout incremental, not monolithic

Message fanout is often where Telegram-style systems lose performance. A single message may need to reach multiple sessions, multiple devices, and multiple replicas. If the system calculates every target from scratch each time, overhead grows quickly.

A better approach is incremental fanout:

  • keep a recipient membership cache,
  • track per-device cursors,
  • enqueue work per shard,
  • and only compute the delta from the last known state.

This avoids scanning all members of a large dialog or channel when only a subset needs work.

Example optimization pattern

If a conversation has many subscribers, don’t derive the full target list on every send. Instead:

  • maintain a shard-local subscriber index,
  • record only changed memberships,
  • and let delivery workers process updates in small batches.

This is especially effective for busy groups where membership changes are rare compared to message volume.

Separate control-plane traffic from data-plane traffic

One of the easiest ways to reduce latency spikes is to isolate control work from delivery work.

Examples of control-plane work include:

  • session validation,
  • configuration refresh,
  • abuse checks,
  • shard rebalancing,
  • and membership updates.

Data-plane work includes:

  • message append,
  • message fanout,
  • push dispatch,
  • and client delivery acknowledgements.

If these share the same worker pools, a control-plane spike can slow down message delivery. Give them separate queues, separate concurrency limits, and ideally separate CPU budgets.

Why this matters

Telegram-like traffic is bursty. When reconnect storms or cache refreshes happen, they should not steal capacity from the path that delivers messages already accepted by the system.

Tune storage for access pattern, not just capacity

A common mistake is to treat storage performance as a generic problem. In reality, your message delivery workload has very specific patterns:

  • mostly sequential writes on intake,
  • mostly point reads on delivery,
  • periodic scans for retries and cleanup,
  • and hot keys for active chats or channels.

Design storage shards around these patterns.

  • Partition by conversation or delivery shard, not by arbitrary user ID alone.
  • Keep write amplification low.
  • Avoid secondary indexes on fields that are not queried on the hot path.
  • Use compaction or cleanup jobs during off-peak windows.
  • Measure queue depth and write stall time, not just disk utilization.

If a shard becomes hot because a large channel dominates traffic, isolate that workload before it poisons neighbors.

Cache carefully, invalidate deliberately

Caching is useful only when its failure modes are understood. For Telegram infrastructure, the most useful caches are the ones that eliminate repeated lookups in delivery and presence decisions.

Cache candidates

  • recipient routing info,
  • session/device lists,
  • encryption/session metadata,
  • recent delivery cursors,
  • and per-conversation membership snapshots.

Caching rules

  • Use short-lived caches for highly volatile data.
  • Invalidate on explicit state transitions when possible.
  • Prefer versioned entries over blind TTLs for important state.
  • Measure cache hit rate per code path, not globally.

A cache that is 95% effective in one subsystem and 20% effective in another should be treated as two separate design problems.

Batch only where it reduces coordination

Batching can improve throughput, but it can also increase latency if done badly. The rule is simple: batch operations that share the same destination, lifecycle, or storage shard.

Good batching targets:

  • acknowledgements,
  • presence updates,
  • retry scheduling,
  • and push notifications.

Bad batching targets:

  • unrelated message deliveries,
  • cross-shard writes with different retry rules,
  • or user-facing actions that need immediate completion.

The objective is to reduce coordination overhead without turning small jobs into large wait queues.

Instrument the tail, not just the average

If you want a real performance improvement, you need visibility into where time is lost. For Telegram infrastructure, average latency is often misleading because a tiny fraction of paths produces most of the pain.

Track these metrics:

  • p50, p95, p99, and p99.9 for send-to-accept and accept-to-deliver;
  • queue depth per worker class;
  • cache hit rate by lookup type;
  • retry volume by cause;
  • and time spent in serialization, storage, routing, and fanout separately.

Good debugging question

When latency spikes, ask: Which dependency appears only on the slowest requests?

That dependency is usually the one to remove, memoize, or move off the critical path.

A practical upgrade plan

If you need a roadmap, start here:

  1. Profile the send path end to end. Find every synchronous call.
  2. Move recipient and device state into a local cache or shard-local index.
  3. Make intake append-only and return after durability.
  4. Split control-plane work from delivery workers.
  5. Batch only messages that share the same destination and retry policy.
  6. Measure p99 improvements, not just average throughput.

This sequence usually produces better results than trying to optimize CPU instructions first. Most message delivery systems are bottlenecked by coordination, not raw compute.

Conclusion

For Telegram developers, the most effective performance improvements usually come from narrowing the hot path. Keep delivery decisions close to the worker, write durably first, push everything else out of band, and isolate unrelated work so it cannot interfere with message flow.

That approach does not change product behavior, but it often changes the user experience dramatically: faster sends, smoother reconnects, and fewer latency spikes under load.

If you are improving Telegram infrastructure, optimize the path that every message must take, not the rare path that only looks expensive in a benchmark.

← All posts