Deep Dive into ParallelSubscriptions: Turbocharging Your Salesforce Platform Events

Nagendra SinghNagendra Singh
5 min read

Introduction

Salesforce Platform Events are the heart and soul of real-time, event-driven architectures in the Salesforce world! By default, an Apex trigger on a Platform Event processes all messages in a single, ordered stream. But what if your event flood skyrockets to thousands per minute? Enter Parallel Subscriptions—Salesforce’s amazing sharding mechanism that runs multiple “mini-triggers” in parallel, skyrocketing your throughput! In this blog, we’ll dive into an exciting hands-on example with a custom event called ParallelProcessingEventDemo__e, show you how to set up and deploy parallel subscriptions, and reveal how splitting into multiple partitions can transform a 15-second job into a lightning-fast 4-second sprint!

Why Parallel Subscriptions Matter

Imagine you're sending thousands of events every minute, like order updates, IoT signals, or churn notifications, and your single-threaded trigger begins to slow down. You can't just add more CPU power; you need concurrency within the Salesforce cloud. Parallel subscriptions allow you to:

  • Scale horizontally by running up to 10 concurrent trigger invocations

  • Maintain order within each shard, while accepting eventual consistency across shards

  • Keep your business logic unchanged, simply tuning the number of partitions.

Sequence Diagram

sequenceDiagram
    actor Publisher
    participant EventBus
    participant Partition0
    participant Partition1
    participant Partition2
    participant Partition3
    participant Trigger as PartitionDemo
    participant DB as ARecord__c

    Publisher->>EventBus: publish 5000 events batch
    EventBus->>EventBus: for each event\ncompute hash(OrderNumber__c) % 4
    EventBus-->>Partition0: events where hash % 4 = 0 (~1250 events)
    EventBus-->>Partition1: events where hash % 4 = 1 (~1250 events)
    EventBus-->>Partition2: events where hash % 4 = 2 (~1250 events)
    EventBus-->>Partition3: events where hash % 4 = 3 (~1250 events)

    par Process Partition 0
        Partition0->>Trigger: invoke with ~1250 events
        Trigger->>DB: insert ARecord__c records
    and Process Partition 1
        Partition1->>Trigger: invoke with ~1250 events
        Trigger->>DB: insert ARecord__c records
    and Process Partition 2
        Partition2->>Trigger: invoke with ~1250 events
        Trigger->>DB: insert ARecord__c records
    and Process Partition 3
        Partition3->>Trigger: invoke with ~1250 events
        Trigger->>DB: insert ARecord__c records
    end

The Demo Setup

  1. Custom Platform Event

    1. ParallelProcessingEventDemo__e.object-meta.xml

       <?xml version="1.0" encoding="UTF-8"?>
       <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
           <deploymentStatus>Deployed</deploymentStatus>
           <eventType>HighVolume</eventType>
           <label>ParallelProcessingEventDemo</label>
           <pluralLabel>ParallelProcessingEventDemo</pluralLabel>
           <publishBehavior>PublishImmediately</publishBehavior>
       </CustomObject>
      
    2. OrderNumber__c.field-meta.xml

       <?xml version="1.0" encoding="UTF-8"?>
       <CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
           <fullName>OrderNumber__c</fullName>
           <externalId>false</externalId>
           <isFilteringDisabled>false</isFilteringDisabled>
           <isNameField>false</isNameField>
           <isSortingDisabled>false</isSortingDisabled>
           <label>OrderNumber</label>
           <length>255</length>
           <required>true</required>
           <type>Text</type>
           <unique>false</unique>
       </CustomField>
      
  2. Apex Trigger

     // NOT A PRODUCTION READY CODE, JUST FOR DEMO PURPOSE
     trigger PartitionDemo on ParallelProcessingEventDemo__e (after insert) {
         List<ARecord__c> aRecords = new List<ARecord__c>();
         for (ParallelProcessingEventDemo__e evt : Trigger.new) {
             aRecords.add(new ARecord__c(
                     Name = evt.EventUuid,
                     testMe__c = evt.OrderNumber__c
             ));
         }
         insert aRecords;
     }
     // NOT A PRODUCTION READY CODE, JUST FOR DEMO PURPOSE
    
  3. Parallel Subscriptions Config
    We’ll start with 4 partitions, sharding by the required OrderNumber__c field:

    Read docs for choosing a good partition key.

    PartitionDemoConfig.platformEventSubscriberConfig-meta.xml

     <?xml version="1.0" encoding="UTF-8"?>
     <PlatformEventSubscriberConfig xmlns="http://soap.sforce.com/2006/04/metadata">
         <masterLabel>PartitionDemoConfig</masterLabel>
         <numPartitions>4</numPartitions>
         <partitionKey>ParallelProcessingEventDemo__e.OrderNumber__c</partitionKey>
         <platformEventConsumer>PartitionDemo</platformEventConsumer>
     </PlatformEventSubscriberConfig>
    

Publishing the Storm: 5000 Events

In an anonymous Apex block or integration script, we fire off 5,000 events with sequential order numbers:

List<ParallelProcessingEventDemo__e> batch = new List<ParallelProcessingEventDemo__e>();
for (Integer i = 1; i <= 5000; i++) {
    // I have used OrderNumber__c as a simple number field, but for production
    // make sure to select a partition key that contains a wide range of values, such as IDs.
    batch.add(new ParallelProcessingEventDemo__e(
            OrderNumber__c = String.valueOf(i)
    ));
}
EventBus.publish(batch);

Measuring the Impact

After publishing, we query the created ARecord__c records and calculate the time between the very first and last record creation:

List<ARecord__c> aRecord = [SELECT Id, CreatedDate, testMe__c FROM ARecord__c WHERE CreatedDate = TODAY ORDER BY CreatedDate];

ARecord__c aRecordFirst = aRecord.get(0);
ARecord__c aRecordLast = aRecord.get(aRecord.size() - 1);

System.debug(aRecordLast.CreatedDate.getTime() - aRecordFirst.CreatedDate.getTime());

I tried changing the partition value and below is the observation and performance boost observed.

PartitionsNumber of Events FiredTime Taken to Insert ARecord__c
15000~15000 ms
45000~4000 ms
105000~2000 ms

Why the Speedup?

  • Event Sharding

    • Salesforce hashes your partition key (OrderNumber__c) and does hash % numPartitions to route each event.

    • 1 partition: all 5,000 events go to one shard.

    • 4 partitions: ~1,250 events per shard.

    • 10 partitions: ~500 events per shard.

  • Batch Size & Micro‐Batching

    • Platform‐event triggers default to a 2,000‐event batch size.

    • 1 partition:

      • 5,000 events → split into 3 micro‐batches (2,000 + 2,000 + 1,000) → sequential execution → ~15 s total.
    • 4 partitions:

      • Each shard’s ~1,250 events fit in one batch → 4 jobs in parallel → ~4 s total.
    • 10 partitions:

      • Each shard’s ~500 events fit in one batch → 10 jobs in parallel → ~2 s total.
  • Parallel Execution

    • When you use multiple partitions, Salesforce starts that many separate trigger jobs at the same time.

    • The overall time you wait is basically how long the longest of those jobs takes.

    • Splitting into more partitions means each job has fewer events to handle—so each job finishes faster, and your total wait time goes down.


Best Practices & Caveats

  • Pick a High-Cardinality Key: Use EventUuid or a required ID field so the hash really evens out across partitions.

  • Know the Limits: Up to 10 partitions, and only for custom high-volume platform events (not standard or change events).

  • Testing Remains Simple: Your existing Apex tests for the trigger still work—Salesforce splits the records across partitions in test context automatically.


Conclusion

Parallel subscriptions in Salesforce Platform Events are a turnkey way to scale your real-time integrations. With a few lines of metadata, you can turn a 15-second, single-threaded process into a 4-second, multi-threaded powerhouse—no code changes, just configuration. Next time you need to wrangle thousands of events without breaking a sweat, remember: shard, scale, succeed!

0
Subscribe to my newsletter

Read articles from Nagendra Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nagendra Singh
Nagendra Singh

Allow me to introduce myself, the Salesforce Technical Architect who's got more game than a seasoned poker player! With a decade of experience under my belt, I've been designing tailor-made solutions that drive business growth like a rocket launching into space. 🚀 When it comes to programming languages like JavaScript and Python, I wield them like a skilled chef with a set of knives, slicing and dicing my way to seamless integrations and robust applications. 🍽️ As a fervent advocate for automation, I've whipped up efficient DevOps pipelines with Jenkins, and even crafted a deployment app using AngularJS that's as sleek as a luxury sports car. 🏎️ Not one to rest on my laurels, I keep my finger on the pulse of industry trends, share my wisdom on technical blogs, and actively participate in the Salesforce Stackexchange community. In fact, this year I've climbed my way to the top 3% of the rankings! 🧗‍♂️ So, here's to me – your humor-loving, ultra-professional Salesforce Technical Architect! 🥳