you could get a view of the data that never actually existed at any point [music] in time. We had one that was days actually to like actually generate the backup. It takes days to do a daily backup. You're in trouble. It was a little embarrassing when that happened. Um we always have a holy war in term whether to call it compaction or retention. Uh there is a concept called a tombstone which kind of a morbid term but it's like the term for when you like delete it from it's like rip. And particularly when agents are reading these, if the abstraction is sound, it's unlikely to have a foot gun. Yeah, it's all about. Okay, Nepan, thank you for joining me today. You are one of the OG engineers here at Convex. You've been here since, I think, the very beginning. Is that right? Uh 4 and a half years. Four and a half years. Yeah. So 2021. Very close to the very beginning. And you've had your hand in pretty much every system. I think that Convex kind of everything. It's part It's part of the It comes with a territory when you're here for a while kind of end up having to work on everything. And so when I asked you today what what could we talk about what could you do an engineer deep dive on you really surprised me when you said backups cuz I was like surely backups are pretty simple aren't they? But turns out no they're not they've got like a lot of depth to them and so I think it'd be fun to go through some of the details to get on the whiteboard. Yeah I think it is one of those things that is like it I mean this type of problem is always really attractive to me. It's like ones that seem like they're easy and then when you dig into them, there's more complexity than you think. Um, I also think it's one of those problems that it's easy to solve in a little bit unound, a little bit gross way, but to get like a good abstraction, a good sound abstraction, which is something like Convex always values really highly. It takes a lot of like attention to get it right. Cool. All right. So, um, where do you think it's the best place to start with this? I thought maybe let's draw up a table of data and then look at maybe what it would look like to to back up a table of data like naively or something. Yeah. Yeah. Cool. Have a look. All right. So, I was imagining we could look at like a users table. Uh and I'm just going to keep it real simple. We're going to put two columns in this table. We'll have your ID and let's say name. So maybe ID 1 npun ID uh these are you like these are random number ids when they're in convex but I'm just going to number them 1 2 3 to keep it simple for the diagram. So two is Mike and say you got a bunch of them. We'll look at all the ids and the kind of naive idea is that we could iterate over all the IDs and then return that. So you might have an API endpoint that says backup and it would just return all of the data in the table. maybe a big JSON out something like that. Mhm. First place where this gets challenging what you have a lot of data and doesn't all fit in one page. So then one of the things that simple thing you want to do is pageionate this. So say just for sake of diagram here we'll pageionate it so that you take four at a time go a little further. So here you've got page one, page two and page three. And so you would call this API endpoint three times and the first time you get page one, the second time you get page two, third time you [snorts] get page three. Uh and then you would need a cursor. A cursor is this word for when you're pageionating where to cut it off. So between the first and second page, you put your cursor here. So what does a cursor look like? Like what does this API and this cursor look like? So now we have an API like we have an API where the input is a cursor and the output is an array of rows something like that. Yeah. And so then this just goes in a for loop. You're continually incrementing the cursor to get the next page as you go through. Yeah. Yeah. So far so good. The way I've drawn it, this cursor might look like something like four. So we've gone up to four and that's where the cursor sits. So this is pretty simple. Works pretty well. Now where does this go wrong? One thing we really want to think about is that we are backing up tables but those tables are being modified. And if you have like a very popular project like if you have a very successful project these things are being modified kind of continuously like you have like many requests happening per second on these popular projects. And on even on these it's very important like these are like the most important projects to be backing up. So on these kinds of projects uh you want to be able to pageionate over all the data and get all of the data out of the project while it's being updated. Now this is a little bit tricky because after you read the first page maybe some data changes on the first page. Maybe between when you read the first and second page some let's say some data changes to number two and the data changes to number seven. say they update their name or they update their address. So if we just do this naive algorithm, we're going to get all of this data from 1 to 10 and we're going to get names and let's say we have an address, something that gets modified. We're going to get all of this data, but we're not going to get the data at a consistent snapshot. What this means is sort of you'll get like my name and my address at a point in the past and then if someone changes their name or they change their address, we will get their name and address at the point in time that this cursor pageionation got to them. And if you have a very large data set, say it takes an hour to get over all of the data. You're not getting everyone's name and address at the same point in time. You're getting the early people earlier in the hour and the late people late in the hour. Now maybe that's good enough and maybe you're happy with that and kind of an early version of this algorithm or a very naive version of this algorithm might just tolerate that and be okay with you're you're getting some set of data over that hour. But what I would call is I would call this an unound abstraction. U it is an abstraction that has some gotchas in it that are like a little bit difficult to explain. like it kind of breaks your mental model a little bit. Like you could get a view of the data that never actually existed at any point in time. Like perhaps you added a name and then later you removed a name and you'll see a view of the data that has both the names at the same time. Yep. Makes sense. My brain is now going ahead to I think what the solution is this is like the write ahead log like you have a record of every change effectively. Mhm. But I'll let you this is bingo. Right. So in like this view that we're seeing here is the representation that like we think of when we look at the database as an abstraction like when we're imagining like what we care about. We care about all the names and the address. This is what we see in the convex dashboard. This is what we see in the convex dashboard. This is what we mentally conceptualize. This is extremely rarely what a database stores underneath. it usually is storing a journal of updates, which has tons of benefits. Um, I'll draw up what it actually ends up showing. Okay, so what we're going to do is we're going to go over a journal or a log. And that is sort of the data structure that most databases use. At Convex, we use something called documents log. So, we have a documents table and it's sort of a low-level table concept like this is underneath the users table and yeah, I think I did a video on like how convex works and I cracked open the SQL light file if you run convex locally. Bingo. So, you would see this if you cracked open the secret light file. Yeah, there's probably four tables in that SQL light file and one of them is the documents table and it's got all of the data for all the tables. And this is effectively the write ahead log that you would get in Postgress. That's a convex version of that is this. Yeah, exactly. So this is the log of changes and in this you'll see roughly like what ID is being changed and what the change is and so we might have we write all the data to the table with some let's say there adds or inserts uh uh update does it have updates? Yeah, say we update two we'll be able to update will update the data in two and yes you can also delete the data. A six is that this is a two. Oh. Oh, I see. Update. Update ID. Okay. Okay. This one got updated later here. Yeah. But presumably there is an ID for each of one of these changes as well. There's also an ID for each of one of these changes. So this is this ID is document is the document ID here. And so this is allowing us to have a log that goes up over time. And so really the kind of primary thing in this table is what we call a database timestamp. And the database timestamp is the column that will increment here. So that is the ID of the write ahead lock the the change. Yeah. ID. Yeah. And it uses something that we I believe it's called a a hybrid logical clock which is a database term for a time stamp that is like a wall clock time stamp with the additional guarantee that time will always go up. kind sounds like a funny guarantee to have to have but make sure that the time stamp is monotonically increasing going is that nanconds milliseconds what was if you get two on the same I believe it is a nancond and what happens is that if you write a row to this table it is guaranteed to be larger than the previous row so it will take the max of the previous row plus one and the current time okay with you okay makes sense it's a hybrid logical clock I think you can find on Wikipedia somewhere yeah well we threatened the grizzly details so here it is yeah uh Cool. So, uh, with a document log like this, now there's sort of a very different kind of natural way that we can think about pageionating how to get all the data that comes out of the database, and that is to pageionate over the document log. So, here we are pageionating over what we call the ID space. So, we're going over ids. This is great when the data is not changing, but then if the data is changing, iterating over the document log lets you uh catch up, sometimes called fast forward, but it allows you to catch up on data that has happened while you were iterating. And so you can imagine pageionating page one, page two, page three over the document log. Yeah. Just like we did here. Yeah. Yeah. Now this has its own ups and downs. One very natural one is that uh like I mean the natural benefit is that it's very easy to catch up on the data that happens very easy to stream data as it changes. And a clear downside you get the same row more than once. You're going to get an old version of row two and then later you'll get a new version of row two. [clears throat] Uh but you're guaranteed to be consistent because every point in this log is like representative of a consistent point in time. It is a like point in time where the data was actually in this state at that point in time. Um so you don't get these unound inconsistent views of the database. But this by itself just iterating over the document log also has its inefficiency. Um in order to get a correct current view of the database you have to literally iterate over all of the documents since the beginning of time. Yep. So if you're running like a like a project that is successful and has been around for a couple years like this is infeasible. So what do we do? What do we do? Yeah. What do we do? Um, for my event sourcing days, my brain goes to snapshots of some sort. So, you have a combination of this and this together. So, you take a snapshot at a certain point in time and then you play from there [snorts] onwards to get to the current time. Bingo. Yeah. So the next kind of iteration of this is to have what we call a like sometimes we call it compaction or snapshot but basically we'll like pick a point in this database like here uh and we'll try to draw a snapshot at this point and what this means is that we should if we like the abstraction we're trying to form is that we want to be able to read this database at time I'm going to give these time stamps 100 let's do 101 102 103 104. We're saying at time 104, we want to read the database. At time 104, we want to materialize this view of the database, this users view, this ID. Um, we could think of it as iterating over the ID index. We want to have this view of the table at that time stamp. Um, and so, uh, that is an abstraction that databases like to be able to provide, which is to iterate over something at a snapshot that is not necessarily in the present. And this is useful not just for backup and export. This is useful for like like I said event sourcing just for materializing the table at this point. In fact, is this how comics just works in general anyway? Effectively all you are seeing is just like a snapshot plus a little bit of a replay. Yeah. So say if you're running a query or a mutation inside of convex when your query starts we are run we pick a time stamp and we run your query at that time stamp and then every read that you get is guaranteed to be at that time stamp and then if it's a mutation we material we create a right set and then we check to see if that right set is still committable even though some other stuff has happened since your mutation began. The only thing that makes this possible is that queries and mutations are guaranteed to be short. I believe there's like a 1second upper bound limit on queries and mutations. Uh, and there's like a new primitive that came out recently that's uh stale reads. So, it lets you run stale queries at even older snapshots, but those ones are inherently not able to mutate. Makes sense. So, are there other issues with this that we have to worry about particularly within the context of backup and like export? Yeah. Yeah. So this we kind of didn't dig into how a snapshot is creatable. Um like what does it take to create a snapshot? If we can do this abstraction perfectly, it's kind of is very feasible to do this. We will do pages of iteration here and then we will do pages of iteration after the snapshot to catch up. And so maybe we got boom boom boom and then this page and then that will be your export. Let's think about what it takes to implement this snapshot. This snapshot if you think about it is a compression of all of the data from the beginning of time like infinitely long ago up until here or the you know the beginning of your project and that is a materialization of this ID space at this at the snapshot. How where do you pick to put these snapshots and how long do you keep them around? Um, keeping a snapshot is somewhat inherently an expensive operation. And so, databases keep like maybe one or two or like there's like a handful of snapshots that are kept at various relatively recent points. Materializing a view that's like old in a database is actually somewhat complicated because we keep the document log, but to materialize a view at some time stamp way in the past if you don't already have the snapshot available at that time in the past is actually pretty difficult. You either have to read all the data or you have to do something that is akin to starting at the snapshot and working backwards. Like both of those are like viable ways to do it and that can be kind of expensive for each piece of data that you have to work backwards from you have to do some operations on the document log to work backwards. So I believe what this means here like logically what this means is that uh as long as you can create a backup fast enough that it is within the recent amount of time that you have snapshots available this works great. So if your backup takes an hour and you keep snapshots for like an hour, it's going to go great. Started becoming a problem. We had people with a really large amount of data and it took hours. We had one that was days actually to like actually generate the backup. Yeah. And the algorithm required to iterate over the ID space kind of breaks down if you don't have the snapshots cuz then you have to start doing all these walks backwards. Supposed to be a daily backup as well. It takes days to do a daily backup. You're in trouble. It was a little embarrassing when that happened. So that really caused us to have to go to the go to the drawing board and like make a better algorithm for this. Oh, cool. All right. Well, that leads very naturally into what is the better algorithm to do this then? Yeah. So to to to dig into this a little bit, we have what we call we call it retention. Sometimes it's called compaction. Um we always have a holy war in terms whether to call it compaction or retention. But it's the process by which we clean up old data that is in the document log and we compact it so that we don't retain data that goes forever backwards. For the document log uh uh sorry non- latest data like we retain the data that's in your database but old versions of documents we retain up to a certain point. Um and for indexing that retention is like I want to say it's like four minutes. It's like a few minutes is the order of magnitude. Um, and so if you want to index over something in the table, you can only index over a snapshot of the table that was within the last 4 minutes. So you can run your backup within the last 4 minutes, things go great. If it takes longer than that, things go okay. It's just every time there's something that's a little bit older than that, you have to walk backwards to kind of catch up. And that essentially becomes quadratic with how much data you're doing because if it takes longer to do each piece of data that you have to go backwards, you have to kind of walk backwards in this table to figure out what it looked like back at that snapshot. Yep. Yep. That is how the snapshotting works. uh what we have to do here to get this to work well like kind of the key insight is that we want to be able to work back and forth between these so that we are making we are starting our ID iteration at a snapshot that we have and then we're continuing to iterate over the IDs while that snapshot is fresh but before those four minutes are up we got to alternate to catching up on the document log and so the key insight is that instead of just iterating over the users table which has its failure modes. Just iterating over the documents table which has failure modes. Iterating over users and then catching up over documents which has its failure modes. We have to do this dance this like kind of elegant dance where you go back and forth between iterating over the users and documents so that you keep your snapshot up to date and then you keep going on the users table. And is this a generic algorithm or do you have to tweak this per customer depending upon because each customer has got a very different kind of workload? You know, some customers might have like one table that is extremely heavy and they're writing a lot to it and like other tables not so much and then some customers might have a lot of tables that are doing a little bit to like continually. So do you have to tweak that algorithm for different customers? the algorithm is the same for different customers, but sort of the the way that the algorithm chooses how to switch back and forth, you'll the customers will see their data come out in different orders. Um, but this allows us to produce snapshots quickly without doing redundant work where we're going over the documents table multiple times quickly, efficiently, and give you like a sound view of your data at a point in time. And one actually kind of cool thing too is that if you pick a snapshot and take a backup and your backup took two hours to make, you have you now have a backup of your data from two hours ago. Great. But if you're catching up on the document log continuously, it kind of doesn't matter how long your backup took. When your backup is done, you have a backup of your data right now. And I think that also helps a lot too. It means you're getting like a very fresh version of your data. It does mean when you start your backup, you don't know the time stamp your data will be consistent at yet because it's in the future. That's okay. Is it? Yeah, [laughter] in fact it's it's actually kind of ideal. Like it's like you will find out at the end of your backup when it finishes when your data is consistent and it'll be like highly up todate at that point. Yeah. One thing that you mentioned back there I wanted to go back to is that you said that we delete some of these old things. Mhm. Like which what things do we delete then? So do you like say okay we added row two of the users table Mike here here and then we deleted it like here. So does that mean that you can get rid of this one and get rid of this one because Mike no longer ever exists in the current snapshot at this time? Can is that like the kind of things you have to do? That's the kind of things. Um uh there is a concept called a tombstone which is like the final that's like it's kind of a morbid term but it's like the term for when you like delete it's like rip. Yeah. Uh but the the there's a tombstone and so for each ID we keep the last row that is relevant for that ID and then the historical rows we will keep I think for documents log we keep it for a week and for the indexes we keep it for this four interesting so once it's gone it's gone after a week I guess you have to do that you know um otherwise this could get very very large this will get very large yeah you can take backups yeah do you know whether postgress and or like MySQL do this as well? Do they they write ahead log? Do they like All of them have a compaction process or a retention process. Call it compaction if you think about the deleting. Call it retention if you're thinking about what you're keeping. But the process is to it is to reduce the overall size. You're basically taking the document log and you're uh compressing it into like you're removing stuff that is historical versions of rows that have been since updated. Okay. So, Nan, is there anything else that we should probably show people here about this particular setup here? Yeah, let's draw let's draw a picture of what it looks like to go back and forth. I like pictures. All right, we'll we'll fit it right here. So, maybe I'll grab a different color. So, I'm going to draw the this as a two-dimensional diagram. So, we've got IDs and time stamps. Um, that's not visible. Time stamps. the the kind of way that we want to think about it is like we're going to iterate over the ID space as much as we can. And so this is a page page one and then page two like this. And so if we did our original algorithm, it would just go straight across here at this time stamp. And this algorithm will go straight down. And so what we're going to do instead is we're going to alternate back and forth. So we go across the ID space pretty aggressively, as much as we can without hitting this 4minute limit. Um, our data sync iterator algorithm, I think, does this for 30 seconds. These knobs are all tunable, but you can see them. And then it'll try to catch up in the time stamp direction. So, your third page is one of these pages and it goes kind of down. So, if we draw some arrows, it's going across, it's going across, it's going down. And so, just to be clear, these are the rows. So, this is like Nepun, Mike, Jamie, whatever. This each of those is like some version. It's like it's the version of Nepun at this time stamp. Okay. And what's three then? Three would be three would be all of the updates that happened between these two time steps. So maybe it's Mike changed their address. Okay, Jamie showed up. Okay, and added to the table. And then once we've caught fully up, then we can go back to iterating over ID space. So then this would be page four going across, page five going across, and then page six might look like this. And it's catching up on all of the data going down. Maybe page seven looks like this. And so this is getting bigger. This block is getting bigger because we're adding more people to our users table. Yep. And there there people you have to catch up on the updates that they do now. And then eventually you'll be able to finish this up. So say you go 8 9 and then say that's the end. So then the way that you get your data will be kind of alternating between these two forms. Um these ones will look like ads. These ones might have updates, changes, modifications. um to the existing data you've seen so far. One of the kind of interesting things is that this algorithm like the original algorithm just gave you all the data once. This algorithm doesn't give you all the data once. It might give it more than once. Um and it doesn't even necessarily give it in timestamp order. It kind of gives it in this mish mash order, but the thing is that you're guaranteed that when you get to the end, you're going to [snorts] have a consistent view at the current time. This is a little bit trickier of an abstraction to describe, but it is sound. And I think the simplest but sound abstraction is the one we want to create. Um, that allows us to make an abstraction that we can use as a base building block to build more complicated features. And particularly when agents are reading these, if the abstraction is sound, it's unlikely to have a foot gun where it'll like use it in cop it up. Yeah. Can't can't mess it up. Yeah. Um, and you know, agents can figure this out. And so I think that's good. And agents can figure it out without having to read the source. They don't have to understand how this works behind the scenes. So I think that is like really like the benefit of having a very good sound abstraction. Yeah, that's what comics is all about. Great. Um, is there anything else that we should talk about here before we move on to potential future future backup work and exciting features that we're planning? Happy with this. I love this diagram. when this came together, it was very like when I when this first went up on a whiteboard and a few of me and my co-workers were looking at it, it was it's a good moment. And just just to be clear as well, can I just grab a pen? Like at some point like here, so say Mike got removed, so there might actually be a hole in this eventually. Is that right? Yeah, that's right. Yeah. So it gets like fragmented. Yeah. and and your it your pageionated iterator might see Mike and then see Mike get removed but you actually see that kind of in real time as it's happening and so that that is like kind of a benefit. All right. So should we talk about some future backup and features that we've got planned. Yeah. So uh this algorithm came together maybe in the last month and a couple months. Oh it's quite new. Yeah it's quite new. And so this powers an endpoint that we have called the data sync iterator. It's slashdata slashs sync. So this is an API endpoint that you can use. I think it might be like /v1 API data sync. And so why didn't we have that before? And why do we need it now? Yeah. So we've had the backup feature and the backup feature was previously I want to say it was using the algorithm where it goes over all these pages and then this page. Okay. Uh and it was okay but it got bad if people have a lot of data. And so for a bit we thought about let's glue together something. so that we can fix it for these larger customers. But we really wanted to step back and make a good solid building block. And having a publicly available API is a really good way to force you to have a very good sound building block. Yeah. Uh because this allows us to unblock kind of making more grand features. Uh and one of the big ones is OLAP, which I know a lot of people have been excited about and thinking about how to how to work with. Can you just explain what OLAP means or what it is? Yeah. So there are kind of two major types of databases. Uh these are kind of historical terms. They're like kind of academic terms. Uh but there are OLTP databases and OLAP databases. Uh and they stand for online transaction processing. I hope I get this right. I think that's right. Uh and uh online analytics processing. Again, I hope I got this right. This going from the dome. Yeah, I'm just thinking now like shouldn't it be like OAP and OTP? Because like online is one word. I guess in 197 something when they came up with this, it was two words. Yeah. Okay. Um, it does that. This one doesn't roll off the tongue, but this one kind of rolls off the tongue. One of those like AOL like Yeah. Anyway, carry on. Um, yeah. So, online transaction processing, think like banks, like this is what it was meant to describe. This is like a transaction in the very like literal bank sense. It's uh I you deduct some money from my account and add it to your account and we don't end up in a state where the money disappears. Convex mutation. It's a mutation. It's like all of this happens together at the same time. uh the way that you organize a database to make transactions possible tends to be somewhere row oriented and it's very good at point fixes like updating a couple rows at the same time and guaranteeing they happen together. OLAP databases are about analytics and they are about large data sets, large data processing. Yeah. And they tend to be column oriented as in when you have columns of data all of the data in a column is stored near each other. So it makes it very easy to iterate over all of the names or all of the addresses. So this is referring to how it's stored on disk and and that's important because the way that you read it back and the way that CPUs read it back. So you get cache hits and things like that. So if you store it in columns is you're expecting to read back a lot of data in the columner form as opposed to reading a row at a time. Yeah. And it's called online analytics processing because typically you want to do this for analytics. You want to go look at all of the addresses and then break them down in certain ways or you want to look at all of the all of the transactions. Like if you have a transaction at your bank and you're trying to mutate them, you'd want this type of database. If you're trying to look at all the transactions that happened in the last two days to figure out where you're spending your money, you'd want to organize this way. Okay. Now, one database can't store it in two ways. So, a very common architecture is that you would replicate data, stale data from an OOLTP data source to an OLAP one. So, convex itself, OOLTP. Uh often when you think of OLAP you might think of uh data bricks, snowflake, click house, duck db like these are the sort of like technologies you think of but this actually goes back a long way like Hadoop is like this I want to say like like a lot of the like core technology at Google is like oriented around this like like the original Google. So con at convex we wanted to enable this problem to be solved where people want to do analytics on large amounts of data and we've had a solution before which is to do a streaming export through our fiverr connector and connect it to an OLAP database. We wanted to like really bring this to be like a first like a first principles firsthand citizen of the convex environment because that does work and it and it works great and it's probably what big enterprise customers want. But like little old me on my $20 convex plan don't necessarily really want to sign up for another third party service for like my mediumsiz business that I have, you know. Yeah. And I think ask having our favorite third party service provider and then having uh connecting people to it like we love fiverr like we actually do recommend it as like the route that you choose to do to uh OLAP it when you have like at scale data but I think it can't be the only option like we wanted to make like a good abstraction so that you can have different ways of getting data out of convex um both for backups but also for analytics processing for streaming it to other services um it can be very this kind of trend like ability. This is where the data sync iterator comes in handy. Data or sorry the data sync endpoint. Okay. Yeah. So now we're connecting it. So how does the data sync apply to this? So data sync gets put into a completely different convex service. Yeah. So the data sync endpoint allows you to stream data out of convex as it's being written. And so you can iterate over your data in this like backup sense. And then if you keep your cursor you can keep it up to date. Yeah. So for the rest of like eternity, you can do these uh timestamp catchup fast forward pages and just keep your thing up to date. Uh and you can keep your destination up to date with the data from the source. So convex would be the OLTP database here and you can export data in a streaming manner to wherever you want. And OLAP [snorts] is like a very natural target destination one would pick. Um it's also good for gray field projects. So, if you uh if you've heard of this term, there's sort of like a green field project and a brownfield project. And a green field project is one where like you start from scratch, you do whatever you want. Engineers love this stuff. The brownfield project is where you, you know, start with your cobalt technology from 1978 and you have to like make it work. And that should be Greenfield. Yeah. But yeah, there's this thing called gray field which is sort of your in between. like you have a thing that is sort of new that you're fitting into something that exists that's sort of old and having a way to export data allows you to operate in these types of projects well because you can have your old part and your new part. I see. So there's like a lot of re like this data sync it export streaming export is sort of a building block that lets you build a lot of things. Fantastic. And just to be clear this data sync API that will automatically give me a snapshot at that. So if I start at time stamp 100, that's [clears throat] going to give me the snapshot from time stamp 90 and then replay from there. Is that right? Yeah. It'll iterate through it. It sort of the initial time stamp that you get is sort of decided at the end of the first phase of the sync. I see. So during the sync you're kind of doing this alternating that we mentioned and then you get a snapshot. How do I know like if Mike if we created Mike up here at time stamp 2 and we start looking at the data sync API export at time stamp 100. How do I know that that user too is called Mike? Where like what does do we have to pull in the initial snapshot first from somewhere or like Yeah. So the data sync endpoint uh it does this kind of cool thing where it returns data kind of continuously in an export and then at the end of each page it will tell you whether this page is consistent yet or not. And so during the first part of the sync the pages are not yet like we haven't like caught up to the point where you're given a consistent snapshot. I see. And then when you get to that point where you have a consistent one you will the page will tell you and then you will know that what you have so far is consistent. And once you know that, you can look at it and [snorts] see if Mike's in there. Mike's in there. You know you got it. Um, and up until then, you have an inconsistent view where you might have some people and not other people, but it's very clear when that is true. Um, and some of our customers do actually look at these inconsistent views because they don't mind, but then that's a choice that they can choose to make. It might be worth talking about. So, what kind of customers are using this and what are they using it for? Yeah. So for for let's look at this OLAP case where people are using the data sync iterator to uh send data to an OLAP yeah like a OLAP database. Uh I think a very common use case is for product analytics. So they'll take their database data send it somewhere else and then use it to uh slice and dice their their customer their customer base or slice and dice like how their transactions are being used to help motivate what the next like quarter of their business plan should be. Um, maybe they use it to make a slide deck to show off to investors. Maybe they use it to decide what to do on their road map. They have three choices. They're not sure which one's the most popular. They'll use some system like this. So, they put it into their own system. They've got some sort of legacy system that they just want to feed the data into. Okay. Rather than using Fiverr or something else like that, they want to put it into their own. Yeah. Maybe they have a data brick setup. Maybe they have a Google BigQuery. Maybe they have their own their own product that they want to send the data to. Okay, makes sense. Yeah. So, one of the things we want to do is sort of like this is out and so you can actually set like I've tried this like I can like oneshot like a an app that uses the data sync endpoint and sends it to an OLAP database. So, this is doable. Cool. Um, and it's here. And so, I think that's kind of great, but I think what we want to do is take it one step further and make it more provide this for you. We want to provide Yeah. Like we want to provide the a simple OLAP solution and then you can use that inside of an action or a query. You could use it inside of an action. Not a query. So this is not Yeah. Okay. Yeah. You know maybe it's a bit weird because we have like full text search which is reactive. So you can use full text search I believe inside of a query I think. Y um but you can't use vector search inside of a query. That has to be inside of an action. Yeah. Um, yeah. My my instinct is we probably don't want to have it inside of a query because queries we want to be fast and be looking at this like kind of within last 4 minute snapshot. But maybe maybe maybe probably not. [laughter] But yeah, for for an action, I think uh I could imagine us having a page on the dashboard where you put in an OLAP query and if you've turned on this feature, you're able to run a query over all of your data and it'll be like slightly stale because it can't be thoroughly up to date, but it'll only be like order of magnitude like seconds to minutes stale. Yeah. So, I think this like I'll call it like managed OLAP is something that like I'm actively working on right now. Me and me and some of my co-workers are very excited about it. So, okay. It'll it'll it'll be good. Uh so, that's going to come out this this year, the next couple of months, sooner, working on it. Nice. Um yeah, so just stay tuned for that one. Um and if you're impatient, you can you can oneshot using this API and send it to your data bricks or your click house or your duct DB. And that's why I like really like to me like clean sound abstractions like public APIs are like the way to go because we'll give you an option and if you don't like the option you can do it yourself however you like. Y and yeah this works out like we'll we'll we'll charge for our managed OLAP like it costs money to run these analytics queries and we'll charge for it but if you want to do it yourself you can. Yeah. Um, and we do uh this is we also charge for this because it is pulling data out of Convex and so that requires database reads, but we kind of we're not like trying to profit off of this. Like it's just the regular price of sending data out of Convex. Yeah. Yeah. So, yeah. Okay. Nepine, do you think there's anything else that we should talk about with backup and sync at this point? Not sync, so backup and export at this point. Um I think I guess I have one more thing about it that maybe I want to point out. Yeah. Um which is like a lot of small customers don't think about this but uh our large customers have dedicated databases. So you can like pay a pretty large sum of money but you can get a dedicated database. I guess if you're VC funded maybe it's not that big of a sum of money. It's not not that expensive. I'll take uh but uh for like you can buy a dedicated database like through convex and on a dedicated database uh we can actually provide backups on the physical level. So this is something called a physical backup and it is using the underlying databases technology to back up the entire database. um on the lower tier like sort of shared uh deployments because your data is shared on a database with other users to drive costs down it you can't use a physical level backup and so we do this full iteration over your data to give you so it' be like RAID 10 or whatever it is. So that that's what you mean by physical. It's like RAID. It's like Yeah. It's like it's doing something at the level of the database and we're not like changing like when you do a physical backup you're not changing the way the database underlying stores the data. Yeah. Maybe a analogous way to think about it would be like we're taking the entire documents table and putting it somewhere. Yeah. Um as opposed to giving you a view of your data. So the nice thing about physical backups is that they're cheaper and they're fast. Yeah. Uh the not nice thing is that you don't really see your data in a format that makes sense, but it's backed up, which is the most important thing. But it's backed up. Yeah. And so sometimes you want to back up because you want to look at your data. Sometimes you want to back up so you can restore it. And on physical backups, those sort of diverge in their use cases, uh both are useful like like we're going to keep both of those. Like even if you're on dedicated, you're going to want both of these things. Okay. All right. Well, I think we've come to the end, but I really appreciate you taking the time to explain this all to me today in a fun. So, this has been very, very cool. So, I appreciate you working on this. Yeah, I always look forward to getting to be in the room with Mike. So, that's good. Thank you very much. All right. Cheers.
How hard can backing up a database be? You loop over every table, copy all the rows out and you're done, right?
That's pretty much what I thought anyway. So when I asked Nipunn, one of the OG engineers here at Convex, what he wanted to do an engineering deep dive on and he said backups, I was a bit surprised. I mean, surely backups are pretty simple aren't they?
Well it turns out no. If your data is changing while you copy it, you can end up with a backup of data that never actually existed at any point in time:
So in this little example Nipunn and I are in the table, then I get removed, and then Jamie gets added. There was never a moment when all three of us were in the table at once, but that's what the backup says happened.
So Nipunn sat me down at a whiteboard, put up with all my dumb questions, and we started from the naive version and worked our way up to what Convex actually does. I learnt a ton from this one, so let's take a look.
Where do we start?
We started with a really simple users table with an ID and a name. The simplest thing you could do is have a backup API endpoint that loops over all the IDs and returns the whole table as a big blob of JSON.
The first place that gets challenging is when you have a lot of data and it doesn't all fit in one response, so you paginate it. Say we take 4 rows at a time, then you call the endpoint three times to get the three pages:
To know where each page should start you need a cursor, which is just a word for where you got up to. So you pass in the cursor, get some rows back, and go round in a loop:
The problem is that those tables are being modified while you are doing this. If you have a popular project you might have lots of requests a second changing your data, and those are the projects you most want to be backing up.
So say your backup takes an hour. You're going to get the people on the early pages as they were at the start of that hour, and the people on the later pages as they were at the end of it:
Maybe that's good enough for you, but that's how you end up with me and Jamie in that backup from earlier. Nipunn calls this an unsound abstraction, because it has these gotchas that break your mental model of what a backup is. You'd assume a backup is your data at a point in time, and this isn't that.
What does a database actually store?
At this point my brain jumped ahead to the write-ahead log, and Nipunn said "bingo".
You see, the table we look at in the Convex dashboard is how we think about the data, but it's very rarely what a database stores underneath. Underneath it's usually a journal of every change that has happened:
Postgres calls this the write-ahead log and at Convex we call it the document log. If you run Convex locally you can actually crack open the SQLite file (I did a video on it) and there it is, a table called documents.
Each entry says which document changed, what the change was, and when. That "when" is a timestamp in nanoseconds that uses something called a hybrid logical clock, which sounds fancy but is basically a normal clock with a guarantee that time only ever goes up. So when a new entry gets written, its timestamp is the max of the previous entry's timestamp plus one, and the current time.
How does the log help?
So now there's a very different way to get the data out, which is to paginate over the log instead of over the IDs.
This has one really big benefit. Every point in the log is a state your database was actually in, so you don't get those views of data that never existed. It's also really easy to catch up on changes and stream them somewhere else as they happen:
The downsides tho are that you get the same row more than once (document 2 is in there as an add and then as an update), and to work out what your table looks like right now you have to replay every change since the beginning of time. If your project has been going for a couple of years that's not really feasible:
So, what do we do?
Thanks to my event sourcing days my brain went straight to snapshots, and I actually got this one right too. Noice!
You take a snapshot of the table at some point in the log, then to get to the current time you only have to replay the changes after that snapshot:
I asked if this is just how Convex works in general and it kind of is. When your query starts Convex picks a timestamp, and every read in that query is guaranteed to be at that timestamp:
So for a backup you read through all the IDs at your snapshot, then catch up on the log afterwards, and you're done. Sounds pretty good right?
Any other issues to worry about?
Well, keeping a snapshot around is expensive so a database only keeps a handful of them, and only for recent points in time. If you want to read the table as it was further back than that you either have to read all of the data, or start from a snapshot and work backwards through the log for each document.
So as long as your backup finishes before its snapshot goes away, this works great. But Nipunn told me they had people with so much data that the backup took hours, and for one customer it took days:
"It was a little embarrassing when that happened" was how Nipunn put it, and it sent them back to the drawing board.
What's the better algorithm?
Convex cleans up old versions of your documents with a process called retention. Or compaction, as apparently there's an ongoing holy war internally about what to call it 😅
For indexes that window is 4 minutes by default, so if you can do your read inside those 4 minutes things go great. Past that every read has to walk backwards through the log to work out what the data used to look like, and the further behind you get the longer each of those walks takes. It goes quadratic:
So just reading the users table has its problems, just reading the documents table has its problems, and reading one and then the other has its problems too. The key insight is that you do what Nipunn calls a dance, going back and forth between the two so your snapshot never gets too old.
He drew it up as a 2D diagram, with IDs going across and timestamps going down, and honestly this was my favourite bit of the whole thing. Here's the real one from the whiteboard:
Nipunn's whiteboard: the users table and document log at the top, and below them a blue diagram with IDs going across and timestamps going down, with numbered pages stepping down and to the right
And here's a cleaned up version:
Pages 1 and 2 go across, reading through the table by ID as far as it can without getting close to that 4 minute limit (by default it starts catching up once it's about 30 seconds behind). Page 3 then goes down, catching up on every change made since it started, like me changing my address or Jamie being added. Once it has caught up it goes back to going across, and it keeps doing that until it reaches the end. The catch-up pages get bigger as you go along, because you're now keeping track of more people.
So you might get a row more than once, and they don't come out in timestamp order either, it's a bit of a mishmash. But when you reach the end you're guaranteed to have a consistent view of your data.
Oh and one thing I really like about this. If you took a snapshot and the backup took two hours, you'd finish with a backup of your data from two hours ago. Because this one keeps catching up, you finish with a backup of your data from right now:
It does mean you don't know what timestamp your backup will be at until it finishes, because when you start it's still in the future. Which is a bit of a mind bender, but as Nipunn says it's sort of ideal, because it'll be really up to date when you get it.
He also made a point I think is really nice about agents. This algorithm is trickier to describe but it's sound, so an agent using it can't really mess it up and doesn't need to read the source to understand how it works behind the scenes.
By the way, I asked about what happens when you delete a row, and it leaves behind what's called a tombstone. Which is a pretty morbid name but I love it. RIP Mike:
Old versions in the document log get kept for much longer than those 4 minutes (the open source backend defaults to 14 days), but they do eventually go. I asked if Postgres and MySQL do this too and Nipunn says they all have some version of this. Postgres, for example, calls it vacuuming.
What's next for backups?
This algorithm only came together a month or two before we filmed, and it powers a public endpoint called data sync that shipped in July.
Nipunn told me that for a bit they thought about gluing something together just for those big customers, but they really wanted to step back and make a solid building block, and having a public API is a really good way to force yourself to do that. Then you can build more on top of it, and so can everybody else.
You call it in a loop just like our naive version from the start, except now it's sound:
1let cursor;2while(true){3const res =awaitfetch(`${deploymentUrl}/api/v1/data/sync`,{4 method:"POST",5 headers:{6 Authorization:`Convex ${deployKey}`,7"Content-Type":"application/json",8},9 body:JSON.stringify({ cursor }),10});11const page =await res.json();1213// apply page.truncates, then page.values, and save them with the cursor14awaitsavePage(page);1516 cursor = page.pagination.nextCursor;17if(page.status.type ==="upToDate")break;18}19
I had my agent run this against the demo deployment for one of my components, and it took 18 pages to get through 162 rows. The first 17 pages all came back with a status of snapshotting, and the last one with upToDate. That status is how you know you've got a consistent copy, as before that you might have some people and not others. It needs a Pro plan, and the docs have the details on saving each page safely.
And if you keep hold of that cursor you can come back whenever you like and carry on from where you left off (within the deployment's retention window), which is how you keep something else up to date with your Convex data.
What even is OLAP?
The big thing this unlocks is OLAP, so I got Nipunn to explain what it actually means.
There are two main types of database. OLTP (online transaction processing) is what Convex is. Think banks, where you take money out of my account and put it in yours, and it either all happens or none of it does. OLAP (online analytical processing) is for analytics over big piles of data. I did ask why it's not OTP and OAP, given online is one word, and my best guess is that "on-line" used to be two 🤷
The difference that really matters is how they store things on disk:
OLTP databases are row-oriented, so all the values for one row sit together, which is great for updating a couple of rows at once. OLAP databases are column-oriented, so all the names sit together, which makes it really quick to go through every name or every transaction. You can't store it both ways, so a really common setup is to copy your data from an OLTP database into something like Databricks, Snowflake, ClickHouse or DuckDB:
Convex has been able to do this for a while through our Fivetran connector, which is still what we recommend at scale. But little old me with my side project doesn't really want to sign up for another third party service.
So now you can point data sync at whatever you like. It's the regular price of getting data out of Convex (database reads and bandwidth), and Nipunn says he's been able to one-shot a little app that sends data to an OLAP database. He's also working on better analytics support built into Convex, which you can follow on that roadmap item.
Anything else about backups?
One more thing Nipunn wanted to point out. If you pay for a dedicated deployment, which runs on its own hardware, you also get what's called a physical backup. On a shared deployment your data sits in a database alongside other people's, so we have to iterate over your data to back it up. With your own database we can back up the whole thing at the level of the database, kind of like taking the entire documents table and putting it somewhere:
Those are faster and cheaper, but you can't download them or look through them the way you can with a normal backup. So you still want both, one so you can look at your data and the other so you can restore it.
So how do you back up your Convex app?
You don't have to build any of this yourself, as all that's needed for a normal backup is to press "Backup Now" on your deployment's backups page in the dashboard. On a Pro plan you can schedule daily or weekly ones too. If you'd rather have the command line do it, this generates an export in the same format:
1npx convex export--path ~/Downloads
2
Conclusion
So just to recap: if you just copy rows out of a live database you can get a backup of data that never existed. Reading the log fixes that but you'd be replaying everything since the beginning of time, and snapshots help until your backup takes longer than they stick around for. So you end up with this dance between the table and the log, which gets you a consistent copy that's up to date when it finishes.
I think what I like most is Nipunn's point about sound abstractions. If you get the building block right then people (and their agents) can use it without having to understand all of this, and I guess that's kind of what Convex is all about.
I went into this thinking backups were pretty simple and came out with a lot more respect for them, so a massive thanks to Nipunn for taking the time. If you liked this one, I did a similar whiteboard session with Ian on how time works in databases too.
Until next time, cheerio! 👋
All gas, no breakages
Convex is the reactive backend platform that keeps up with you and your agents. Database, functions, workflow, sync, search, file storage, and more. All TypeScript, zero glue.