Notion's analytics pipeline was choking, and the instinct was "buy a bigger warehouse." The actual problem was that they were re-copying 100% of their data to capture the 1% that changed.
Notion is a database of blocks — every paragraph, toggle, and checkbox is a row. Across 480 shards, the workload has a peculiar shape: on any given day, only about 1% of blocks get updated. But they get updated constantly. It's an update-heavy, not insert-heavy, world. And their off-the-shelf pipeline — Fivetran into Snowflake — was built on an assumption that quietly didn't hold.
To analyse your data, you copy it from the live product database into a separate "warehouse" built for big queries. The simple way to keep the warehouse fresh is to periodically re-copy the tables. That works beautifully when data mostly gets added.
Notion's data mostly gets edited. So a re-copy meant hauling enormous tables across the network to capture a sliver of genuine change — like photocopying an entire book every night because someone fixed three typos. Worse, a Notion-specific query (walking the permission tree up through nested pages) was so heavy it timed out in the warehouse entirely.
Stop copying tables. Start shipping changes.
The fix is a technique called Change Data Capture (CDC). Instead of asking the database "what does this table look like now?", you tap into its write-ahead log — the running journal every database keeps of every change — and stream only the deltas: this row inserted, that row updated, this one deleted. You move 1% of the data because only 1% changed.
Notion built an S3 data lake fed by this pipeline: Debezium connectors read the Postgres write-ahead log (one connector per Postgres host), publishing to Kafka with one topic per table across all 480 shards. From Kafka, the changes land in Apache Hudi — a storage format designed for exactly this update-heavy case, using a COPY_ON_WRITE / UPSERT model to merge incoming changes into existing files without rewriting everything. Spark runs analytics on top.
Why Hudi and not plain files on S3? Data lakes are historically append-only — great for logs, terrible for updates. Hudi adds upsert and delete semantics to the lake, which is the precise capability Notion's 1%-churn workload demanded. The database's shape dictated the storage engine, not the other way around.
Worth knowing
The dollar figure is the part executives hear, but the durable lesson is architectural: ingestion dropped from over a day to minutes-to-hours because the work scaled with change, not with size. When your pipeline's cost is proportional to total data volume instead of to how much actually changed, you don't have a capacity problem — you have a design mismatch.
The gap it reveals
Plenty of engineers can name CDC. Fewer can articulate when it's the right call: an update-heavy workload where re-snapshotting wastes effort proportional to the data you didn't touch. And fewer still connect that to needing a lake format like Hudi because vanilla data lakes can't handle updates. Recognising that the access pattern (1% churn) dictates the entire pipeline is the gap.
In the interview room
In a "design an analytics pipeline" round, the differentiator is asking one question: "is the source data insert-heavy or update-heavy?" If it's update-heavy, batch re-snapshots are the wrong tool, and CDC into an upsert-capable store is the move. Saying that unprompted tells the interviewer you reason from the workload, not from a menu of buzzwords.
The reframe
The seductive answer to a slow pipeline is always "more horsepower." Notion's win came from the opposite instinct: looking at the shape of their data and noticing the pipeline was doing 100x the necessary work. The breakthrough wasn't a faster warehouse. It was matching the architecture to the fact that almost nothing changes — but what changes, changes a lot.
Don't scale the work. Question whether the work needs doing at all.
Primary source →
notion.com — Building and Scaling Notion's Data Lake