Wiki
猫Table of Contents
- Rate Limiting
- Overview
- Default behavior
- Where the limiter applies
- Two operating modes
- Wait mode
- Drop mode
- Global limit versus per-chat limit
- How retry_after is handled
- Chat-scoped versus global cooldowns
- Context cancellation behavior
- Practical tuning
- Small and medium bots
- Bots with bursts across many chats
- Bots with heavy uploads
- Low-value, high-volume bots
- Configuration points
- Caveats
- Recommendations
- Related pages
Rate Limiting
Russian version: Rate-Limiting-RU
This page explains how Laniakea throttles outgoing Telegram API requests and how it reacts when Telegram answers with 429 Too Many Requests. The built-in limiter is designed to protect both the global bot throughput and hot chats that might otherwise overwhelm the API.
Overview
Laniakea wires a RateLimiter into the internal tgapi.API client during NewBot(...).
That limiter combines:
- a global token bucket;
- per-chat token buckets;
- global cooldown locks;
- per-chat cooldown locks.
In other words, the library handles both steady-state throttling and reactive backoff after Telegram explicitly says to wait.
Default behavior
The built-in limiter starts with these defaults:
- global limit:
30requests per second, burst30; - per-chat limit:
1request per second, burst1.
When a bot is created, BotOpts.RateLimit can override the global rate. The per-chat limiter remains 1 req/s per chat in the current implementation.
Where the limiter applies
The limiter is used by:
- normal JSON Telegram API requests through
tgapi.API; - multipart uploader requests through
tgapi.Uploader.
That matters because rate limiting is not only about SendMessage(...). Upload-heavy bots still benefit from the same cooldown handling and retry behavior.
Two operating modes
The limiter supports two modes:
- wait mode;
- drop mode.
The mode is controlled by BotOpts.DropRLOverflow and passed into tgapi.API as the limiter's overflow behavior.
Wait mode
Wait mode is the default and usually the safest option.
In wait mode:
- if capacity is available, the request proceeds immediately;
- if a limiter bucket is empty, the request waits;
- if a global or chat cooldown lock is active, the request waits until that lock expires;
- if the context is canceled while waiting, the request returns the context error.
This mode favors reliability and delivery over latency.
It is usually the right choice for:
- bots where losing messages is unacceptable;
- admin or workflow bots;
- bots that send important transactional responses.
Drop mode
Drop mode rejects requests immediately when a limiter would otherwise block.
In drop mode:
- requests do not wait for limiter capacity;
- requests do not wait for cooldown locks to expire;
- the limiter returns
ErrDropOverflowinstead.
This mode favors responsiveness over guaranteed delivery.
It can make sense for:
- noisy bots with low-value updates;
- bots where stale replies are worse than skipped replies;
- telemetry or best-effort notification workloads.
Be careful with it in user-facing command flows, because it can turn load spikes into visible dropped messages.
Global limit versus per-chat limit
The limiter checks both global and per-chat constraints.
Global limit protects the bot as a whole:
- too many concurrent requests across all chats will hit the global bucket first.
Per-chat limit protects one chat from becoming too noisy:
- a flood in one chat does not automatically consume the entire chat-level budget of another chat;
- chat cooldowns are scoped to the affected chat.
This is especially helpful for bots used in large groups and private chats at the same time.
How retry_after is handled
When Telegram replies with error 429 and a retry_after value:
- Laniakea logs the cooldown;
- the limiter stores a cooldown lock;
- the lock is scoped to the chat if the request had a chat ID;
- otherwise the lock becomes global;
- the client waits for the specified time and retries the request automatically.
This behavior exists in both the normal API client and the uploader path.
That means Telegram's own feedback actively reshapes future request pacing instead of being treated as a plain error.
Chat-scoped versus global cooldowns
If the request is associated with a concrete chatID, Laniakea applies retry_after as a chat-specific lock.
If the request has no chat context, Laniakea applies it as a global lock.
Examples of global-scope requests:
- requests that are not tied to one chat;
- some infrastructure or metadata calls;
- requests created without an associated chat ID in the low-level API.
This distinction is important because it prevents one noisy chat from unnecessarily freezing the entire bot when Telegram's limit is actually chat-local.
Context cancellation behavior
Waiting is always context-aware.
If the bot or request context is canceled while the limiter is waiting:
- the wait stops immediately;
- the request returns the context error instead of hanging until the cooldown finishes.
This matters for graceful shutdown, because rate-limited requests should not keep the process alive longer than the caller intends.
Practical tuning
Small and medium bots
Start with the default global rate or a conservative custom value. The defaults are usually good enough unless you already know your workload characteristics.
Bots with bursts across many chats
Increase BotOpts.RateLimit carefully if:
- handlers are fast;
- your infrastructure can absorb the parallelism;
- you are not already seeing Telegram
429responses.
Do not assume that raising the global limit alone solves everything. Per-chat pressure can still trigger chat-local cooldowns.
Bots with heavy uploads
Remember that uploader requests also participate in rate limiting. If your bot sends media aggressively, watch for retry_after behavior there too, not only in message sends.
Low-value, high-volume bots
Consider drop mode only when skipped messages are acceptable. It is a policy choice, not a performance upgrade in all cases.
Configuration points
The main knobs exposed through BotOpts are:
SetRateLimit(limit)for the global request-per-second limit;SetDropRLOverflow(drop)to choose drop mode instead of waiting.
There is no high-level bot option today for changing the per-chat 1 req/s limiter. If you need a different per-chat policy, that would currently require working with the lower-level limiter implementation directly.
Caveats
RateLimit <= 0does not replace the built-in global limiter; it leaves the current limiter settings in place.- Drop mode returns
ErrDropOverflow, so callers that care about delivery should surface or log that explicitly. retry_afterauto-retry still depends on context lifetime; cancellation wins over waiting.- Low-level requests created without chat IDs can only benefit from global cooldown scoping, not chat-local scoping.
Recommendations
- Default to wait mode unless you have a strong reason to prefer dropping.
- Tune the global limit gradually and based on observed traffic.
- Expect
retry_afterto happen occasionally and design handlers to tolerate delayed delivery. - For chatty bots, monitor which flows are producing the most requests before raising limits.
Related pages
- Bot-Lifecycle for request execution during startup and shutdown
- tgapi-Overview for the lower-level client layer
Navigation
Start here
Runtime and Architecture
- Bot-Lifecycle
- Webhook-Runtime
- Middleware
- Runners
- Error-Handling
- Logging
- Update-Routing-Model
- Policies
- Scenes
Interaction and Telegram API
- Inline-Keyboards-and-Payloads
- Auto-Generated-Commands
- Drafts
- Rich-Messages
- Localization
- Rate-Limiting
- tgapi-Overview