Hello! Welcome back to our course on system design.
In our last lesson, we established the fundamental trade-off between normalization and denormalization, concluding that we should start with a normalized model and denormalize strategically to optimize for read-heavy workloads. We answered the why and the when.
Today, we'll focus on the how. This lesson is dedicated to the practical techniques you can use to denormalize a data model effectively. Your product design background involves making decisions to optimize a user's experience; denormalization is similar, as we are intentionally redesigning our data structure to optimize the "experience" for our application when it needs to retrieve information quickly.
Our learning outcome is to design denormalization strategies to optimize read-heavy workloads. We will explore specific techniques, see how they are applied in real-world systems, and discuss how to manage the complexities they introduce.
A Quick Recap
From our previous lesson, remember:
- Normalization minimizes data redundancy to protect data integrity, making it ideal for write-heavy systems. The trade-off is slower reads due to
JOINoperations. - Denormalization intentionally adds redundancy to eliminate
JOINs, making it perfect for read-heavy systems. The trade-off is slower updates, increased storage, and the risk of data inconsistency.
1. The Goal: Performance by Avoiding Joins
The primary motivation for denormalization is to avoid performing expensive JOIN operations at query time. In a highly normalized database, a single request might require the database to look up and combine data from several different tables. While modern databases are very good at this, at massive scale, the cost of these joins becomes a significant performance bottleneck.
By denormalizing, we are essentially doing the "joining" work ahead of time, storing the combined data in a ready-to-read format.
Let's watch a video that clearly explains this core principle.
Learn Database Denormalization
This video from Decomplexify provides an excellent deep dive into why denormalization is used for performance, focusing specifically on the goal of eliminating joins.
Please watch the segments on 'Denormalization for Performance' (07:51 - 11:36) and 'Denormalization to Avoid Joins' (11:36 - 15:38). Focus on: The distinction between the 'logic layer' and the 'processing layer'. The Task/Subtask example and how adding Task_Status_Code to the Subtask table speeds up reads but slows down updates.
The video makes a crucial point: denormalization is a trade-off. We make reads faster at the expense of making writes more complex and potentially slower. Now, let's explore the specific techniques for implementing this.
2. Core Denormalization Techniques
There are several established strategies for denormalizing a data model. We'll focus on the most common ones that you'll encounter and use in system design.
The following article provides excellent, clear examples of these techniques.
Normalize vs. Denormalize Database: Key Differences
This article from SolarWinds, 'Normalize vs. Denormalize Database', clearly outlines the most common denormalization techniques with simple SQL examples. You don't need to be a SQL expert; focus on the structure of the tables.
Please read the section 'Denormalization implementation'. We will be discussing each of the techniques mentioned: Data duplication, Summary tables, Materialized views, and Pre-aggregated data.
Let's break down those techniques with some more context.
a) Duplicating Columns (Embedding Data)
This is the most straightforward technique. You copy a column from one table into another to avoid a JOIN.
- Example: The article shows copying
customer_addressfrom thecustomerstable into theorderstable. When you fetch an order, you don't need to join with thecustomerstable just to get the shipping address. - Use Case: Ideal for data that is frequently needed together but changes infrequently. For instance, the shipping address for a completed order will never change.
b) Pre-calculating Aggregates and Summary Tables
Instead of calculating values like counts, sums, or averages on the fly, you compute them beforehand and store the results.
- Example: A social media profile page needs to show the number of followers. Counting followers from a massive
followerstable every time the profile is loaded is inefficient. Instead, you can have afollower_countcolumn in theuserstable that is updated whenever someone follows or unfollows. - Use Case: Perfect for analytics dashboards, user profile stats, or any scenario that requires aggregated data. The article's
sales_summarytable is a classic example for a business intelligence dashboard.
c) Materialized Views
This is a more advanced database feature that automates the creation of summary tables. A materialized view is like a saved query whose results are stored physically on disk.
- Example: The article's
sales_reportmaterialized view pre-joins and aggregates sales and product data. When you query the view, you are reading from this pre-computed table, which is extremely fast. - How it works: The database itself handles refreshing the view's data based on a schedule (e.g., every hour) or when the underlying source data changes.
- Use Case: Complex reporting queries that are run frequently but can tolerate slightly stale data (depending on the refresh schedule).
3. Real-World Applications
Denormalization is not just a theoretical concept; it's the backbone of many high-performance systems you use every day.
Denormalized Data Explained: Boost Database ...
This article from Zenduty, 'Denormalized Data Explained', provides several concrete examples of where these techniques are used.
Read the section 'Real-World Applications of Denormalized Data'. Notice how the techniques we just discussed (duplication and pre-calculation) are applied in e-commerce, analytics, and social media.
Let's connect the techniques to the applications:
- E-commerce Product Page: Duplicates the
category_nameand pre-calculates theaverage_ratingand stores them in theproductstable. - User Profiles: Pre-calculates and stores
follower_countandtotal_postsin theuserstable. - Analytics Dashboards: Uses summary tables to store pre-aggregated metrics like
daily_user_counts.
4. Managing the Downside: Data Consistency
Denormalization's greatest weakness is the risk of data inconsistency. If you have multiple copies of data, you must have a strategy for keeping them all in sync.
The Challenge
Imagine you have denormalized author_name into a posts table. If the author changes their name, you must update it in the authors table AND in every single post they have ever written. Missing even one update leads to inconsistent data.
Strategies for Updating Denormalized Data
-
Synchronous Updates: Update the denormalized data in the same transaction as the source data.
- How: When an author's name is updated, the application logic also issues update commands for all their posts before confirming the change.
- Pros: Data is always consistent.
- Cons: This can make the initial write operation very slow and complex, defeating some of the purpose of optimizing for performance.
-
Asynchronous Updates (Eventual Consistency): Update the source data first, then update the denormalized copies in the background.
- How: When an author's name is updated, the application just updates the
authorstable. A separate background process (e.g., triggered by a message queue or a scheduled job) is responsible for finding and updating all the relevant posts later. - Pros: The initial write operation remains very fast.
- Cons: There is a short window of time where the denormalized data is stale. This is known as eventual consistency. For many applications (like a name change appearing on old blog posts), this delay is perfectly acceptable.
- How: When an author's name is updated, the application just updates the
Denormalized Data Explained: Boost Database ...
The Zenduty article also provides a good summary of the challenges and best practices for managing them.
Read the sections 'Important Factors When Denormalizing Data' and 'Best Practices for Denormalization'. Pay close attention to the advice on designing robust update mechanisms.
Conclusion
You now have a toolkit of specific strategies for denormalizing a data model to achieve high read performance. The key is not just to apply these techniques, but to do so thoughtfully, with a clear plan for managing the resulting complexity.
Key Takeaways:
- The primary goal of denormalization is to eliminate expensive
JOINs at read time. - Common strategies include duplicating columns, pre-calculating aggregates into summary tables, and using materialized views.
- These techniques are widely used in read-heavy applications like e-commerce sites, analytics dashboards, and social media feeds.
- The biggest challenge is maintaining data consistency. You must have a robust update strategy, choosing between synchronous updates (for strong consistency) and asynchronous updates (for fast writes and eventual consistency).
Preview of the Next Lesson:
We've discussed how to model and store our data. But in large-scale systems, data has a lifecycle. What do we do with data that's months or years old? In our next lesson, we will design data retention and archival strategies, exploring how to manage data over time to control costs and maintain performance.
Can't find a good explanation? Sign up and we'll make it for you
Sign up