---
source_url: https://www.pubnub.com/docs/general/resources/rate-limiting
title: Rate limiting
updated_at: 2026-05-25T11:26:37.615Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Rate limiting

PubNub provides multiple strategies for controlling message rates in high‑occupancy scenarios. Rate limiting maintains optimal user experience and manages system usage when thousands of participants interact simultaneously.

This document focuses on PubNub's [Functions](https://www.pubnub.com/docs/serverless/functions/overview)‑based rate limiter, a flexible solution for controlling message flow in live events, chat applications, and other high‑traffic use cases.

## What to consider

When implementing rate limiting, consider how message volume affects user experience and system usage.

#### Message frequency

Chats become noisy when too many messages arrive from too many participants. For meaningful dialogue, excessive noise lowers attention until the chat reaches saturation and becomes unusable. Reducing audience size per channel creates opportunities for conversation.

For engagement‑focused experiences without dialogue, saturation tolerance is higher. Business owners decide the appropriate traffic level. Messages should be readable, though users may lack time to respond.

#### Event occupancy

High occupancy increases both message frequency and system usage. As occupancy grows, each message must be delivered to more subscribers. Consider a scenario with 10,000 users celebrating a moment during a live event:

| Scenario | Effect |
| --- | --- |
| Single channel | All 10,000 users receive every message,creating high volume for all participants |
| Ten sharded channels | Each group of 1,000 users receives only messages from their channel,reducing noise |

Channel sharding improves user experience by creating smaller, more manageable conversation spaces.

## Functions rate limiter

:::tip Recommended solution
The Functions‑based rate limiter is PubNub's recommended approach for controlling message rates. It provides dynamic throttling without requiring additional infrastructure while preserving the shared experience of being part of a large audience.
:::

When 100,000 fans are celebrating together, you want them to feel the energy of the crowd and not feel separated into smaller rooms. The Functions rate limiter achieves this by intelligently throttling message rates while keeping all users in a single shared channel.

This approach:

* Preserves the "stadium atmosphere" where users feel part of the full audience
* Dynamically adjusts throttling based on current message rates
* Requires no user separation or channel assignment logic
* Works automatically with minimal configuration

For scenarios where approximate rate control suffices, this solution maintains optimal user experience without fragmenting your community.

### Distributed system challenges

Rate limiting in distributed systems introduces complexity:

* Coordination and synchronization: Multiple nodes must coordinate to enforce limits. State information propagates across nodes with latency, creating temporary discrepancies that allow brief limit violations.
* Scalability: Managing rate limits grows harder as nodes, clients, and users increase. Solutions must scale efficiently with system growth.
* System failures: Node or network failures disrupt rate limiting. Robust solutions handle failures gracefully and recover quickly.
* Performance impact: Rate limiting adds processing overhead per request. Poor optimization degrades latency and throughput.
* Configuration complexity: Defining and enforcing rules across multiple nodes or services complicates management.
* Fairness versus efficiency: Balancing equal user opportunities against system performance requires careful tuning and monitoring.

### Throttling solution

Absolute rate limiting requires fan‑in‑fan‑out architectures where a single component consumes all messages and republishes at the desired rate. This creates a single point of failure and increases latency.

Most applications need only approximate rates to avoid saturation and maintain user experience. Approximate limiting preserves PubNub's low latency and reliability guarantees.

The solution uses two components: a [message throttler](#message-throttler) and a [message sampler](#message-sampler).

## Message throttler

The throttler is a before‑publish [Function](https://www.pubnub.com/docs/serverless/functions/overview) that controls message rates based on configuration.

### Configuration

Control messages update the Function's throttling configuration. Each Function instance stores configuration and persists it to the [KV store](https://www.pubnub.com/docs/serverless/functions/functions-apis/kvstore-module) for other instances to retrieve.

Periodically, instances check the KV store for configuration updates and synchronize their settings.

### Throttling logic

When a non‑control message arrives on a rate‑limited channel, the Function looks up throttling parameters. Using probability and channel configuration, it randomly decides whether to throttle.

Throttled messages route to an analytics channel rather than reaching subscribers. This preserves messages for analysis without impacting user experience. Configuration can also discard throttled messages entirely.

## Message sampler

The sampler subscribes to rate‑limited channels and tracks message rates. It generates control messages to adjust throttling dynamically.

### Sampling configuration

Configure the sampler to monitor specific channels. Define a sampling period N and target rate per period. During each period, the sampler tallies messages per channel.

### Control messages

At period end, the sampler compares counts against target rates and computes throttling adjustments. If throttling changed, it publishes control messages for the throttler to consume.

## Complete architecture

The throttler and sampler form a feedback loop that maintains approximate rate limits.

## Sample code

### Message Sampler

Standalone Node.js application that monitors channels and calculates throttling rates:

```javascript
const http = require("http");
const url = require('url');
const PubNub = require('pubnub');

//specific solution with port listening is for testing purpose only
const PORT = process.env.PORT || 5000;

//TODO: replace with channels you plan to track, those are for testing only
const TRACKED_CHANNELS = ["channel.it", "channel.fr", "channel.en"];
const CONFIG_CHANNEL_POSTFIX = "-config";

//TODO: replace with tracking window length
const rpnN = 20 * 1000; // 10sec

const DEBUG_ON = false;

//TODO: set correct limits of messages per RPN per channel, for now all set to 5 messages by default
const rpnLimits = new Map();
rpnLimits.set(TRACKED_CHANNELS[0], 5);
rpnLimits.set(TRACKED_CHANNELS[1], 5);
rpnLimits.set(TRACKED_CHANNELS[2], 5);

const rpnThrottlingRate = new Map();
const rpnCurrent = new Map();

let lastTimestamp = Date.now();

TRACKED_CHANNELS.forEach(ch => { //let's initialize counters
    rpnCurrent.set(ch, 0);
    rpnThrottlingRate.set(ch, 0);
});

// do stats flushing each rpnN
setInterval(calculateAndPublishLimits, rpnN);

//TODO: replace with your own pub/sub keys and UUID
const pubnub = new PubNub({
    publishKey : "pub-c-81a06b6a-71a7-481e-89b5-fc264e868084",
    subscribeKey : "sub-c-73242256-f1de-4572-a704-a7884c4287b0",
    uuid: "28efed67-e230-4fbe-9237-24fcb3f48498"
});

//TODO: this can be changed in actual application, but for now we'r just starting nodejs server to test against
const server = http.createServer(async (req, res) => {

    const queryObject = url.parse(req.url, true).query;

    var channelTo = queryObject.channel;
    var title = queryObject.title;
    var messageTo = queryObject.message;

    if (req.url.startsWith("/publish-test")){ //TODO: for local testing purposes only
        var msg = new Object();
        msg.title = title;
        msg.text = messageTo;

        publish(channelTo, JSON.stringify(msg));
    }

    res.writeHead(200,  { "Content-Type": "application/json" });
    res.end(JSON.stringify("ok"));
});

async function publish (channelToPublish, messageToPublish){

    try {
        const result = await pubnub.publish({
            channel: channelToPublish,
            message: messageToPublish
        });

        //console.log(" >> message published w/ server response: ", result);
    } catch (status) {
        console.log(" ?? publishing failed w/ status: ", status);
    }
}

function calculateAndPublishLimits()
{
    //console.log('\nFLUSH: current counters: ' + JSON.stringify(Object.fromEntries(rpnCurrent)));

    var controlMessage = new Object();
    //controlMessage.rpnLimits = mapToObj(rpnLimits);
    //controlMessage.rpnCurrent = mapToObj(rpnCurrent);
    controlMessage.lastTimestamp = lastTimestamp;
    controlMessage.periodInMillis = rpnN;
    controlMessage.messageType = 'controlMessage';

    //let's calculate throttling rates for upcoming period for rach channel
    TRACKED_CHANNELS.forEach(ch => {
        var cur = rpnCurrent.get(ch);
        var lim = rpnLimits.get(ch);

        if (lim == 0 || cur == 0) {
            //do nothing, we just don't throttle this channel regardless of RPN
            rpnThrottlingRate.set(ch, 0);
        }
        else
        {
            var currentReal = (cur / (100 - rpnThrottlingRate.get(ch))) * 100;
            // lim = 5 currentReal=2

            var throttleRate = Math.round( (1 - (lim / currentReal)) * 100);  //(1-(RATE/CURR))*100 = 80
            rpnThrottlingRate.set(ch, throttleRate < 1 ? 0 : throttleRate);

        }
    });

    controlMessage.rpnThrottlingRate = mapToObj(rpnThrottlingRate);

    // let's flush the control message to propagate it back to function settings to be stored in kv
    publish(TRACKED_CHANNELS[0], JSON.stringify(controlMessage));

    //end of an interval, so let's reset counters
    TRACKED_CHANNELS.forEach(ch => {
        rpnCurrent.set(ch, 0);
       // rpnThrottlingRate.set(ch, 0); //we don't reset intentionally to be able to restore full amount of messages based on old throttling rate
    });
}

// handler processing each listened message
// stage1: Calculate rpN for specific channel and store into counters map
// stage2: Can be modified to buffer messages till N of rpN is reached and then publish only those messages, which are not to be throttled.
function onMessage(messageEvent){

    try {
        lastTimestamp = Date.now();

        if (typeof messageEvent.message === 'object' || JSON.parse(messageEvent.message).messageType != 'controlMessage') { //control messages are not to be taken into account
            var currentVal = rpnCurrent.get(messageEvent.channel);
            currentVal = (currentVal === undefined) ? 1 : ++currentVal;
            rpnCurrent.set(messageEvent.channel, currentVal);
        }

        displayDebugInfo(messageEvent);
    }
    catch (e){
        console.log(e);
    }
}

function displayDebugInfo(messageEvent){

    if (DEBUG_ON) {
        console.log(JSON.stringify(messageEvent.message));
    }
}

server.listen(PORT, () => {
    console.log(`server started on port: ${PORT}`);

    // add listener
    const listener = {
        status: (statusEvent) => {
            if (statusEvent.category === "PNConnectedCategory") {
                console.log("Connected");
            }
        },
        message: (messageEvent) => {
           onMessage(messageEvent);
        },
        presence: (presenceEvent) => {
            // handle presence
        }
    };
    pubnub.addListener(listener);

    pubnub.subscribe({
        channels: TRACKED_CHANNELS,
    });

});

// ~ utils
const mapToObj = m => {
    return Array.from(m).reduce((obj, [key, value]) => {
        obj[key] = value;
        return obj;
    }, {});
};
```

### Message Throttler

Before‑publish Function that applies probabilistic throttling based on configuration:

```javascript
var gConfig = {};

const kvKeyName = "configStorage";
const CHANNEL_POSTFIX_THROTTLED_MESSAGES = "-somePostForThrottledMessagesChannel"; //FIXME: change to proper channel e.g. one listened by E&A

var gKvConfigUpdatePeriodInMillis = 20 * 1000; //20ms set as initial period
var gLastKVUpdateTimeStamp = Date.now();

export default (request) => {

    var console = require("console");   
    var db = require("kvstore");

    var channel = request.channels[0];

    // rate limiting control message came, so let's store it into global variable
    if (typeof request.message === 'string')
    {
        var jsonMessage = JSON.parse(request.message);

        if (JSON.parse(request.message).messageType && JSON.parse(request.message).messageType === 'controlMessage'){

            gConfig = jsonMessage;

            db.set(kvKeyName, gConfig)
                .catch(function(err) {
                    console.log("An error occurred saving the random number.", err);
                });

            gLastKVUpdateTimeStamp = Date.now();

            return request.ok();
        }
    }

    // update kv only from time to time, ideally only during same update periods
    if ( (Date.now() - gLastKVUpdateTimeStamp) > gKvConfigUpdatePeriodInMillis) {

        var resp = db.get(kvKeyName).then(function(globalConfig) {
            if (globalConfig) {
                
                gConfig = globalConfig;
                gLastKVUpdateTimeStamp = Date.now();
                gKvConfigUpdatePeriodInMillis = globalConfig.periodInMillis;
                
                //console.log("Local global variable was updated with " + gConfig);
            } 

            return;
        });
    }

    if (gConfig && gConfig.rpnThrottlingRate && gConfig.rpnThrottlingRate[channel] > 0) {

        var specificMessageThrottlingPercent = gConfig.rpnThrottlingRate[channel];
        var lrpnThrottlingRate = specificMessageThrottlingPercent / 100;
       
        var throttleProbabilityInput = {0 : lrpnThrottlingRate, 1 : (1 - lrpnThrottlingRate)}; // 0 - throttle, 1 not throttle
       
        if ( 0 == generateRandom(throttleProbabilityInput)) // throttle
        {
            console.log(' - - - -  This message is being throttled with [' + specificMessageThrottlingPercent + '] probability'); //TODO: comment in prod version
            request.channels[0] = request.channels[0] + CHANNEL_POSTFIX_THROTTLED_MESSAGES;  //TODO: postfix to update
        }
        else  //TODO: remove else block in prod version
        {
            console.log(' + + + +  This message is NOT being throttled with [' + specificMessageThrottlingPercent + '] probability');
            //TODO: add corresponding logic here
        }
    }
    else  //TODO: remove else block in prod version
    {
        console.log(" о о о о  This message doesn't need to be throttled");
    }

    return request.ok();
};

function generateRandom(set){

  var sum = 0;
  for(let j in set){
      sum += set[j];
  }

  var pick = Math.random() * sum;
  for(let j in set){
    pick -= set[j];
    if(pick <= 0){
      return j;
    }
  }
}

function isJson(obj) {
    var t = typeof obj;
    return ['boolean', 'number', 'string', 'symbol', 'function'].indexOf(t) == -1;
}
```

We recommend this approach because it:

* Maintains optimal user experience during high traffic
* Responds to dynamic traffic fluctuations
* Maintains low publish latency
* Supports different rates per channel
* Adjustable sampling periods match traffic profiles
* Retains messages for analytics
* All users receive identical message streams
* Approximate rates create organic, natural experience

:::note Considerations
Keep in mind that the actual rate may exceed or fall short of target and that poor sampling period configuration may cause rate variance.
:::

## Channel sharding

:::note When to use channel sharding
Channel sharding is appropriate only when logical groupings exist, such as different languages, team affiliations, or geographic regions. If there's no natural way to divide your audience, use the Functions rate limiter to preserve the shared experience.
:::

Channel sharding divides large audiences into separate conversation spaces. Unlike the Functions rate limiter, this approach separates users into distinct rooms rather than keeping them in a shared experience. Most applications benefit more from the Functions rate limiter.

Channel sharding makes sense when there are logical groupings, such as:

* Language-based separation (Spanish-speaking fans chat with other Spanish speakers, etc.)
* Team affiliation (home and away fan sections)
* Geographic regions (local fan communities)
* Premium tiers (VIP rooms for special access)

You may want to avoid channel sharding if you want users to feel part of the full audience ("100k stadium" experience) and there's no logical way to group users, as random distribution would fragment the community feel.

For detailed implementation guidance, refer to [Live Event Rate Limiting](https://www.pubnub.com/docs/entertainment/live-event-moderation/live-event-rate-limiting).

## Related patterns

* [Architectural Choices](https://www.pubnub.com/docs/general/resources/architecture)
* [Message Aggregation](https://www.pubnub.com/docs/general/resources/design-pattern-message-aggregation)