Dubious Database Design

This video features Andrew Godwin at DjangoCon US 2015 in Austin, Texas, USA.

Dubious Database Design
0:38:03
Published November 3, 2017
2,126 views

Dubious Database Design by Andrew Godwin

Everyone has seen plenty of articles about how to design data storage solutions well - but nobody is getting up there and talking about how bad their storage design is.

Rather than just listen to more things to do and vague reasons why, come and see some truly awful examples of storage design, and the lessons we can learn from it. What happens when you end up reimplementing indexes? Why shouldn't you turn off durability? Why not make a table for every user? And how can you render templates purely in the database?

All this, and more, as we delve into the realm of datastores and examples both historic and current that you can learn from, and hopefully come away with a better idea why the rest of us design things the way we do.

Help us caption & translate this video!

http://amara.org/v/HGSe/

Summary

Andrew Godwin argues that database design rules are usually lessons learned from earlier failures, so developers should ask why rather than follow patterns blindly or dismiss established practice. He illustrates this with examples: replacing relational queries with Redis, embedding mutable data, splitting related records across too many tables, creating schemas dynamically, relying on fragile persistence, underprovisioning for cache loss, adopting too many data stores, treating auto-incrementing IDs as meaningful numbers, and putting application rendering in PostgreSQL. The practical advice is to normalize data when relationships and updates require it, choose simpler infrastructure where possible, plan for failure and recovery, treat IDs as opaque values, and test or reason through the consequences of a design before committing to it.

Key takeaways

  • A read-only site can still need relational database features such as filtering, joins, and pagination, and recreating them in Redis may be slower and harder to maintain.
  • Embedding mutable author data in every post makes updates expensive, while normalization keeps frequently changing information in one place.
  • Separate tables for every account type or language can create excessive queries and schema-management costs; use a shared typed table, JSON, or an entity-attribute-value design where appropriate.
  • Every datastore adds operational work, backups, failure modes, and possible single points of failure, so small systems should prefer a limited number of dependable components.
  • Cache loss, failed persistence, and insufficient disk space can turn apparently safe systems into outages or duplicate payments, so recovery capacity and durable state matter.
  • Auto-incrementing IDs are opaque identifiers rather than timestamps or numbers for arithmetic, and application rendering logic generally belongs outside the database.

Summarised automatically from the transcript.

Chapters

  1. 0:16 Learning from Database Failures Andrew Godwin introduces the talk's counterexample-driven approach to understanding database design rules.
  2. 2:33 Redis and the SpaceLog Database A read-only NASA transcript site illustrates how choosing Redis led to reimplementing relational database behavior.
  3. 7:07 Joins and Data Normalization The talk examines why avoiding joins or embedding related records can create inefficient and hard-to-update data.
  4. 10:14 Database Persistence Failures Redis persistence problems, disk exhaustion, and silent failures show why database storage needs monitoring and capacity.
  5. 12:33 Reliable Payment Processing A naive payment workflow demonstrates how lost database writes can result in repeatedly paying the same recipients.
  6. 14:05 The Cost of Excessive Tables Splitting each authentication provider into its own table creates increasingly expensive queries across user listings.
  7. 17:12 Dynamic Schemas and Translation Data The speaker explains why creating tables or columns at runtime is problematic and presents JSON and entity-attribute-value alternatives.
  8. 21:04 Cache Cold Starts A lost cache can multiply backend traffic and overwhelm systems that were provisioned around a high cache hit rate.
  9. 23:23 Too Many Data Stores Adding Postgres, Elasticsearch, Redis, and other systems increases operational work and creates additional failure points.
  10. 27:16 Auto-Incrementing IDs The talk challenges assumptions about ID ordering, numeric identifiers, and scaling auto-increment keys across database clusters.
  11. 29:37 Application Logic in the Database A deliberately absurd example of rendering templates from PostgreSQL illustrates the risks of putting application logic in stored functions.
  12. 33:29 The Reason Behind Database Rules Godwin closes by encouraging developers to ask why established practices exist before replacing them.
  13. 35:03 Questions The audience asks about preserving institutional database knowledge and using JSON or other schema types in relational databases.

Transcript

7,715 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:16

Thank you very much for the introduction. So yes, hello everybody, I'm back again this year, as I always am at DjangoCon. I have this time though want to talk to you a bit more about It's not about migrations, but about databases in general. As mentioned, I Wrote South Migrations. I also wrote Django Migrations. I am a senior software engineer at Eventbrite. We have a booth just down the hall you should come and visit. Very lovely people over there. And I have a small hatred of MySQL, which is not it's somewhat relevant here, but this is actually a little less bashing onto MySQL than I used to. So if you're looking for that, look out for it. But what I want to address here really is a common thing I found, especially when I was a young

1:02

younger, more junior developer in the industry, is this idea of people just saying Just do this. We do things this way. And often there's no explanation why. People are like, well, you should you should just do this pattern. Especially like a university is often quite common. For a place of learning, it's much more dictatorial, like, okay, we just design things like this, we normalize like this, and sometimes you don't quite get the backstory. So what I'm going to try and do here today is is show you why by counterexample. I'm going to take eight or nine different Bad ideas and just run through with them until we get to a point where it actually turns out what the problem with them is rather than just saying they're bad And this is all part of what I call learning from failure.

1:47

So if you saw my talk at PyCon this year, that was about, it was called What Can Programmers Learn from Pilots? And a big point of that talk is failure is an expected thing. And in particular, um one of the things with both aviation and in some way with this is that every rule, every reason, there's usually something has happened to cause that rule. So in aviation, every law An accident has usually happened to bring that law into effect. Like every time a crash or incident happens, we look at the results and institute a new law and that goes into the books to stop happening again. And in software development, that is not quite as coherent. People have their own little specialties, but a lot of the accepted knowledge is there for a good reason because people Decades and decades before us

2:33

have been through the same stuff, they've been through the same wheels, they've reinvented it. So let's come to the first example here, which is a cycled space log. Now spacelog is a way of digitizing the audio, well sorry the written transcriptions of the audio between the Apollo and Mercury missions from NASA in the 70s and 60s and 80s. um into a sort of website format. Now the only format these were in beforehand was a scanned angled badly typewritered PDF. So like you know, they they typewrote it in the 80s, stuck it into a folder somewhere in NASA. Then about 20 years later someone comes along, scans them. Don't scan great, they're at an angle, they're hard to read, they you can't search through them, the OCR doesn't work.

3:19

So we took all that data, we cleared it up, we put it into a nice website. Now there are two key factors with spacelog that are relevant. The first one, so yeah, kind of an excuse for myself is that space log was written in a week here. This is thought uh Fort Clanc, uh on the Isle of Albany in the UK. Now Fort Clanc is a little unique. It is a Napoleonic-era seafort. This causeway here floods at high tide, isolating it from the entirety of the island. And the island's very small. The island you can walk end to end about 20 minutes. And even then, like the whole thing isolates off at high tide. It's a wonderful place. It was occupied by the Nazis in World War II as well. But the key thing is

4:04

Um it's part of a thing called uh DevFort. And DevFort is a thing where you go somewhere for a week and it's a very intense week of writing a single thing and launching at the end of the week, which is my excuse for the very bad architecture I'm about to show you So the nice thing about spacelog is that it's all very old data. This isn't going to change. Like, you know, Apollo 11 isn't still going on. And so we can be pretty sure that all this data is pretty much read-only forever because once a mission has happened, the transcripts don't change, there's no user features. I highly recommend you make a site with no user features, it's great. Because there's no logins, there's no moderation, there's no spam, it just sits there and presents to the world. And naturally, um in our sort of somewhat

4:50

locked away, panicked state, we were like, well, it's read-only. What's really good at read-only stuff? That's it. Redis is great at this. Redis is fantastically fast at reading operations because you can say, hey, here's a key, give me a value. And so we took all the data, we split it into different chapters, sort of like, oh, here's takeoff, here's entry, here's going around the moon, and we put them into Redis keys. Thought, great, done. Ship it. Well, not quite. The problem is, of course, that you need pagination. So we'll put every entry into its own key. And so we can load, we can do a multi-get on the keys, still very fast, and we could load fifty of them at a time and do pagination that way. Perfect. And you know, we we assumed that the numbers incremented and didn't nasty things like that. So, okay, cool.

5:36

So now we have Pages that do one multi-k against Redis, still pretty fast, that's fantastic. But then you have a problem like, well actually We don't have purely numerical identifiers on these, so we need to give each of them a score, which is its position in the chapter. and then query which keys we want as names by a range by score, and then take that range we get back and then put that into the multi-get and then do that. And then also we need to get the details and the pictures for each speaker. So we're going to then take all the speaker names in that result set and then make them into a list and query that list against another collection in Redis and get the details back from there. And of course what we've reinvented is select at that point.

6:21

This is a very roundabout way of doing a select with a joy and it gets on the table. And Of course it was much slower than a real database was because it can't do the optimizations in memory. And so to this day, space log still runs on this truly special um Reddit installation that we wrote. And it's it was perfect it was perfectly fine. Um and there is a varnish cache in front of it because it's not necessarily great at doing things like showing you random pages, but it does tend to work. But yeah, this is the place where A normal relational database would have been a perfect the bet much better solution and we were blind to that reasoning until we got to the aim of like, oh We obviously should have picked this, you know, four days ago, but it's now day seven. We have to leave in like three hours, we can't fix it.

7:07

So a somewhat unique situation there. And this kind of comes on to the second part of this. It's like people like to ignore join. And this was this is this is a bigger thing when MongoDB and things were much more popular. People have this impression that joins are really bad. And if you ask any person who does relational database stuff, like no no joins are a very important part of the whole thing. Like no no joins are back as slow. And to be fair, joins are slow. So let's assume that joins are really slow Cool. So let's go, well how three do stuff. So we have some documents, let's call them. I have some forum posts. By myself, and we have a few other authors. And we have some author records. So yeah, imagine a forum, you have avatars, you have real names, you have like date joined, that kind of stuff.

7:56

And what we want to do is we want to show a page in the forum. So what we want to do is we want to take all those forum posts and for each one of them we want to find the corresponding author document and put it in put it in place But of course, to do this, the naive way is to, for every single post, scan the entire author table, find one, put it in, for next post, scan it. And that is very slow. That's n times n operations. Now, you're not stupid developers, I know this because you're all here and you're wonderful people. So you would of course go, well, Andrew, it's obvious we can take all of the authors as like a collected set, like I said in the last example, and just do one query with them. I was like, that's great. So what we can do now is we can scan all the posts

8:42

We can then build a dictionary and says, okay, this well this post has this author ID and then do one big author lookup and then take the bulk result and put all those details back onto the posts. Yeah, pretty simple. You've re-invented hash join. Well done. This this this is what join does. And join is better at this because it's not having to round trip across the network. And so when I see people reinventing this, I'm like, that's that's lit for you what a join is. But of course it could be but of course some people go no but Andrew you've done it wrong. In documents you embed things inside other documents Okay, let's do that. Let's put the authors inside the forum posts. This is kind of the recommended way of doing stuff. So now you can just fetch the post Everything is everything is great. That's fine until

9:29

so let's say a common feature you see on forums is a date of say like you know this person was last seen at So let's put that into our author record. Now this means whenever I log into your site, you have to scan every single post in the entire database And on every single post I've ever written, you have to update that record to be the right thing. And so now you've made the sign-in process potentially 30 seconds to a minute long if I've been around the forum a lot. And this is why normalization exists, right? So and it's one of those things that like if you've been down this rabbit hole, you will have come across this particular problem like I have in the past. And gone, ah, that's why we do it that way. But it's a very important observation of like, you might think like, oh people change their usernames like once in a blue moon, it can't be that bad.

10:14

But there's a lot of other things on a user that can be very rapid fire change. You've made every single change on that document on that table essentially very, very slow. Another one that is definitely somewhat in my own fault. So when we were at Lany, I used to work at Lanyard before we were acquired by Eventbrite. And one of the things we had at Lanyard, and we still have at Lanyard, is a very wonderful set of uh cached Redis values. So in particular, so this is better than the first example. These are things that should be in Redis. So basically we we take our data set, we work out a few key things like, well, you know, these are speaker scores and whatever, and and this is like some clustering information. We put it into Redis for later use in the site so we can sort of get it out quickly.

11:01

But the unfortunate thing is Redis, at least in the past, and MongoDB in the past as well, when this happening They had a s you know they had a saving system and neither of them are particularly great. So Redis is one in particular. Redis, it's an in-memory data store generally. If you want to persist, it would write a brand new save file out. So write it to disk. When it finished writing, okay, okay, I finished writing it, looks good. Then it would delete the old save file and move the new one into place. Fantastic. The problem is when you run out of disk space, or rather when you have less than twice the disk space for this file, because You try and write the new file, but you've run out of space to write a whole new version of the file and Reddit just doesn't write it. And in the in the past when this happening It used to not write it, but it it kept going.

11:47

It kept accepting requests. It kept serving things from memory. And so all this data was just going into memory and not being persisted. And you had no idea. Like we didn't have monitoring at the time for this. We do now. And so, of course, come one day that server reboots. Ah, we can reboot the server, it's fine. We lose like six months of cache data because that data was only in memory, it was never persisted. Thankfully it was all rebuildable in a couple of hours from our main data store. But if you if something more valuable was in there, it would have gone away. And this happened to me again on a personal project of like a year later and I should have learnt my lesson, but I clearly didn't Um another thing I learned in the in the hallway this morning was that um MongoDB used to do this as well. MongoDB used to if it if it ran out of space just corrupt its own save files. It wouldn't even try and and f

12:33

so you know I I hope that's now being fixed as well, but there 's a lot of There's a lot of things with running out of disk space. Like give your database servers a lot of room because you do not want them stopping to serve requests or just corrupting themselves or even worse silent failure which is keeping on going and you think it's fine but it will just it will it's just eating all your data essentially so be aware of that. A small example of how that could be bad. Say we have so that event pays up money to organizers. Now our system is much more robust than this. This is not how we do it. I want to point that out. But if we were like young and naive, we could do it like this. We could do a query to find our unpaid organizers. We could write a road to say, okay, this one's being processed, because obviously if it's a cron

13:20

job. We don't want two of them to run at once and both pay the same person. So we lock the borough and market is processing. And then we call our bank as an API and say, hey, can you send account number so-and-so this many dollars And then when that call comes back and says success, we mark them as page. But of course, what happens if your database isn't is taking rights but not saving them? Well You never mark as processing and you never mark as paid. And so your cron job continuously finds all the clients who are ever not paid and continuously pays them money, which as you can imagine is not great. We see this is why we have proper locks in place of Membro and systems and things like that. But like if you were doing it naively, be very careful about money So another thing is

14:05

we come to the the other end of the scale now. So previously we've been people who were like, well, this normalization database stuff, it's a bit much. And now we come to the person who goes I really love normalization. I love tables. I love them so much. We're gonna make a table for everything. This was me three years ago. So at Lanyard, Lanyard had, when I joined, it was just Twitter authentication. And we were like, okay, we're going to add emails and passwords, we're going to add LinkedIn, we're going to add Facebook, we're going to add all of the socials. Um and just have like a you can sign in with just uh you know any number of things. And I went, that's great. Now these different backends are different things, so like for emails, have a table with email Um and that kind of stuff in it. Facebook have the Facebook tokens for LinkedIn and

14:51

each have their own sort of identifiers. And so we made a table for every single one. Sounds fine. Now the initial problem with this is When you try and show somebody their user profile page with all their things set, you have to go and query every single table and go, okay, well You have and then for every like you have zero LinkedIn accounts, you have three GitHub accounts, you have two Facebook accounts and Twitter accounts. That's not too bad. That's like six or seven queries on a given single page. Login is fine because login, you know the methods, you just hit the one table But the problem comes when um this page, so this is a speaker or directory page on Landed. And if you notice, um there are some icons below the people. These icons show that all the accounts they have assigned to Lanyard.

15:37

In particular, you know, they have at least one Twitter account, at least one LinkedIn account. That means for every row here we're doing a number of select queries and the number of like seven select queries per row. And so this quickly spirals into a Oh my goodness, we have to like do seven queries per row on this table and like we can kind of aggregate them ish, but we can't really um it quit you know it quickly makes what could be a simple single select into a much worse prospect And and this in particular took us a while to find and fix uh and clear up. And the key thing here is that database isn't magic. It can't just, if you give it more tables You're kind of promising your database, I'm gonna you're gonna use them sensibly. You're saying like, you know, I am a competent developer, I understand that tables cost space, they cost time.

16:24

They're meant for querying things that are separate. And so the correct solution to this is to have a single table with a type column in it and like, oh, type email, type LinkedIn, type Facebook. and then like an identifier column where you've like you put the one key thing you look them up by and then it's a single query. But we didn't know that at the time necessarily And following on from this, you have the person who really, really, really loves tables. So I have been developing South and migrations for too many years. I think it's about eight now. And I got a lot of emails. Now a lot of the emails are Andrew, status terrible, doesn't work. I'm like, it's okay, we'll fix it. And some emails are like, Andrew, how did I do crazy thing X? And one of the crazy things X is I want to make tables at runtime. My reaction to this is this picture.

17:12

So in particular I've had people request, so um I-18N is a very common request here. People are like Hey, I want to make a column per language. So, you know, this is like Django model translation, for example. Like you have a multi-language site, people have to insert content in multiple languages. So naturally people's first instinct is to store one per column or one per table in some cases. Like oh this is the English table, this is the French table. Sometimes I had people come to me and go Well, we're doing kind of a white label hosted solution, but we want to have like a whole set of tables per customer. At which point my action is and how many customers are you planning on having exactly? Um and then some of the even worse ones are writing a CMS and we want people

17:58

we want random users in the organization to be able to just add columns by clicking buttons in the back end CMS. So it's right. I was like just sitting there just clutching my head going, why? So let's take one of these examples, which is the language one. So say you want to call into language, and say you become a site like Facebook that has a global reach. There are approximately 300 to 400 language variants. There's you know obviously there's usually one per country, but there's often regional variants. So for example, there's British English and American English, and they're very different. Like we use spell colour differently Um in British English you say bin, in American English you say trash, and a lot of other words that you want to make sure are translated properly. And so you've not got just language changes, you've got locale changes. And imagine you have a couple of those columns per table

18:44

You're looking at a table with over a thousand, possibly two thousand columns. That's not sensible. Like your database isn't going to be happy about that. In particular, um if you're just trying to select from that table and don't do like column restriction. It's gonna try and send you back every single column every time. And that's gonna really, really hurt your network connection and slow you down a lot. So don't do that If you do want to this model, then be very wary of this. But make sure you use dot values and just pick out the columns you actually want rather than requesting everything. But even then I would really like if you didn't do this, because even if you are adding columns, if you have the wrong database, mighty cool. Or if you have Uh a default value in a column, adding

19:30

a column is very expensive. It has to rewrite the storage for every row. Some tables at Eventbrite can take days to add columns to sometimes because we just have so many, so much data in those tables And we use MySQL, much to my dismay. And so you've got to consider that like, sure, you can add a column, but if that's an interactive thing from like if you have like a web page where you click a button and it adds a column Be prepared if that website web page takes like potentially hours to return. Of course it won't because it will cut off with a with a time limit. But you've got to consider that like adding columns isn't necessarily cheap and fast. And it can there can be table locks in place, they can all this kind of stuff. And Postgres, as usual, does a lot better at this kind of stuff and we'll let you get away with it now and again, but it's still tricky. So I kind of recommend that what you do is you

20:17

either store your translator stuff as like JSON blobs, if you if you can retrieve from within JSON blob like by language. So for example in Postgres you can select just certain keys from a blob in JSON. H store is similar in in Postgres where you could say, okay, this is this this field is like title is just a h store of Engb is this, ENUS is this, FRFR is this. And you can just say you select the language from that thing. Or the sort of generic way is to have a entity attribute value style table, which is what EOV stands for here, which is a table that's just like, okay, this is column, this is language, this is value. And Joining against those things is something that databases can do pretty well. And it will save you a lot of headache in trying to look at the schema and not having it scroll off the side of a screen and like three miles down the street.

21:04

Another fun one says this didn't happen to me, thankfully, is the cold boot. So this is a fun one because it gets really good engineers. So You've written a site, the site has a very good cash hit rate. Like you're 75, 80, 90% of the page on the site are hitting the cash. And you're feeling pretty smug. And even better, you've got like you know, you've you've bought down your app servers so they're like if they're running at sort of like 70% capacity, like you know, you're not wasting money, you've got a good hash cash hit rate, you're doing you're doing fantastically well. Like, great engineering, good job. However, I'm evil. Uh I'm gonna come in and make those cache servers go away. They could have a failure, they could just stop working.

21:49

But let's say the data in them is lost permanently. So in the case I'm talking about, a power failure hit the servers and they and they're running memcache, so they lost all the data. Now you have this problem where you are running at 77 % capacity with a 90% cash hit rate. That means that without the cash, you've got ten times the traffic coming to those back-end servers What's gonna happen is your normal rate is gonna is gonna plummet when the cache goes up and when the connection drops, and then as soon as people can get back in, they're gonna just overwhelm your servers with ten times the normal traffic and you're gonna just be drowned. And even worse, as soon as you hit the cap on those servers, things start backlogging and filling up a backlog or a sort of like the number the sockets will sit there waiting open and it becomes this immense firefighting situation that's just

22:38

really hard to recover from. And it can take hours to repopulate that cache. Um one of the things we do with EventBite when we put builds out is the builds actually pre-warm a cache before they go fully live, just to prevent this a smaller version of this kind of thing happening. But it's very sort of nasty thing because it can happen to you with a really well planned, perfectly done system. Like you have to think Can your system handle like either can it scale in a few minutes if that's something you think you can do on a like a cloud server? Or do you have the spare capacity? Or can you turn off features for that recovery period or one of those things? So it's something to bear in mind. The next thing is something I call the the optimist. So the optimist is a very

23:23

Well let's say enthusiastic developer. And the optimist goes, okay, I've been through a few companies now, I know what I'm doing We have some great stuff. And we're in a brand new startup, small company, agency, whatever, and we had to build a new project. And so I'm going to take all the things I learned previously and we can use all of them at once. So we can have we can have Postgres, we're going to shard it because we might scale in the future. We can have Elasticsearch for search. We can have React for stuff that we think is inconsistent but we can do with storing. We can have Redis for a sort of cache slash data store work around. We have some flat files for like logging and big blobs and stuff and do all of these things at once. And that seem it seems very attractive. It's a very it's a very sort of tempting situation where you're like, oh well

24:11

They're all shiny. As a developer, like at least I am, I'm just drawn like a moth to a flame to like, it's a new data store and just like wandering towards it. Like we can put this into the site and install it, it'd be great. One of the things had lanyard in particular was that we weren't allowed to add a data store without removing a previous one, and you'll see why in a second. Because sure you have these let's say five or six different data stores. So of course for all those you've got redundancy, right? And you've got backups for all of them. Cool. Okay, so that's your ops workload already already much higher than it was And then what happens if any one of them dies? Likely if it's a new project, you haven't really built it so the site can handle them going away. For example, you know, what should happen if Elasticsearch goes down is that your s your search has stopped working

24:56

And just your search. But of course, what happens is Elasticsearch is quite good at a lot of things. It's good at like doing big aggregate pages So you start being like, oh, we'll build the home page of search as well, and then we'll build all the sort of the sort of intermediate pages of search as well, then just have the main pages be the main view. And so suddenly you've made Elasticsearch a key a key key part of the site that if it goes down you consider a major loss. And similarly Redis, like say, oh no, we use Redis for our sessions. So if that goes away is a crucial part of the site. And what you've done is, unless you're very careful, you've made every single one of those backends into a single point of failure. Because if that thing goes away, some crucial part of the site dies. And even if you don't think it's crucial, like some other person, like if it's their pet project or like it's a very important marketing push or whatever, it might be crucial to them.

25:42

So you have to consider this thing where sure more data stores is generally fine and in particular if what your problem is isn't shaped to an existing store very well. So say like You know, you're trying to put a graph database, a graph, so a strong graph database into like Postgres. It can do that. It's not particularly great at it, but it can do it. So if it's a really big part of your site, like hey, we want to get performance improvements here, we want to have a proper store, that's fine. But it's not free. Every store you add is extra redundancy work, extra backups, you've got to make sure it's working and not running out of disk space, like I said before. You've then got to make sure that like it never goes down. And it's just making your surface area bigger. So like if you're a small project or company, really

26:30

try and use one or two things. It's a lot easier when it's three in the morning and one of them goes down because that happens like much much less of the time because there's only two of them. And if it's Redis or Postgre or something, those things are pretty much solid. And so like you're working on a lot less than than less reliable stores. MySQL. It's fine. It's fine. It's actually quite the one thing it's good at is staying up generally. And then this comes this comes to a different kind of optimism, which I've seen quite a lot, is We all have and Jang Django kind of encourages this. Django has a primary key field that is an auto-incrementing integer. In particular, uh It has this sort of wonderful thing that as you sort of add more records, it increments.

27:16

That's what auto-incrementing means. And so people go, that's great. So we have this number that goes up. That means that the highest value primary key is the most recent one. I mean that's not true, right? So first of all, if you're running that for precise ordering, well, different pages can go through to the back end different times. So like you should put timestamps on them, for example. If you're running a cluster, that's definitely not true. And that and clustering is a problem here because this works on a single instance, but you quickly realize that auto-increment is a really nasty feature to scale. Um to the point where Twitter wrote entire software stacks to do this for them to just make sure their numbers auto-incremented roughly correctly. Because if you think about it, if you've got a cluster of like 20 database storage servers, They have to all agree on what the next number is and they have to not pick the same number.

28:06

And so you end up all these solutions like well okay we'll we'll sign each of the each of the servers in the ring a number modulo 20 and so this one only does like assuming this is is kind of a a bad thing. And the other one I've I've particularly annoyed is IDs are numbers we can do maths on. No, they are not. They are opaque things, you should never treat as numbers. I have this big thing wherever I go, and I've been doing this internally in Eventbrite for a little while, is making making IDs as strict, even if they're numbers, making them strings. Because they are they are not numeric. You can't do maths on them, right? That doesn't make any sense. I can't add no I can't add three to an ID to

28:51

go somewhere. It's not even like a pointer where that might do something useful. It just is like, you know If you try and do pagination by adding numbers to IDs, what are you doing? Um I've seen occasions like people want to um inflate their IDs to look good. So for example, they might say they might start a brand new site and go Okay, we don't want to have like this this customer's page be ID one because it looks really bad for our customer. So we'll like we'll start at number 3804. But we'll do it all in the front end, so we'll subtract that number whenever WordPress comes in, we'll add it when it goes out. You see where that ends up, right? Um so yeah, like if you want if you want IDs but don't give away how many rows you have, use UU IDs. Like they're built into databases.

29:37

They're basically guaranteed not to collide. Like you can we have native support for them in Django 1. 8. It's it's great. You know, don't don't sit there and and be that person who does this. But of course we come on to my favorite part of the presentation. So I was at PyCon Ukraine many years ago, um three or four years ago, and I saw a wonderful talk. And the crux of the talk was, well, we have this database. It's Postgres. And Postgres Can run Python. Like why why are we wasting network overhead querying to and from the database doing doing views when we can just run it right there and save the overhead? And so Postgres has functions. Now I have some problems with functions and sort of standardized queries.

30:24

A lot of enterprise software in particular will write all of their complex operations as basically functions. That you can run. So to you know to add a page, you just call like select add page and then brackets stuff, stuff, stuff, stuff, stuff. And the idea is it's meant to encode all the complex logic into the database so it happens there. The problem is that you can't version control that code, right? That code isn't in version control, it's in the database somewhere. You need to put it in a migration system to have it version controlled and even then have it overwrite properly and it becomes a whole mess. But that's not even what this is about. This is about Python functions and people who are slightly over. And I will say I've never seen this in production, but it's such a great thing I had to show you anyway. So They're Python functions. You can import stuff in them. In particular, you can do templating these Python functions.

31:11

And so if you were say particularly um enthusiastic You could go, well, we can render the template straight in the database. Why would we, you know, why we could just use Ginger in in a Python function? And at this point you're thinking, well Andrew, that's that's a silly idea. No one would ever do that. Well Here is the code that does this. If you want to go to it, if you have if you want to have internet right now, I'm going to show it to you right now on the screen. So that that SQL file I just showed you, bitly slash y not pg um contains a schema and a function that renders HTML directly from the database. In particular you can just you do select render brackets template name page name it would and it um outputs uh here we are this you get this out. You just get a

31:56

column back, which is the rendered HTML of the page. And even better, because Postgres has regular expressions, you could put the UL routing into a select query that uses the regular expression from the page. It's awful. Uh I d uh that so the code the code is not to be used in production. This is a very express thing from me Um but I just thought so I like here's the here's the example of of that text. And like you know, it's pretty simple. We import Ginger. Um PL Python will just import things from your standard system Python path. We can just execute a select query against another table in PLPython. It's pretty great. It comes back as a dictionary of like column names to values. We then just select the template string from another table and then just pass that to Ginger. Like Ginger will take the dictionary, it takes the template name and just render the HTML.

32:44

It doesn't it doesn't know it's doing it in a database And you could do more. You could do views in here, you could do like custom saving stuff, you could do all manner of things. Don't, but you could. Um so yeah, that that that that's my my th and like the the presentation ended up with this uh j PyConvice was like d wh why why is this a thing? And it didn't come with quite as strong as a warning as this one does. So It's possible, don't do this, do it in Django. Django is written by mostly sensible people, uh myself excluded. But the k so the the crux of the thing is there's a reason behind every rule, right? Like there's a reason why people generally stray away from stored procedures and functions. There's a reason why we don't generally make a table per s individual type. And all of these have exceptions, right? Like you shouldn't

33:29

nothing in programming is a is a hard and fast rule. In particular A lot of people just won't believe things or you know technology changes and gets faster and we get better CPUs. So You should every piece of information or recommendation you get you should ask why. Like if somebody tells you don't do this, just ask, be like a small child, go why And just like 'cause like often there's reasoning there and it's very sensible reasoning. Like if you if you come into a company that's been around for like uh many years, they'll have certain internal processes and design patterns and often there's a good reason why things are written like they are. But as a new engineer, your instinct is to go and just go, this is all terrible, let's rewrite it. And you'll go into that that sort of pothole of, well actually, there's all these hidden requirements that you never realize and then

34:18

it'll take you like a year to rewrite it and then you end up with the same complex code as before. So just take a moment, ask why, or just if it's if it's a simple thing, just As I've done here, take the thing you think is good and just try it, see what happens, see what the eventual result is. Sometimes that's not possible because it requires a scale to break, but you can do like thought experiments and just like work through it in your head But just don't write market contents. Don't go, pshh, they don't know what they're doing. Don't like, you know, don't dismiss 40 years of database research. Don't dismiss people who know what they're doing, like you know, there there are many, many engineers at Eventbrite who are much better than I am, who have been around the industry and programming for a lot longer and like they're full of really interesting and useful information from very esoteric parts of programming that You know, there

35:03

's very good reasoning behind everything that they say, and sometimes there's not enough time to say why the thing is like that is, but sometimes there is. So to just ask why, basically. Thank you. So I believe we have some time for questions. So so the question is basically like going into company and not f really is fine, but like how do we get the knowledge I'm talking about here in the first place? How how do we get that embedded knowledge of Don't make tables of every translation and stuff. Um I it's a difficult problem. Like part of it is doing this kind of thing and talking to people and giving explanations But I think part of it also is like, you know, you can still go into a company and and be a positive influence.

35:49

You can be that person who asks why and occasionally the why will be we don't know why. And then they can go and ask why and you'll discover the thing is t is a terrible idea. So if you just sort of if you leave something better than when you joined, like it's a cleaning up rule, right? So you should ask why and you should try and come in because like there are things that you as a developer will know better than other developers, right? Every one of us had our specialty. We gain it slowly over the years, but does happen. And so there are things that you'll be better at. And so don't be afraid to ask why and then provide like, well, I do it like this way because of this. And I think slowly but surely you can improve institutional knowledge that way to make things better. Okay, so the question is what's my feeling on columns like JSON and HDOL and like schemas data types in columns in relational databases? I really like them.

36:35

I'm a big proponent of mixing schemas. So in particular, one of my big things I like to do on new projects and things where I can is In any given table, there are certain columns that are core, they're important, like the ID column, the name column, title column, created date. And there are columns that aren't as important. There are things that could be stored in JSON. So what I quite like doing is if you're going to query on the column, make it a full column. If it's just sort of a a user-creable thing, so a CMS is a great example of this, right? You can have a CMS where Every CMS page has a URL slug, it has a created date, it has an author, it has all this stuff, but they might have like a whole bunch of configurable things Put those in the JSON blob and put the core fields as table rows. And then you've got this this good combination of if you add all the like

37:21

the complex changing lot of the time stuff is in a nice blob, doesn't need schema updates And the core fields that you're querying and doing, like ordering against, are all in proper table rows. And so I think that's a great combination. And even without a proper JSON type, you can do it like in MySQL, as long as you're not querying into it. You can still store things at JSON blobs. You just can't necessarily query into them as efficiently as as as you otherwise could. But it's still a perfectly valid schema design, I think. Cool. Thank you very much.

Questions this talk answers

Why was Redis a bad choice for storing SpaceLog data?

The Redis design gradually recreated relational-database operations: pagination, range queries, lookups, and joins. A relational database would have performed those operations more directly and efficiently, but the team recognized the problem too late to change it during the one-week build.

Discussed at 5:36

Are database joins really bad, and what does a join actually do?

Joins can be slow when implemented naively, but a database can efficiently match records by collecting the relevant keys and building a lookup structure—essentially a hash join. Reimplementing that logic in application code usually adds network round trips and duplicates what the database already does.

Discussed at 7:42

What happens when Redis runs out of disk space while saving data?

Older Redis versions could continue serving requests from memory while failing to persist a new save file, without making the failure obvious. A later restart could therefore erase months of data, so database servers need ample disk space and monitoring for persistence failures.

Discussed at 11:27

Why are failed database writes dangerous when processing payments?

If a payment job marks an organizer as processing and later paid, but those writes silently fail, every run can continue finding the organizer as unpaid and send another payment. Money-handling workflows need proper locking and failure-safe coordination rather than relying on naive database updates.

Discussed at 13:20

Why can making a separate database table for every account type cause problems?

A profile page that needs to show many account types may require separate queries for every table and every row, turning a simple lookup into a large number of queries. A single table with a type column and an identifier can represent the different account types and allow one query.

Discussed at 16:24

What is a better database design for multilingual fields than adding a column for every language?

Adding language or locale columns can produce tables with thousands of columns, make unrestricted reads expensive, and make schema changes slow because existing rows may need rewriting. The talk recommends JSON or hstore data where appropriate, or an entity-attribute-value-style table storing the field, language, and value.

Discussed at 18:17

How can losing a cache bring down an otherwise well-designed website?

If application servers are sized for a high cache hit rate, losing the cache can multiply backend traffic—potentially by ten times in the example—and overwhelm the servers. Systems should retain spare capacity, scale quickly, disable nonessential features, or pre-warm caches before going fully live.

Discussed at 21:49

Why is using many different data stores risky for a small project?

Each additional store adds backup, redundancy, monitoring, disk-space, and operational work. It can also become a single point of failure when features gradually start depending on it, so small projects should generally limit themselves to one or two stores unless a specialized store solves a real problem.

Discussed at 24:11

Why shouldn’t auto-incrementing IDs be used for ordering or treated as numbers?

Auto-incrementing IDs do not reliably represent creation order, especially across multiple database servers, and coordinating them across a cluster is difficult. IDs are opaque identifiers rather than values for arithmetic or pagination; UUIDs are a better choice when exposing row counts or sequential IDs is undesirable.

Discussed at 27:16

Why shouldn’t application templates be rendered inside a PostgreSQL database?

PostgreSQL can run Python functions and technically render templates, but putting application logic in the database makes it difficult to manage and version, and it mixes responsibilities in a fragile way. The speaker demonstrates that it is possible but explicitly recommends rendering templates in Django instead.

Discussed at 31:04

How should developers evaluate database design rules and advice?

Developers should ask why a rule exists and understand the failure or operational problem behind it instead of dismissing established practices. Rules are not absolute, but testing assumptions with examples or thought experiments can reveal why patterns such as normalization and limited use of data stores are common.

Discussed at 33:09

Presenters

Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.

More videos by Andrew Godwin

More videos from DjangoCon US