Pew, pew, pew. Since free is one of my favorite words, we're gonna start talking about as many fun and free games that we can. Are you excited? Because yes, we all want to play the best in the latest and the greatest of games. But unless you have hundreds of dollars just laying around to buy all the games, and you're like me, and you have to play free games. That being said, if there's a free game that you absolutely can't get enough of and you would like to hear me talk about it, please leave your suggestion in the comments below. We're gonna kick off our free to play series with the first person combat shooter called Dirty Bomb. You gotta say it like that too, Dirty Bomb. Dirty Bomb has been an open beta for a few months now. And let me tell you, Zesty Mike has not stopped playing it. He is a huge player of games like this. So of course he got me playing it as well.  But I have to say that for a free to play game that this one is very well done. In the near future, London stands abandoned, a dilapidated shell of its former self after a mysterious disaster. The international crisis and disaster agency stepped in to clean up the mess and decontaminate the city. They failed badly. Now the streets of London are the playground of private military companies, PMCs, who battled to control what remains of the city's riches, after all. What's a little radiation sickness when there's money to be made? If that's got you interested, let's keep on talking. Dirty Bomb is a fast-paced team game that is supposed to challenge you and kick you in the teeth. There's no controller support or aim assist, so you have to rely on your skill and your reaction. In the game, you can choose three players called Mercs, short for mercenaries, to be part of your team. Each Merc has its own stats for health, run speed, and abilities. You can rotate between your team after each death, and you can refresh your teams after each match is over. The Mercs are unlockable, and you only start off with a few that are actually free to choose from. The free ones are Aura and Sky Hammer, and they also rotate in different free characters from time to time. You can either use real money to buy new Mercs and load out cards, or you can accrue in-game currency as you play to buy them. Although each Merc has their own strengths, the game really drives hard that you need to play as a team, creating balanced mercenary squads and coordinating with your teammates. Missions are in place to give you something to work towards, like getting 10,000 support XP. Once you complete the mission, you get in-game cash to put toward your upgrades. Now, one downside that they have not fixed yet in the beta is hacking. Hacking has become quite a big thing, apparently, and hopefully they will find a way to fix this if they ever want to launch the game as not beta, because these are some pretty big hacks. People are using cheats to highlight where people are on the map and to even shoot enemies through the walls. Totally not fair. Well, that's it for us talking about Dirty Bomb.  I hope that you guys do enjoy us talking about some free-to-play games because it never hurts to expand your Steam library for free. If you guys enjoyed this video, please leave a like, and you can check me out on my channel, Brits Games. My links are down below in the description. Thank you so much for watching, and I'll see you next week. Goodbye! Oh, hello, who is it? Uh, Bart? No, I'm not Bart. I'm Bella, who's this Bart you're talking about? Impulse is produced by the Freedom Network. Ever wanted to earn revenue from your videos? Freedom offers a great partnership program which includes access to hundreds of thousands of audio tracks, sponsorships with great partners, and most importantly, a no-lock-in contract. You can leave at any time for any reason. We accept everyone, both big and small. For more information, click here or visit the link in the video description.


So today I'm going to be making a potato corn soup. It will more than likely be closer to a chowder. Now it is the summertime but I like soup still during the summertime because it's quick, easy and affordable. So the ingredients we have garlic powder, onion powder, Italian seasoning, pepper and salt, some flour. I'm going to use one chicken bouillon. Okay. We have some corn. More than likely I'll use a cup, maybe a cup and a half. I have four red potatoes. You can use any type of potatoes that you like. I have some garlic. Probably use about one tablespoon of garlic. The rest of this onion, this is 75% of the onion, two sticks of celery, three sticks of carrots and we're going to use about half a cup of half and half. So I'm looking at it eyeballing it and I'm going to go ahead and add my corn. See if I need to add any more liquid. I don't want to add too much liquid because I don't want it to be soupy and of course the flour will thicken the soup up. If I feel I need it a little bit thicker, once the potatoes are almost done I can literally use my ladle here, the spoon and smash up some potatoes in the side and the starchiness from the potatoes will help thicken up the soup. I'm going to go ahead and add my corn. I am going to do one and a half cups of corn. All right that's my corn, one and a half cups, almost two cups to be quite honest. Let me go ahead and add my seasonings. I have my onion powder, my garlic powder, a little bit of Italian seasoning and of course some black pepper now. I'm going to reserve adding any sodium until it's nearly done y'all. I really am trying to watch. Well it's pink salt, mostly with pink salt but I'm going to wait to add it. Yeah it looks like it's going to need some more liquid so let me go ahead and add a little bit more chicken stock. You can use vegetable stock okay. So now all I'm going to do is bring this up on a high and if you could tell I left some skin on on some pieces that's okay. I'm going to bring this up on high, reduce it on a low, let it simmer until the potatoes are done and then I'm going to pour in a little bit of the half and half and it will be done. This is a great soup that you can add different types of vegetables. If you wanted to add peas you could do so. You can definitely make this vegan. It's a great soup that you can also freeze and this will last us for lunch for the rest of the week. It is only Wednesday so let me go ahead turn this all the way up on high and then I'm going to put a lid on and bring it down to a simmering boil.


Okay, let's look at how to find an image in Creative Commons. Step one, type the website address in the address bar. Step two, type the picture you want. For example, we want an image of Creative Commons. Step three, click the Flickr box. Flickr has a lot of images that we can use. Then you choose an image that you like. You click on the picture you want. Then you need to record the image information. Step one, type the image title into a new document. Step two, copy and paste the URL. Remember, the URL is the address. Make sure you get the whole URL. You can click and hold and scroll from beginning to end. Step three, click on the three dots to find the name and license. Now you need to find and record the creator and the Creative Commons license. Can you see the name? This is the creator's name. Now find the Creative Commons license. You can click on it to see more about the license. There are several kinds of licenses. See the handout entitled Types of Creative Commons Licenses. For this license, write CC-BY-SA 2.0. Now you need to save the image. Step one, right click on the image. After you right click, this message will appear. Step two, select a size and click on it. The next step to save the image is to right click on the image again to save it to your computer. You can also click on download. Now we need to attribute the image. We're going to open the image. Attribute means to tell the image source or tell where it came from. Step one, open paint. Step two, open the image. Now we're going to attribute the image. We're going to make the attribution text box so we can write on the image. Step one, click on the insert text button. That's the A. Step two, draw the text box and click inside. Now let's write the information about the image on the image. How to write the attribution. So first we write the image title. The creator's name goes here. This is the license type. It's a best practice to hyperlink the image in license. You may search online for how to hyperlink the text. Do you see the attribution on this image? Look down at the bottom. Remember to ask yourself, can I use this image? Look for this symbol, the CC. If you see this, the item has a Creative Commons license. And you are free to use it as long as you give attribution to the creator. Again, that means you tell where you got the image from. Okay, good luck.


I will be focusing today on the optimal module, what is known in 5.7 and 8.0. So, we have two sessions in the evening about generated problems and JSON, so I will be focusing on the 5.7 part, we skip over to the 8.0, this is the standard safe hardware statement. Yeah, so I will start off with invisible indexes. So, a lot of times the DBA space is problem where they have a bunch of indexes and they have a bunch of queries and they are not sure whether all my indexes are actually relevant or they are actually being used significantly. So, one thing you can do, so what happens is that one thing you can do is always drop that index, run your queries and say okay I have some indexes that are slowing down, you do an explain on it and say that yeah that index would have helped and then you have to recreate it. Now, recreating an index is always a very costly operation. So, all you need to do here is, so all you need to do here is converting to an invisible mode and then run your queries and maybe look at your explains and if that query is not and if that index is not being used then that is good you can just drop that index. So, this is like a dry run of dropping that index without any real cost associated with it. Next we have the descending index. So, even in 5.7 you could say something like this where you could create a secondary index which was say a descending. Why is this useful? Basically, if you have a query that has ordered by a descending. So, there are two ways that the server could deal with it. One way is it will either do a backwards scan in the sense that it will come from 10 to 1 or it will bring all the data to the server and the server will do a 5 sort. Now, 5 sort is doubly expensive because one thing is you need some temporary table type structure and you also need some computational time. So, it is very expensive. Instead, if you can just create this sort of a descending type thing then you can just store all the values in a descending order and it will just be a plain read rather than any sorting or anything like that. Next we have common table expressions. So, to understand a little bit about common table expressions you need to know what are derived tables. So, derived tables are basically that you have a subquery. So, you have a subquery which is treated like a table in the sense that first whenever you give a query like this first this subquery is analyzed and the output is treated like a table with the name derived and then it is used for the rest of the query. So, there are some drawbacks here which we will discuss but with common table expressions what you do is you bring that part out of the query. You write it and then you have your actual query. So, why? Well, first of all there is better readability in the sense that you do not have to, you have select start from then you analyze this and this part will be used outside. So, all that kind of confusion is sort of gone now. Then there are deeper problems that when you are writing derived tables you cannot do chaining like this. For example, you cannot do a self-joint like this which is select start from T1 comma T1 you cannot do that you would have to write the subquery all over again. So, the query will become something like this you will have to say a subquery derived and the same subquery again and derived one. So, even for a simple self-joint type thing you cannot use it. So, what will happen here is that this will be analyzed separately this will be analyzed separately. So, for the same subquery you will be doing two levels of analysis. Instead with CTs that problem will go away you can just say derived and derived. That is all. Self-joints become very easy. Then whether it is chainable derived tables are not chainable you have a subquery derived but you cannot do this you cannot write the derived tables. You would have to actually put it in a CT. So, why are recursive CTs good? So, recursive CTs have basically two parts. The first is the seed part and the next is the recursive part. In the sense that this will supply the first part of the value and this will supply the rest of the values and will go on until as far as it keeps going. So, if you have used oracle maybe it is called connect by and the syntax is mostly similar to the one that mssql also uses. So, if you have queries there you can just quote them. Now, you have recursive CTs which are basically going from 1 to 10. You want to print values from 1 to 10 look at the last sentence. Select start from qn then you have qn this is the seed. The first value will be supplied here and this query will run as long as this condition is satisfied as long as 10 is satisfied you come down. Similarly, you have the Fibonacci values. So, it is similar to printing 1 to 10. What is the real world example here? The real world example is that if you have a table which is basically very simple and you say you have an ID, you have a name and a manager ID. How do I create a reporting structure in the sense that I report to my manager, my manager reports to a director, then VP, then the CEO. How do you get a structure like that? With normal SQL queries that is not possible. You have to use CTs and you have to use recursive ones. So, let us just go back and look at it here. The only person who has a null here is the CEO. So, this guy John here reports to Yesmina. Similarly, Pedro reports to John and John reports again to Yesmina. So, that is the kind of structure we want. Now what you can do is you can write a simple recursive CTE where you have this structure that is you have the employees where the manager ID is null. So, basically the first query, the seed query will give you only Yesmina who is the CEO. The rest of the guys will basically come from this query which is a join of this CTE. See, they explain the employees extender is the CT name and employees is the table and there is a join and based on that you will get it. So, you can see that John has 333 in the 190 which is basically their reporting structure. They will want to look at someone like Sarah, she reports to Pedro, Pedro to John, John, Yesmina. So, just by simple concatenation and recursive CTE, you are able to accomplish this. We have improved the performance of scans. So, what happens when you say select start from the application? So, what happens when you say select start from? What we do in the server is we go to the storage engine and say come on, I want all the rows that are in this table. What INVDB does is that it goes to its own B3 and says okay, do I get all the rows or do I get some rows? So, initially it knows that it needs to get all the rows. So, it will just bring in batches of 4. Now, what we are doing is we are saying why bring in batches of 4? Why do you want to tell us B3 every time? Why not just bring in batches of much bigger number? So, what we do is we supply this guy with a buffer, the storage engine with a buffer and the storage engine will bring that many number of rows. What is that number? Well, it will depend on some system variables that can be configured and the row size. So, if you have a very quick row, maybe you bring only 200 records at a time. If you have a very small row, maybe you bring 2000 or more at a single time. So, it basically saves you a lot of fetching time. What are the queries that will improve its performance with this? Not my table scans, which are basically select star from T. Range scans, which are basically, okay, I want all the values from 1000 to 10,000. Maybe this earlier would take about divided by 4, number of rows divided by 4, and now it will take number of rows divided by 100 or 200. Also, joints, but only for the first table. The second table is anyway a ref access, so you will really be helped by that. On dbt3, we noticed that with this feature, we have better performance for almost 18 queries, out of which two queries have shown around 15% improvement. So, this is a standard dbt3 value. Hints. So, yesterday we talked about force index. Force index is also a hint, essentially telling that, okay, I want you as a quality to use this particular text. There are other kinds of ways of accomplishing similar things. So, if you say, okay, you are not done, if you as a dbt3 feel that optimizer is not doing the good job of generating plans, you say this is not the best plan, and if I turn off this particular optimization, I will get a better plan. So, what you do is, you do a set with that optimization off for the optimizer switch. Then you run the query. Your job is fine there. But the problem with the optimizer switch is that it will basically affect all the queries that fall away.  That is essentially two round trips extra to your server from your client. Whereas with hints, you give much more flexibility in the sense that it is part of the query. And it doesn't have to even apply to the whole query. You can decide to go on some query or a particular table or something like that. So, we have new hints in 8.0, which is joint fixed order in the sense that if you say you have 10 tables. Now, what the optimizer does when I get 10 tables is I'll say, okay, I have to do a search of what is the best order. But it so happens that sometimes dbs know what is the best order. So, they don't want optimizer to spend a lot of time doing that. We can just say, join fixed order. Or you know that for a subset, it is taking a lot of time. So, you just say join order. Similarly, prefix suffix. How does it work? Yeah. So, you have this query. And you have two tables. Customer and order. If you leave optimizer for its own devices, it will do orders first. Then it will do customer. But according to the DBA, they feel, okay, you know what? That's probably not a good thing. Maybe my data is continuously changing. Or, you know, optimizer, somehow in this case is not doing a good job. Good. Just say join order. Just put this much. You put the customer table first. Order stable next. Get customer. Order. That's it. So, in your case, we put a force index here. This will be a new addition. This particular syntax will be new? Yeah. It's an 8-odd. So, this is an 8-odd 0? Yeah, this is an 8-odd 0. It's already the lab's version is out. Also, prior to this, there was no optimizer here in join order. Join is not there. Well, there was something called straight join. But straight join didn't give you much flexibility in the sense that if you had five tables, all the five tables, order was specified in that order. Whereas with join order, let us say you had a third table, something else, tp. You can say that for customer-in-orders, I want this order, but the third one, I don't care. You can put it back in here. So, it's a little more granular. Yeah. A lot of times when we have derived tables like this, optimizer will try to merge it into the rest of the query in the sense that we'll break up this whole query. We'll send the table out and we'll say join t1, join t2, and then do this. Maybe in some cases, that is not a good thing. And the DBA can just say, OK, I don't want this merge. You can just say no merge. And we won't do the merging. So the table will still, the sub-query will still be preserved. Better UUID and IPv6 support. So UUID is a standard value that is generated, that is unique across platforms, machine-based time zones. Everything, the problem here is that it's very big. The first part indicates the hours, minutes, and seconds. The next part indicates the days and then comes the months and years. So when you want to store it, you either have to store it either in a blog or in a big barcat 36, which is not very ideal. So a lot of people, what they do is they break up this UUID, they remove these extra dashes, and they just store it in binary 16. But as you know, you'd have to write a very big store procedure to it, maintain that store procedure, things like that. Now you don't need to do that. You can just say UUID to binary and binary to UUID. And you just give your value, get the value. And you can also check the validity. Easy UUID. There is another advantage here. Notice that in my previous slide, I said the seconds come first. Now, if you want to create an index on something, having seconds for doesn't really make sense. Because then your data is not meaningfully contiguous. So if you want something to be meaningfully contiguous in the sense that you want it, you want the years to come first, actually, this part. So all you have to do is UUID to binary with an extra argument, and that is done. So once this part is done, you can store it in a binary 16, and then it gets that value. And your data will actually be contiguous. So any data is not of contiguous, but it's meaningfully contiguous here, because it is a timestamp. And similarly, you have this bin to UUID with the extra argument. Why will these be important? Because a lot of times customers and users come back with saying that we have these huge store procedures that we've written just to do this particular function, or just to write this particular function. So we just want to create an index, and you create a reliable way of doing it, and this is reliable. IPv4 and IPv6 support. So IPv4, these values are generally stored in Begink, and when you apply a mask on it with an AND value, it works fine. But when you have IPv6, this is generally stored in a binary or a block, and binary and block are not supported for AND, that is, bitwise operators. So you would get incorrect results. A lot of times customers would just write multiple store procedures just to break up this value, then do the AND, and then return it to themselves. So now you don't need to do that. You can just say these bitwise operators are supported for both binary and block. So AND or all these will be supported. What is the future roadmap? There will be afternoon, we have some sessions about JSON, so you can ask questions about the advanced JSON functions that will be here, so here's the main one. Because in doing that, the cost model improvements in the future histograms are coming in with also the start distinguishing between data in memory versus this. So the cost model will take into account whether the query is used a lot. Then there are windowing functions, which is also a popular feature. Improve support for prepared statements. You can say prepare, the optimization part will be done, and when you say execute, from there only the execution part will be done. So that you won't be spending time deciding the join order, should I use range access, ref access, should I use this index, that index, all that will be gone. Just do an execute, the query will go directly to the execution. It's already there, but a lot of the optimization still happens in the execute part. We'll move completely to the prepared part. There is the optimization for GIS operations. So a lot of support for R-prease, which are used for GIS, and also a lot of new GIS functions that are part of the SQL standard. So these are the features. Any questions? I have a question. In some databases, you can store the optimized plan of the query. So next time the query will run, it will run according to that optimized plan. It will not calculate again. So is this something available in my SQL? It is available to a certain extent already. You can say prepare, run your query. So a lot of the passing part even in 5.7 is there. Even in 5.6 is there. You can say prepare, the passing, a lot of the semantic analysis, that sort of thing will be done. So if you want to just say, does this query even work? You can just say prepare. And when the execute part comes, a lot of these passing part, that will not happen.  But from 8.0 it will be even more, the walls will go up saying only the execution will happen. So 5.7 is still useful in the sense that if you have very big query within 100 values, you don't need to go to whether each value is correct every time you execute the query. You can do that in the prepare executions. I have a question on the recursive CTEs. Just I gave an example of the organization where the CEO, manager, and employee. So how expensive is it in terms of performance or memory? If you have, let's say, very large organization with many levels. And one other question is when you query, are you able to query certain parts of the the tree or something like that? So the first question I got, and you're saying, let us say we have a company like Oracle, which has hundreds of thousands of employees. Yes, it will be expensive. Second of all, you're doing the union all. I mean, the recursive CTE anywhere is based on union all. So union all is always expensive in the sense that you might have to use some temp tables. Internally, the server will use some temp tables. It is expensive in the sense that earlier you were not even able to do something like this. And now you're able to, you would have to know the depth of the organization structure earlier to write a very big query. But now, at least in terms of this, it's useful. And the temp tables are pretty much optimized in the sense that there won't be more than one temp table created per layer. So for example, for John and Tarek, they're both at the same level. So to generate their records, one temp table will be used. So going to the next one, another temp table will be used. And there is some repurposing of it. So it won't be very costly still. The temp table, I can use like a memory base instead of the temp table. So if it's a small one, it's generally on the heap and in memory. But if you have a very big temp table, which is created, it might be flushed to ignore you. Yeah. Is there such thing as a SQL profile or a SQL base name in my student? Sorry, I don't know what that is. I'm over at LBA. I'm thinking of this performance tuning. Is there such thing as SQL profile or base name or React if you run something with the query and Yes. That is the performance tuning. We have a session on that also. So you can monitor what is happening on the table or which particular phase it has spent a lot of time on the table. Sometimes it so happens that the query itself, the execution is very small. But choosing the join order takes very long because you had some 10 tables. We have optimized this 20 node in it and just choosing the join order. So that you can get some performance tuning. Any questions? Any other questions? You told that sometime we use temporary tables of primitive. Yeah. Did we do that? It's there in 5.7? It's there in 5.7. And what happens is that temporary tables in ODP you can actually tinker with it in the sense that by default it uses the one, the 10 tables on the heap. So it tries to create a 10 table on the heap. And if that size exceeds beyond the point it will flush the 10 tables to you know but in 5.7 you can set a system variable which will basically never use the heap, it will go directly to you know heap. So the heap we do not let it expand beyond the point. So does it more intelligently? Does it more intelligently for CTs? For CTs. For other queries there are minor improvements which are bug fixes Any other questions? So actually it's not given to us I have a question directed at Sanjay which I have booked on the last one Why does the question jump from 5 to 8 if I remember is this thing supposed to be the development? So we had we've been at 5 for such a long time so it was always 5.5, 5.6, 5. So there was a 6.0 that we had planned and that 6.0 you know there were just too many things that we had done there and we kind of lost our way with 6.0 so we decided we will take the best features put it into 5.6 and 5.7 so 6 was not possible because people get confused 7.0 is used for the MySQL cluster which is our you know in-memory database cluster it was gone and then we said ok it's only 5.8 let's call it 8.0 and then we have more possibility of making it 8.1, 8.2 and then 9 and 10 and I don't know all it was at 11 why don't you want to have major numbers? That's what I'm saying so we should have bigger numbers people feel more that they're being more innovation if the numbers are bigger I don't know this they might go to 108 for just now Arsha I have a question on the 10 table so in MySQL for the 10 table happens it can happen for each transaction it can be generated if the transaction meets the 10 table but then we'll be able to do is there any way to limit the number of 10 tables limit the number of 10 tables in things of number of 10 tables I'm not sure but you can limit the size of each 10 table using the 10 table size yeah 10 table size so you can set it to a minimum of 10 to 4 or you can set it to okay so so basically the this we will have to we will have to calculate the RAM for this server based on the number of the next heap that probably will be needed based on the number of transactions the other problem with having a very small size of 10 table is that you keep creating more of those and each 10 table will have some small metadata and all that there's a small overhead in terms of saving that 10 table so if you set it very small even that can explode is it the size of all the data files like in the Oracle database it could be the size of all 10 tables yeah that is resizing but not on the fly so once the fly is done the next way one thing to mention the last point you mentioned that there is an overhead to create the metadata right that's gone off the introduction of data so we don't persist the metadata on the disk it's just the thing is that the part of support has this advisory that is very small because maybe it's not just metadata but other internal structures that you maintain which you can also use in that case thanks


The Carnegie Mellon vaccination database talks are made possible by Autotune. Learn how to automatically optimize your MySleep call and post-grace configuration at autotune.com. And by the Steven Moy Foundation for Keeping It Real, find out how best to keep it real at stevenmoyfoundation.org. I welcome to another session for the Vaccination Database Memoir Theory's first edition. We're excited today to have Harry Student S. He is a group engineering manager at Microsoft on the Azure Cosmos DB team. He's been at Microsoft since 2008. Prior to that, he worked on the SQL Server database engine. He did his master's degree at University of Washington in the undergrad at BitSpline, which is a good school. So as always, did you have any questions for Harry as he's given the talk? Feel free to unmute yourself, say who you are, and fire a question to him anytime, the way he's not talking to himself. And Harry, again, thank you so much for being here. The floor is yours. Go for it. I really appreciate this opportunity to talk with people from CMU and affiliate it to it. So I think Andy already provided my intro, so I'll probably dive right into it. So here we are going to talk about Headstrap with Azure Cosmos DB. And this is hybrid transaction analytical processing. And so the problem statement that we were after in this particular endeavor is that we saw that a lot of customers were trying to take data out from our world to be transactional store, trying to have a pretty sophisticated ETL pipelines, et cetera, and to insert data into some other analytics system just for doing operation analytics. So the cost and the effort that was on the customer side for this was pretty high. And we said, why don't we try to solve this problem for customers and try to make it seamless. But the most important thing was can we reduce the cost for the customers in having to get to this operational analytics. So that kind of summarizes the problem statement that we are after here. So it's a pretty simple problem statement, but the way we thought was it was a segue to solve more problem for customers in making their presence in Azure more rewarding. So in terms of the next set of goals that we are after here is that when we think analytics, right, we didn't want to invent a new language or anything at this point. So we wanted to meet where the customers are. And so we kind of tried to make it interoperable with Spark and T-SQL where a lot of mind share exists in order to do analytics on top of our data. So some of the value prop of our cost must to be OLTB transactional store is that customers continue to take advantage of the schema freedom that we provide in terms of the JSON documents. So they don't have to do database development and they can evolve application semantics at a pretty high rate. And so we want to continue to preserve that in our pursuit of enabling analytics on top of our data. So apart from the schema freedom being globally distributed and elastic, which means that customers can do local region data access in each of the regions in which their applications are presented. So as well as they're able to increase the storage and throughput without any a lot of pre-planning because we are able to do that seamlessly while being online for workloads. Those experiences have to carry forward in this headstab addition of offering tools. So those were some of the first principles that we had in designing this. So the first and foremost is that our customers are take advantage of the low latency, single digit millisecond latency for reads and write operation on the OLTB. And we don't want to regress on that. And this means that the analytical queries, the disk bandwidth, IOS and the query computation, CPU cost, et cetera, we wanted to keep it off of the hot path engine so that our OLTB workloads are not affected in any way. So this just reiterating that we want to continue to have the same SLAs for OLTB that we have had before. And there's also naturally a natural derivation is that we don't do any remote IO transactions, et cetera, and the OLTB is a transaction path because of this new addition offering. We wanted to continue to keep the transaction path as late as possible. So in terms of the feature set itself, we wanted to expose analytics with archival support. And the reason for archival support is that usually customers want to address a larger space of data like a two-year history and so on. And for that analytical view while from creating the OLTB to a smaller working set that they usually update and modify. So we wanted to enable that opportunity here and that means that we do need a much cheaper storage medium in order to enable such a large amount of data for analytical queries. And time travel queries and snapshots which kind of go along with it are some of the things that we wanted to add into our roadmap as we plan this architecture. And going back to where we started is to reduce the total cost of ownership of the operational analytics space and trying to keep the data within the ecosystem. So with that, in terms of what we are really looking at, we have customers using SQL API or code offering and MongoDB API and so on. We have other offerings too. And the customers kind of insert their data using these APIs into our transactional store. And we kind of have a decoupled storage medium which keeps data in a format that is efficient for analytics, which is the column store format. And we try to keep the storage in sync and make sure that the freshness is maintained. And also the data transaction is consistent while being able to serve queries with snapshot isolation from all these analytical query and time set on the right side. And so this isolation, the fact that query run times do not reach out to the transactional store during query execution was one of the core things that we wanted to achieve through this. So in this entire screenshot that you have in front, the central box of how the storage is designed and how it works with all the high availability, your application and elasticity features, etc. And how things are kept in sync and what is the format, etc. Those are some of the topics that I'm going to go deep into in the subsequent slide. So that's the box I'm going to focus on. So in terms of the storage option, since we said that, okay, it's going to be a decoupled storage and we want to reduce an amount of processing that happens in the world to be database engine to maintain this decoupled storage. Logstructured storage seems like a pretty good choice because writes are batched and amortized and the number of iOS we do is pretty set and so that it doesn't add any unpredictable order of an operations or anything like that for persisting one batch of operations. So we did go with logstructured storage. And so it's apparently stored as a lot of this audience would be aware of, and where it replaces and deletes generate invalidations for the old versions of the records.  And the rights are batched and done in the background. The unit of right as I would refer to again and again in the subsequent slides is what we call as a segment this is the unit of rights that we have batched into one file, and we make it durable and that's what we call as a segment so we can think of this as a segmented storage system if any of you have come across that such a thing before. So now the introduction of our format. So, you know, we didn't. We started on this journey from scratch and we didn't have a format earlier. And we thought, why don't we, where should we introduce a new, a new format of our own making, which may not which may lack in probability with other open source capabilities and readers and ecosystem tooling support, etc. So we started, we decided on starting with just to park a as a base format for that we will use, but again park as in any column format is needs work on top to reflect updates and deletes in an efficient way and as well as, you know, a table format on top is also required. So it makes sense of the entire gamut of files that are present in a folder. Right. So, we choose to be simpler with at least the base format so that, you know, the unit of read could be easily readable with the several open source libraries that are present for, I think quite a few of you might have had to deal with here. So it's a column of format where a bunch of rows could be grouped together before coalescing into columnar chunks, which we call as row groups, the set of rows that are grouped together. And each column chunk has a bunch has a set of pages, and so on and so forth. The important two points that I want to highlight about this format itself and the one that, you know, that's very important for our context is that park a requires a friend schema before writing data, which means that us being a schema free we don't take a document data model. We do need to do schema inference so we did implement a schema inference component that kind of computes the union schema that's upcasting that there's a lot of that and converts that into a schema. So there are some gorgeous within compatible data, data type conversions and so on but parking that aside. It is that that's the that's the mechanism we use in order to write a schema from the data that was schema that's to begin with. One of the most important things with Jason in general is that it has capability source to support nested structures nested objects and so on and a number of levels of nesting and also a lot of these fields are optional in nature in Jason like the number of items can repeat zero or more times and so on and so forth. So in order to persist the nested levels and everything in a way that it is captured with full fidelity, such that when you reconstruct the Jason draw from the park a data, it needs to be identical the way we sent it earlier. Right and that involves quite some encoding and we again choose to do something that is more commonly done, which is terminal encoding this is a picture from the terminal encoding paper. And this is where we encode the definition repetition levels for optional in order to disambiguate whether option fields are present or not. There are so many times the elements have repeated and so on. And so this also has a wide adoption in the park AMR libraries and in this park ecosystem and so on so that's something that we adopted and, you know, our back and has done C++ so we kind of did it on top of the C++ library but the reason we adopted this encoding because quite a few readers did the decoding pretty well in the OSS ecosystem. And summing all of whatever we spoke in bits and pieces together, you know, coming back to the big picture a little bit here. So we have OTP transaction traffic that's coming to a cosmos to these shots. And each of these shots maintain the analytical storage footprint of their own in the in a decoupled storage and any other any analytical runtime let's park a T SQL, would just talk to the decoupled storage, contain the IOs and everything to just that medium and be able to serve analytical queries. So that's, that's kind of summing up what you know, a little bit on the big picture before I go further in depth into the lock structure story itself. What are the IND stands for? I'm going to come to that Andy in a bit. They are something called as inviridation files. I'll be deep diving into it. Alright, so, you know, going a bit deeper into the next levels of lock structure storage so let's say that we have rights and we have passed the first set of 100 records and return to the segment one, and the next set of 90 records to segment two, the next set of 80 records to segment three, and the next set of 90 records to segment four, etc. The important thing to note here is even though we said apparently storage, etc. They are not actually physically may collocated with each other the subsequent blocks. So, one dot Park is written to storage stamp one, two dot Park is written to storage stamp two, etc. And so they are, we did this deliberately in order to kind of spread the bandwidth because you know, we have it since we are targeting the storage medium for this. The bandwidth constraints might also be a little harsh and so we wanted to make sure that we are able to spread the rights across several such stamps to be within the bandwidth limits and also be able to, you know, keep the cost low and keep the policies for showing up in an article storage loop. So, which means that in order to logically stitch things together to form a logically consistent view of the database at any point in time. We would need something like a root segment that that's what we call it, but in database systems you would have, you know, heard of checkpoint files manifest files etc and different systems. So, every segment tidy has a corresponding physical uri. And so that's how they are tied and these physical uris one dot uri and two dot uri could actually be in different data centers but within the same region. And that enables us to be resilient to capacity constraints within any one particular data center, etc. And so on. And we also have something called this is a very smaller miniature view of the root segment is much more complicated stuff in it but in but this conveys the essence. So in terms of the statistics we have things like a total number of records, what are the number of invalid records at any one point and so on and so forth. And this, this is one number that would change right because if I had pretend some keys into segment one, and if I have replaced or deleted it in segment two and segment three. Then it means that the number of invalidations in segment one is going to continue to go up. If it becomes completely invalid our query run times will ignore the file. If it becomes more than 50% invalid or garbage collection compaction will pick it up, and so on and so forth. And the other column that we have here is the transaction ID, which is the world to be transaction ID with which these records were associated with and this enables us to provide the snapshot isolation marker for any queries that start off of the query run times. And we also have a property bag in it, which says something like a last durable segment ID last durable transaction etc. And these are the markers at which the query that starts right now holds on to and provides the version of the database that is consistent as of this particular transaction ID. All right, and so this also enables us to continue to persist new rights, continue to mutate root segment etc with new updates, though the query that started already will continue to provide an earlier version of the data. So now I'm going to go a bit deeper into the invalidation than the inb files that I've been showing to understand that better. There is a concept that that I've shown here which is log offset. Okay, so it's a logical offset. And it refers to a logical location in which this version of the key is currently present that is the latest version of the key is currently present in the decouple storage medium. And so it's a triplet what it says is one zero two zero one nine what it says is the the latest version of this key is currently present in segment one row group zero and at the row index of two zero one nine that's what it says. And the reason we maintain this is if there is a subsequent updates using replaces or deletes, we would be able to generate invalidations because for the old locations. Without having to look up into the decouple storage and these invalidations that we generate will be persisted in the inb files I have something to show for that in the next slide. And these invalidation offsets would be used by the query run times to push down as a filter predicate to consider these roses invalid, and so that it won't even materialize those rows. It will invalidate those rows before materialization. Okay, how do we actually generate these invalidations just an animation to show that we have a let's say we have a the this is the content in the old table as of this point, and we have two documents and with these as keys. And let's say the dark one is getting a new version that's getting a replace operation. We are replacing it with a version two. Okay, and so the current file that's accepting rights for the dark structure batch segmented right is three, and it's going to allocate a new offset in three, or the new version of the dark one. However, it's also going to add an invalidation for the current offset of the version one of the document one dark one. Right. And so, and once we allocate a new offset, it is written back and the transaction is submitted when with the new log offset. So you can imagine that there are some crash recovery scenarios that this opens up where the in memory rights are the in the three dot segment is not it persisted or a remote checkpointed. And so there is an operation log behind this document table that I mentioned here that I haven't necessarily shown in the ppt that's that serves as a right to head log for the analytical storage so that we can recover all the versions without loss even if there is a crash. And once a good number of rights have accumulated into three. I have shown just one here. It will be flushed to remote and the and this act of flushing things to remote doesn't make it immediately visible to any queries. It's only the next step of checkpointing that actually makes it visible in the root segment. So the three dot file is now if you just see the difference between the previous segment and now the difference is that three is now valid with a bunch of records in it hundred records. And not just that the ones in validation count went up by one. So from 10 to 11, right as part of committing this so it's either the causality and the effect would be both visible that is a new version of the document that got inverted it will be present in three as well as the automatic count on one will go up or neither would be so that's the atomicity for this. And in terms of the. What's the average size of like your pocket files in cosmos. So we try to.  And we try to achieve around 300 to 400 MB of pocket file at the minimum, so that you know query performance doesn't have to suffer due to too many years. So in terms of invalidation design itself. One thing we had in mind is that we don't want to do too many iOS in the in the frontline path that in order to keep the pressure low on the on the frontline transaction path even though we are not doing the remote on the front line transaction path we still want to keep the latency to reflect it in the remote analytical storage low. And so, some of the principle we started with was that one party file data file flash should also involve just one invalidation file flush right. But because one data file flush can actually accommodate invalidations for many segments in the past, including itself. But that shouldn't end up in us doing so many number of iOS into the number of segments attached in order to persist those invalidation so this is something so that we said in order to kind of keep the pressure on the relative system low. And this should such a design should also allow for snapshot isolation of queries because we are not changing any data, not doing any in place updates so anything that started off with the previous version should continue to work. And the queries and ingestion should be completely decoupled such that you know queries don't get blocked on some rights that are trying to checkpoint and and vice versa so that you know one doesn't hold on hold on hold the processing on the other. And to kind of accommodate these statements, the invalidation itself is a log stream. And the invalidation files though they have called out as INV. It's just semantically different but physically they are just pocket files themselves. And that allows us to kind of compress the it's nothing but a array of offsets along with some metadata about which transaction made it invalid. And it allows us to kind of since we sort it as well before putting it in. We allows us to, you know, do run length encoding and compress quite a bit of work because of pocket format. So, in terms of how we merge that if there are too many invalidation files that build up like how do we merge and kind of, you know, restrict the number of Ios that happens on the query side is, you know, we do a merge strategy and every time we merge we see the number of we see the file size that comes up and if it is beyond a size we put it to the next level, etc. Something similar to the log-structured merge strategies you would have seen before. And each of these files would have the invalidated offsets again sorted so it every merge allows us better gives us better compression. And this is what the invalidation file itself kind of looks like. So the first three columns are nothing but the log-offs at triplet. And the rest of the columns are some metadata about what made it, what is the timestamp or what is the transaction that made it invalid. And also the final thing is the operation type that made it invalid. It's used in some pretty neat features but not so important. Yeah, so that's the invalidation format itself. So summing it all up again, right, putting the bits and pieces together once again.  And we write that to the table and then all of this from this point onwards happens a bit asynchronously without blocking the transactions. And that would involve schema inference and then parquet invalidation generation and flushing the files and then checkpointing it to make it possible. So now talking about the freshness optimizations, right, the one Andy alluded to earlier, like how do we make sure that we are able to give a good freshness between the time it takes to process something in the transaction store and how long does it take to reflect in the analytical store. And the more we try to compress that in order to provide better latencies. It comes at the cost of generating smaller parquet files because enough number of writes haven't accumulated to create a larger file and so on. So what we do is we kind of make the last segment has have several incremental flushes with several smaller files, but we don't finalize the segment until we come back and redo it with the larger files. So we do intermediate merges for the last segment. If there are too many trickling rights and the right rate is not high enough, then this will take care that the number of files we create for the lifetime isn't isn't going to be too many. So we do quite a bit of intermediate merges for the last segment until we have accumulated enough sizes that one final flush of the last segment again would create a good size and then we move on to the next segment.  And so whenever threshold drops down below a particular threshold of validity in a particular segment that is more than 50% 40% invalid etc. Then we kind of pick up those segments for relocation. And this relocation, it means that we are relocating the valid data in it so that the entire segment can be called invalid. And, and we kind of interleave it with the existing rights as well. And so it lands in the data block while invalidating the old locations for itself so that that's one level of GCB that we do. There's another compaction that we do that is kind of we do with along with the different partitioning strategy that I'll talk about just touch upon it a little bit in the last slide. So this that okay so here, I think that kind of some summarizes in terms of the local storage one unit of storage design now the rest of the slides are going to talk about how it had this big couple store analytical storage is going to deal with the rest of the high availability and the elasticity features that Cosmos DB provides. So, Cosmos DB today provides a geode application, we call it global distribution, your application, etc. But it enables customers to have data in each regions of their choice. And we have an internal state machine for your application just like we do. We have an internal state machine for local high availability within a region, and the geode application we currently do to every region that the customer configures. And so there is a copy of data in every region that the customer wants. So in even for the analytical storage that we have, we wanted to retain that that even the analytical storage should be in every region that the customer has the pre storage provisioned in, and so that they they don't see any semantic differences, and if they are able to if they compare their own DB views and analytical views on the same region it should all add up and so on. And any kind of geo failure was where we switch the primary status leader status from one region to another region. And that should kind of transfer the status for the ownership of analytical storage as well. And one of the core things that we did in this particular thing is that we used to do geological replication and we continue to do that. But for let's say we have a one petabyte of some data that's present in one region for a customer and that customer chooses to add another region. And at that time, doing, you know, a record level replication and, you know, reconstructing parking on the other side etc is pretty expensive in terms of CPU memory so we do physical copy of files and we interleaved with the logical replication of the study state data etc in order to see the new region with the existing copy. And in terms of when configuring georeplication all the records and will have the same log offset in each region though they will be physically pointing to a different physical file in each of these regions because because we don't share data across regions. So those are some of the important aspects so as I you know this picture kind of summarizes what I spoke about where we do physical copy for the files while interleaving it with logical replication all of these are resumable to make sure that any of the primary failures that we have in the database etc is able to resume the copies in the. Maybe if it's more like I should ask at the beginning. This is something that the customer always gets or they have to come on and say they have to turn this on. Yeah, they have to turn this on it's because it's it's also cost for them to have a present so it's it's their decision. But I mean, sorry, I know georeplication to turn up but anything that's generating these parquet files. Do you have to turn this feature on or cosmos is always doing this for people. No, that also they have to turn on because they do have a larger presence in, you know, larger involvement in the analytical query runtime part of the story. So that's why it's it's again their choice to have the copy of data more optimal for this. Thanks. So partition splits right so cosmos TV is elasticity story such that we are able to kind of scale to any amount of throughput and storage by scaling out. And this happens seamlessly bear customers that they've got workloads continue to be online while we do the partition splits. And when we do the partition splits one parent partition gives birth to two or more children it's usually two right now. And when the parent partition dies after that split process is completed, and that kind of device a logical key range of data between the two children, right. And since it's a since it's an activity that might impact if availability if it goes on for too long because after a point, the nodes capacity of the parent will run out so we were there has to be a right pause on the system of the split state too long. And so we so well, what do we do with the analytical storage when portions puts happen right because each of the namespace in the decoupled storage was owned by a partition. Now it's going to die. But it can the data die probably not because it has some archival data that that that is present only there. So it has to be continued to be referred as a valid data in the container. Problems in that space and so we, you know, in order to keep the splits continue to happen at a good rate, we ship only the active snapshot and OTP, you know, when doing the splits. Also, so the parent partition namespace will retain the archive storage, and we continue to keep the partitions that mutation history, in order to make sure that we are able to reach all the archive storage through the current children and so there will be some kind of ownership transfers etc. In order to, in order for the children to be able to reach out to the parent namespace. So, you know, pictorically, you can think of the, you know, the partition and the top kind of getting split into two, and the, we are not copying the physical files of the parent because the data is going to get divided between two partitions so we cannot just copy it access. All right. So we instead be replicating we kind of copy only the local data that is present in the world to be sure and let the children generate new files, honoring the log offsets that were there in the parent and not just that while, while a parent dies, like for example in this, in this depiction P0 dies by giving birth to P1 and P2 and P1 dies giving birth to P3, P4 and P5. So when P0 dies, it actually transfers ownership of its archive storage to P1 and when P1 dies it transfers its own archive storage to one of the children that is P3. Sorry, it is P5 and it transfers its parent P0's archive storage ownership to P3 and that way, all the child live partitions of a container at any one point has a reference, a referential ownership to one of the parents, at most one of the parents in the ancestry and that way we'll be able to, in the query runtime will be able to refer to the archive storage of the entire container. And when somebody adds a new region at this point in time to complicate things, these partitions which own the archive storage also on copying the its own as well as its archive storage to the other region as well in order to continue to provide the same, you know guarantees that we have spoken about so far. You know, I'm not covering a few topics related to multi right, because we made it a bit too simple on that and cluster migrations and so on, but that kind of summarizes the, you know, the technical deep dives that I was trying to go into. And coming stepping back right why did we do all this. So, this architecture is something that we are using it for other features as well like point in time restore and a few others outside of headstab as well. But with the with the headstab scenario. And we were able to, you know, kind of compare the total cost of ownership between someone adopting our new solution versus doing the traditional way of doing ideal pipelines to traditional, you know, analytics system and so on. So, not going to go into every detail of this but this is the kind of, you know, the total TC woven that we have in terms of comparing ourselves to a to a system that that is popular. So, I'll just give it a minute on the slide. Probably less than that. What is the bronze layer. Okay, so that's kind of the Delta lakes medallion architecture, where they all the addition comes into the kind of the bronze layer that's the recommendation architecture and all the new addition, the raw layer that comes into the bronze layer, and where a lot of the deal happens and, and then it needs to be merged into existing data on the same keys etc that's happens in the silver layer and so on. So there is, you know, complicated stories there and, and then kind of the processing power to do it and the end to end latency of reflecting the updates just go up. Like, the data versus like, there's like a Delta Lake term. Correct. Sorry. Yeah. I think you've gone. Okay, cool. So, in terms of the customer feedback just carrying a couple that came to mind yesterday and so these are customers who have really used our solution to their cost and cost advantage and they're able to run it with great cost as well. In terms of their solution. So, what do we do about the roadmap, right. So, giving up a different partition key for the analytical storage is something that has come up quite a bit with our customer conversations and that's something in here. We have previewed right now, and that's an important win as well. And we do have something else in progress which is to add a lot of metadata indexes in order to allow queries to print out a lot of files based on the properties and the filter predicates and the projections. And we do want to provide customers a way to provide a reduced column set for persistence and analytical storage than do it all that we have right now. And then expose as of timestamp query since our we have all the versions we should be able to travel back in time so you know we do want to expose the query and complete the dark. These are just some things that come in mind in terms of the customer facing differentiated offerings but in terms of the internal architecture we have quite quite a few improvements lined up that's going to benefit even other features on top of this as well. Yeah, but like, I mean, I realized you have an app in the future like what percentage of the columns in the road store to people actually want to clue it in the local storage. What percentage of customers like you know this like this reduced column saying basically saying only shut out like these subset of the columns on the table. My question is like, do you have an idea of like roughly what percentage of the columns people would want to only include like can you see this for the analytics of like of the queries are actually running that like most queries only touch 10% of the columns on the table 20% or so forth. Got it. Yeah, I don't have that statistics right now with me, but it's something that we can definitely observe in our query and time telemetry and and I'm guessing that it's going to come down to around 40 to 50% that's pretty much the set of columns they are kind of interested in because you know, since we are in the Jason Jason document model. A part of the schema is more like fixed because that's like bread and butter for that application semantics and a part of the schema is like more evolving and very sparse. So it's likely the ones that are going to be there for every rule that they are likely going to be able to do some very meaningful analytics on top so I do believe it's going to be around that percentage mark. So in terms of the learnings, you know, we have quite a few but synthesizing the top two. So in terms of, you know, if we if you see this architecture where the world to be is also responsible for the analytical storage which is in a decoupled medium and so on. There is there is an availability dependency that you know, because we need to do recovery, etc. And you know, as we want to increase the amount of party file size the amount of recovery we have to do also increases when the world to be primary fails over etc. All right, so we innovative quite a bit here in order to reduce the impact of this recovery on the oil to be stores availability like a primary swap happens, due to load balancing or note failures we wanted to happen fast and not be stuck behind recovery of this remote storage and so on. So we did quite a bit of, you know, features in the space in terms of asynchronous recovery and so on, in order to kind of keep the order of constant recovery time due to analytical storage. And we also did certain things like, you know, treat the incremental file flashes as as if it's durable for the purpose of recovery and so on in order to continue to optimize on that. And another thing in general is that I created a development as much as it's good to go to market and gather customer traction. It is, it is quite challenging in terms of in a, especially in a past service where we own the data, as well as on the upgrade stories on the servicing etc. So we have to, you know, kind of continue to support the customers that are in all the different formats in different iterations of our offering. And so that's quite a bit of effort that goes in planning those updates in a non destructive way and in an impactful way so that's something that we continue to learn from and optimize, but it does take quite a bit of time in this. And last but not least, I'm almost at the end of team is hiring a lot. This is just one of the fish to fry. We have a lot more bigger fishes to fry in terms of running a global distributed database service, a lot of late reduction problems in doing geo failures, etc. So, and we are also embarking on distributed SQL etc. So, you know, a lot of great problems to solve. So happy to hear from a lot of this bright minds at CMU and have them guide us where we should take and how we should plan for the next All right, thank you. Thanks for your time. I'm open to questions. I will follow that everyone else. Most people here was called actually are from Carnegie Mellon, so there's other awesome people at CMU, although we are awesome. Okay, so I open the floor to the audience if anybody has any questions feel free to unmute yourself and fire away at the hurry. Otherwise, I will ask my questions. You're all suffers. So, this is interesting that you guys are making heavy use of parquet for almost everything.  You know, what aspects do you find in parquet frustrating or limiting, and you would actually want to prove them. And, you know, if you could change to parquet spec, what would you change. And then, you know, is the, you know, do you think it can have a better compression ratio, or do you think it have better computational efficiency for processing the files. But what's what's the main limitation you see with parquet. So I would say to one is one is the fact that the support for schema evolution. Right. Like, for example, data, because in our Jason free Jason's schema free model, a column can be an integer, though it's it's rare, and it doesn't happen that often in a, you know, carefully designed workload. It can be integer on one day, but it can be a string on another day. And in those cases, color parquet doesn't kind of support as far as I know, doesn't support the same column name being two different data types, etc. And that is quite important for fidelity because, you know, one needs to because because we are trying to push the data cleaning aspect to customers. You know, the query layer, because they will be able to better make sense of why it would have happened and so on. So it's important to even reflect that correctly. And to that to the customer side so that they are able to better process it but right now we are not able to reflect it well unless we do we have to provide another option called full fidelity schema inference for customers to opt in. But that would make it pretty hard to write queries because you would have to say column a underscore and select column a underscore and from table and select column a underscore string from table and whatnot right so it's not very easy to design that so that's one thing. And the second thing would be the some level of support to mutate a file once it's written right. So, what would it take to add, maybe add some metadata and some property back, etc. It's not, it's like, it's like, you know, once it is written it's, it's that's it and that's kind of making it a little harder. But those are the two things that I would say. The big problem like the patchy hoody guys have or one house right that like you have to maintain the delta for the updates and then merge it later. Right. Yeah. All right, question for virtual. Yeah, two simple quick quick questions number one is the grooming from oil to be into your lab. What is the time interval you do that is that and when how do you tune it is it in seconds minutes. It's a we try to do that every 30 seconds now that should be that should be the case so. Yeah, we try to force a flush with whatever records have come in in the last 30 seconds and that would generate an incremental file. Well, and when a good amount of records have accumulated for this current segment we finalize into one larger file later. And second part is, do you support time travel queries. Currently, our storage is able to support it, but we haven't worked on exposing that in the query layer yet. So that that's that query surface area has to be hooked up. Cool. Thank you very nice work. Thanks. Anybody else. When did, when did Cosmos TV or when did you get that proper sequel? Is it recent or is it a few years ago? T SQL you mean. Yeah. That was only made possible through this, because we have our own version of SQL querying. Not exactly T SQL that is, but it is based on ANSI SQL. So it does provide a good amount of feature coverage on but that is to query our oil to be data. Okay. I guess the question is that I think my question is sort of more about JSON stuff or JSON data, of course, you guys see a lot of it. So you need to characterize it.  Like you mentioned that there's like the core data that you have certain keys or attributes in documents and then sort of random stuff. And then he said there's sometimes people make mistakes or this random data where it should be 99% of the time it's integer, but someone throws a string in there. I guess I mean, how common do you see this in those applications? It's not common in production quality workloads. It is more, you know, it is more like development time workloads as well as, yeah, so it's not as common as we thought.