Auto-generated transcript Welcome back guys, we're going to continue building this go redis clone. In the previous part of this series we built the rewrite function over here in the AOF struct in order to rewrite our AOF file to take up less space to remove all of the outdated old set commands and just Turn it into a much more compact and minimal file backup, right? In this video, we're going to build database transactions So let me just first walk you through how they work in redis and then how we can build this right so we're going to let me just close our current server and start the official redis server there we go awesome now let's run the redis cli now this is using the redis server that's actually built by redis not by us right not this project what we can do is in order to start a database transaction we run the multi keyword right and this is going to return okay to tell us that the transaction has been started and you can see over here we get this tx over here right which just tells us that we're inside a direct inside a database transaction now we can run any kind of commands like key one xyz and key two i don't know def or something right just assigning random variables and random values over here every time you run a command it's going to say queued queued right so it's just going to queue all the commands that you run and it's not going to actually execute them until you run execute right and then when you do it's going to return the output of every single command like this in an array right and if you want to discard the transaction if you don't want to execute the commands you can run the discard command which it's not going to work unless you're inside a transaction using multi right but you can use that to discard a transaction if you don't want to execute the commands right so that's what we need to do we basically need to implement command handlers for the and let me go let me go where is it to handlers there we go let me go up to the handlers list and add all of these. We basically need to create command handlers for the multi command, the exec command to execute, right? And by the way, there is a package in Go named exec, so we're not going to use this keyword. I'm just going to add an underscore before it, just so it doesn't conflict with the OS exec package, right? that's built into go and we want to handle the discard command as well there we go I was gonna write discord instead of discard okay there we go so we want to implement command handlers for these three commands but before we do that we also want to create a transaction struct that can hold all of the commands that we want to execute right because remember we want to add all of these commands inside a queue and then execute them all together right so let me just save that and go back to the DB struct over here and down here we're going to create a transaction struct right this will have a bunch of commands which will be a list of pointers to TX command this will be transaction command right add Struct over here. There we go. And then let's create the TX command struck as well This will basically have a value Which will be the actual command that the Client sends to us and then also a handler which will be a handler basically a command handler function the same as what we've been writing over here right in the handlers file and And yeah that all we need Let also create a new transaction function over here To create new transactions this will return a pointer to the transaction And There we go awesome now, that's all we need to do now, let's implement the these three commands over here so let me uncomment these and create the Command handlers for all of these over here See client V is the value and then state is the app state. There we go And it will return a value let me just copy paste this three times right and This one will be called exec notice the underscore so it doesn't interfere with the exec package right and this one will be discard now before we actually implement any of these we want to be able to store the database transaction in some sort of global state variable right and the best way to do that is to just add it to the app state over here let's go into the app state create a transaction variable I'll just call this TX for transaction and this will hold a transaction pointer as such And now we can actually start to implement this so in the multi command handler first of all Let's just make sure that there is no transaction currently being Used right so if the state dot transaction is not nil if there is already a database transaction that's being used Right, we should return an error Type error and And Redis does not allow multiple transactions to be nested together, right? So we'll say error multi calls cannot be nested. All right, we'll say that. And if there is no transaction currently running, then we'll just say the state transaction equals a new blank transaction. And then just like you saw over here in the official Redis server, it's going to return a success message and OK string. So we can do that as well. Just return OK over here. And that is all we need to do for the multi command. As soon as we run this, it's going to start the start the new transaction. And then we can just queue our commands and so on and so forth. Now, let's also implement the discard command over here. and first of all let's just check if there is already a transaction or not all right if there is no transaction then discard doesn't make sense right just like you saw over here if we try to run discard without using multi first without being inside a transaction first then it returns this error over here right discard without multi so let's return that over here as well if there is no transaction running we return an error which will be error discard without multi right there we go and if there is a transaction then we just say state transaction equals nil so there's no more transactions and then we return an okay success message just like this there we go that's multi and the discard command handlers all built now before we before we implement the exec execute command handler let's first go up to the handle function over here right so we've already implemented the multi command which is going to start the transaction and help us to queue all of the upcoming commands right but how exactly do we queue them right we do them in the handle function over here. So we're going to go down here after the invalid command handlers have been checked, after the authentication stuff has been checked, down here we're going to say if the state transaction does not equal nil, so there is a database transaction running and the command that we received does not equal execute or the command does not equal discard So basically the only two commands that can you know change the transaction state basically execute or discard the transaction. If the command that we receive isn't this, then we need to queue that command inside the transaction, right? So what we're going to do is we're going to say tx command equals this new tx command struct that we created the value will just be the v variable over here that we receive in the handle function right and the handler will be the handler over here up here right there we go this handler that we get from the handlers list will pass it in over here and then we just append this transaction command into state.transaction.commands by saying append and then just append a pointer to it over here like this then we want to write the success message to the client so we'll say type string and the string will be queued just like you saw over here right queued queued right flush it and return because we don't want to actually execute the handlers we just want to queue them all up over here like this right now all of the commands that we that we sent to the server after running multi are going to be queued up in this transaction until we run execute or discard so we've already implemented discard over here right now let's implement execute so first of all just like discard if there is no transaction then we should return an error over here and we can call this error exec without multi because if there is no multi command if there is no transaction that's being used currently then what are we even executing right so make sure to check that and then we can say replies so the reply to every single command you can see that when you when we run exec over here it's going to execute every single command that we queued up right and it's going to return an array and each of the element inside this array is going to be the output of every single command that we queued up right so let's do that we want to create a list of replies which will be the reply that we send from every single command in the queue so let's run the make command over here this is going to be a list of values the length of this is going to be the length of the state transaction commands list right and then we can just loop over this we can say for index command range state dot transaction dot commands and we can say reply equals cmd dot handler which will be the handler function we can pass in the client the value and the state and by the way instead of passing in the value like this we want to pass in the command dot value over here right from this command variable over here in the loop this will return the reply from this command handler and then we can assign that to the replies list using this replies i and just say reply there we go awesome the reason by the way that i do this direct assignment using the index over here instead of using the append function is performance right we can already create a list of values right and the length of this list can already be like received from the state commands list right that the state transaction commands list the reason I use this direct assignment approach instead of the append function is performance Basically, if we try to append every single reply to this replies list, it just becomes very bad for performance because you basically reallocating and growing the capacity and the length of the array in every single append call right and that just not a good way to handle these kinds of things especially when we know what the size of this array is going to be we know that the number of replies is going to be equal to the number of commands inside this transaction right so we can pre make a list of replies and the length of it we can already pre-allocate to the state transaction commands right and then we can just directly assign them over here and this is just a much more performant way to create this list rather than using the append function which will have to reallocate and and reassign all of these different elements again and again, right? So hopefully that makes sense. That is why we use this direct assignment approach instead of the append method. But yeah, now we can build the final reply. And the final reply will just be a value of type array. And the array in here will just hold the replies. We can say that the state transaction is now nil because we've executed it. And then we can return the reply as a pointer. And that's all we need. Now, let me shut down the currently running Redis server over here. There we go. And run my own server. Awesome. Clear everything. Start a client. Let's say multi to start a transaction. It returns an OK. and it gives me this TX keyword in the bracket over here, which tells me that I am inside a database transaction. Now I can run a bunch of commands. Let's say key one equals hello, and key two equals world, right? And you can see that it's going to queue both of them up, right? I can also say get K2 and get K3, right? K3 doesn't exist but basically we can still queue it all up and I can say execute and it's going to return the output of both of these right so first of all we set the keys k1 and k2 it gives us an okay success message for that we try to then get the k2 key which is world then we try to get k3 which doesn't exist so it returns nil right awesome so this works I can try another transaction and this time I can say set name to Hassan again and I can then just discard it right so let's say I don't want to make these changes I can run discard it's going to say okay and it's not going to run those commands for us right and yeah that's how database transactions work inside of inside of Redis this is actually a lot simpler than how most database systems run database transactions right transactions in things like my sequel or postgres or something right there are a lot more complicated than what redis does over here transactions and redis are actually very simple so yeah I hope you learned something from this we've built database transactions here now and yeah thank you for watching hopefully you learned something like share subscribe comment all those wonderful things in the next part of this series we're going to look at I believe let me check yeah we're going to look at eviction policies and memory management and how we can track the memory usage of our database and how we can basically manage it to make sure that we don't run out of memory and if we do we can remove a bunch of keys to make more memory right and so on and so forth right so we're going to look into all of those super complicated details of databases right that's gonna be very fun but yeah thank you for watching this one and I will see you in the next video bye bye