 This now is going to be the which we've sort of finished everything we wanted to do with talk about transactions and current troll in the context of multi-versioning So now we want to spend time talking about data structures We need to have and how this fits into the overall system and how we can do transactions on top of this So there'll be sort of three lectures on indexing. So today's class will be a sort of an overview of a traditional existing techniques to do locking and latching in in in indexes and then on Wednesday you guys will be reading the paper We wrote here lower last year on the a lat tree or lot free index the BW tree Which is the default index in our new database system and then on Monday next week. We'll see how an alternative to doing That's sort of the key base indexes that we're talking about here and on Monday If how to use like red X trees or tries to do More efficient order preserving indexes the main takeaway that I guess the spoiler would be and you'll see this when you read the paper Is that I was super enthusiastic about bidding a BW tree here at Carnegie Mellon We know building one and turn up the and that paper you know read says why it sucks. So let's jump right into this right today's agenda is really again like a high-level overview about concurrency control mechanisms we need And PowerPoint crashed hold up give it a sec Give it a sec All right. Sorry. All right the Yeah, today's agenda so today is getting an overview of concurrency control methods We need in context of transactions or sorry in indexes and it'll be a combination of understanding How do we make our indexes thread safe and how do we make sure that our transactions see the correct information? So we'll talk about the difference between indexes and locks and latches We'll talk about how do you actually implement a latch and then we'll talk about the logical physical techniques to protect the indexes using the combination of these things So I should go it's go without saying we don't need to describe it index here But just again to remind ourselves is that the the database indexes that we're talking about for this class and going forward are these data structures that the database system is going to maintain that are going to allow it to Speed up the execution of queries that need to retrieve data and we're primarily me focusing on OTP indexes, so how do we make our transactions run faster? There are indexes to speed up analytical queries But in practice oftentimes if it's a if you have a column store data or column store database and it's compressed And you have a good execution engine. It might just be faster to just scan the entire table rather than using indexes So we're not going to worry about how do we let the next is to make the analytical queries go faster We'll see when we talk about hash joins we build a hash table, which is like an index on the fly We're talking about here are actually indexes We want to maintain and keep in sync with the underlying tables to make the transaction queries run faster so again, there's this class of trade-off between How many indexes we want to have versus how how much additional space is going to cause us to maintain them Right everything's in memory indexes don't come for free So that's going to be an issue and then there's also the other issue of how do we actually update these indexes as we insert Updating delete entries in our underlying tables like that all doesn't come for free as well So you could just build an index on every possible column combination you'd want that would speed up every query But the expensive to maintain those things and nobody actually does this So again, the the basic idea is just index is just like a glossary in your textbook You can figure out what page you want to jump to without having to do a sequential scan through the whole thing So there are two classes of index data structures we can have right and again, this shouldn't be anything mind-blowing This is just algorithms or data structures one-on-one, right? You can have our order preserving indexes or the hashing indexes or hash tables So order preserving indexes will maintain the keys for that you're indexing on in some kind of sorted order, right some left to graphic order like newest to old or Smallest or greatest or in the other direction And in practice this is going to allow us to do all possible predicates you want to have in our queries We can use the the index And we'll be able to achieve of its approximate log in lookups The alternative to this is a hash table index and this is just like an associated array That's going to map the keys to the particular record that has that value so you can only use the hash indexes for a quality predicates when you have the entire key that you can't do any partial key lookups in Hash index and but the benefit of this is you're going to get oh one lookups You may have to land in a bucket and depending on how your hash table is implemented You may have to scan until you find the entry that you want which we'll talk about later in the in the semester But again, it's oh one versus log n So our focus on here going forward for the next three lectures is on this first class of things This will only primarily be useful for us when we talk about hash joints When you call create index and pretty much every single Database system out there you're going to get one of these it might be a B plus tree might be a skip list Maybe whatever right, but in general every database system will give you one of these in like some systems like Postgres Another thing is you can say create index using and then you tell it what data structure you want so in Postgres You can say I want a hash table index But the default is this Everything an obvious guess why? Yes Exactly range queries But most people do when they first built index is like you know do like a select count star some link in some range Just I'll do some quick look up and it'd be People get pissed off you build an index and then like you can't use it for any other query You can't use it for the first query you throw out so these things are more general purpose And this is this is what everyone goes with so that's where we again focus on here today so One additional thing I want to clarify also before we go forward is The distinction between a B tree and a B plus tree. I'm going to probably use these terms interchangeably Sometimes I'll say B plus trees. Sometimes I'll say B tree also times you'll see in Documentation of existing, you know real systems they will sometimes say B plus tree and some other systems will say B tree in Practice though, it's always going to be a B plus tree So the original B tree from 1972 It was you know one of the first self-balancing trees The way it was set up was that you can have the key value pairs for the data you're indexing be stored at anywhere in the tree So it could be in the leaf nodes or it could be on the inner nodes, right? And the reason why they did this is because they want to make sure that every key value pair or every key for that matter Only pairs once in the entire tree right in a B plus tree the Key values that are actually in the index like to say what the what the index is based on or what the index is actually storing At a logical level. They're only stored in the leaf nodes and then in the inner nodes in the tree Those are just like guideposts that say whether you go left or right as you traverse it Right and so what'll happen is you could end up deleting logically deleting a key In a B plus tree and removes it from the the bottom layer of the leaf nodes But it may still exist in the in the upper upper nodes because it's because it hasn't done a splitter merge to remove it out right, so the The benefit the reason why everyone everyone implements everyone implements this is that if you want to do now range scans It's super efficient to do this because you just go you just do a log and look up to get to the bottom of the tree Of what your starting point is for your range And then you just scan along the leaf nodes and you find all the entries you want and that's going to be a sequential scan depending on how you organize the Those nodes whereas in this thing you may have to bounce up and down and therefore keep pointers in both directions to figure out How to go down and come back up? Right so again in practice as far as you know Everybody that says they implement a B tree in a real system is More often than not implementing a B plus tree so Postgres will say if you look at the source code It'll say B tree, but as far as I can tell is it's a it's a B plus tree Further confusing this is that there's also so there's a there's a there's the original B tree and the original B plus tree But nobody actually really implements the original B plus tree as well the modern variants of them Combined features of all these other types of trees that came around came out around the same time in the the 1980s and 1970s I think there's a B star tree the B link tree was invented here at CMU And so the B plus tree that people implement now now includes bits and bits and pieces of all these other types of trees All right, so having sibling pointers that comes from the B link tree All right, so again, sometimes I'll say B plus tree sometimes a side beach B tree But I really mean this one here. We're all the leaf the the actual key value pairs are always have to be in the leaf nodes Okay, all right It will say this also that actually another reason why people do this as well is that it makes it much easier to do concurrent access or concurrent changes to the indexes because What'll happen is since everything's always in the leaf node any change you make always starts at the bottom and oh and goes up Right whereas in the in the B tree you could have one guy that the top node They make a change and have to do a split and merge and another guy below it's doing a split and merge And now you have to manage those Those changes coming from two different directions whereas in this one It's always coming up and make your life a lot easier All right, so the Where we're at now is that we spent the the first couple classes talking about how to do a Concerned control for transactions on tuples, so we know how to use locks to protect the The objects in our database where we just use Tuesday's locking and we have our shared locks or exclusive locks and depending What the transaction wants to do? We know how to set those locks and protect mix the sort of weird thing about indexes Is that it's storing data, but it's not considered the primary source location of the data, right? that's always going to be in the tables and The way we can treat our indexes even though we want to read and write to them the same way we read and write to tables it's not the same it's we have to actually treat it differently because We don't actually care What the physical data structure looks like of our index as long as it's logically consistent? So what I mean by that is if I do a look up in the index and I say do you have key? One two three the index come back and comes back and says yes if I if now somebody else assert a billion keys Into that index and now the physical location of where that key I looked up before is now in a different place. I don't care as long as I come back and say do you have key one two three and It answers yes Then that's fine So for this reason We can't use the traditional concurrential methods that we talked about before but we don't want to do two-phase locking in Our index because it doesn't make sense for someone to hold a lock on a node in the index the entire for the time life of the transaction Right. We just want to come in do our do our change or do whatever look up We want to do and then throw you know give up all our latches and locks and then come back later on and just do it all over again So it's like a really simple example what I mean So we have a simple B plus tree here to it holds two keys key zero and key two And it has one one node right so if I do my look up I want to read K2 well, and behold I find it right there That's fine, but now another transaction comes along and does an insert in K1 It should go in between these two guys since I can only hold two elements I have to do a split so now my index looks like this where K2 now has been it been moved down to this other node here and K zero has been moved over here and again now when I come back in the same transaction I Don't care that it's it's primary location is now no longer in the know that I looked at before it's now over here That's fine because I asked the question to the index. Do you have this key? It said yes That's all I care about right so this is an example of We'll see it throughout this class like we'll use latches to to make sure that Transactions that are accessing the index at the same time don't have pointers to you know invalid memory locations And then we'll also see how we use index locks in the paper you guys read about how to protect the logical contents of the of the index right So if you take the intro class Here with me last semester then we spent time talking about the distinction between locks and latches and this is in the paper that you guys read which is a really Again the reason why I have you guys read this even though we're not going to actually implement anything that they talk about in that paper It's a great survey of just like all the things you got to think about when you're building indexes to make them make them work correctly in transactions So we have to make this distinction in databases between So if you're coming from an OS background They would claim that what we're going to call latches is what they call locks. They're wrong. Okay a Lock is going to be a higher level of Protection met primitive that's going to protect the logical contents of our of our database a logical contents of our index Right and what will happen is we're going to hold these locks for the entire duration of the transaction because we want to protect again some logical Notion a logical concept or entity in our database And the way we're going to program using locks is that we got to have extra mechanisms in our database system To be able to roll back any changes that we may may make to these objects that we're protecting with locks Now distinctly distinguish this between a latch The latch is going to be a a low level primitive that's going to protect a critical section in the indexes internal data structure By the way, you think of a node itself like that that region of memory when we want to do reads and writes to it We can protect that with a latch And so these things are going to be short lived because we're only going to hold them for the op the duration of the operation that we're doing So I can take a latch on a node do my read and then give it up right away all right, and for this we don't need to be able to roll back any changes because Again, we're only doing it for the small change that we're doing to the actual physical data structure So I can hold a latch make a change and then soon as I release the latch then it's done And any any additional logic I need about rolling back the overall changes like last class we talked about I Add something to an index and I abort I need to roll back and then remove that thing I would that's a that's a higher-level concept that's picked by a lock not a low-level latch So he also has this great table so grits graphy was a You know a well-known database researcher or database systems and engineer His name is going to come up multiple times throughout the semester when we talk about Cascades query optimization of the volcano iterator model for doing query processing these sort of a he writes these great Sort of more recently he writes these papers that are these these surveys about covering everything You need to know about like one topic so everything you need to know about you know B plus trees is Something he's been spending time on which is really cool So he has this great table that says that the distinguishing locks and latches about What the protecting and how we're going to use them so a lock what's going to separate user transactions And we're going to protect the actual logical database contents hold them for duration actions We have these four modes and plus all their additional attention locks We can have in the way we deal with that locks to do deadlock detection resolution using mechanisms like timeouts of boards and Just stalls and waiting and then we can keep this additional information in and a centralized lock manager Again the latches are again the low-level primitives for protecting the physical data structure And so this is protecting threads from each other like two threads trying to update the single node and index at the same time We're only gonna have read and write latches We don't have anything else like we don't intention Tension modes and the way we're going to avoid deadlocks is through careful Programming so what I mean by that is us as the database systems implementers It's up to our job to make sure if we're using latches that we can't have deadlocks Right because there's not going to be some some background thread doing deadlock detection to bail us out So if we end up programming our system to have a deadlock We're doing no one else's there to help us So we need to be very careful about how we use our latches and make sure that we can't have problems, okay? all right so another thing that comes up in the paper and And we'll see again next class. We'll focus We'll focus on this in a bit more detail, but he talks about how people claim that they have Lock-free indexes, but it's unclear what they actually mean And he basically says that there's two choices when someone says they have a lock-free index So the first is that It could be that they have the index has has no locks as defined by us no logical database locks Right, so that means that transactions don't have to acquire locks to access and modify the database But they're still gonna have to use latches to protect the physical data structures when they make modifications So you can sort of think of this is like the OCC method that we talked about a couple lectures ago, right? We still have to use Latches to protect the tuples as we make change to them like append new versions, but I'm not acquiring a lock on the tuple itself And there's nothing there's no lock manager keeping track of who holds what box The other choice is a index that has no latches and for this would be we're gonna use compare and swap Primitives to flip pointers around to atomically install updates without using any low-level latches like new texas all right And so of course this when you still have to validate your transactions because the transaction may make a change to your lock-free index it may install a key that Again, it was allowed to do physically But logically there's some higher level concept about the correctness of the transactions that that ran that we had to roll back that change right, so For the way to think about this is we already talked about how to do this with the the memory MVCC stuff We talked about before and then next class you guys you read how to how to build one of these, okay? All right, so let's actually talk about how you actually can implement a latch, right? You know in the in the intro class we did last semester I think you guys just use the standard mutex latch to protect things I forget whether we provided that for you or whether there was a wrapper We now want to talk about different ways to actually implement a latch going beyond the the default one You get with C++ in the standard temple library, so I think I asked before who here has knows what compare and swap is I just want to go over this real quickly at a high level and make sure we're all on the same page so a compare and swap is an atomic instruction that's going to allow us to and it's in a single step in a single instruction check the the current contents of a location and memory and It's what we what we think it should be then we're allowed to install our new updated value Right, and so this here Thinkful compare and swap like this is like a CPU intrinsic I get in in C++ and there's different variants of them for different Data type sizes, right? This one's doing a big bully and I think Actually, this one returns a bully and whether it succeeds or not So say we have a memory location here So when we invoke this function we pass along that memory location and we say this is the value when we want to compare Against and if that value is currently this then install our new one here So when we invoke this right we check this it's 20 So we're allowed to install our change like that and that's all done in a single instruction, which is super super fast same right so There's different ways to actually write code. I don't think you want to write the long long from like this There's like helper functions Or CPU intrinsics that will then get compiled down or the compiler rewrites them to be The single instruction like this right intrinsics are a basic way to like think of it like it looks like a function in the actual code itself But the compiler knows it actually maps to a little instruction, right? So you're not writing raw assembly did to actually do this All right, so again, this is the basic primitive We're going to use to implement a bunch of different latch latches so the The most easiest latch to happen if you take in a basic OS course This is the one everyone knows about is just a blocking OS mutex, right? so the In C++ 11 they added this now the standard standard mutex and this is just a Rapper around the p-thread mutex t actually just a wraparound a few So we know what a few tex is All right sensor fast user space mutex. So the The way this basically works is that It's going to have a spin latch which we'll talk about the the test and set spin left in the next slide it has that in user space because those things are really fast and If you if you if you can acquire that then you're done You know you have you have the latch if you can acquire then you go down to the operating system and Tell it that hey, I need I need this I want to get this this mutex at this memory location I can't get it De-schedule my threat And so it goes in the scheduler and updates your status is that you're waiting for this member location Therefore, you shouldn't be given any time slices So that's really expensive All right, it takes about 25 nanoseconds to do this Right doesn't sound like a lot, but it is compared to like the test and set is a single instruction we saw in the last so Right the way you write your code is like you define your mutex and then you call You know lock and unlock here and again what will happen is if you can't get the the fast spin lock in user space You go down the operating system and say I can't get this don't sketch me and again any time you go in the operating system That's always expensive. It's a sys call The the operating system is going to have the kernel and these in Linux is going to have its own Latches protecting its own data structures down below Which it may now start contending with other things running the system, right? They're not even related to your database system, right? There's some file system code running it may be down in the kernel as well and now you're contending against that So sys calls are our enemy the operating system is our frenemy We want to avoid talking to it as much as possible. So that's why you never want to use The standard template mutex you will always want to write our latches in in our databases in itself So I checked this morning on our the new code and we seem to be pretty clean about not using mutexes anywhere Which I'm quite happy to see last year on the old code. We had these things all over the place, right? It was like there's just like spreading It was really bad and I felt it felt like you know I come into this class and I tell you guys don't use mutexes and then we were like You know being hypocritical because our system had it had them all over the place. It's a mess All right, so again The key thing I understand about this is that with a few texts you get a user space spin latch first If you can't get that then you go down the OS and use a full mutex, which is bad slow All right So what is a spin what is a spin lock or test and set spin lock? So again this this part sucks because like in the database is a database systems We're going to call these latches, but the latch we're going to use most often is often called a spin lock, right? It's fine All right, so these are super efficient because it as I showed when I showed you the compare and swap It's a single instruction to go check that memory location check the value and then swap it with our new one If we if it's what we expect there to be right, so that's super fast The downside is we'll see in a second is that this is not very scalable and it's not very cash friendly Right in explain what I mean in a second So you can get this again as C++ 11 They now have this atomic template and then you pass in whatever the type it is that you want to be atomic All right, and what will happen is if it if it the the thing you put in here is The size of it is is enough for a Like the built-in instructions to do compare and swap like if it's a 16 bit value or type you're putting in here There's a 16 bit compare and swap instruction So that can be fully atomic if you put in something that isn't there isn't a quiz on a compare and swap instruction then the the The compiler with memory write that using Mu texas like it you know if you know they'll rewrite the code for you or the binary the Assembly to use mu texas and make it look like it's really atomic, but it's not sorry. It's atomic, but it's not the compare and swap the faster one All right, so here's how we would implement this so again C++ 11 provides you a nice atomic flag And this is just an alias for atomic Boolean, but this is actually guaranteed to be Always implemented with a compare and swap instruction, right in depending what Harvard you're on you may end up with like as I said You may say may end up with one that's using mu tex But the the language guarantees that this will be compare and swap So we define our latch and then we have if you want to go acquire it We always have this little while loop that says if latch dot test and set and then if this This one says take whatever this wants to set the latch to be true if the current value is false And then it returns whatever the old value wants So in this case here if it's already set the true meaning somebody else holds the latch Then this thing returns true because you or the previous value is true So that's why this loop So now if I do this I try to set it and I can't this comes back as true Now I'm in the side this while loop and I have to write some logic to figure out what I actually should do So the easiest thing to do is just just retry this again right spin forever That's why it's called a spin lock a spin latch because you're just spinning it over and over again trying to acquire the latch until you You can get it again these latches are protecting Critical sections that are not supposed to be really big so The idea here is that someone went and say you know updated the node That's going to maybe take a few nanoseconds or a few instructions So they're not going to be holding this latch for a long time So rather than me going down to the operating system and de-scheduling myself I'll just spin until they're done and then I can pop in and get it Right so again the easy thing to do is just retry if I try it for a couple of times And I realize I'm not going to get it I could yield my thread Or I get to say I've tried too much and I have to abort myself And then whatever else you know whatever logic I have Whatever operations I did that were depending on this thing, you know this thing to succeed If this doesn't succeed then I have to mainly roll that back myself, but ideally you don't want to write code that way right so What's the problem with this? Let me take a guess. All right, so one thing here is that the The operating system thinks we're actually doing useful work wreck because we're just keep exceeding the instructions It doesn't know that it's your weight. You're doing your useless computation Doesn't know that you're waiting for this memory region to get freed up before you can acquire it So the OS is in keep scheduling your thread. That's fine but you're basically be executing the same over and over again and If now you have a lot of cores trying to all acquire the same latch at the same time This is going to end up with a lot of traffic on the actual underlying hardware So let's really simple example here. So you have two cores, right? All right, you think of like this is two sockets right each each with a single core and so the latch I want to acquire is Over here in memory for this CPU here So for this thread if he's just spinning That's sending those instructions over the underlying bus on on the motherboard to this this socket here to try to get that try to set that value in the You know for that memory region over here So let's say that some of the thread holds this two guys are both trying to get it the memories here So this guy can go to test and set Locally and that's fast, but this thing is is going over the over the bus and trying to set it over here So now instead of two threads, what if there's a you know, 20 threads all trying to do this All right, so the The the cash look how do you get from this is is really bad if if now you start scaling this out Right because again for every test and set is another instruction that goes over the wire all right So there's a way to improve this And these are called cube a spin lock. So sometimes sometimes called MCS locks MCS stands for the the two dudes that made this Malori Malore crummy and Scott So this was actually added. This is I think it's been around for a while But Linux added this in their kernel protect their data structures in like 2014 But this is something database people have been known out for a while as well So these are gonna be more efficient than the stupid dirty OS mutex I shouldn't be stupid but the dirty OS mutex and then but it's gonna get better cash locality Then the simple test and set spin lock, right? so the way it would work is that We have to manage this ourselves like so this is this is just saying that I'm declaring in this in this standard template that I want to have a pointer to something called a latch Which I'll show you in a second that I want this thing to be atomic But the the C++ isn't gonna do what we're describing here for us. We have to do this ourselves All right, so let's say we have a base latch Which is what this thing is is pointing to and this base lashes just to struck that just has a pointer to A 64 bit pointer to the next latch in the queue So at this point here nobody holds the latch because this thing is set to null so my first CPU comes along and It wants to acquire this latch. So what's gonna do is gonna set up its own latch In the queue with this own next pointer as well And it's gonna go to the first one and try to do a compare and swap on this on this next field and set it to be It That succeeds if a compare and swap here succeeds and now points to his black then this guy has successfully acquired the latch It can go up and do it do whatever it wants to do the next thread comes along and again, it follows the the starting memory address for our for our our spin lock there's our latch it lands here Try to do compare and swap on this. It's it fails because CPU one has already set up that pointer So then it follows that pointer and says all right. Well, this is the next one in my queue Let me try to do a compare and swap on this guy and then this succeeds and now points to him So now he's now spinning waiting waiting on this latch here this memory mission If any other thread comes along they do the same thing compare and swap fails follow the follow down the list Apparent compare and swap fails again follows down the list then the compare and what compare and swap succeeds And now it's in the queue. So now these two threads are waiting for the first guy So this one's going to spin on this memory address and then this one's going to spin on that memory address Right so the underlying harbor and the OS is smart enough to recognize Oh this thread running on maybe this other socket here is always trying to read this memory address on this other socket over here So let me just move that memory address To be now physically stored in this in the numeric region where this guy is actually running Right you can actually provide hints also as well like you can do this one using numeric control Well, we'll see this later on but you can actually tell the the CPU move this memory address to from this dim to this dim Right and then now you don't have any network traffic going over the bus between sockets I'm just accessing thing that's in my local CPU cache and that's really fast All right So this seems awesome, right? This is this is a definite win The only tricky thing that it's the if you had to pull out guys Work to to take somebody from this then in that case where I just say alright. I'm done and you walk away, right? So I don't think we had this implemented in our system Actually, we use the the Intel thread building block library, and I don't know how they implement their spin latches We should look into that But again, I think this this is if you're working in a highly parallel environment. This this is a better approach All right, so the last step is a latch you're called reader writer latches And what this is allows you to have current readers running at the same time And then a single writer so for this one a latch now is going to be comprised of Two types of latches the relatch in the right latch And then you're gonna have counters to keep track of the number of threads that currently hold the latch and the number of threads That are currently waiting to acquire the latch So my first thread comes along and wants to acquire the relatch So at this point here both the right latch and the relatch are on sets, right? There's nobody nobody holds it yet So I can do a compare and swap to now acquire it set this thing to one and now I hold the latch Now the third comes along same thing I see that this thing is set to one nobody else is waiting There's no writer waiting to acquire this so I can update the counter and then now I've had it too So I can have good current readers access the data structure or the current the critical section at the same time All right, where's the single spin latch you can't do that because all you know is whether it's set or not So our right our writer thread shows up tries to get the right latch that fails because the relatch is being held So then we just we we we can spin and wait Now there you go spin away or fire that and we update ourselves as they let everyone else know that we're waiting The third threat comes along tries to get the relatch All right, this one sees that the there's somebody holding the right latch or waiting for the right latch So we will just we will stall wait for ourselves here So that way when these guys drain out and can then acquire it and Then that's starving out our writers because otherwise this thing can keep going forever and our writer latches or writers would never acquire the latch So this is really useful in data structures where you know that the where you know that the the access pattern of the of the workload is is such that it's prime You know, it's it's being mostly read-only, but every so often you use somebody to come along and actually do a right Right, and you will see this actually in the for the first project. There's a particular You know, I'm not saying you use a reader right a latch But there's a clear example where something is going to be reading something. There'd be more things reading something than than writing something Yes His question is the condition for Determining whether you whether you wait When you try to acquire the relatch and also be is being held by somebody else is that if anybody's waiting for a right Then I should I should wait so it This is one way to implement it I'm not saying there I'm not saying there is one way better than another Like you can tweak this implementation based on what you think the access pattern will be for the critical section that you're protecting So maybe the case that you want to you know, have your reader latches go, you know To have a reader threads acquire the relatch as fast as possible Who cares whether something's waiting waiting here that may be bad right because you may start about the writers But then depending what you're protecting that'd be okay. Same thing, too Now I have other writer threads coming along here should they be allowed to go You know when this guy gives it up go get him You know immediately afterwards as well, or should I ping pong and let this other one get it depends on the implementation Depends on what you're protecting Okay, I So now that we know how to implement latches Let's talk about how to do Use them to protect our you know a tree data structure or tree index so for this one we'll use the B plus tree as our Sort of canonical index to understand how we want to do this But the techniques we're talking about here are still applicable for For other types of trees or I read black trees and things like that so For this one we're going to show how we want to do what's called latch crabbing or coupling is Basically the standard technique for allowing us to allow for concurrent reader writers on our on our B plus tree, right? So what's going to happen is that as threads enter the index they're at the choir the latch On that note before they're at to actually look at it and figure out whether the data looking for it is in it or not And so what will happen is as you go down? You want to release the latches behind you in things from those you already visited when you know it's safe to do this And we're gonna we're gonna consider a parent node or ancestor node be safe from a child node if we know that We're not going to any splits or merges below us So if we're doing insert we know that whatever is below us in the tree will not cause us to have to split the node That's above us So in that case we can release the latch on it, right? For same thing for doing a delete long as the the the nodes at least half full that we don't we know we're not doing any cold lessing We know we know we're not gonna do any merging from Because of that delete so we can release latches above us Right, so the standard technique way to do latch crabbing again start from the root take relatches all the way down and unlock the parent immediately because because you're not modifying anything and then for insert and deletes as you go down You acquire right latches as needed and then if the if your child is safe that you can release the all the latches on your parent So let's look at a simple example to do search on 23 So it's a really simple index we want to look up and try to find this key here 23 So since I'm read only I take a relatch on the first guy get to the second one Once I have the latch on this then I know I can release the latch above me, right? Because I'm it's read only I'm not making any changes, so I don't the worry about Any modification You know this are ascending up the tree and modifying this thing so we can go ahead and release that latch Then we get down here get our get our latch on F And then we release latch on C and then we can do our read and we're done, right? So again, we're we're coupling locks as we go down or it's crabbing because it's supposed to be how crab walks Once we know we can access the node Safely we then move to it and release our parent All right, so now you want to do a delete get a right latch on the root get down to the C and this case here because this thing is is half full or less We don't know whether this thing would cause us to do the delete down below In the leaf knows we don't know whether that's going to cause us to have to do a merge up above this So we can't release the latch on this yet But then once we get down here where we actually want to do our delete We know this thing is more than half full Right, so at this point here the C and a are considered safe So we can go ahead and release the latches on those guys because we know whatever change we make down here is localized to this Right, we can blow it away. All right insert 40 same thing Right right latch get to here right latch this thing here because we have space for any Key we may have to store in this we can release the latch on a then we get down to where we're going to actually insert here In this case here now we're actually have to do a split. So we have to still maintain the latch on this Do our insert and then rebalance like that, right? Again the way we're going to admit these latches are using one of the techniques that we talk about for most likely for these operations It could be a simple either the MCSQ based locks or spin latches or the regular spin latch You wouldn't want to do a reader writer latch for this all right, so what's the first thing we did for all of the the the Modifications we made to our index. Well, we always took the the right latch on the route Right, and that means that's gonna be a Single bottleneck in the entire system because we're always going to that that first node Acquiring the right latch that prevents anybody else from reading or writing to it at the same time So therefore any time we modify the index that's gonna become a big bottleneck So we can do a better approach which actually comes from this paper from 1977 where you basically assume Optimistically that you're not gonna have any conflicts or not gonna have any splits and merges when you get to the bottom to the leaf nodes in the B Plus tree so therefore you take relatches all the way down as if you're doing a Search or a look up and then when you get to those leaf nodes then then you actually take it that the the right latch you need right So this is pretty straightforward So again, I take a relatch on a relatch on C at this point here. I know I'm safe because I'm deleting But I'm bleeding here. Yeah, I'm bleeding here But I know that I can absorb this actually that's wrong Yeah, fuck No, no, no, sorry. That's right. That's right. Yeah, you take relatches all the way down, right? And then just when you get here you take a right latch Then I can delete my entry and I'm okay if I got here when I try to cry the what the right latch and recognize that I would have to then go back and Merge this guy then I have to abort the operation I didn't make any change yet So that's okay and this repeat the process using the right latches all the way down I see you go relatches all the way until you get to the last one then acquire the right latch And then if you if your assumption was wrong, then you bought the operation if you're something was correct, then you're okay, right? So at this point here Right, and you just take relatches all the way here Then when you examine this thing you say, oh, I'm okay. Therefore release everything and then do my change. Yeah, sorry Okay, so Again, the key thing to point out here is that the the latches we're using protect the physical data structure Again, we don't care about whether things get moved around inside of memory We just worry about Making a change to the index and having it now point to some memory location that doesn't exist anymore right that would be bad because that would we get a segfault when our threads try to read something so The way to think about this that we're releasing latches immediately after we're done whatever operation We want to do is and again, that's how we define what our latches are doing But that's going to not going to protect us From all the anomaly stuff that we talked about before that we wanted to avoid when we run transactions of seeing data that that You know seeing data that that wasn't there the first time we did a look up And then it's there the second time or doing a look on data and then the data disappears Right, these are the phantom issues you want to deal with So let's look at two scenarios where latches are not going to help us So let's say we have a transaction that was to a read on 25 Right, so we just do the the crabbing stuff. We take latches all the way down. We get down here To this node and then we see that 25 isn't there. That's fine. Okay but now My transaction goes off transaction one goes off and does other things transaction to starts and it doesn't insert 25 So again, right latches all the way down. It's allowed to do that gets to this point here and search 25 and it's done Now transaction one comes back and doesn't Says well, I knew 25 didn't exist before so let me try to insert it now So now when it gets down here, I took latches all the way down That's fine because again that protected the physical data structure Nobody else was making modification the same time I was so that's all fine But now when I try to do my insert 25 I thought it didn't exist because when I asked it whether existed in the first time it said no Then I try to insert it and now the index says, oh, no, no, you can't insert that I do have this right So latches aren't going to help us with this we're still screwed So that that again, that's a phantom. That's one problem The next issue is now we want to do like a range scan So it's here this transaction. What's your range scan on 12 to 23? So again, I take read latches all the way down. I get to my leaf node Right. This is the starting point for my scan 12 and now I'm going to scan across the leaf nodes and take latches in that direction and To see this range, right? Then the transaction one gets stalled does other stuff transaction two comes along inserts 21 Right finds it here and serves it into this node here again It was allowed to do that the latches required the correct latches everything's fine But now if I do the exact same scan again I'm going to get a different result because now I'm going to get 12 21 23 whereas before I only got 12 and 23 right So again latches are protecting the physical data structure But we need something else to make sure that we don't have these anomalies Now when we talked about the in-memory vaulting version courage control from hyper hecaton and cicada They had other mechanisms Then what we're talking about here to put make sure that these things didn't happen There so everyone remember what hecaton would do To prevent this one It would run the scan all over again when you did validation and if it got back a different result It wouldn't allow your transaction to commit So again latches don't protect you just that extra scan protects the logical contents to say Yeah, I didn't when I ran it the first time. I saw everything. I saw I would see when I ran it again later on Right So this is what this is what index locks are going to protect help us with so the index locks are going to a way to protect the logical contents of the indexes from Other transactions that are modifying at the same time So again, the reason why we have to do this extra stuff is because The index are essentially is like this auxiliary data structure. That's a copy of What's in the table? Right transactions are updating the the tables and we would see those changes and they can update these indexes as well So we need to make sure that we see things correctly So the difference between what we're talking about here versus index latches or is that same stuff? I talked about with with that table in the beginning from the paper So these index locks are to be held for the duration of the transaction We only have to acquire them in the leaf nodes We don't care about what the bulb us in the index or in the inner nodes because again the in a B Plus tree the leaf nodes are where the actual data is being stored Now one difference though between latches here is that we're not going to actually want to store the these locks in Our data structure itself in the case of those latches those latches we actually store in memory inside the node Right. It's for every several index node. You have a little header and the header you is where you want to store Maybe your latch You don't want to store these locks in the index because as we'll see in a second Or as we saw when we're doing splits and merges the memory location of where the logical keys Exists are going to move around and now if I have to keep track of what locks I hold Then anytime I do a split and merge I have to figure out well who holds this lock Let me go update its pointers and now point to its new location if I'm pointing just directly to the node that has it So by decoupling this we can allow the physical data structure to get to change its organization Without having to update where we you know how we find out what locks we hold So the techniques we're talking right here again, we'll describe it in the context of B Plus tree But as far as I know these these can be used for any order preserving index They're not specific to a B Plus tree right where the latch coupling stuff is is Specific to a B Plus tree All right So the way we're going to maintain the information about our locks is the classic lock table that we talked about under two-phase locking So there's some auxiliary data structure some auxiliary hash table That's going to have a way to do lookups and say for a particular lock type and in a lock, you know scope Here's the hash table that can that that that maintains the the list of the transactions that are waiting to acquire the locks or hold that locks Right and I also keep track of what mode that the locks are being held under right so again What makes this sort of confusing is that? We take latches as we traverse the index to get from node to node then in order to protect certain regions inside the index Domain we have to do lookups in our lock table, which we also have to protect with latches Right, so we take latches into the index and then take latches into the lock table to look at what locks are being held on the index All right so As far as I know No in memory database system actually implements any what we're talking about here The reason why I'm covering it because I think it's useful to understand Again part of the distinction between the disk based systems and the in-memory systems I don't understand like it's more than this. Oh everything's in memory Everything's on disk there's fundamental changes to how the architectures actually being implemented and the memory database systems Choose not to do these techniques because instead they rather do the the validation stuff We saw on hecaton or the precision locking stuff we saw in in in hyper because those things actually are much faster when everything's in memory In a disk based system the index actually may get swapped out the disk But this the lock table always stays in memory And so that's you know because this is always in memory. This will be this is better for things that may you know reside on this But you pay the penalty having to do this look up as we'll see in a second to go look in this lock table I figure out what locks are being held on index nodes Okay So let's talk about five different locking schemes that were covered in the paper So the first one predicate locks again Nobody actually implements this but it's good to understand for historical reasons And then we'll start with the key value locks and then sort of build up and add more and more things till we get the final one Which is a hierarchal locking. This is like the full the full locking suite. You can have the full protection And this is what this is like what commercial guys like DB to that they implement these things so predicate locks are Our typological locks that were actually first proposed in from the IVM system our project You've heard me talk about system our before the reason why I like it so much is because you know, it was One of the first database systems that relational databases systems They were building back in the 1970s and they basically said hey, here's this abstract idea of relational database Let's get eight really smart people with PhDs in a single room and they'll figure it out And they figured it out right and one of the things that they invented was this this idea predicate locking because they recognized they needed to have a way to protect Protect data and indexes Beyond just sort of this simple two-phase locking techniques that we talked about before to avoid phantoms So the way it's gonna work is that you're gonna have shared locks for any predicate You have an aware clause for a select query and then you define exclusive locks for the wear clauses that are that are defined in a update in certain delete queries and the way I think about this you're gonna map this to a multi-dimensional space See whether there's there's intersections between these different locks And if you do then you know that there's a conflict and you have to make a decision about whether one one queries ladder procedure versus another So let's look at a really simple example and hopefully on a 2d 2d projection. This one makes it make sense So say I have two queries at one table at the account table The first query wants to do a summation on the balance for the account where the name equals biggie And then there's another query a transaction that was to insert a new record For the count biggie with a value of 100 So this is gonna be a 2d projection of all the records in the account table So if I want to determine whether these two queries Conflict right they're both operating on the same same data Then I can sort of map. I look at the where clause here and I say well This region of the table space is where all tuples for the have the count name biggie and then for this For the insert query here's another region where it's the name equals biggie and the balance equals 100 Because this thing intersects with this thing Then I know that these these queries are actually conflicting and Therefore I have to make sure that if this thing here ran first I Would have a phantom if I allow this query to run do the insert because I wouldn't have seen the insert Before I ran the query, but I would see it afterwards and therefore that would be a phantom and phantom anomaly And I can't allow the insert query to run So again, this is super super easy for us to look at this example here and see oh, yeah These are these are conflicting but now think about like I have like really big where clauses With a lot of predicates or in a lot of queries running at the same time How do I actually map that and find those intersections is is really hard? And so that's why nobody actually nobody actually ever implemented this The precision locking stuff from hyper is an approximation of this then for them rather than looking at the intersection between different queries They only look at the where clause from one query and then look at the What tuples or what would delta records overlap with it? But you can sort of think of the Delta records as like a Materialized tuple and you just check to see whether the where clause evaluates to true for each tuple in the Delta record Right, so they're not trying to take arbitrary queries and comparing them Which in the case you to do it and in predicate locks Now the advantage of this is actually you can for the most part you can almost always figure out whether two queries are gonna conflict with actually running the query Right in the case of hyper that you you can only do the precision locking after you run the query Right because you have to get the Delta records for this thing here I don't actually need to run anything in the database. I just look at the queries themselves. Which is nice But in nobody implements this all right So the next so let's actually talk about it's actually practical what people would actually implement So the most simplest type of key index lock is a key value lock And this is super simple to understand. It's just a way to say I now have a Lock on a single key in my index right so I can take a lock on 14 And that prevents anybody else from from from deleting this or changing this So now the tricky thing though is sometimes I want to take a lock on things that don't exist yet Let's say I want to take a lock key or you know key value lock in my index on 15 15 doesn't exist yet. It would go here Right so my I did that one example where I showed like I did a look up to see whether the key existed And then it didn't and then I tried to insert into it But then somebody else came along insert before I did I could take a key value lock and say I'm gonna insert 15 Let me let me go actually go lock it And this is another reason why you can't store these locks inside the key the node itself is because the the size of these These you know virtual nodes or gaps or infinite, right? And if I have to pre-allocate slots in my node in memory to say here's all the The key value locks I could be storing this node You know you spend more space doing that than the actual data itself So that's why you put everything out into a separate hash table the next type of lock are gap locks Right. So again, this is this is the example I said before we're like now in between these keys I can maintain I have these gaps and I can now define locks and say I have a lock between Where to the gap between two keys? So this is a gap lock between 14 and 16 exclusive So if anybody tries to insert something at the value fit, you know 15 whether it's a decimal or or the or the the integer then this lock would then protect that So now we do more complicated things and build upon this if you'd have Actually go back to this real quick and another good reason why you can't store this in the index itself or the node itself is because Let's say that I I had the gap lock on the space here between 14 and 16 But now I had to do a split and now 14 is on one node and 16 on another node And now my gap spans two nodes If I was storing this gap lock in the index where should it actually be should it be on 14's node or 16's node All right, that's not easy to do if I just have it up in my my separate index lock table Then that solves that problem All right, I'm trying to rush through because I want to get through the teach you guys about profiling real quickly key value locks again. This is now building upon the the the Using the gap locks and keep in key value locks to now take locks on ranges and now we also want to introduce lock modes The same way we had lock modes and a two-phase locking because now if we want to have larger regions covered by locks We may want to not take an exact exclusive lock or a shared lock on it We may want to say here's an intention of what we want to do right, so let's say here I want to do a Next key lock from 14 and 16 14 inclusive 16 exclusive So this is the combination of taking the key value lock on 14 and then the gap lock on the space in between them, right? Alternatives do a prior key lock 12 to 14. So this gives me 14 inclusive and then 12 exclusive So as far as I know, I think you always have to go in one direction. So it either has to be a Next can only be next key locks or only prior key locks You can't mix them because I think you would end up with dead locks and you have to do you have to work to be able to deal with it Now doing hierarchical locking and now we take locks on larger regions. So we could take a Range lock on 10 up to the gap to 16 but again instead of saying that it's either shared or exclusive I can do an attention lock and say I'm I'm intentionally exclusive of that somewhere inside this this region I'm going to take an exclusive lock. So I could take an exclusive lock on 14 and 16 Now another transaction come along. It could also get the intention or exclusive lock on On on the entire this region as well But then inside of this it takes an exclusive lock on just this piece here and that doesn't conflict with this And then so they're allowed to commute Everything can guess why you want to do these higher level intention locks if you took the intro class you should know why What that? Exactly, so you said to reduce them are locks you have to take in the lock manager So say if I want to do a share lock on the entire node I Don't want to do a share lock on every single key value and every single gap. I can take a share lock on the entire thing Right because that's that's fewer lookups in the index the index lock table And so these intention locks allow me to sort of protect myself from I don't know maybe what region down along I'm going to be doing something But let me start with that see whether I can actually get this this higher level intention lock If I can then I can go ahead and take the smaller lock down below the more more refined one But I basically I can figure out before I get to the piece I actually want to take out the lock on am I ever actually going to be able to get it get it or not So instead of saying getting getting individual locks and checking to see can I acquire it maybe to the last one You finally can't get it, right? I can check at the very beginning whether it's going to succeed or not Yes Yes What this question is if I want to scan from the 13 to 15 Yeah, so his question is if I want to scan 13 to 15 what locks do I take you would take the you take a range lock from From 12 exclusive to you said 13 or what 15 right 12 12 inclusive 13 inclusive to 15 inclusive you can take locks on any size of ranges But the meaning I'm pointing out is like you're not it's not like a key value lock You're taking I know this key exists. I'm taking a lock on it. You're taking a lock on ranges Right and now again like it's sort of like the predicate locks Right where it can be sort of arbitrary Wear closets essentially arbitrary ranges, but really the the number of operations I care about To do these comparisons are actually quite small, right? It's either like is something this key or is something in this range, right? The number of operations I can do that to protect myself against are limited So taking the range lock from this to this is Not a problem for us to figure out what people conflict if those are not a key-range lock with a B3 because if I'm if I'm taking if I'm scanning from certain 15 I can take a key-range lock from 13 to 15. Then I think it is not It has nothing to do with what is inside the tree leaf a tree leaf not like drawing in this picture. Yeah, so this question is this question is if I can take a Range lock from say 13 to 15. Yeah That doesn't actually exist in this index node, then how does this relate to the index lock? So that has the physical data structure relate to the index box. We're talking about here Yes, because No matter no matter what keys are For example in this node, yes, I have to take the right lock from 30 15 Yes, so so what is the relationship between the right lock and That's so his question is what is the relationship between a range lock and The contents of a B plus three node. Yeah, so again, we're not storing any of those locks in the node itself so I can take a lock on in the index lock table without actually having to reverse the index So he said that if I want to take a range lock on an index I don't need to traverse the index to figure out what's in it or not I can take it in the lock table. Correct. Yes. Yes again because things could move around in memory and It doesn't make sense to store like the physical regions of for my locks Because it may change as other trans other threads are updating the index Okay, right. That was a bit rushed, but that's okay. It's not because I want to get to the stuff That's related for the first project. Okay, so The main takeaway when this is that the pal time I should have fixed that sorry Just still true. We're still not doing We're still not doing serializable isolation, right? We only do snaps isolation because we're not gonna implement the precision locking stuff That's from hyper in the case of pal time with this old code like in the paper you guys read For that like the the staring to the abyss paper the um, no the best paper It talked about being serializable even though we don't support still lies Well, that's because we rewrote all the benchmarks to not do any range cans You don't have range cans. You don't have a you know, you know, you don't have serialized it serializability problems All right So again the main takeaway from all this is that there's a bunch of extra crap We could do that we're not going to do with index locks to Provide serializability But instead we can target on the in-memory approach as we saw with the other systems To achieve basically the same thing of the index locks give for us right and that the The the coupling of the the data structure of the index lock table and the actual physical data structure It provides us the ability to take those locks At arbitrary points in the key domain without worrying about how the underlying physical data structure is actually implemented We use latches to protect the physical data structure and locks to protect the logical contents Okay, we have five minutes. Let's jump through this. So I want to give a really quick crash course on the Profiling in in the database system. So for project one, you guys are required to use perf quick show hints Who has used perf for four? One perfect. Okay. Who here has used call grind before or Val grind? All right more. That's what I expected. Okay, fine. Good so Let's talk about what what we actually want to do So Let's start with a really simple program that has two functions foo and bar, right? So say we want to speed this thing up, right? And we only have gdb The stupidest thing we could do or the easiest thing to do is just Run our our program and every in gdb and every so often we pause execution We look look at the stack trace and say well where are our threads executing and then we just mainly count The where what functions are being invoked the most, right? Maybe we can be a little smart about this We can maybe have a little robot hit the keyboard and just you know hit pause pause pause, right? But still that's that's stupid, right? So say we then collect 10 stack traces for this and You know whether it's a robot or not it doesn't matter and then we look at those stack traces and six out of the 10 times we got samples from these we were going to be we were in the foo the foo function Right, so we know from this that roughly 60 percent of our time is spent in the foo function And obviously if the more samples we get where you know We have a robot or not then we'll get better accuracy with this, but for this assume that that it's 60% So say now we optimize foo to be two times faster What's the expected overall speed up of this? 30% why Yes, he said it's 30% because you only shaved half the time off of 60% so he who here's heard of Alameda's law All right, not not very many. Okay. That's fine. All right, so 60% of the time of foo drops in half But 40% of our time we spent in bar that's still all there So I'm those law basically gives us this nice little formula and says what is the percentage of of the time We're doing in the part we speed up how much time to be actually speed it up We plug and chug the numbers and then let's tell us what our proven is going to be So even though we made the foo function two times faster The improvement is really going to be that we're only going to run 1.4 times faster or 1.4 times faster Because we're not spending a hundred percent of time in that foo function Right, so what I'm does law is useful to do is for you to say as I look at my program and I'm trying to figure out what what you know What are the high poles in the tent? What are the parts of the code that I'm spending most of my time on and I want to speed up you can then now start to approximate what the importance permit you're going to get is and At some point it's a you get diminishing returns because I can optimize this piece of code that it may be super expensive But I only call it once every every hour Then it's not worth the time to speed that thing up You want to spend time looking at the the part that actually is exercised the most and see what the benefit you're getting So I'm does law is a very useful thing to again to approximate this with the benefit you're going to get it So what tools can you actually use to profiling other than this the stupid paws that on the keyboard? so the two classes of programs are Valgrind and Perf so these are Linux specific. I don't know what it's called in Mac or Windows. I don't care Right, so right, but I think these things exist or purpose exists on the Mac, but I think Valgrind does right yeah, so I visit X code. I think you know an OS X that the Apple provides basically the same thing All right, so the two approaches are To do binary instrumentation to in the actual program as it's running to figure out where it's actually executing the code Another approach is actually just use the harbor counters inside the CPU provides To estimate what events are occurring in different parts of the code So we'll go through these one by one So in Valgrind is not a single program It's actually a sort of a generic framework that allows you to implement a bunch of different tools that do different kinds of dynamic Analysis, I don't know how many there are but these are the three that we care about the most Memcheck is what I think Valgrind originally started off as is the ability to see whether you have memory leaks Call grind is a way to see where you're actually being You know what pieces of the code are being invoked the most And then a massive is a way to track the memory usage of your program over time So this is actually really useful for us because we're in memory database We want to know what you know what parts of the code You know are we allocating the most amount of memory right again another shitty thing we did in Peloton was In the case when we did beat for our BW tree It would allocate the entire hash table to do the mapping The the indirection layer you guys are read about it would allocate that entire data structure When you started when you immediately when you started the you know created the index even though there wasn't anything in the index So it used to be when you turn on Peloton It would immediately without putting any data into the database it would spike up to like 130 megabytes I just the program just the program itself. We 130 megabytes and see we use massive to figure out why the hell This was the case me figured out it was the BW tree All right, so the way to use call grind Is from the command line you sort of pass in These flags like this and then you just run the function that you want and then this will spit out a File called call grind out usually with and then the process number And then you can use a nice tool like cash cash K cash grind to actually visualize what the What the the performance trace looks like so this is what if you run the concurrent read benchmark You'll get something like this so along this side here. You have the cumulative time distribution for different functions And then this has a nice visualization to do call graph for the call graph or what's calling what it tells you how Many times it called it doesn't what percentage of the program time is spent in this In this part of the code and then you can drill down these functions and actually see The actual source code if you have the debug symbol set up correctly So this is a really nice tool that that I'll say right where are you actually spending the Where where are you spending the instructions extra source code? But it's not going to tell you anything about where you're actually spending time Because this is not going to capture things like CPU cycles and it is this this just captures instructions right So this is what perfect is going to do for us So perfect is going to allow us to use again the low-level porters counters that's available in in in the hardware and It basically it's going to do these just like call grant. It does these samples and figures out How many different events are being spent in different parts of the code so the one we care about is cycles Which I think you get by default But you actually see the full list of like other things the thing captured it'll capture like cache misses Branch mis predictions and things like that which is really useful to get like low-level Understanding of what the program is actually doing to do debugging right? So basically it just uses counter-destructive events and when the counter workflows it just does a sample right? So what'll happen is you run perf on the program. It'll generate this I think perf dot report file and Then you just run this perf report command and that'll generate Sort of a trace visualization like this right and then again along this side You're seeing again that the Cumin event distribution and then you can look at the very top and say well what where am I spending most of my time? right, so I think for the first project we've asked you guys to look at Begin transaction and commit transaction so you now drill into these two functions and see what parts of the code Am I spending most of my time? Where am I spending all my cycles? Right instructions will tell you what instructions you're executing But it's not gonna say if you stall on a on a on a spin latch Right those are cycles because you're waiting to acquire those things or you know having it go fetch reads the memory We'll take longer cycles too. So this is why call grind won't capture those things but perf will So this kind of sucks a look at But and this is what I you know I grew up on this is what I would use right you can kind of look at these are like mangled C++ Symbols to say like you know transaction manager law commit like you can sort of read this, but it's not easily decipherable I haven't figured out a way to fix this in Linux But this is why I also love the internet because I did this lecture last semester and some guy tweeted that me said Hey, there's this great program called hotspot for Linux and it'll generate you nice visualizations of Perf readouts, so you just load in the same perf perf file that perf generates instead of using the command line tool You can get a nice visualization like this and now you have actually more easily read Function names and class names so you can also do things like Filter out regions of time during the execution So the benchmark you guys are get we're giving you has basically two phases the first phase You load the database then the second phase you actually run the benchmark where you're doing all the reads So you can see that here in these events like this is the region where we're doing all the loading Then everything after this is where we're doing all the actual execution So you want to filter out the this and only look at this to try to identify what the problem is Right, you can do this from the command line, too. I just don't know how right The other thing it gives you also is a nice flame graphs Then be able to drill down even in more detail of like where you're actually spending spending your time Which is again, it's a really useful tool To see what's actually going on at a high level and then you drill down to see what's what's happening I don't know whether this hooks into the source code or you can see the actual instructions But if you want to see actually what the assembly doing as well peripheral give you give you this as a Nice breakdown with again with the the number of vents for each each like line of instruction, which is amazing Okay so Again purpose other things not to cycles cache misses branch misses. You can see the full list if you do this And so here if you want to get record a bunch of stuff like cycles and last-level cash cash misses You can pass in a comma-separated list of all the events you want You know dump that all into a single file and then in the tool itself You can say what you know what what event do you want to be organized on? Okay? All right, so there's a bunch of references again I'll post this on the slides online so you can get to these things I think a bunch of these are already in the link for the for the Project so for this project We're having used perf because again most of you have not tried it for this for the final project for the larger piece You want to actually use a combination of both of these so call grind I'll tell you where you may be doing stupid It's you know making stupid calls for things, but when you want to get to low-level optimizations Oh, like where am I spending where am I having cash misses? Where's where's my data misaligned? Right perf is the only way to be able to figure those things out. That's why you need to be familiar using both of them Okay All right next class keys more keys or how to sort keys how to do something or a little more about memory allocation and garbage collection But then we'll spend most of our time talking about different data structures the lock free data structures Although tea trees aren't block free or lash free. Okay? any questions 60 degrees Stop And I'm able to see saying I was on the label no short you know, I got him I take off the cap and first I tap on the bottom Don't want to be in the freezer so I can kill it careful with the bottom baby Cuz ain't I said the pain I'm wet you drink it down with the guys Take back the pack of dust it won't get you some same knives and drink it to the stars We guys be a man to get a kind of play