Video: Scale Your Apex Performance With Asynchronous Processing | Duration: 3724s | Summary: Scale Your Apex Performance With Asynchronous Processing | Chapters: Welcome and Introduction (0.08s), Queueables and Events (408.065s), Queueable Apex Explained (568.505s), Queueable Implementation Examples (1022.87s), Queueable Job Implementation (1355.3301s), Platform Events Overview (1524.16s), Platform Event Subscribers (1617.385s), Platform Event Metadata (1753.43s), Partition Configuration Explained (1893.72s), Platform Event Callbacks (2019.78s), Parallel Event Processing (2140.59s), Platform Events Demo (2332.8s), Performance Analysis Results (2892.675s), Asynchronous Processing Practices (3147.87s), Resources and References (3355.545s), Questions and Conclusion (3425.25s), Apex Cursor Features (3445.085s), Apex Cursor Questions (3539.3599s), Conclusion and Farewell (3640.715s)
Transcript for "Scale Your Apex Performance With Asynchronous Processing":
Think. But I may come and check the chat from time to time. Hello, everybody. Welcome to this SalesforceBen webinar, scale your Apex performance with asynchronous Processing. My name is Peter Chittum and I'm the director of technical content here at SalesforceBen. Now you know SalesforceBen, of course, from our website and loads of great articles that we have out there. You should also know we have a newsletter letter that we would love you to sign up for. We have events like this one right here, but we also put together in person experiences at many of the big Salesforce events out there in the world. We have a YouTube channel we'd love you to subscribe to and catch our videos. And, there is a podcast. It's been on a bit of a hiatus, but we are looking at rebooting that. So keep stay tuned on that, because that will be coming back. We also have a research arm. We actively look at what's going on in the Salesforce ecosystem and community. The current survey that we are running is our 2025 developer survey. This is kind of exciting. This is the first time we've done a second survey. We just started the developer survey about a year ago. So we would love to have your responses and your interest scan the QR code. Go fill it out after the webinar, of course. But you can scan the QR code now and have it all loaded up and ready to go, because I'm sure you wanna do that after listening to me for an hour. And then finally, we wouldn't have all the great content that we have if it wasn't for folks in the community like yourselves who write articles for us. So if you are interested in that, go to Salesforce Ben. Go to the bottom of the page and click the write for us link. And, yeah, we'd love to hear what your ideas are. Now, of course, before we get started on the core of the webinar, we have to do a big shout out and thank you to our sponsor, Salesforce. Salesforce gives sales and service customers access to more of Salesforce and is now included with your existing Salesforce implementation. Salesforce has made it easier than ever to get started with the connected customer three sixty by building their foundational apps into your CRM at no additional cost. Yes. That's right. For free. Salesforce is a sorry, Foundations is a free add on. It gives Sales Cloud or Service Cloud customers, on enterprise edition and a free access to Agent Force, sales, service, marketing, commerce, and data cloud. After activating Foundations, you can leverage any one of the features shown on this slide, use them to enhance your business, or test proofs of concepts with different agents, from Agent Force, all for free. Just give, just to give you two quick examples, there is a new AgentForce for sales agent that's focused on lead generation, and there are marketing cloud camp cloud capabilities provided for your business and, of course, all free via foundation. So go check that out. Our sponsors would really appreciate it. And that brings us to our agenda. So kind of a short agenda with a lot to cover. We're gonna do a quick overview of asynchronous Apex, and then we're gonna dive into some examples with queueables, with platform events on Apex, and then just some general considerations, best practices, etcetera, to finish up with. Now, to get started, let's just do that overview of asynchronous Apex. And, actually, I'm gonna take a full step back just in case anybody is new to the Salesforce, platform and talk a little bit about the Apex, sorry, about the Apex programming language and do a bit of an overview. Apex is a domain specific language. It runs on the Salesforce platform and only on the Salesforce platform. It's server only run time, no local run time. It's strongly typed, object oriented. It lives in the c family of languages. And it has some cool features for being so tightly coupled with the platform such as when you make a change to the schema. The API of Apex updates automatically with information about those schema changes and also maintains referential integrity to any compiled references to that schema. It has tight integration to several different data languages and platform like, platforms like SOQL, SASL, DataWeave, and DataCloud, and is publicly API accessible provided you implement it that way. Now, Apex is executed either synchronously or asynchronously. The top three there, trigger, invocable, and or enabled on the left hand side, are probably 95, maybe even, like, 99%. Maybe not 99%, but it's the vast majority of the synchronous Apex that you are gonna see in a given org falls under, those probably. And then on the asynchronous side, there's a bunch of things that we can do. However, in this particular webinar, because we're focusing on asynchronous, we're also just gonna focus on two specific aspects of asynchronous Apex, queueable and platform event triggers. And that's because there are some new features that have come come about in those spaces in summer twenty five. But first, let's talk about what asynchronous means in Apex. When Apex has asynchronous, functionality, what that means is that in one Apex transaction, there's gonna be some call that puts some work in a queue or in a waiting place somewhere. So, that could be sort of a trigger, that could be from a button that you clicked on the user interface, whatever that thing is that's happening synchronously in Apex. We put a little bit of work in the queue. And then at some undetermined, and, unknown time in the future, most of these have no SLA as to when that's actually gonna happen. Some additional Apex logic will occur. Now the interesting thing is that the async part of that just means it's probably gonna run synchronously in its own transaction, just decoupled from the original transaction. And that's really typically what's meant by asynchronous. So, as we said, Queueables, that's one of the ones we're gonna focus on. A Queueable is a Queueable because it's a Apex class that has implemented the Queueable interface. It has a lot of features that make it a very maintainable, scalable way to do asynchronous Apex. You can monitor the jobs. You can monitor the queue. It has the ability to take any Apex type, which is a little bit better some from some of its predecessors, things like retrying or capturing failures with finalizers, chaining calls. And then it's highly configurable for things like the delay before running, the stack depth that you can do when calling, recursively. And you can also even identify unique job signatures so you don't accidentally requeue the same job twice. So a lot of cool features on queueables. Now I wanted to spend some time on platform event triggers because of a particular feature that's been launched recently. But it's notable that platform events are not Apex only, and I'll come back to that a little bit later. But in the Apex context and generally speaking, platform events use a publish subscribe pattern. The delivery of platform events is not guaranteed, so that's just something to be aware of. But the Apex subscriber is essentially a trigger on an s object that represents the event. The match batch max batch size is 2,000, which is a little different from a regular s object trigger. And then it does support a callback feature. Now this is, the rest of the, asynchronous gang of Apex features, shall we say. Now, the thing the reason another one of the reasons why I decided to go with the two that I went with is that if you look at where Salesforce product and engineering seems to be putting their energy and their new features and functionality, it all seems to be going into queueables and platform events. And I think that's actually something to pay attention to when we are users or implementers of a technology. You know, where where's the investment taking place? And I'll be honest, it's not taking place in any of these four in any obvious ways that we've seen. That's not to say that these aren't valid use valid features to use. It's just that queueable and platform events seem to be having a lot of changes, which makes it really worthwhile to stay up with them. Them. Now any of those Apex asynchronous Apex types except for platform events, you can actually track them in the Apex job, queue, and you can find that under setup environments, jobs, Apex jobs. So it's really useful just to kinda keep tabs on what's going on. And if there are failures, they will report here. Okay. So now let's dive into the actual use case that I'm gonna use throughout this presentation here. I originally was going to create kind of an order use case with platform events to sort of explore that. But as I dug into what I found really intriguing, which is the performance of these parallel subscribers, I just found that really interesting. So what I ended up creating was actually sort of a parallel subscriber test application, that that's basically what I spent my time on and that's what I'm gonna share today. So let's take a look at what this, looks like and how it works. So, of course, starting out, we have the event bus, and then we have the Salesforce. In that database, there are two s objects that we're gonna be used. One is an order object, and that's actually what we populate with the platform events. And then because I basically built some functionality around measuring the performance of the platform events, I also have a trace table. Actually called it something slightly different. I think I called it request tracker. In fact, both of these objects are named slightly differently from the slide. I'm just trying to keep the slide simple in this instance. So what happens if with our platform events scenario? We're gonna run a test and because my test is 50,000 platform events, I had to use a future, Apex method to launch them. Otherwise, I was running out of CPU time. So my future publisher goes and it stuffs 50,000 platform events into the event bus. The event bus then gets those picked up by a trigger subscriber and then some order records are created. So that's kind of the basic flow of these platform events, and I wanted to test the performance of those. So I also created a queueable, tracker Apex class that writes data into this trace object. And, basically, for every batch of the Queueable trigger, at the start of that, it writes one record, at the end of that, another record. And using those records, we're able to see how long each batch takes. And by extension of that, each total test run takes as well. Now, of course, this is a scratch org, so I was quickly running out of space. So I also wrote a queueable, sort of cleanup Apex class that actually uses a new beta feature called a cursor in a Queueable to clean out those orders. And that's kind of the full story. So what I'm gonna do is talk through the Queueables first. So it's kinda like starting at the end like a Quentin Tarantino movie, and then we'll come back and talk about the actual platform events. Okay. So Queueables. Now the Queueable process looks something like this, where somewhere in some Apex, you're gonna call system dot n q job with an instance of a queueable class. Now as we said earlier, what makes it a queueable class is that it will implement the queueable interface. So you can see here on the left, and actually, let me grab my little pointer. Oops. There we go. Pointers are very handy. Okay. So there is my system dot in queueable call and you can see here it's it's instantiating this trace handler class and then calling enqueue job on that instance. That sticks, the queueable instance into the queue, which then gets picked up at some point in the future, and gets, and the execute method in that instance is run, which then goes and performs the logic of our Queueable class. Now another important feature of Queueables and really designed into them is the ability to chain or, in other words, recursively call the same queueable, class. So here, you can see that my again, my I enqueue my job, that puts it in the queue, the queueable runs, and then somewhere in the logic of that queueable, if the circumstances are right, I may decide to reenqueue another instance of that same class. Now that actually can be the same instance or a whole new instance. It just depends on what you're trying to do and how much state you're trying to maintain across calls. But chaining is definitely one of the key use cases of Queueables. Now Queueables have a bunch of options you can define before you actually enqueue them, and these do things like define, what the call depth is going to be, the signature to prevent, prevent duplicate, queueable instances being put into the queue. So all of these things are configured in the async options class at run time in the code just before you go in and queue your queueable. And then in your queueable logic, you need to look for the async info class to determine what's the current state that you're in. So you might wanna track the total number of calls that are available. You do have a maximum number of queueable calls, that you can exhaust, and you wanna make sure that you're staying below that number. You also have, there are some other limits that may can't come into play and that that, the stack depth one is the one you can see here, where, essentially, I'm saying, you know, get what's the current stack depth and compare that to my maximum stack depth. And if I'm beyond if I'm about to go beyond that, I wanna stop so that I don't get an exception. Now one last feature of Cuemath that's really handy is something called a finalizer. Now finalizers are, the finalizer isn't is also an interface that can be implemented in its own separate class or in the same class as the queueable as a second execute method signature with a different, different parameter type. And so, in that instance, in your queueable, at some point, you would identify the finalizer. And then the nice thing about a finalizer is, whether there is a failure or not, the finalizer can then go about the process, of recording what happened. So, if an error occurred and the actual queueable didn't complete successfully, that actually gets reported in the parent job status, and then you can log that or, you know, possibly try and recover it or requeue. There's a different different strategies you can take, to approach that. And then finally, not directly related to Queueables, but, but definitely related to them in some degree is the new cursor class. This is a beta feature that was launched in summer twenty five. I believe it first became available in beta. And this is kind of a new, more flexible way of running Apex over large data volumes. You can see I put some limits up there, but the nice thing is that when you combine the cursor class with Queueable, you can access quite a lot of data. But the one limit that I would just draw your attention to is that, you know, although a single cursor can process up to 50,000,000 rows, per day in your org, you only get a 100,000,000 rows. So very clearly, they're not aren't expecting you to run this every ten minutes. If you are, you're gonna run out very, very quickly. Just to show you the use of the the, cursor, you can see here, database dot get cursor, we pass in a query. Incidentally, there's a version of get cursor. I think it's called get cursor with bindings. I recall I I forget the exact name. That allows you to have binding variables, in dynamic Apex. Oh, sorry. Dynamic SOQL as well. So you can actually pass in sort of dynamic variable values to this. I I didn't do this in this instance. So calling database dot get cursor basically goes and gets all the rows. It's It's gonna tell you how many rows it's gonna get back, and it's gonna put that in memory in the database. And then cursor dot fetch is going to retrieve a chunk of those in memory records. Max batch size is 2,000. So per fetch, that's the most that you can do. And you do get a maximum number of fetch calls per transaction, so keep an eye out for those as well because those can come out to bite you. Alright. So let's jump in and take a look at the demo that I have for Queueables. I'm gonna show you, an example of the Queueable interface, the use of async options and async info, and the cursor. So, and I actually have two Queueables. The first one I'm gonna show you is the Queueable tracker. Mhmm. And so I'm actually gonna start in the trigger subscriber handler and show you where I'm actually calling those to, to to track the sort of batch the start and end of each batch of executions of the, platform events trigger. And then I'll come back and show you the the order cleaner queueable because that's where I'm using the cursor. Alright. So let's jump on over. Okay. So I'm in Visual Studio Code in a project here that I have open sourced and I will share you the link a little bit later in the call. And you can see here actually, let me yep. Okay. So this is actually the the tail end of my platform events. And what you can see here is, you can see, there's my handle inbound orders method, that gets executed. And at the beginning of that, we're getting some information. I'm getting a batch ID. I'm getting the name of the event. I'm getting a a time stamp from system. Now so I can have the full millisecond time. And then I am calling tracer dot trace. So that's actually gonna go and do my queueable call underneath that tracer. Trace method. And then at the tail end of that, I have another where I'm updating the timestamp and then I'm calling tracer.trace again. So that is where I'm going about the process of tracking for each batch what's the start point and what's the end point and some information about that. And let's go take a look at tracer class. So here's my tracer class with, as you would expect a static method called trace. And in there, we're simply storing, taking the parameters, putting it into this trace info, Apex object, and then we're passing that into, the trace handler queueable class and then we're enqueuing that, that queueable instance. And, of course, the last step is let's go take a look at trace handler. Trace handler, you can see there's the execute method. Right? We passed in the trace here, and we're simply going in and, again, getting a time stamp. And this is because I was also storing time stamps in the actual order records, early on. That's really not relevant to the actual order thing. But then we basically create our request tracker record. Oh, actually, yeah. This is an order. This is the the the the tracer. So and then we're gonna store that trace record. So there we go. At the beginning and end of each batch, we simply store a record that passes in the information about, you know, all the information we need to to trace what's going on. Okay. So that's one example of a Queueable. Now we're also you I'm also using Queueable in this, this order cleaner class. And I'm just invoking it with, an a, anonymous Apex, script here in my project. And you can see that I set maximum queueable stack depth to 50. Now when I set out to build this webinar, I had no idea I could go to 50. I thought sort of five was the limit. And I was like, how do you actually make this go big? And it turns out you can set that to a rather high number. I don't know what the upper bounds limit is. And in fact, if somebody knows, because I'm sure there's a lot of smart people out there, I I'd be curious. I didn't hunt really hard for it, but it wasn't something I sort of ran across either. So feel free to chime in in the chat and let me know if you know what that is. Okay. So here, I create my, to to to right. So async options. I set that up, and then I create an instance of order cleaner directly in the enqueued job call and pass in my async options. In that way, I can track total number, the the total stack depth compared to, my, my maximum stack depth that I've set. Taking a look at order cleaner, the interesting thing in here is actually the use of the cursor. So here, database dot get cursor, that line right there, is how I'm going and setting up the query. And, again, that goes to the database, runs the query, and basically creates a cursor. We then so that happens when I instantiate the class. Then in the, yeah, in the execute method, we're going to create a set of orders. So that's the the order, s object that I'm basically trying to clean out so I can run tests again because I'm all out of, of rows. And I'm doing a check here. Basically, what I wanted to do was get all as many records as I could up to the 10,000 DML limit and then do one delete call. And so I'm checking to see if my order size is hitting my DML row limits. If I haven't hit that limit yet yet, then I basically iterate over calling fetch and adding all of the records to the orders list. So this here allows me to go in 2,000 at a time, go and get the records, fill it up till I get to to 10,000, and then I call it delete orders. Now I wanna also make sure that I'm not running out of, stack depth, and I, still have some records to clean out. So I have this cursor handle dot get numb records, and then if that's greater sorry. If position is less than that, then I'm gonna reenqueue the job. And this was another trick I wasn't super aware of, but I can actually include in in queue this exact instance of my queueable. And the cool thing about that is that it maintains state across multiple queuing of the same instance. So it's just a little trick if you do need the main state maintain state across, several runs of the same queueable instance, that's how you do that. Alright. And with that, that pretty much wraps up everything that I wanted to go over in queueables. Just gonna do a quick check on q and a, and it looks like no questions yet. So, I'm just gonna plow right ahead. Alright. So let's move on to platform events. Now platform events are a publish subscribe feature on the platform. A platform event itself is kind of a special s object that has no permanent storage. There are a bunch of standard platform events that primarily get used for event monitoring, that part of Shield, as well as some of the free events that you can publish or sorry. You can subs sorry. That you can subscribe to, that are, related to certain system events. And again, just to reiterate, there is no guarantee of delivery of a platform event, so you just wanna factor that into any design that you build or any architecture that you build around platform events. It's you know, don't really see or feel any of it, but it's maybe worth noting that underneath the covers, platform events are running on Apache Kafka. I'll take a little sip there. Now as we said, platform events have loads of different subscribers you can use. We're gonna focus on the Apex trigger subscriber, but, of course, you can subscribe to them from a flow, from LWC using the EMP API, package. GRPC is a standard remote standardized remote procedure call, feature for that's what that was kind of been championed by Google. And then there's kind of the, really the old stalwart Comet d slash Bayou protocol type of long polling integration that we have available. And any of those can be subscribers to the event bus for platform events. So event gets published, put on the event bus, and then you could have one subscriber on one of these. You could have subscribers on all of these, and any subscribers would then get the it would pull the events out of the event bus. Now on Apex, a couple of things to, oops, to be aware of. I'm just gonna get my pointer again. So see that underscore underscore e, that's what tells us that this is a platform event type s object. So in order to So in order to publish a set of events and, normally, if you have many events going out, you want to bulkify them just like you would your s object your regular s objects. So we create a list of the inbound order event, and then we pass that to event dot event bus dot publish, of course, after we populated them with some actual values in reality. And so, essentially, that's the event.bus.publish call right there that sticks the platform events on the event bus, and then the subscriber is essentially a trigger in Apex. And, again, we can see that it references the underscore underscore e object. But the one thing about platform events subscribers or triggers is that the they only subscribe to the after insert event. There are no other trigger cycle events to actually use in platform event triggers. Events themselves have some metadata. Of course, these get defined in the setup menu normally. But if you are looking at it in your project, it looks like this. A couple of important things. The old standard platform event types, are in the progress of being phased out. I think right now, they are only not phased out because some, AppExchange partners have still not retired them yet. They're still using them, and they haven't ported over to the new high volume platform event structure. But that is something that Salesforce is aggressively pursuing to try and retire the old standard platform events functionality. The other thing to pay attention to is the publish behavior. You can either publish immediately, which is basically fire and forget. And in that instance, you need to be careful because if your data hasn't been committed, that's gonna still go and do the thing on the other end. So you wanna be really careful. And, you know, in the the implementation that I did, I'm kind of firing off an event to create some orders, but I'm doing it as publish immediately. In reality, anytime you're kind of sending data over the event that would be committed, you probably wanna make sure that that is on the, what's it called, publish after commit behavior. I have it right on the slide there, after commit. So just, be aware of some of those considerations. You would also have some of your own custom fields on there. That's where you would put your data. And then every platform event has a standard field called event UUID, which is basically that event's identifier. Now subscribers themselves have metadata configuration, and this is called the platform event subscriber config metadata. As of today, you have to manually create this metadata either via the metadata API, or via the, what am I looking for? Tooling API. There we go. Too many vocabulary words today. There's some really important configurations that you need to do in here. And so the first one is directly relevant to the thing that I was exploring in this particular use case, which is partition number or num partitions is the actual name of the property, and that's you can see right there. So this one is is whacked all the way up to 10. You can actually go higher than that. Although speaking to Raj Advani briefly, before this webinar, you know, generally, you know, you're not gonna get a whole lot more bang for your buck except very rare circumstances going more than 10 partitions. And I think the data that I found in testing this out pretty much bears that out, but just wait. We'll come back to it. We'll show you that data in a moment. Now partition key is also important when you have partitions because that allows you to define whether, your events the partition key essentially determines, or is the thing that ensures that your event isn't passed off to two subscriber partitions at the same time. So that's really important because you don't want your event to be processed twice by accident. But if you use something like event UUID, which is unique to each and every single event, then, in that particular instance, your subscriber will sort of have a randomized distribution of all the events that come through. If you need ordinal behavior as events are queued in, it's sort of first in first out, then you need to use a partition key that is not unique to every single event, but is some kind of classifier. So you could, for example, use, like, a region is an example that Raj gave, when he gave his talk at PDX London, this year. So that way, you know, every single region value of west would always go to the same subscriber partition, which does mean that you can get backups in those subscriber partitions, so it's just something to be aware of. You can also configure the running user, which you can see that I've done here, and that's particularly handy if you want to attach to the debug logs in, sorry, using the SFCLI, which is what I was doing. So that's part of the reason why I did that. And then, yeah, there's a bunch of other subscriber metadata configurations that have to do with managed package configuration if you are an ISV slash app exchange partner. Okay. So, there is also the feature in platform events called callback. This does exist in the Apex subscriber functionality as well as the gRPC one. And callback is simply, if if if things went well or if, you know, you can basically send a message back and said things went well. So it's just a way of getting confirmation that a platform event, did process successfully. Now I actually kind of gave this away a little bit. Right? The problem with a single partition in platform events is that that single partition can encounter bottlenecks. Now remember the max batch size of your subscriber is 2,000. If your batch sizes are regularly below 2,000, that's fine. But if you have a constant high workload, this can cause a backup. And let's actually illustrate this a little bit better than Lucille Ball stuffing candies in her shirt. So, and her mouth. So, so let's take, we're gonna talk take, an example of a single subscriber across three seconds of its life. Right? So second number one, fifteen hundred events dumped into the queue. Now what happens oftentimes is the subscriber the the first time it go if it's been at rest, the first time it pulls events out, it will often do fewer than its maximum batch size. So let's say this time, you know, a thousand, events are processed, but it takes two full seconds for it to process that, which is not unreasonable for that number of events. So second number two, we still have 500 events, those events in yellow, right, that are still kind of, like, sitting around from the last set of events, but 1,500 new events have been dumped in as well. But our subscriber is still processing. So this second, nothing gets shifted out of the queue. And if, again, the following second, second number three, another 1,500 events, get dumped in and even, let's say, our subscriber is able to pull all 2,000 of its maximum batch size out of the queue, we're still gonna have 1,500 events left over that we need to pick up not the next second or the second after that, but maybe in three seconds time, which, again, based on some of the observations I made, is not an unreasonable amount of time for, for 2,000 events to be processed by by a trigger. I I did see that happen from time to time. So, this is where the backup really, can start to stack up. And this is where a lot of, you know, customers that were having high volumes of platform events were coming to Salesforce looking for solutions, and that's why they came up with this idea of parallel subscribers. So in that same instance, in that second number one, fifteen hundred events are dumped in, and let's say we have three parallel subscribers. Immediately, each of those takes 500. That takes point five seconds to actually process. The next second, the event queue is clear and we can continue processing. In fact, we probably have enough throughput to handle all the way up to 2,000 every second. And that's just with three. Like I said, you can actually go higher and higher if you want to. Okay. And that brings us to our next demo, which is this whole platform event testing setup that I, put together. Because I wanted to run tests back to back, I actually created two platform events, one that was configured for parallel partition and one for single partition. Right? And I'll show just one of those. I'm not gonna go through each and every one. I'm gonna go not gonna go through, all the stuff twice. So there was a test, a publisher. Each of those publishers would dump 50,000 events into the event bus, and then we had our subscriber either solo or parallel pull those out. And then there was a single publisher class that would get called, to then process those into order records. So that's kind of the that's how we went about that. And let's go take a look. Alright. And, actually, I'm just realizing that one of the things I didn't do in the last demo was actually go ahead and launch, the, any of these queueables. So that's what I'm actually going to do now. Now I have this data left over, and part of the reason I'm gonna do this now is because it takes a little while for this to process. So this is my actually, I'm gonna go to there we go. So there we go. So these are the now a 100,000 records that are in my, my order table. And, oops. There we go. Yeah. And I'm gonna go ahead and launch, so, Salesforce Apex. The clean orders, apex class. So this is actually the the anonymous apex that that's gonna launch the, order cleaner class, and I'll go ahead and do that. It does warn me that I'm on the, wrong version of the Salesforce. I didn't update that, last week. And, of course, because this all that this bit of Apex is doing is enqueuing a queueable, It comes back right away, and it basically says, there's one SOQL query that happened, and that's pretty much it. Like, this doesn't really seem like anything happened. But if we go back here and I go ahead and run my export again, you can see that it's starting to tick down the record account. Okay. So we can see that that's starting to clean out, that order object using that queueable. I'm gonna let that run because it does take about five minutes. And I'm gonna go back, and now let's talk about the platform events. So this is a platform event just like I showed you previously. You can see here, there's the high volume platform event configuration and the fact that we have it set to publish immediately. Again, if you wanna make sure that your transaction is completed and roll back, you know, if there's a failure on your transaction, you want to make sure to use publish after transaction. Now in addition to that, we also have some metadata in the form of the configuration for the subscriber. And you can see here I've set up the the parallel subscriber to have 10 partitions. I'm using event UUID, which means it's gonna evenly distribute all the events across those partitions. And then my user is demo, at quarter storm, and that is actually I have that so that I could attach to the debug logs so that I could track when things were happening or not. It's really useful just to sort of know things because you can't really watch platform events or other asynchronous Apex. Now here's the publisher. So this is what is being called from the anonymous Apex, which I'll run-in a moment, and that's basically calling publish events. I pass in some data. I create a list of those. I populate them, and then I finally call eventbus.publish. Now you can see there's another version of this for my single partition down here, and that was because I sort of had to, you know, I wanted to have two subscribers, one with one partition and one with multiple partitions at the same time that I could just call without having to change configurations. You know, that was just for the purposes of the test. Now, once that goes into the platform event queue, then we're gonna have a trigger, and that's a very simple trigger that's just calling a trigger handler. I do have two separate triggers again, and this was kind of the most important thing is because the configuration of the partitions is on the trigger. I needed to have two separate triggers, but I only wanted one single handler class. So you can see I'm using the generic s object, to handle the platform events in this instance. And then the actual handler itself looks like this. There's a little bit of a weird shadow going on. There we go. Now I already went in and showed you the the trace calls here, which we looked at this code earlier. In this instance, we're just gonna take a look at how we're creating the orders, which is pretty straightforward now. All of the wiring of the platform event is already done and over with. You can see here that we have our list of s objects. Again, those are our platform events. And just like you would normally expect in bulkified Apex, we sort of pull those out from the order event using the generic sObject referencing using the get method. We put them into the order s object that is storable. That's the underscore underscore c, and then, of course, we insert that. So fairly straightforward bit of logic there. Now I'm gonna go back and check and see where we're at as far as the deletions. I'm guessing we're probably a little less than halfway through. So if we take a moment and run export again oh, actually, almost done. Fantastic. Yeah. So that means I can basically go again. Alright. So, let's go ahead and run. Okay. I'm just getting a warning that we have to wrap up in fifteen minutes, so that's good. No. It's test. And this is test order event multisub. So the first one I'm gonna do is the multiple subscribers. So this is the one that I showed you that's set to 10 partitions, and I'm actually gonna run a different query. It actually should be finished by now. There we go. I'm gonna go to this query called demo traces, and you can see that I have some preexisting traces that I ran earlier. These were both, parallel subscriber. Although one took a little bit longer than the other because it had fewer partitions. So on this so now that I've got that ready to go, I'll go ahead and run my, first test with platform events with 10 partitions. And what you'll see there we go. Pretty much straight away, you can see here, this is gonna go up to 10,000 because these are traces at the beginning and end of every batch. Sorry. Yeah. And so that means I have sort of duplicate, counts of the actual records created. So that created 50,000 records using a 168 batches, which if you do the maths, you know, it tells you that it's using much smaller batch sizes than the 2,000. And I think when we look at the data when I'm done with this demo, you'll see but I think that's a key to a lot of the performance that you get with parallel subscribers. Now let's go ahead and do the, one with the single subscriber, and that's, actually, what I'm gonna do is clear this out so you can kind of see at the top, And we'll run that. And so I'll go ahead and start running export again. And what you'll see is that the amount of time it's taking for the actual records to tick up is way, way slower, and it stands to reason. We have one worker basically versus 10 workers. It's natural that this would take a lot more time. But let's jump back to the slides and see just how much more time and some of the other dynamics of that that I was able to get from doing these traces and pulling the data out and doing some massaging of it as well, with the help of, Python and Matplotlib and Google Gemini to be fair. So let's go take a look at that. Okay. So data. Probably the most fun part of this project was to get to the bottom of what the performance look like when you have parallel subscribers versus a single subscriber. And this is the average total time each time I ran a test, because I ran 10 tests at each subscriber or sorry, each partition level. So 10 with one partition, 10 with two, ten with four partitions, six partitions, eight. And so the average across those 10, you can see here what that looked like. I think the interesting thing to learn about this is that you get pretty good bang for your buck once you go up to four, five, maybe even six subscribers. But once you go above there, the amount of additional gain you get is pretty incremental. And I think that's really useful to inform yourself if you're going to use this feature, how many actual partitions are you going to need to create, and it's typically not gonna be a ton if provided you're using the random UUID assignment. I also wanted to see what batch size distribution looks like. Because one of the things I saw kind of looking at the raw data is that in the single partition use case, there were a lot of batches that were totally whacked up to the full 2,000 batch size. And I think it's really informative to understand that the algorithm for the subscriber is very greedy when it is lacking resources. And it's almost like it's it's probably, like, stuffing itself as full as it can before it goes off and does some work. But the minute you add a second one and there's a weird effect here that I was never able to get to the bottom of as far as all these outliers in this box plot. But you can see all the rest of them, none of the batches ever reach the full size of 2,000. So that's really informative that, you know, a good efficient, processing of your platform events probably means not maxing out the batch size, which is very different from a synchronous Apex trigger with a regular s object, isn't it? And I think it's just something to think about and get your mind, you know, sort of reoriented around if you're using this as a feature. And then to me, this was kind of the most fun thing to explore, which was to try to find a way to visualize the concurrency that occurs across the different batches across the different partitions. And I wanna be really clear that this isn't visualizing how long each partition is running. There is no visibility and no observability that tells you that this particular event was processed by partition a, b, c, or d. But what it does give us is based on the start and end time of every single batch, what what other batches were running at the same time. And you can see there's a high degree of concurrency, especially kind of in the middle period of that one. I did run this particular plot for every single test. So I've got, what is that, 50 of these plots to to look at. And I kind of pulled out the ones that sold the, I think, the best story. But if you compare that to four partitions, four partitions really still has really good concurrency, and so it's understandable why you get pretty good performance out of there. But, you know, of course, a single partition, that thing is working a 100% all the time every single, batch. And so you can understand again why this, you know, just takes a bit longer. I did this in Google Colab with Python, matplotlib pandas, you know, and like I said, a a little bit of help from Gemini because I do know Python but not that well. And, if you wanna take a look at that particular collab, I have shared it and made it public, at this Bitly link right here, which I will share again later. Alright. Other best practices. You know, a lot of these that I have here are pulled directly out of the asynchronous processing decision guide on architect.salesforce.com. We all know that as of today, it's not being maintained, but there is still a ton of really good information on there. It hasn't exactly aged out 100%. Although, certainly, features like the, the parallel subscribers, that's not gonna be reflected in any of those decision guides as of today. So it's just something to keep in mind you know, keep an eye out for. And I think probably the most important things to remember are, you know, asynchronous processing is not for every use case. It never gives you an SLA as to when it's gonna complete. There's no guaranteed consistency between processing times, which, again, is totally, totally obvious when you look at these plots right here. You know, how soon a particular record gets processed, just no guarantee whatsoever. Yeah. And just one thing to, you know, always incorporate monitoring into your solution. Because asynchronous is out of sync with the actual transaction, you need to make sure you have a way of understanding and collecting errors and faults, and faults that have taken place in your asynchronous processing. With parallel subscribers, just remember this is an Apex only feature as of today. Max, sorry. Batch size is not a maximum. Sorry. Batch size is a maximum, not a target. So it's rare if your if your subscriber is healthy that you're gonna reach that max batch size, I think, is the world of that story. You get a lot more performance gain from the first four to five partitions, maybe six, but beyond that, it's kind of incremental. And then remember, partition key, if you use a unique field, you'll get super fast processing, but if the order is nondeterministic. If you use sort of some side of some sort of classifier field, then it is deterministic processing in the order for each partition, but you may encounter bottlenecks if one particular value is more prevalent over another. Some things about queueables, remember to use chaining for iterative work, and that way you can really go further with your queueables. You configure runtime, properties in async options, and then you can read them out during runtime at async info. Use finalizers to do retries and to log errors. And remember, your configs include things like step stack depth, batch size, and, signature. And finally, cursors. Remember, cursors are beta. It was a lot of fun to use these, and I think it kind of looks like it's sort of meant to be an alternative to batch Apex at some point in the future. But, certainly, when combined with Queueable, you can reach very close to what you can already do with batch Apex with Queueables only. And remember, mind your limits with these. There's a lot of things to bear in mind when, handling these. And to be fair, all of these features are complex. You can't just go and write them up, hack them together over a weekend, and then get them to work and put them into production. You do need to plan these out, architect them, and make good decisions when deciding when to implement them or not. And then here's a list of resources. So the GitHub project, which is still work in progress, I did have plans to put together a lightning web component, but I went too far down the data hole and I kind of ran out of time. Hopefully, you appreciated the data analysis slides for that reason, and you won't ding me too badly for not giving you a lightning web component to run your tests with. But I I found that to be really intriguing stuff to dig into. And then if you wanna see how I did the data analysis, you can take a look at the Colab Jupyter notebook at the bitly link. That notebook, when you start it up, will not have the data. But if you want to clone it and then take a look at it yourself and make changes to it, you can do that. And then you can actually get the data files from the GitHub project. And then I've listed all the docs that I used to help me with this particular webinar, the platform events developer guide, the Apex developer guide specifically around queueables, Apex reference guide, soap rest metadata API developers guide, and then the architect decision guides on async processing event and event driven architecture. These were all super useful in putting this together. I wanna say that, you know, this was kind of the first time I've dug in massively into platform events. Hopefully, you learned something from this. I certainly learned a ton prepping this webinar. And with that, we have a few minutes for questions. Manoj, asynchronous Apex transaction has a heap size limit of 12 megabytes. Are you asking that or telling me that? That sounds about right. So here's my thing that I always say about Apex limits. I used to remember them really well, and then I realized they were always changing, and I could look them up anyway. So if you look that up and you're telling me that's the case, I totally believe you, and that sounds about right. It is something you definitely need to be wary of. And, you know, for for instance, in the cursor, one thing that you could definitely do is, you know, if you're running into a heap size limit, you could sort of change the dynamics for, you know, how much you're fetching from your batches or how much you're storing between, fetches. And so there are ways you could fine tune what you do in that instance and still take advantage of what the cursor gives you. And then for high increasing data volumes, does the approach change? Well, what I would say is today, no, because cursors are beta. So, but I would certainly strongly encourage everyone to take a look at those. In fact, just today, I saw a really, intriguing looking post from Andy Fawcett, who looks like he's put together an infinite scrolling lightning web component using the new Apex Cursor feature. So I think it's gonna be a really interesting feature to look into for your projects where you have large data volumes interacting with Apex. Does Apex Cursor support, big objects? That's a very good question. I don't know for sure. My gut would say probably not, but that's one I'd be happy to take a look at. And what I could do is, post this, you know, there are usually so what I'll do so who's that from? Christian Gopalyadov. Okay. What I'll do is I'll try and post this, out to LinkedIn. So keep an eye on my LinkedIn in the next twenty four to forty eight hours. I'll try and look that up pretty quickly after the webinar and post an answer to the question as to whether Apex cursors support big object. Also from Krishnan, does Qubil fire platform event, in failure like batch Apex? Oh, so yeah. The asynchronous platform event, failure event, that's actually that's for all asynchronous Apex, I believe. That's what I recall about that. Again, that's something I would need to double check, but that is my recollection of how that particular platform event works is any asynchronous Apex. And to be more specific, queueables, batchables, future methods, I believe, maybe schedulable as well. I I believe it's all the things that show up in the Apex job queue, have the ability to fire that platform event. And can you use platform events for observability? Absolutely. Although you don't have to because I'll just put a plug in here for Nebula Locker, which I know uses platform events. So it's, there are already frameworks out there that will do that for you. Although, if those don't fit the bill, of course, we all know that anybody can throw together their own framework for doing observability and do it themselves, which go for it. If you're not so if you have if it's something you want in addition to what you get from those existing loggers, absolutely, you could use platform events for observability. And I think that winds us right up to 05:00 UK time where I'm at, which is the top of the hour and the end of the webinar. So I wanna thank you very, very much. I also want to quickly just give a final shout out to Salesforce. Salesforce, again, are sponsor for this. These QR codes here for, going to learn about Salesforce. There's another one for going to activate it. And, of course, there is a Trailhead trail that you can use to go and, learn using the using the learn the Trailhead way. So with that, thank you very much, and have a lovely rest of your day or evening.