End Procrastination Tomorrow! Doing things later can make your system better
About this video
This session was presented at DevConf South Africa 2023.
Are you looking to improve the performance, scalability, and reliability of your system while doing less work right now? Many people believe that doing things right away is the best approach, but what if putting some things off until later is a good thing?
In this session, we will explore the benefits of designing your system to embrace procrastination. We’ll look at techniques like delayed delivery, and long-running business process patterns such as the Saga and Process Manager patterns. We will discuss the types of delays that can occur in a system, how the Saga and Process Manager patterns can handle failures and complex workflows, and review real-world examples. Attendees will leave with a deeper understanding of when and how to implement these patterns for simpler systems architecture.
🔗Transcription
- 00:00 William Brander
- Hello everyone. I was tempted to start the session a little bit later, but we're on a schedule, so I'm hoping there's going to be some latecomers that'll exemplify leaving things to the last minute. Today, we're talking about procrastination, and not how you or I procrastinate because I don't think anybody needs help procrastinating themselves. Instead, we're going to be talking about how we can make our systems procrastinate, specifically with the focus on how systems that procrastinate can potentially scale better than other systems. My name is William Brander. I'm a solutions architect at Particular Software, and as you can see, I'm very good at doing nothing. Not entirely true. There was a lot of effort to take this picture. The tree was at a weird angle and there was a broken off branch stump sticking and poking me in the chest. Felt a bit like the tree was trying to do open heart surgery.
- 01:01 William Brander
- I'm often impressed at the lengths I will go to to make it look like I'm doing nothing. Case in point for this specific presentation, instead of doing enough dry runs, I spent time practicing the McDonald's, McDonald's, Kentucky Fried Chicken and a Pizza Hut dance. Does anybody want to see me do that? Too bad. Now, you're probably sitting there thinking to yourselves, "Well, this William character is probably just really lazy." And if you are thinking that, first of all, how dare you be so entirely correct? I am very lazy, but secondly, that's not what we're talking about today. Laziness and procrastination are different things. Let's look at some definitions. Procrastination, according to the dictionary, is defined as a noun. "A putting off to a future time; delay; dilatoriness; delay." The definition's missing something, though. There's a magic to procrastination. There's a bit of an art to it.
- 02:01 William Brander
- Not everyone can procrastinate well, and some people say that procrastination is an obligation, it's a duty. It's me. I'm some people. I say that. So if procrastination is putting things off to a future time, the opposite of that is precrastination, and this is completing things too early. There's a picture of these people working, or supposed to be working, but I really hope that just looking at a picture of a meme or something, because I've never in my life looked like this while working. If you look like this while you're working, I don't know how to communicate with you. We are too different.
- 02:39 William Brander
- So procrastination, leaving things to later, precrastination, doing them too early, and then laziness is sometimes just not doing it. It's being averse to doing things. I do have to apologize. I thought it would be funny to put a "todo: find a picture." You all deserve better jokes than that, but you're not getting them. That's where the bar is today. Sorry. So we've got the three definitions. Putting things off till later is procrastination, precrastination, doing them before they're needed, and laziness, sometimes not doing them. For the rest of this talk, we're going to be using a video store as an example. I'm a little nervous to ask this, but is anybody in the audience young enough to never have been inside a video rental store? Oh, thank goodness. I was terrified. Just in case you haven't and you were too scared to put your hand up, though, we'll just cover.
- 03:33 William Brander
- So a video rental store is like a library for movies. Maybe we should take a step back. A library is a big building that's got books in that you can borrow. A book is like a screen that you have to turn instead of scrolling. So the system we are going to be building is for a video rental store, and what might happen is we might want to model some code that says, "Well, when a user checks a DVD out, what we'll do is we'll take the user ID and the movie ID, stick them together into a rentals table, and maybe have some type of thing that says, 'This DVD must be back in three days,'" or whatever. That's fine.
- 04:11 William Brander
- And then business comes us and says, "Right, now we need to do some late penalties if people return DVDs late." So they might give us the business rule that says every six hours, find all the late rentals and then add 50 rand as a penalty. So think, "Okay, cool, well that's fine. Every six hours, that's a batch job." We'll take a batch job and what we'll do is we'll find all the rentals that haven't been returned that should have been returned and we'll just add 50 rand to the LateFees property of each rental. Question: is this code lazy, precrastinating, or procrastinating?
- 04:47 William Brander
- The clue is there. That's where you have to look. And in this specific case, this code is lazy. What that means is it's sometimes not going to do what it's supposed to do because that time filter that we are looking at, that period there, isn't going to be exactly when things are supposed to happen. What do I mean by that? Well, if we look at it on a timeline, and let's assume that we run the ChargeLateFees method every six hours like the business told us to. So we run it at 9:00 in the morning, 3:00 in the afternoon, 9:00 at night, 3:00 in the morning, et cetera. But a customer's DVD is late at 9:41, so at this point in time, we should have already added 50 rand late penalty to their account, but we don't because we've just finished running the previous ChargeLateFees method.
- 05:30 William Brander
- That's fine, we'll get it on the next one. Except the customer returns the DVD before the next one runs, in which case, we've completely skipped doing their late fees penalty. And in this case it's a misunderstanding of time. Now, I've got a theory as to why this is. Everything that we're going to do today is related to the progression of time, and as humanity, I think it's safe to say that almost all cultures in humanity have got a fairly strong grasp of time. As South Africans, we have a much better grasp though because we've got now, now, just now, right now where the rest of the world just has now, but for our entire lives we're used to the way that time progresses. It goes forward. It's an arrow of time. Sometimes it feels like the hours are going quickly. Sometimes the days are going slowly, usually at the end of the month before payday, but we are fairly accustomed to how time flows.
- 06:27 William Brander
- So much so that humanity has anthropomorphized... I said that right. Has anthropomorphized time multiple times in the past. So this is a picture of Cronus, who is a Greek titan. He's pictured here carrying a child. This is his child, and I believe he's carrying the child as an emergency snack. He had a bit of a thing for eating his children, eating stones, drinking anything that was given to him, bulimia. Safe to say, he's got some body image issues. The sad thing is that he's not even in the top 20 weirdest Greek mythology figures, either. I think we can all agree that Greek mythology is a bit of a trip. So Cronus, why I'm showing a picture of him, is Cronus, chronological, Cronus... the progression of time. After the Greek gods defeated the titans and they cast him out, he was cursed to walk the earth forever getting older and older as he did it to measure eternity, and eventually he became Father Time.
- 07:27 William Brander
- That's what we colloquially refer to him as. He didn't always have a sickle. That was a bit weird because he didn't need one as a titan. The sickle, the theory that I read is that it comes from Cronus and Cronus, who was a Greek card of harvest, and eventually the names were similar enough that people confuse them and he walked around with a sickle and an hourglass now instead of children. So presumably he's not talking about eating his children anymore, he's now talking to everyone about intermittent fasting. Good for him. So we've got this concept of time that's personified as Father Time. How can we use that in our systems and what can we do with that? Well, let's procrastinate this code. So this was the original rental code. Now instead of just saying, "Well, the return date should be X, three days in the future, whatever," we're going to use a method call here that's a bit magic, and the method is going to be called RequestTimeout and it accepts two parameters.
- 08:23 William Brander
- The first parameter is an object, and the second parameter is a date. And that's the date that that object must be given to us. And what this method does is it says to Father Time, "Take this object and then in three days' time, come back to us and execute a bit of code that accepts this object type as a parameter." So Father Time scuttles off and does whatever he needs to do for three days, and then he comes back and he gives us a RentalIsLate object and says, "This rental should have been returned by now." And at this point, we can go and check, has it been returned? If it has, great. We're done. If it hasn't been returned though, then we add 50 rand to the LateFees property.
- 09:04 William Brander
- So that's the first business requirement that we've met. So as soon as the three-day period elapses, we can have this bit of code executed that we can then go and add 50 rand to the late fees collection. The next part is we have to do this every six hours, right? So why don't we just tell Father Time that, "Hey, come back in six hours and call the same method again?" So every six hours he's going to come in and give us the same rental object, assuming it hasn't been returned.
- 09:32 William Brander
- So the way that this looks in a timeline is at 9:41 when the DVD becomes late, Father Time rushes forward and says, "Okay, cool. Now you need to go and check, is this DVD actually late?" If it is, we add 50 rand and then we send another message to the future. So we are basically writing a little envelope that says, "Do not open until..." and we send in that and eventually we'll open it in the future. So in six hours' time when the DVD has been returned a little bit before that, Father Time is going to come and call that bit of code again. We're going to check, well, has the DVD been returned? Yes it has? Okay, cool. Now we don't have to add the 50 rand late fee. And if we look at the two bits of code side by side and yes, I know they're not actually side by side. I couldn't get them to fit side by side, so bear with me.
- 10:21 William Brander
- So we've got the original bit of code in the top left and the new code at the bottom right. The biggest difference between the two is that the original bit of code is doing all rentals that were late at a specific time, whereas the individual one or the new one is now doing a specific rental at a time. And if you're doing a lot of operations, it's harder to scale those operations than it is to scale an individual operation, specifically because the only way you can scale that query up is to scale your database server up, get more power and more performance there, whereas with this individual code, if you want to scale this out, you just need to deploy that code to more places and it'll be able to update each record and scale out. So that's kind of cool. We're sending messages to ourselves in the future, but how do we do that?
- 11:05 William Brander
- RequestTimeout is sort of a weird thing, right? Many ways you can do it. You could just store them in a database table and pull them and check, "Well, are there any of these that have elapsed? Do I need to do something?" That's fine. My preferred preference, given it should be no surprise given how I feel about messaging, is to use native broker support. So some brokers have native support for deferred delivery. As a service bus, for instance, over here, you can specify when a message must be enqueued. So you send the message right away, but any processing endpoint won't see that message until that scheduled enqueued time has elapsed.
- 11:43 William Brander
- If you're doing SQS, AWS's SQS, you can also defer messages into the future. It's got native support for that. I believe the limit is either 15 minutes or 30 minutes into the future. The workaround for that though is you defer to a FIFO queue, and the only job that that queue has is to check if messages at the front of the queue must be redeferred or not, so you can work around that. Anybody using RabbitMQ? It's a fairly popular broker. RabbitMQ doesn't have native support for deferred messages, but you can put it into RabbitMQ by doing some really cool stuff involving dead letter exchanges, routing keys, and pattern matching the time to live. The way that this works is you define your routing key to be of a format similar to this, X.X.X.X, where each X is either a zero or a one. Right? Binary.
- 12:35 William Brander
- Now what that allows you to do is you can represent numbers, duration, seconds as a binary number. For instance, one second, you would define your routing key to be 0.0.0.0.0.1. If you want to do one day, that's 86,400 seconds, 0.0.0.1.0.1.0.1, et cetera. You can do the same with a week or whatever duration you want. That routing key is just a binary representation of the number of seconds that a message must delay. Now, how does this help us? Well, when you send a message to RabbitMQ, you would define all of your exchanges and queues for each level, each digit in that binary routing key. And what happens is you send a message and then RabbitMQ uses the pattern matching on the routing key to determine whether or not that message must go to an exchange or queue. So in this first case, it goes and checks, does this routing key start with zero dot?
- 13:33 William Brander
- If it starts zero dot, we just put it in the next exchange. If it starts with a one dot, then we put that message into this queue, and this queue has a time to live. And what a time to live means is that if nothing takes the message off the queue within that duration, then RabbitMQ will automatically take that message and move it somewhere else to the next exchange. So in this case, if you've got a time to live of two to the power N minus one, where N is the position in the routing key, each queue will wait for two to the power N minus one seconds before that message disappears. So you put a message in at the top and RabbitMQ routes up between each of these things. If it needs to sit in a queue, it sits there until the duration expires, then it moves to the next exchange and the next exchange will go and check, "Do I have a zero or a one in the next digit?"
- 14:24 William Brander
- If it's a zero, straight through to the next. If it's a one, it delays longer. So by doing this, you can have native support for delayed delivery in RabbitMQ. You do end up with a whole bunch of queues in your system, though, and a whole bunch of changes, but I think the benefits of being able to back up your delayed delivery mechanism along with your rest of your messaging infrastructure is pretty nice. It also means that if you've got high availability, if you scale out your messaging infrastructure, it all just goes along with it. So the first thing that we can do if we want to procrastinate is we can let time be our friend. Let Father Time sit and interact with our system. Imagine he's sitting there behind a terminal with his little wristwatch on and he's checking for the time and now we need to do something, and he simulates a button click that does something within your system.
- 15:12 William Brander
- Once you've got that sort of infrastructure in place, it means that you can do things like when a new account is created, maybe for 10 days that account is a premium tier account, and immediately as the account is registered, you can send a message for in 10 days' time to switch that account to a normal tier. You can also send a message for seven days to send an email to say, "Hey, by the way, your premium tier is expiring." If you're doing fleet management, as soon as you add a vehicle, you can send messages into the future to do a scheduled maintenance for those. Maybe if billing fails, you give customers a week to update their credit card details before you terminate their account. There's a bunch of things that you can do that, based on an event, there's a certain time period where you need to respond. And having something like a RequestTimeout allows you to model that within your system fairly easily.
- 16:01 William Brander
- There are some requirements, though. What happens if things go wrong? So let's take this RentalIsLate method. If we've got a thread that comes in, processes it, we check if it's late, if it's been returned. It hasn't, so we add 50 rand to the late fees collection and we save to the database. If the save operation fails, that's not good because it means that we've taken that message over the queue and we haven't charged the person 50 rand late fees. Worse than that though, if the save commits but the broker operation doesn't, it means that we're going to try to process that message again and we're going to double charge the person a hundred rand. And when you're dealing with money, you obviously don't want to do this.
- 16:37 William Brander
- People like it when you don't steal their money, strangely. So there's some requirements that you need from whatever infrastructure is providing this RequestTimeout functionality to you. The timeouts and the business data, they need to share the same transaction. Now, I don't mean they need to enlist in the same actual transaction, but if the one commits, the other has to commit, and if the one rolls back, the other must roll back. They need to be consistent between the two. Each of these timeouts must be successfully processed exactly once, which means if you are using messaging, you need to have at least once delivery and then have a pattern like the outbox to ensure that you don't get duplicate processing attempts or duplicate successful processes.
- 17:18 William Brander
- And then, each of these timeouts must be able to be retried if something goes wrong. If something does happen and infrastructure isn't available, the timeout must be able to be executed again. With those in place, then you can use the request timeout quite happily. Let's talk about batch jobs. The first RentalIsLate check was an example of a batch job. We might have a few others. Maybe if we've had customers that have rented more than 5,000 rands' worth of movies from us in the last year, we want to make them preferred. And preferred customers get a personal masseuse to walk around with them in the video store, perhaps? I don't know. So we'd have a batch job that at the end of every night, we'll go and execute and check, "Well, are any of my customers preferred? Have they spent more than 5,000 rand in the last year or not?" Is this lazy, precrastinating, or procrastinating?
- 18:15 William Brander
- It depends. So if the business rule is that the customer's preferred status can change intraday, so by that I mean if you order a DVD in the morning, you return it, and that morning DVD takes you over the 5,000, if you come back in the afternoon to rent another DVD, this won't reflect the preferred status because it'll only do it overnight as a batch job, in which case that'll be lazy. Otherwise, it's just procrastinating. Let's take a look at what a precrastinated version of the same code would look like. And this isn't the way you should do this, but it's by way of illustration to show that there are differences. So a precrastinated version, you would calculate whether or not each customer is preferred when that customer places an order. So as soon as the person comes to rent a DVD, we go and check, "Well, let's add all the rentals that they've done over the past year and see what the value is."
- 19:10 William Brander
- I hope this is obvious that this is precrastinating because you're doing this for all the customers on every single rental that they do, even if most of them have only rented one DVD a week. There's very little chance that they are going to be close to the 5,000 rand cap. We are going to put a pin in this one and we're going to return to this example a little bit later, and hopefully it'll be a bit better, too. Another batch job that we might have in our systems is monthly invoices. So at the end of every month, we have to send invoices to all our customers and the batch job might just go through all the customers, generate an invoice, and email them. Well, let's see if we can turn this around. We've already got the RequestTimeout thing, so why don't we... whenever a customer registers, whenever a new customer comes, we can send a message for the end of the month to say, "Send them an invoice."
- 20:00 William Brander
- Okay, cool. And then Father Time can go and scuttle off, come back at the end of the month, and say, "Now you need to send this customer an invoice." That looks fine, except if we look at it from a timeline perspective, what's going to happen is at the end of the month, we're going to send that customer an invoice, but we don't only have the one customer, right? We've got lots of customers and they all need to have that invoice sent at the same time. So splitting up the batch job here hasn't achieved anything. In fact, all we've done is made it worse because we've gone and added deserialization and serialization and messaging over on top of the cost of actually just doing the batch job anyway. And the trick here is that procrastination is really cool, but you got to know sometimes not to do it. We're not amateur procrastinators that procrastinate all the time. We need to put the pro in procrastinate. Choose the right time to do it.
- 20:54 William Brander
- If your operation doesn't have a chance that it will be distributed across multiple time periods, there's no point trying to split that up because it's just going to add overhead. Sorry. So the second technique is, make sure that you actually think about whether or not it's valuable to procrastinate with that bit of code. Let's talk about buyer's remorse. So buyer's remorse, that's when you place an order and then maybe the seller gives you a day or something to cancel the order before charging your credit card. Our video rental store is now selling DVDs, so you can place an order, and as soon as you place an order, it sets the state of that order to reserved and we send a timeout to ourselves in the future to say, "In one day, come back and tell us if the order has been confirmed," which means a day later, we get the message to say, "This order is confirmed." So we set the state to confirmed, we pack the stock, and we charge the person's credit card.
- 21:49 William Brander
- Of course, the customer can cancel, and if they cancel, we just set the state to canceled and release the stock back to the general public again. Oh wait, this is a problem because now what happens if they cancel? We're going to get the order confirmed message in a day anyway, so now we need to check, "Okay, well we'll have a state check in the confirmed thing to make sure that the state is not canceled. And then if they cancel as we're processing the order confirmed, so we'll have to now check in the OrderCanceled method if the state is valid..." And you can kind of see where this is going. There's going to be state checks on state checks on state checks everywhere.
- 22:28 William Brander
- And an order isn't a very complex process. If we're going to have a bunch of state checks here, why is it going to be so ugly to work with? Let's look at a finite state machine that represents an order. So the order starts when it's placed, then it transitions to a reserved order. A reserved order can be canceled, which is a terminating state. A reserved order can also be confirmed. A confirmed order can be paid for, a paid for order can be shipped. Payment can fail, in which case if the payment fails, maybe we want to ask the customer for new credit card information and then it can become paid for, or maybe the person just doesn't have any money and then the order can be canceled. So we've got all these states that represent what an order can be throughout our process, and if we're going to have to have if statements everywhere to check the state to make sure we're in the right place, it's going to be a bit messy to read our code and understand what it's doing.
- 23:21 William Brander
- Thankfully, there's things that do this for us and solve this problem. There are ways to distribute state transitions and state machines, and the one we're going to look at today is the actor model. So the actor model is a way of distributing your state machines. An actor within the actor model has four things. An actor has behavior. So the behavior in this case is if I am in a confirmed state and I get a canceled event, what do I do? What are the steps that my code must do? That's the behavior of an actor. Each actor also has a concurrency model. Almost always in the actor model, it's single concurrency only. So that means for a specific order, only one state transition can happen at a time. There can be multiple orders and multiple state transitions happening in the rest of the system, but for one order, it's one transition at a time.
- 24:12 William Brander
- Each actor also has state, so that's everything surrounding the order that it needs to know. So maybe it's the DVDs that the person is ordering, the shipping address, et cetera. And then the last thing is that actors communicate via a mailbox. And all this means is it's a fancy way of saying that actors don't directly interact with each other. They interact by saying, "This has happened," and do that. They don't change state inside another actor directly. So these four things constitute an actor within the actor model. And if we had an actor to represent our order process, if we had a framework that was doing our actor model for us, the way our code would look is we would have something like a PlaceOrder, which would send a start order message. So the start order message would create a new instance of an actor.
- 24:59 William Brander
- The start order Handle method would set the state to reserved and send a message to the future to say, "Confirm." If the order is confirmed, we pack and charge the credit card. This will look familiar. And if it's canceled, we just release the stock back, mark as complete. If the payment succeeds, we can set the state to payment and send a ship order. If the payment fails, we can request new payment information. And if it's shipped, we can then close the order. There's a few things to note here. The first is two slides back, I only had the code for three states on the slide and it took everything, took the entire screen. Whereas here, we've got all of the different state transitions in one place on one side, so there's a lot less code. Each of these methods are only doing a small thing. It's, "Update this, send a new thing. This has happened."
- 25:47 William Brander
- When you're doing small little bits at a time, you can scale those a lot better than if you're trying to do a whole bunch of things. This is the only place where we send in a large object, so this has got all of the items, the addresses, et cetera. Everything else that we send in is very light. It's IDs that we're publishing around and sending around. We've got a data dot property, which is something that your actor model framework would provide. So this is where you store all the state, and it makes sure that that state is hydrated along with every time your actor invokes something. And then we've got this MarkAsComplete thing in the OrderCanceled and the ShipOrder. This ensures that once a actor is complete and closed, if there's any other message or state transitions that come into it, you can't get into an invalid state.
- 26:34 William Brander
- So using the actor model, there's a bunch of ways that you can distribute your state transitions in a way that you have to do less code. So in .NET, there's a few ways to do it. Sorry for everyone who's not .NET. NServiceBus, MassTransit, and Rebus and Akka and Orleans. In NServiceBus, MassTransit and Rebus, they refer to the actor model as sagas, whereas in Akka and Orleans, it's actors. The other difference is in Akka and Orleans, there is a more deliberate emphasis on the state transitions between the states of actors, whereas NServiceBus, MassTransit, and Rebus focus more on the behavior for the state transitions. So the third technique is do as little as possible at each stage. So if you're a procrastinator, it's nice to just procrastinate on everything else except that little bit that you actually have to do, and then do the rest later.
- 27:30 William Brander
- Another bit of code. We'll take a step back and look at adding the item to the cart. So this code goes and checks whether or not the customer has ordered more than a hundred rands' worth of DVDs in the last week. If they have, they get a 10% discount. Is this lazy, precrastinating, or procrastinating? Well, this is lazy because it doesn't do what it's supposed to do. What I mean by that... so let's assume maybe my wife and I share the same user account for this website. So I'm busy adding a DVD, some romcom, and my wife is on another computer somewhere adding a thriller onto hers. So my thread goes and starts calculating how much has been ordered in the past week. As I do that, my wife clicks to the submit button and adds a thriller on. So I've now calculated that we have ordered 50 rand in the past week.
- 28:22 William Brander
- This DVD is worth 30 rand. And then as it's busy doing that little check, the next thread goes on my wife's side and says, "Well, you've ordered 50 rand in the past week. This DVD is 40 rand." So on my side, as far as my thread is concerned, there's 80 rands' worth of value, and on my wife's there's 90 rand. Together, there should have been more than a hundred, but because we are doing them at the same time, we've missed that bit of discount. So my thread goes and we save, my wife's eventually goes and saves to the database, and we've lost that discount. So the code hasn't done what it was supposed to do. There's a few ways we could solve this. We could use the actor model here because the concurrency would ensure that we can only add another item to the cart at a time. That's a bit overkill, though.
- 29:10 William Brander
- We could also do a row lock on all of the orders. Life pro-tip: if you are ever lonely, you can put row locks in your systems because it'll give the DBAs an excuse to come talk to you when they shout at you. That's how you make friends. Maybe we can do something else, though. So let's revisit what this WHERE clause is doing, and let's visualize it on a timeline. I'm quite fond of those in this talk. So if you imagine that this timeline represents all the dates in the future and the height of each bar represents the value of the order that was made at that point in time, then when we were trying to work out how much was ordered in the last week, if you think of it like a window that slides along, this window is the time period and we want to add all the orders that happened during that time period. So at the start, we might just have the blue and the yellow graph added together, because that's what was ordered in the last week.
- 30:09 William Brander
- As time moves forward and we progress and we get more DVDs, we then add the orange line on, because that now pops in. Time goes forward again, we add the red line. Time moves on, and then the blue disappears. So we've taken that away, we've subtracted that, and then we move forward again. The dark blue comes, we add that, and then as things move out of the window, we take them out, we subtract them. So the WHERE clause, that date range WHERE clause, if you squint and look a little bit funny, you can imagine that being two separate operations, an addition and a subtraction, just happening at different times. What that looks like in code is that if we take the same method and instead of summing all of the orders, we'll just store a column in the database table that says, "This is how much has been ordered by this account for the last seven days." Okay, that's cool. What does this give us, though?
- 31:10 William Brander
- Well, it means that when a thread comes in, we only have to do a single lock, a single row lock, rather than a multi-row lock, which means that if there's two threads at the same time, the first thread has got the lock. The second thread is going to sit and wait. It's not going to be able to do anything yet. The first thread is going to go and it's going to say, "Well, 50 rand plus 30 is 80, no discount." We get down to the end and we add the 30 rand to that column and then we save, which means that when the second thread gets the lock, the second thread can go and look, "Okay, well, how much has been ordered in the last week? Oh, it's now 80 rand," because that previous transaction has committed and the lock has ensured that they didn't operate at the same time.
- 31:50 William Brander
- 80 plus 40 is 120, 10% discount applies, and we add 36 then to the item price or to the total that account has ordered in the last seven days. So this is one part of the filter clause. We've precrastinated on the addition because we're doing this every single time there's an order. The flip side, though, is that we need to also subtract. So what we do is after we've added the item or the value to how much that account has ordered for the last week, we send a message to the future to say, "Hey, in a week's time, subtract." So in a week's time, we are going to then come in and get invoked and says, "You need to reduce the weekly order amount for this account by X," whatever X was. And X in this case is going to be the item price.
- 32:44 William Brander
- This is the procrastinating part, and the precrastinating is the addition that we're doing early. So by taking our WHERE clause and splitting them up into two distinct operations, we can scale this very, very well because we don't have any locks except on the individual rows, so one row lock when you add an order and one row lock when you remove the amount. Doing small locks like this scales very, very well as opposed to doing multi row locks or using an actor model. So by taking these two operations or taking this one operation and splitting it into two, we can get some interesting ways to solve the same types of problems. So if you precrastinate and procrastinate at the same time, you can get some really cool ways of solving things. A little bit early. Quick summary. So if you can treat time as a first-class actor within your system design, it enables you to have these cool little things where time interacts and triggers things in your system for you.
- 33:43 William Brander
- But don't use it everywhere. You need to make sure that if you are going to procrastinate, that it makes sense to do it. If everything is going to happen at the exact same time, there's no point trying to procrastinate, just get it over and done with and do it in one go. If you distribute your state, it allows you to do less work at each state transition at each time. So if you can split the work up and then leverage something like an actor model to be able to do your state management for you, it means that your code has to do a little bit less each time. And then if you combine procrastination, and what did I say? Precrastination. If you combine pre and procrastination, you can get some interesting ways of solving the same problems that you might not necessarily have thought of beforehand.
- 34:31 William Brander
- There are some references. I will put this up again soon. I'll make you a deal. I'll do the dance at the end of the day if everyone here goes up and gets recorded. I've been told to ask you to get a recording of an interview done. So if you go and do that and my talk is rated the top talk of the day, which is not going to happen, then I'll do the dance in front of everyone for you. Okay, I'll leave this here. Are there any questions? We've got some time still. Otherwise, you can always grab me afterwards. I'm happy to talk about being lazy and not doing things. That's kind of my thing. Great. Thank you, everyone.