DEV Community

Cover image for How Do You Delete Billions of WhatsApp Statuses Every Day Without Bringing Your Database to Its Knees?
Ravi Vishwakarma
Ravi Vishwakarma

Posted on

How Do You Delete Billions of WhatsApp Statuses Every Day Without Bringing Your Database to Its Knees?

Imagine you run a messaging app used by billions of people.

Every day, people upload photos, videos, and text as Status updates. But there is one important rule: a Status should disappear after about 24 hours.

Now imagine you have to clean up billions of expired Statuses every day.

Your first thought might be:

“Easy. Just run a query that deletes everything older than 24 hours.”

Something like:

DELETE FROM statuses
WHERE expires_at < NOW();
Enter fullscreen mode Exit fullscreen mode

Simple, right?

Unfortunately, at WhatsApp's scale, this can be a terrible idea.

A giant delete can put enormous pressure on the database, causing slow queries, high disk usage, replication delays, locks, and even outages.

So how do large systems actually approach this problem?

Let's understand it from the ground up.


1. First: What Is a WhatsApp Status?

A Status is basically a temporary piece of content.

For example:

  • Alice posts a photo at 10:00 AM.
  • Bob posts a video at 2:00 PM.
  • Carol posts some text at 8:00 PM.

After roughly 24 hours, these Statuses are no longer supposed to be visible.

From a database perspective, you might store something like:

status_id user_id content created_at expires_at
101 Alice photo.jpg 10:00 10:00 next day
102 Bob video.mp4 14:00 14:00 next day
103 Carol "Hello!" 20:00 20:00 next day

The database now has a simple job:

Find expired records and remove them.

But there's a catch.

At massive scale, there can be an enormous number of expired records.


2. Why Can't We Just Delete Everything at Once?

Suppose there are 1 billion expired records.

You might write:

DELETE FROM statuses
WHERE expires_at < NOW();
Enter fullscreen mode Exit fullscreen mode

The database now has to find and delete a huge number of rows.

That can cause several problems.

Problem #1: Huge amount of work

Deleting a row isn't always as simple as removing a line from a file.

The database may need to:

  1. Locate the row.
  2. Modify indexes.
  3. Write transaction information.
  4. Update storage structures.
  5. Replicate the changes.
  6. Eventually reclaim the space.

Multiply that by billions.

That's a lot of work.


3. The Database Has Other Jobs Too

This is perhaps the most important idea for beginners.

Your database isn't sitting around waiting for the cleanup job.

While the cleanup system is deleting old Statuses, users are still:

  • uploading new Statuses,
  • viewing Statuses,
  • sending messages,
  • opening chats,
  • checking contacts.

The database needs to handle those requests too.

Think of a restaurant.

Imagine a restaurant has one kitchen.

During dinner, hundreds of customers are ordering food.

Now someone says:

“Before serving any more customers, let's completely clean the kitchen.”

The kitchen might become extremely busy cleaning instead of cooking.

A massive database deletion can create a similar problem.


4. The Basic Solution: Delete in Small Batches

Instead of deleting one billion rows in one operation, divide the work into small pieces.

For example:

DELETE FROM statuses
WHERE expires_at < NOW()
LIMIT 10,000;
Enter fullscreen mode Exit fullscreen mode

Then repeat.

Conceptually:

Delete 10,000
       ↓
Delete 10,000
       ↓
Delete 10,000
       ↓
Delete 10,000
       ↓
...
Enter fullscreen mode Exit fullscreen mode

Now the database gets many small jobs instead of one enormous job.

This is called batch deletion.


5. Why Batches Are Better

Suppose you need to remove 100 million records.

Instead of:

100,000,000 rows
       ↓
ONE MASSIVE DELETE
       ↓
Database suffers
Enter fullscreen mode Exit fullscreen mode

you could do:

10,000 rows
10,000 rows
10,000 rows
10,000 rows
...
Enter fullscreen mode Exit fullscreen mode

Each transaction is relatively small.

That means:

  • transactions finish faster,
  • locks are held for less time,
  • memory pressure is lower,
  • replication can keep up more easily,
  • other queries get opportunities to run.

The goal isn't merely to delete data.

The goal is to delete data without disturbing the rest of the system.


6. But There's Another Problem

You might now say:

“Fine. We'll delete 10,000 rows at a time.”

But how do we find those 10,000 expired rows?

Suppose the table looks like this:

statuses
--------------------------------
id
user_id
content
created_at
expires_at
Enter fullscreen mode Exit fullscreen mode

If expires_at isn't indexed, the database may have to scan a huge portion of the table to discover which rows are expired.

That's expensive.

So we usually want an index.

For example:

CREATE INDEX idx_status_expiry
ON statuses(expires_at);
Enter fullscreen mode Exit fullscreen mode

Now the database can efficiently find records whose expiration time has passed.

Think of an index like the index at the back of a textbook.

Without the index, you might have to read every page to find a topic.

With the index, you can jump closer to where the information lives.


7. Don't Delete Randomly — Delete in Order

There's another optimization.

Suppose expired records have IDs:

1001
1002
1003
1004
...
Enter fullscreen mode Exit fullscreen mode

Instead of repeatedly asking:

“Give me any 10,000 expired rows.”

we can process them in a predictable order.

For example:

DELETE FROM statuses
WHERE expires_at < NOW()
ORDER BY expires_at
LIMIT 10,000;
Enter fullscreen mode Exit fullscreen mode

Or, in some database designs, we can use an ID/cursor-based approach.

Conceptually:

Start at record 0
       ↓
Process 10,000
       ↓
Remember where we stopped
       ↓
Process the next 10,000
       ↓
Repeat
Enter fullscreen mode Exit fullscreen mode

This is often called cursor-based processing or keyset pagination.


8. The Secret Weapon: Don't Wake Up Every Millisecond

Here's another beginner mistake.

You might create a worker that constantly asks:

"Anything expired?"
"Anything expired?"
"Anything expired?"
"Anything expired?"
...
Enter fullscreen mode Exit fullscreen mode

That's unnecessary.

Instead, a cleanup system can work on a schedule.

For example:

12:00 → clean expired data
12:01 → clean expired data
12:02 → clean expired data
...
Enter fullscreen mode Exit fullscreen mode

Or it can continuously process a queue at a controlled rate.

The important concept is backpressure.


9. What Is Backpressure?

Backpressure simply means:

Slow down when the database is under heavy load.

Imagine your cleanup worker can delete 100,000 records per second.

But the database is already extremely busy.

Instead of saying:

“I must delete 100,000 per second!”

the cleanup system can say:

“The database is busy. I'll reduce my deletion rate.”

For example:

Database healthy
→ delete faster

Database busy
→ delete slower

Database overloaded
→ pause cleanup temporarily
Enter fullscreen mode Exit fullscreen mode

This is extremely useful in large distributed systems.

Cleanup is important.

But serving users is usually more important.


10. Even Better: Use a Queue

Instead of having one giant cleanup process constantly scanning the database, you can use a queue.

Imagine every Status gets an expiration event.

When Alice creates a Status:

Alice creates Status
        ↓
Status expires in 24 hours
        ↓
Create expiration task
        ↓
Queue
Enter fullscreen mode Exit fullscreen mode

Later:

Queue
 ↓
Worker
 ↓
Delete Status
Enter fullscreen mode Exit fullscreen mode

You can have many workers processing the queue.

For example:

                 ┌── Worker 1
Queue ───────────┼── Worker 2
                 ├── Worker 3
                 └── Worker 4
Enter fullscreen mode Exit fullscreen mode

Now cleanup can scale horizontally.

Need more capacity?

Add more workers.

Need less capacity?

Remove workers.


11. But What If a Worker Crashes?

Distributed systems assume something will eventually fail.

A worker might crash halfway through its work.

That's why cleanup jobs should generally be idempotent.

Don't worry about the fancy word.

It basically means:

Running the same cleanup operation again should not cause a disaster.

For example:

Delete Status 123
Enter fullscreen mode Exit fullscreen mode

If Status 123 is already gone, trying to delete it again shouldn't break the system.

This makes retries much safer.


12. A Very Important Idea: Soft Delete vs Hard Delete

There are actually two different meanings of "delete."

Soft delete

Instead of physically removing the row, mark it as deleted:

deleted = true
Enter fullscreen mode Exit fullscreen mode

The application simply ignores deleted records.

Hard delete

Actually remove the database record.

For example:

DELETE FROM statuses
WHERE id = 123;
Enter fullscreen mode Exit fullscreen mode

Large systems may sometimes use a combination of both approaches.

For example:

Status expires
      ↓
Immediately stop showing it
      ↓
Physical cleanup happens later
Enter fullscreen mode Exit fullscreen mode

This is useful because the user-facing requirement is:

“Don't show expired content.”

It doesn't necessarily mean:

“The physical database bytes must disappear at exactly 24:00:00.”

Those are two different requirements.


13. This Difference Is Huge

Suppose a Status expires at:

10:00:00
Enter fullscreen mode Exit fullscreen mode

The application can immediately treat it as unavailable:

expires_at <= current_time
Enter fullscreen mode Exit fullscreen mode

So the user can't see it anymore.

The physical deletion might happen at:

10:02
10:05
10:20
11:00
Enter fullscreen mode Exit fullscreen mode

depending on system load.

That's often perfectly acceptable.

This gives the cleanup system flexibility.


14. What About Photos and Videos?

There's another important detail.

The database might not actually contain the entire photo or video.

A database record might simply contain metadata:

status_id
user_id
storage_location
created_at
expires_at
Enter fullscreen mode Exit fullscreen mode

The actual video could live in an object-storage system.

So deleting a Status may involve multiple steps:

Database record
      +
Media object
      +
Caches
      +
Indexes
      +
Metadata
Enter fullscreen mode Exit fullscreen mode

You need to make sure all of these eventually get cleaned up.


15. Don't Delete Everything at the Exact Expiration Time

Here's a subtle but important design idea.

Imagine 100 million people uploaded Statuses between 10:00 AM and 10:01 AM.

Twenty-four hours later, a huge number of objects become eligible for deletion at roughly the same time.

If your system tries to delete all of them immediately, you get a thundering herd.

Instead, you can spread the cleanup work out.

For example:

10:00 → 100,000 deletions
10:01 → 100,000 deletions
10:02 → 100,000 deletions
...
Enter fullscreen mode Exit fullscreen mode

The work is distributed over time.

This makes the system much smoother.


16. Partitioning Can Help Too

At very large scale, a single enormous table can become difficult to manage.

One technique is partitioning.

Imagine splitting Status data by time:

statuses_2026_08_20
statuses_2026_08_21
statuses_2026_08_22
statuses_2026_08_23
Enter fullscreen mode Exit fullscreen mode

Now old data is grouped together.

Instead of deleting billions of individual rows one by one, a database may be able to remove or detach an entire old partition.

Conceptually:

Old partition
      ↓
No longer needed
      ↓
Drop partition
      ↓
Large amount of data removed efficiently
Enter fullscreen mode Exit fullscreen mode

This can be dramatically cheaper than row-by-row deletion, depending on the database architecture.

However, partitioning isn't a magic solution. It has its own design and operational trade-offs.


17. A Realistic Architecture

A simplified large-scale design might look like this:

              User creates Status
                       │
                       ▼
                 Status Service
                       │
             ┌─────────┴─────────┐
             ▼                   ▼
        Database             Media Storage
             │
             ▼
      Expiration Scheduler
             │
             ▼
       Cleanup Queue
             │
       ┌─────┼─────┐
       ▼     ▼     ▼
    Worker Worker Worker
       │     │     │
       └─────┼─────┘
             ▼
        Batch Delete
Enter fullscreen mode Exit fullscreen mode

The key idea is that expiration and physical deletion are separate concerns.


18. What Happens If the Database Is Overloaded?

A good cleanup system should be able to say:

“Not now.”

For example:

Cleanup worker
      ↓
Check database health
      ↓
Is database overloaded?
   /          \
 Yes           No
  ↓             ↓
Slow down    Continue
or pause     deleting
Enter fullscreen mode Exit fullscreen mode

This is a crucial production principle:

Background work should be a good citizen.

User-facing traffic gets priority.

Cleanup can wait.


19. Monitoring Is Just as Important

You don't want to build the cleanup system and forget about it.

You need metrics.

For example:

Expired items waiting

50 million
Enter fullscreen mode Exit fullscreen mode

Cleanup rate

200,000 items/sec
Enter fullscreen mode Exit fullscreen mode

Database CPU

45%
Enter fullscreen mode Exit fullscreen mode

Queue size

2 million jobs
Enter fullscreen mode Exit fullscreen mode

Cleanup latency

Average: 3 minutes
Enter fullscreen mode Exit fullscreen mode

Now you can see whether the system is keeping up.


20. What If Cleanup Falls Behind?

Suppose:

New expired Statuses:
1 billion/day

Cleanup capacity:
800 million/day
Enter fullscreen mode Exit fullscreen mode

You're losing the race.

The queue will grow forever.

So the system needs to scale.

Possible solutions include:

  • increase worker count,
  • increase batch size carefully,
  • optimize indexes,
  • partition the data,
  • move cleanup to replicas or specialized stores where appropriate,
  • spread work over a longer window,
  • improve storage architecture.

The important thing is to measure the system instead of guessing.


21. The Beginner's Mental Model

You don't need to understand every database engine to understand the core idea.

Think of expired Statuses as garbage.

Imagine a city produces billions of pieces of garbage every day.

You wouldn't wait until the end of the month and tell one truck:

“Collect everything.”

You'd probably use:

  • many trucks,
  • scheduled pickups,
  • different neighborhoods,
  • manageable loads,
  • monitoring,
  • extra capacity during busy periods.

Database cleanup works in a similar way.

Don't create one giant cleanup operation. Build a controlled garbage-collection system.


22. The Big Lessons

If you remember only a few things, remember these:

1. Don't perform gigantic deletes

Break the work into batches.

10,000
10,000
10,000
...
Enter fullscreen mode Exit fullscreen mode

2. Make finding expired data cheap

Indexes on expiration-related fields can be important.

3. Don't block user traffic

Cleanup should run as background work.

4. Use queues and workers when appropriate

They allow cleanup to scale horizontally.

5. Use backpressure

When the database is busy, slow down.

6. Consider partitioning

Time-based data can sometimes be efficiently managed through partitions.

7. Separate logical expiration from physical deletion

A Status can stop being visible immediately while physical cleanup happens later.

8. Make cleanup retry-safe

Failures are normal in distributed systems.

9. Monitor everything

Know how much data is waiting and how quickly you're processing it.


Final Picture

So, how can a service like WhatsApp deal with billions of temporary Statuses?

The answer isn't one magical SQL query.

It's an architecture.

Instead of:

Billions of expired records
          ↓
     ONE DELETE
          ↓
     💥 Database
Enter fullscreen mode Exit fullscreen mode

you want something closer to:

              Expired Statuses
                     │
                     ▼
              Cleanup Queue
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
       Worker      Worker      Worker
          │          │          │
          └──────────┼──────────┘
                     ▼
               Small Batches
                     │
                     ▼
              Database / Storage
Enter fullscreen mode Exit fullscreen mode

The trick is not to make the database delete billions of things instantly.

The trick is to make the system continuously and safely remove expired data at a controlled rate.

That's one of the fundamental lessons of large-scale backend engineering:

At massive scale, the question isn't just “Can we do this?” It's “Can we do this continuously without hurting everything else?”

Top comments (0)