It is Friday afternoon. An upstream team ships a harmless-looking change: they add a region field to the events your service already emits, and somewhere along the way, a sensor starts reporting temperature as "72F" instead of 72. You did not write that producer. You do not control its release schedule. And yet by Monday, your ingestion is either dropping those records on the floor or flat-out stalling. Every data engineer has lived some version of this, and now has nightmares reliving this data disruption.
The fundamental question when designing these pipelines is: āHow strict should ingestion be about the shape of your data?ā
Today, we are introducing the Zerobus Rescue Column (Beta), offering a way to answer that question in a way that makes sense for your organization. This is a designated Variant type column used to ācatchā any extra or schema-mismatched fields.
I have heard both arguments, and honestly, both have merit.
One camp says, "I don't want to stream garbage into my lakehouse. Gate it. If a record doesn't meet my contract, fail the producer; it's their problem."
The other camp says, "Accept everything. I would rather capture a messy record now and clean it up later than lose it forever. I donāt control the producers, but Iām responsible for ensuring data lands."
Most teams live somewhere on this spectrum, and where they sit changes based on how responsibilities are delegated across your organization. The design goal for Zerobus was never to pick a side for you. It was to let you choose and to make that choice a property of your table rather than the ingestion service, because we know you (as the data engineer) cannot change the political cards; they are simply dealt to you.
Your Delta table schema is the contract. Zerobus validates every incoming record against that contract, and you decide how strict or how accepting the contract is. Think of it as a choose-your-own-adventure. The same service can enforce a rigid schema, accept a flexible subset of columns, or catch everything that does not fit, depending entirely on how you define your table.
Zerobus Ingest holds three fundamental principles:
That last principle is the one people underestimate: Zerobus is predictable by design. Its number one job is to land your data durably and predictably in the lakehouse. If it quietly reshaped your table every time a producer sent something unexpected, it would not be predictable for downstream consumers. Everything below is built on top of that guarantee.
Make every column nullable. Producers can send any subset of the columns, and anything they omit is written as NULL.
CREATE TABLE main.default.air_quality (
device_name STRING,
temp INT,
humidity INT);
This is flexible about what you omit, but still strict about what you add.
Mark columns NOT NULL to require them. Every record must supply those fields, or it is rejected. This is the strict end of the spectrum, and NOT NULL is your gate.
CREATE TABLE main.default.air_quality (
device_name STRING NOT NULL,
temp INT NOT NULL,
humidity INT);
This is the answer for the first camp. If you want to guarantee that garbage never lands, NOT NULL is the lever.
Add a VARIANT rescue column to capture the fields that do not fit, instead of rejecting the record. Fields that match the table are written to their own columns as usual; any extra or non-conforming fields are grouped into the rescue column as a JSON object. This is the most accepting end of the spectrum, with nothing lost to rejection.
This is the answer for the second camp. It is also the new capability we are introducing today.
Earlier this year, we added Variant type support so you could ingest semi-structured JSON without pinning down every field in advance. The rescue column is the natural extension of that work.
The distinction is one of intent:
You can schematize less up front and still keep that insurance policy. Anything extra a producer sends lands in the rescue column instead of being lost, and you keep the durability guarantee that matters most.
There is no magic column name. You designate a column as the rescue column by giving it three properties:
Zerobus routes each field in a record based on how it fits the table:
|
Field in the record |
Outcome |
|---|---|
|
Matches a column by name and type |
Written to that column normally |
|
Not present in the table schema |
Captured in the rescue column |
|
Present in the schema, but the value's type doesn't match and the target column is nullable |
Captured in the rescue column |
Here is the whole thing in one example. Consider a table where rescue is nullable, uses VARIANT, and carries the zerobus-rescue tag:
CREATE TABLE main.default.air_quality (
device_name STRING NOT NULL,
temp INT,
humidity LONG,
rescue VARIANT
);
Now ingest a record with both a type mismatch and an extra field:
{
"device_name": "sensor-1",
"temp": "72F",
"humidity": 87,
"extra_field": "some value"
}
The result:
| device_name | temp | humidity | rescue |
|---|---|---|---|
| sensor-1 | null | 87 | {"extra_field": "some value", "temp": "72F"} |
Both non-conforming fields, the mistyped temp and the unknown extra_field, are grouped into the rescue column's JSON object. That Friday-afternoon change from the opening? It lands cleanly, and you still have every byte to reconcile later.
A few things to keep in mind as you design tables around this:
Our whole philosophy is the following: Zerobus makes your data durable and is reliable when it comes to landing your data. You decide how much to schematize along the way.
Your adventure, your rules.
Ready to try it? Check out the Zerobus rescue column documentation to design your contract. Be sure to enable the Beta feature first.
Have questions, or want to share how you are shaping your schemas with Zerobus? Join the discussion below!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.