Deep Dive into ParallelSubscriptions: Turbocharging Your Salesforce Platform Events


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
Custom Platform Event
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>
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>
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
Parallel Subscriptions Config
We’ll start with 4 partitions, sharding by the requiredOrderNumber__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.
Partitions | Number of Events Fired | Time Taken to Insert ARecord__c |
1 | 5000 | ~15000 ms |
4 | 5000 | ~4000 ms |
10 | 5000 | ~2000 ms |
Why the Speedup?
Event Sharding
Salesforce hashes your partition key (
OrderNumber__c
) and doeshash % 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!
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! 🥳