---
title: "Lightweight deletes for Tinybird Data Sources now in Beta"
excerpt: "A new endpoint exposes ClickHouse's lightweight DELETE functionality in Tinybird: pick between a synchronous call that blocks until the delete is done, or an asynchronous one that you poll for partition progress."
authors: "Jesús Botella"
categories: "Product updates"
createdOn: "2026-06-23 12:00:00"
publishedOn: "2026-06-30 12:00:00"
updatedOn: "2026-06-30 12:00:00"
status: "published"
---

Tinybird's ability to delete data predates ClickHouse{% sup %}®{% /sup %}'s lightweight delete functionality. Back when we shipped `POST /v0/datasources/{name}/delete`, the only way to delete data in ClickHouse was through a heavy mutation that rewrites parts on disk, and we exposed it in a safe way: it has worked fine for years, but it has never been cheap to run.

This was improved in ClickHouse via a native lightweight DELETE. Today we're exposing it in beta through a new endpoint, `POST /v1/datasources/{name}/delete`.

## How Tinybird deletes used to work

`POST /v0/datasources/{name}/delete` runs an `ALTER TABLE … DELETE WHERE …` mutation under the hood. ClickHouse schedules it in the background, iterates through every part that could contain matching rows, rewrites each one without the rows that match the condition, and swaps the new parts in.

The result is durable and consistent, but expensive. The more partitions your condition touches, the more I/O and CPU the cluster has to spend just to remove a handful of rows. Because the mutation can take a while, the API returns a `delete_data` job that you poll until it reports `done`.

For the occasional cleanup, that's fine. For GDPR requests, surgical fixes after a bad ingest, or anything where you just want the row gone fast, the part-rewrite was hard to justify.

## A new lightweight delete instead

The new endpoint runs `DELETE FROM … WHERE … SETTINGS enable_lightweight_delete = 1`. Instead of rewriting the affected parts, the ClickHouse database materializes a hidden `_row_exists` mask and reuses the unchanged column files in place, without copying any data. Queries filter those rows out through the mask at read time. The physical rows get dropped later when background merges run.

The practical difference:

- Deletes are acknowledged in milliseconds to a few seconds, not minutes or hours.
- The mutation only materializes the `_row_exists` mask and reuses the rest of the column files in place, so the cluster isn't paying for a full part rewrite when you only wanted to mark a few rows gone.
- Queries stop seeing the deleted rows as soon as the mutation finishes.

Same `delete_condition` semantics as `/v0`, plus a new optional `partition` parameter for when you want to scope the operation to a single partition (or a few).

## Sync or async, your call

Two modes:

**Synchronous (default).** The call holds the connection open until the database confirms the mutation is done, then returns `rows_affected` and a `mutation` block describing the partitions touched. This is the mode you want for cleanup scripts, right-to-be-forgotten requests, and one-off fixes. Send the request and get the acknowledgement. The wait is capped at 60 seconds; if the mutation runs longer, you get a `200 OK` with `mutation.is_done = false` and you can keep tracking it through the `system.mutations` table.

One caveat on `rows_affected`: the ClickHouse database doesn't expose the affected row count for lightweight deletes, so Tinybird computes it by counting the rows that match `delete_condition` immediately before issuing the delete. Treat it as a best-effort number, not a strict guarantee.

**Asynchronous (`wait=false`).** The call returns immediately with a job envelope. Poll the job through the [Jobs API](https://www.tinybird.co/docs/api-reference/jobs-api) and each poll surfaces partition-level progress as the ClickHouse database works through them. Reach for this mode when the delete is large enough that you'd rather not hold a connection open, or when you already have a worker polling jobs and want to plug lightweight deletes into the same workflow.

## When does it come handy

- **GDPR and right-to-be-forgotten.** Filter by user id, send the request, and your matching rows would be deleted.
- **Removing errors, tests or dirty data.** Bad ingest, wrong source, dev traffic leaking into production
- **Partition-scoped cleanups.** Combine `delete_condition` with `partition` to narrow the delete down to specific partitions, so the ClickHouse database doesn't even consider parts in partitions you need to remove data from.

## Try it

From the Tinybird CLI:

```bash
tb datasource delete events --sql-condition "user_id = 42" --lightweight-delete
```

Or directly against the API:

```bash
curl \
  -H "Authorization: Bearer <token_with_ADMIN_scope>" \
  -X POST "https://api.tinybird.co/v1/datasources/events/delete" \
  -d delete_condition="user_id = 42"
```

Step-by-step examples live in [Replace and delete data](https://www.tinybird.co/docs/forward/guides/replace-and-delete-data#lightweight-delete). The full parameter list is in the [Data Sources API reference](https://www.tinybird.co/docs/api-reference/datasource-api).

If you try it, [tell us how it goes](mailto:support@tinybird.co) or join our [Slack community](https://www.tinybird.co/community). We're especially interested in places the lightweight path would help but doesn't quite cover what you need yet.
