Rate Limiting ============= *A wzgram extension* Telegram answers a client that sends too fast with a ``FloodWait``: a number of seconds you must not send in. wzgram handles those when they arrive — but a limiter that *never lets you get there* is cheaper than one that recovers afterwards, because a flood wait charges for the request that triggered it too. Every client has one. :obj:`~pyrogram.Client` builds a ``RateLimiter`` with sane defaults whether or not you ask for it, and :meth:`~pyrogram.Client.invoke` — the choke point every raw call passes through — acquires from it before the request goes out. ----- Categories ---------- Telegram does not enforce one limit; it enforces different ones for different work. The limiter mirrors that with a token bucket per category and one global bucket over all of them: =========== ========== ========== ============================================== Category Rate/s Burst Covers =========== ========== ========== ============================================== ``message`` 20 30 sending, editing, forwarding text ``media`` 5 10 anything carrying a photo, video, audio, file ``query`` 30 50 ``Get*``, ``Search*``, ``Check*`` ``admin`` 15 20 bans, promotions, pins, toggles, deletes ``bulk`` 3 5 update-difference fetches, pings ``account`` 10 15 account-level changes ``global`` 30 40 everything, on top of its own category =========== ========== ========== ============================================== A request is classified from the name of the raw function it wraps, so a new method lands in the right bucket without anything being registered by hand. Tuning it --------- Pass ``rate_limits`` to the client. Only the categories you name are changed: .. code-block:: python app = Client( "my_bot", rate_limits={ "media": {"rate": 2, "burst": 4}, "global": {"rate": 15, "burst": 20}, }, ) Lower is slower and safer. The defaults are already below what Telegram publishes, so raising them is how you get flood waits. Reading the pressure -------------------- .. code-block:: python print(app.rate_limiter.congestion()) # 0.0 idle … 1.0 saturated print(app.rate_limiter.available) # tokens left per category ``congestion()`` is the worst bucket, not the average — it answers "am I about to be throttled", which is the question worth asking. A long-running job can use it to back off before the limiter has to make it wait. ``acquire_nowait()`` takes a token only if one is free and reports whether it did, for work that would rather be skipped than delayed. ``update_limits`` changes the limits on a live client. How the bucket behaves ---------------------- Two properties are load-bearing, and both were bugs before they were properties: - **The wait is served holding the lock.** Sleeping outside it woke every waiter at once for a single token, and a deficit of a fraction of a token makes that sleep microseconds long — a spin, under exactly the load the limiter exists for. Five waiters cost twenty sleeps. - **Admission is in arrival order.** ``asyncio.Lock`` hands over in order, so holding it across the sleep also means a waiter cannot be starved by later arrivals. Gotchas ------- - The limiter is per client. Two clients in one process do not share buckets, so N clients on one account can still flood — lower the limits rather than assuming they compose. - It bounds requests per second, not bytes. A slow uploader is not what it protects against; that is :doc:`performance`. - ``sleep_threshold`` on the client is the other half of this: it decides how long a ``FloodWait`` wzgram will sit out for you rather than raise. The limiter tries to keep you from ever finding out.