<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>article Try the new Zerobus Rescue Column: Choose-Your-Own-Adventure Schema Management  (Beta) in Technical Blog</title>
    <link>https://community.databricks.com/t5/technical-blog/try-the-new-zerobus-rescue-column-choose-your-own-adventure/ba-p/164413</link>
    <description>&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Choose your adventure: navigating data schemas" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29568i557D751367D3F1E9/image-size/large?v=v2&amp;amp;px=999" role="button" title="Gemini_Generated_Image_8qnkgv8qnkgv8qnk.png" alt="Gemini_Generated_Image_8qnkgv8qnkgv8qnk.png" /&gt;&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;It is Friday afternoon. An upstream team ships a harmless-looking change: they add a &lt;/SPAN&gt;&lt;SPAN&gt;region&lt;/SPAN&gt;&lt;SPAN&gt; field to the events your service already emits, and somewhere along the way, a sensor starts reporting temperature as &lt;/SPAN&gt;&lt;SPAN&gt;"72F"&lt;/SPAN&gt;&lt;SPAN&gt; instead of &lt;/SPAN&gt;&lt;SPAN&gt;72&lt;/SPAN&gt;&lt;SPAN&gt;. 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The fundamental question when designing these pipelines&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;is: “&lt;STRONG&gt;How&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;STRONG&gt;&amp;nbsp;strict should ingestion be about the shape of your data?”&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Today, we are introducing the &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/ingestion/zerobus-rescue-column" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;Zerobus Rescue Column (Beta)&lt;/STRONG&gt;&lt;/A&gt;&lt;SPAN&gt;, 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Two camps, one spectrum&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;I have heard both arguments, and honestly, both have merit.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;One camp says, &lt;/SPAN&gt;&lt;STRONG&gt;"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."&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The other camp says, &lt;/SPAN&gt;&lt;STRONG&gt;"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."&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Most teams live somewhere on this &lt;/SPAN&gt;&lt;STRONG&gt;spectrum&lt;/STRONG&gt;&lt;SPAN&gt;, 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;The table is the contract&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;STRONG&gt;Your Delta table schema is the contract.&lt;/STRONG&gt;&lt;SPAN&gt; Zerobus validates every incoming record against that contract, and you decide how strict or how accepting the contract is. Think of it as &lt;/SPAN&gt;&lt;STRONG&gt;a choose-your-own-adventure&lt;/STRONG&gt;&lt;SPAN&gt;. 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Zerobus Ingest holds three fundamental principles:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Zerobus gates the data.&lt;/STRONG&gt;&lt;SPAN&gt; It validates every record against the target table and rejects anything that does not fit. It never guesses, and it never silently drops a column. It fails fast and loud!&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;You define the contract.&lt;/STRONG&gt;&lt;SPAN&gt; Marking columns as required or nullable and adding a rescue column are how you decide what "fits."&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Zerobus never augments your table.&lt;/STRONG&gt;&lt;SPAN&gt; It does not add columns, change types, or evolve your schema to accommodate a record. You evolve what Zerobus accepts by evolving the table, not the other way around, making your table the data contract.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;That last principle is the one people underestimate: &lt;/SPAN&gt;&lt;STRONG&gt;Zerobus is predictable by design.&lt;/STRONG&gt;&lt;SPAN&gt; 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Choose your own adventure: Schema edition&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Schema Management: One table, many producers" style="width: 818px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29567iE9B3A72C2A932E85/image-dimensions/818x460?v=v2" width="818" height="460" role="button" title="Vicky_Bukta_DB_0-1785344147290.png" alt="Vicky_Bukta_DB_0-1785344147290.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Scenario 1: Accept a subset (all columns optional)&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Make every column nullable. Producers can send any subset of the columns, and anything they omit is written as &lt;/SPAN&gt;&lt;SPAN&gt;NULL&lt;/SPAN&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt;CREATE TABLE main.default.air_quality (
  device_name STRING,
  temp INT,
  humidity INT);&lt;/LI-CODE&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1", "temp": 22, "humidity": 55}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt;&amp;nbsp;is &lt;/SPAN&gt;&lt;STRONG&gt;accepted&lt;/STRONG&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;All columns present.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1"}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt; is &lt;/SPAN&gt;&lt;STRONG&gt;accepted&lt;/STRONG&gt;&lt;SPAN&gt;.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;temp&lt;/SPAN&gt;&lt;SPAN&gt; and &lt;/SPAN&gt;&lt;SPAN&gt;humidity&lt;/SPAN&gt;&lt;SPAN&gt; are nullable, so they are written as &lt;/SPAN&gt;&lt;SPAN&gt;NULL&lt;/SPAN&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1", "temp": 22, "region": "us-west"}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt; is &lt;/SPAN&gt;&lt;STRONG&gt;rejected&lt;/STRONG&gt;&lt;SPAN&gt;.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;region&lt;/SPAN&gt;&lt;SPAN&gt; does not exist in the table.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;This is flexible about what you &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;omit&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt;, but still strict about what you &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;add&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Scenario 2: Enforce specific fields (the NOT NULL gate)&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Mark columns &lt;/SPAN&gt;&lt;SPAN&gt;NOT NULL&lt;/SPAN&gt;&lt;SPAN&gt; to require them. Every record must supply those fields, or it is rejected. This is the &lt;/SPAN&gt;&lt;STRONG&gt;strict end&lt;/STRONG&gt;&lt;SPAN&gt; of the spectrum, and &lt;/SPAN&gt;&lt;SPAN&gt;NOT NULL&lt;/SPAN&gt;&lt;SPAN&gt; is your &lt;/SPAN&gt;&lt;STRONG&gt;gate&lt;/STRONG&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt;CREATE TABLE main.default.air_quality (
  device_name STRING NOT NULL,
  temp INT NOT NULL,
  humidity INT);&lt;/LI-CODE&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1", "temp": 22, "humidity": 55}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt; is &lt;/SPAN&gt;&lt;STRONG&gt;accepted&lt;/STRONG&gt;&lt;SPAN&gt;.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;All required columns present.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1", "temp": 22}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt; is &lt;/SPAN&gt;&lt;STRONG&gt;accepted&lt;/STRONG&gt;&lt;SPAN&gt;.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;humidity&lt;/SPAN&gt;&lt;SPAN&gt; is nullable, so it is written as &lt;/SPAN&gt;&lt;SPAN&gt;NULL&lt;/SPAN&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1"}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt; is &lt;/SPAN&gt;&lt;STRONG&gt;rejected&lt;/STRONG&gt;&lt;SPAN&gt;.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;temp&lt;/SPAN&gt;&lt;SPAN&gt; is required and missing.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;This is the answer for the first camp. If you want to guarantee that garbage never lands, &lt;/SPAN&gt;&lt;SPAN&gt;NOT NULL&lt;/SPAN&gt;&lt;SPAN&gt; is the lever.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Scenario 3: Catch everything else (the rescue column)&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Add a &lt;/SPAN&gt;&lt;STRONG&gt;VARIANT&lt;/STRONG&gt;&lt;STRONG&gt; rescue column&lt;/STRONG&gt;&lt;SPAN&gt; 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 &lt;/SPAN&gt;&lt;STRONG&gt;most accepting&lt;/STRONG&gt;&lt;SPAN&gt; end of the spectrum, with nothing lost to rejection.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;This is the answer for the second camp. It is also the new capability we are introducing today.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Rescue to the rescue&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Earlier this year, we &lt;/SPAN&gt;&lt;A href="https://community.databricks.com/t5/technical-blog/schema-free-data-ingestion-with-zerobus-and-variant-type/ba-p/148399" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;added Variant type support&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; so you could ingest semi-structured JSON without pinning down every field in advance. &lt;/SPAN&gt;&lt;STRONG&gt;The rescue column is the natural extension of that work.&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The distinction is one of intent:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Variant&lt;/STRONG&gt;&lt;SPAN&gt; is for when you &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;expect&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt; semi-structured data and want to store it natively. You reach for it deliberately.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;The rescue column&lt;/STRONG&gt;&lt;SPAN&gt; is for when you &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;have&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt; a schema you believe in, and you want a safety net for the strays.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Configuring a rescue column&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;There is no magic column name. You designate a column as the rescue column by giving it three properties:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;It &lt;/SPAN&gt;&lt;STRONG&gt;allows null values&lt;/STRONG&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;It uses the &lt;/SPAN&gt;&lt;STRONG&gt;VARIANT&lt;/STRONG&gt;&lt;STRONG&gt; type&lt;/STRONG&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;It has the &lt;/SPAN&gt;&lt;STRONG&gt;zerobus-rescue&lt;/STRONG&gt;&lt;STRONG&gt; tag&lt;/STRONG&gt;&lt;SPAN&gt; applied in Unity Catalog.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2&gt;&lt;SPAN&gt;What lands in the rescue column, and what doesn't&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Zerobus routes each field in a record based on how it fits the table:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE&gt;
&lt;THEAD&gt;
&lt;TR&gt;
&lt;TH&gt;
&lt;P&gt;&lt;STRONG&gt;Field in the record&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TH&gt;
&lt;TH&gt;
&lt;P&gt;&lt;STRONG&gt;Outcome&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TH&gt;
&lt;/TR&gt;
&lt;/THEAD&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Matches a column by &lt;/SPAN&gt;&lt;STRONG&gt;name and type&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Written to that column normally&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Not present&lt;/STRONG&gt;&lt;SPAN&gt; in the table schema&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Captured in the rescue column&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Present in the schema, but the &lt;/SPAN&gt;&lt;STRONG&gt;value's type doesn't match&lt;/STRONG&gt;&lt;SPAN&gt; and the target column is nullable&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Captured in the rescue column&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;Here is the whole thing in one example. Consider a table where &lt;/SPAN&gt;&lt;STRONG&gt;rescue&lt;/STRONG&gt;&lt;SPAN&gt; is nullable, uses &lt;/SPAN&gt;&lt;STRONG&gt;VARIANT&lt;/STRONG&gt;&lt;SPAN&gt;, and carries the &lt;/SPAN&gt;&lt;STRONG&gt;zerobus-rescue&lt;/STRONG&gt;&lt;SPAN&gt; tag:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt;CREATE TABLE main.default.air_quality (
  device_name STRING NOT NULL,
  temp INT,
  humidity LONG,
  rescue VARIANT
);&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Now ingest a record with both a type mismatch and an extra field:&lt;/SPAN&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{&lt;/SPAN&gt;&lt;/FONT&gt;&lt;BR /&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;"device_name"&lt;/SPAN&gt;&lt;SPAN&gt;: &lt;/SPAN&gt;&lt;SPAN&gt;"sensor-1"&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;/FONT&gt;&lt;BR /&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;"temp"&lt;/SPAN&gt;&lt;SPAN&gt;: &lt;/SPAN&gt;&lt;SPAN&gt;"72F"&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;/FONT&gt;&lt;BR /&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;"humidity"&lt;/SPAN&gt;&lt;SPAN&gt;: &lt;/SPAN&gt;&lt;SPAN&gt;87&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;/FONT&gt;&lt;BR /&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;"extra_field"&lt;/SPAN&gt;&lt;SPAN&gt;: &lt;/SPAN&gt;&lt;SPAN&gt;"some value"&lt;/SPAN&gt;&lt;/FONT&gt;&lt;BR /&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;SPAN&gt;The result:&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE style="border-collapse: collapse; font-family: Arial, sans-serif; font-size: 15px;"&gt;
&lt;THEAD&gt;
&lt;TR&gt;
&lt;TH style="border: 1px solid #ccc; padding: 14px 12px; text-align: left; background-color: #f2f2f2; font-size: 14px; font-weight: bold;"&gt;device_name&lt;/TH&gt;
&lt;TH style="border: 1px solid #ccc; padding: 14px 12px; text-align: left; background-color: #f2f2f2; font-size: 14px; font-weight: bold;"&gt;temp&lt;/TH&gt;
&lt;TH style="border: 1px solid #ccc; padding: 14px 12px; text-align: left; background-color: #f2f2f2; font-size: 14px; font-weight: bold;"&gt;humidity&lt;/TH&gt;
&lt;TH style="border: 1px solid #ccc; padding: 14px 12px; text-align: left; background-color: #f2f2f2; font-size: 14px; font-weight: bold;"&gt;rescue&lt;/TH&gt;
&lt;/TR&gt;
&lt;/THEAD&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD style="border: 1px solid #ccc; padding: 8px 12px;"&gt;sensor-1&lt;/TD&gt;
&lt;TD style="border: 1px solid #ccc; padding: 8px 12px;"&gt;null&lt;/TD&gt;
&lt;TD style="border: 1px solid #ccc; padding: 8px 12px;"&gt;87&lt;/TD&gt;
&lt;TD style="border: 1px solid #ccc; padding: 8px 12px;"&gt;{"extra_field": "some value", "temp": "72F"}&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;Both non-conforming fields, the mistyped &lt;/SPAN&gt;&lt;STRONG&gt;temp&lt;/STRONG&gt;&lt;SPAN&gt; and the unknown &lt;/SPAN&gt;&lt;STRONG&gt;extra_field&lt;/STRONG&gt;&lt;SPAN&gt;, 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Recommendations&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;A few things to keep in mind as you design tables around this:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Evolve the table, not the ingest.&lt;/STRONG&gt;&lt;SPAN&gt; Zerobus never auto-evolves your schema. When your data shape changes permanently, evolve the table first (for example, with &lt;/SPAN&gt;&lt;STRONG&gt;ALTER TABLE&lt;/STRONG&gt;&lt;SPAN&gt;), then send records against the new schema.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Keep your schema backward compatible. &lt;/STRONG&gt;&lt;SPAN&gt;Adding a nullable column is non-breaking, so you can roll out table and producer changes on separate timelines.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;The rescue column is a landing zone, not a destination.&lt;/STRONG&gt;&lt;SPAN&gt; Once data is durable, you can transform and schematize it downstream. A common pattern is to &lt;/SPAN&gt;&lt;STRONG&gt;fan out&lt;/STRONG&gt;&lt;SPAN&gt;: read the rescue column, promote fields that have become common into real typed columns, and route the rest onward. The rescue column buys you time to make those decisions with the data safely in hand.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Combine strictness with rescue deliberately. NOT NULL&lt;/STRONG&gt;&lt;SPAN&gt; on the fields you truly require, plus a rescue column for everything else, gives you a table that both &lt;/SPAN&gt;&lt;STRONG&gt;guarantees&lt;/STRONG&gt;&lt;SPAN&gt; your core contract and &lt;/SPAN&gt;&lt;STRONG&gt;never loses&lt;/STRONG&gt;&lt;SPAN&gt; the extras. That combination is where most teams end up.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;SPAN&gt;Try it out today&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Our whole philosophy is the following: &lt;/SPAN&gt;&lt;STRONG&gt;Zerobus makes your data durable and is reliable when it comes to landing your data. You decide how much to schematize along the way.&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Your adventure, your rules.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Ready to try it? Check out the &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/ingestion/zerobus-rescue-column" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Zerobus rescue column documentation&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; to design your contract.&amp;nbsp;Be sure to &lt;A href="https://docs.databricks.com/aws/en/admin/workspace-settings/manage-previews" target="_self"&gt;enable the Beta feature&lt;/A&gt;&amp;nbsp;first.&lt;BR /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;I&gt;&lt;SPAN&gt;Have questions, or want to share how you are shaping your schemas with Zerobus? Join the discussion below!&lt;/SPAN&gt;&lt;/I&gt;&lt;/P&gt;</description>
    <pubDate>Wed, 29 Jul 2026 21:11:44 GMT</pubDate>
    <dc:creator>Vicky_Bukta_DB</dc:creator>
    <dc:date>2026-07-29T21:11:44Z</dc:date>
    <item>
      <title>Try the new Zerobus Rescue Column: Choose-Your-Own-Adventure Schema Management  (Beta)</title>
      <link>https://community.databricks.com/t5/technical-blog/try-the-new-zerobus-rescue-column-choose-your-own-adventure/ba-p/164413</link>
      <description>&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Choose your adventure: navigating data schemas" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29568i557D751367D3F1E9/image-size/large?v=v2&amp;amp;px=999" role="button" title="Gemini_Generated_Image_8qnkgv8qnkgv8qnk.png" alt="Gemini_Generated_Image_8qnkgv8qnkgv8qnk.png" /&gt;&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;It is Friday afternoon. An upstream team ships a harmless-looking change: they add a &lt;/SPAN&gt;&lt;SPAN&gt;region&lt;/SPAN&gt;&lt;SPAN&gt; field to the events your service already emits, and somewhere along the way, a sensor starts reporting temperature as &lt;/SPAN&gt;&lt;SPAN&gt;"72F"&lt;/SPAN&gt;&lt;SPAN&gt; instead of &lt;/SPAN&gt;&lt;SPAN&gt;72&lt;/SPAN&gt;&lt;SPAN&gt;. 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The fundamental question when designing these pipelines&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;is: “&lt;STRONG&gt;How&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;STRONG&gt;&amp;nbsp;strict should ingestion be about the shape of your data?”&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Today, we are introducing the &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/ingestion/zerobus-rescue-column" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;Zerobus Rescue Column (Beta)&lt;/STRONG&gt;&lt;/A&gt;&lt;SPAN&gt;, 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Two camps, one spectrum&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;I have heard both arguments, and honestly, both have merit.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;One camp says, &lt;/SPAN&gt;&lt;STRONG&gt;"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."&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The other camp says, &lt;/SPAN&gt;&lt;STRONG&gt;"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."&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Most teams live somewhere on this &lt;/SPAN&gt;&lt;STRONG&gt;spectrum&lt;/STRONG&gt;&lt;SPAN&gt;, 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;The table is the contract&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;STRONG&gt;Your Delta table schema is the contract.&lt;/STRONG&gt;&lt;SPAN&gt; Zerobus validates every incoming record against that contract, and you decide how strict or how accepting the contract is. Think of it as &lt;/SPAN&gt;&lt;STRONG&gt;a choose-your-own-adventure&lt;/STRONG&gt;&lt;SPAN&gt;. 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Zerobus Ingest holds three fundamental principles:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Zerobus gates the data.&lt;/STRONG&gt;&lt;SPAN&gt; It validates every record against the target table and rejects anything that does not fit. It never guesses, and it never silently drops a column. It fails fast and loud!&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;You define the contract.&lt;/STRONG&gt;&lt;SPAN&gt; Marking columns as required or nullable and adding a rescue column are how you decide what "fits."&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Zerobus never augments your table.&lt;/STRONG&gt;&lt;SPAN&gt; It does not add columns, change types, or evolve your schema to accommodate a record. You evolve what Zerobus accepts by evolving the table, not the other way around, making your table the data contract.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;That last principle is the one people underestimate: &lt;/SPAN&gt;&lt;STRONG&gt;Zerobus is predictable by design.&lt;/STRONG&gt;&lt;SPAN&gt; 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Choose your own adventure: Schema edition&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Schema Management: One table, many producers" style="width: 818px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/29567iE9B3A72C2A932E85/image-dimensions/818x460?v=v2" width="818" height="460" role="button" title="Vicky_Bukta_DB_0-1785344147290.png" alt="Vicky_Bukta_DB_0-1785344147290.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Scenario 1: Accept a subset (all columns optional)&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Make every column nullable. Producers can send any subset of the columns, and anything they omit is written as &lt;/SPAN&gt;&lt;SPAN&gt;NULL&lt;/SPAN&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt;CREATE TABLE main.default.air_quality (
  device_name STRING,
  temp INT,
  humidity INT);&lt;/LI-CODE&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1", "temp": 22, "humidity": 55}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt;&amp;nbsp;is &lt;/SPAN&gt;&lt;STRONG&gt;accepted&lt;/STRONG&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;All columns present.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1"}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt; is &lt;/SPAN&gt;&lt;STRONG&gt;accepted&lt;/STRONG&gt;&lt;SPAN&gt;.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;temp&lt;/SPAN&gt;&lt;SPAN&gt; and &lt;/SPAN&gt;&lt;SPAN&gt;humidity&lt;/SPAN&gt;&lt;SPAN&gt; are nullable, so they are written as &lt;/SPAN&gt;&lt;SPAN&gt;NULL&lt;/SPAN&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1", "temp": 22, "region": "us-west"}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt; is &lt;/SPAN&gt;&lt;STRONG&gt;rejected&lt;/STRONG&gt;&lt;SPAN&gt;.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;region&lt;/SPAN&gt;&lt;SPAN&gt; does not exist in the table.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;This is flexible about what you &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;omit&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt;, but still strict about what you &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;add&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Scenario 2: Enforce specific fields (the NOT NULL gate)&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Mark columns &lt;/SPAN&gt;&lt;SPAN&gt;NOT NULL&lt;/SPAN&gt;&lt;SPAN&gt; to require them. Every record must supply those fields, or it is rejected. This is the &lt;/SPAN&gt;&lt;STRONG&gt;strict end&lt;/STRONG&gt;&lt;SPAN&gt; of the spectrum, and &lt;/SPAN&gt;&lt;SPAN&gt;NOT NULL&lt;/SPAN&gt;&lt;SPAN&gt; is your &lt;/SPAN&gt;&lt;STRONG&gt;gate&lt;/STRONG&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt;CREATE TABLE main.default.air_quality (
  device_name STRING NOT NULL,
  temp INT NOT NULL,
  humidity INT);&lt;/LI-CODE&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1", "temp": 22, "humidity": 55}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt; is &lt;/SPAN&gt;&lt;STRONG&gt;accepted&lt;/STRONG&gt;&lt;SPAN&gt;.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;All required columns present.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1", "temp": 22}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt; is &lt;/SPAN&gt;&lt;STRONG&gt;accepted&lt;/STRONG&gt;&lt;SPAN&gt;.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;humidity&lt;/SPAN&gt;&lt;SPAN&gt; is nullable, so it is written as &lt;/SPAN&gt;&lt;SPAN&gt;NULL&lt;/SPAN&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{"device_name": "sensor-1"}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;SPAN&gt; is &lt;/SPAN&gt;&lt;STRONG&gt;rejected&lt;/STRONG&gt;&lt;SPAN&gt;.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;temp&lt;/SPAN&gt;&lt;SPAN&gt; is required and missing.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;This is the answer for the first camp. If you want to guarantee that garbage never lands, &lt;/SPAN&gt;&lt;SPAN&gt;NOT NULL&lt;/SPAN&gt;&lt;SPAN&gt; is the lever.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Scenario 3: Catch everything else (the rescue column)&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Add a &lt;/SPAN&gt;&lt;STRONG&gt;VARIANT&lt;/STRONG&gt;&lt;STRONG&gt; rescue column&lt;/STRONG&gt;&lt;SPAN&gt; 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 &lt;/SPAN&gt;&lt;STRONG&gt;most accepting&lt;/STRONG&gt;&lt;SPAN&gt; end of the spectrum, with nothing lost to rejection.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;This is the answer for the second camp. It is also the new capability we are introducing today.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Rescue to the rescue&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Earlier this year, we &lt;/SPAN&gt;&lt;A href="https://community.databricks.com/t5/technical-blog/schema-free-data-ingestion-with-zerobus-and-variant-type/ba-p/148399" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;added Variant type support&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; so you could ingest semi-structured JSON without pinning down every field in advance. &lt;/SPAN&gt;&lt;STRONG&gt;The rescue column is the natural extension of that work.&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The distinction is one of intent:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Variant&lt;/STRONG&gt;&lt;SPAN&gt; is for when you &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;expect&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt; semi-structured data and want to store it natively. You reach for it deliberately.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;The rescue column&lt;/STRONG&gt;&lt;SPAN&gt; is for when you &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;have&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt; a schema you believe in, and you want a safety net for the strays.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Configuring a rescue column&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;There is no magic column name. You designate a column as the rescue column by giving it three properties:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;It &lt;/SPAN&gt;&lt;STRONG&gt;allows null values&lt;/STRONG&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;It uses the &lt;/SPAN&gt;&lt;STRONG&gt;VARIANT&lt;/STRONG&gt;&lt;STRONG&gt; type&lt;/STRONG&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;It has the &lt;/SPAN&gt;&lt;STRONG&gt;zerobus-rescue&lt;/STRONG&gt;&lt;STRONG&gt; tag&lt;/STRONG&gt;&lt;SPAN&gt; applied in Unity Catalog.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2&gt;&lt;SPAN&gt;What lands in the rescue column, and what doesn't&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Zerobus routes each field in a record based on how it fits the table:&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE&gt;
&lt;THEAD&gt;
&lt;TR&gt;
&lt;TH&gt;
&lt;P&gt;&lt;STRONG&gt;Field in the record&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TH&gt;
&lt;TH&gt;
&lt;P&gt;&lt;STRONG&gt;Outcome&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TH&gt;
&lt;/TR&gt;
&lt;/THEAD&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Matches a column by &lt;/SPAN&gt;&lt;STRONG&gt;name and type&lt;/STRONG&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Written to that column normally&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;STRONG&gt;Not present&lt;/STRONG&gt;&lt;SPAN&gt; in the table schema&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Captured in the rescue column&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Present in the schema, but the &lt;/SPAN&gt;&lt;STRONG&gt;value's type doesn't match&lt;/STRONG&gt;&lt;SPAN&gt; and the target column is nullable&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD&gt;
&lt;P&gt;&lt;SPAN&gt;Captured in the rescue column&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;Here is the whole thing in one example. Consider a table where &lt;/SPAN&gt;&lt;STRONG&gt;rescue&lt;/STRONG&gt;&lt;SPAN&gt; is nullable, uses &lt;/SPAN&gt;&lt;STRONG&gt;VARIANT&lt;/STRONG&gt;&lt;SPAN&gt;, and carries the &lt;/SPAN&gt;&lt;STRONG&gt;zerobus-rescue&lt;/STRONG&gt;&lt;SPAN&gt; tag:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt;CREATE TABLE main.default.air_quality (
  device_name STRING NOT NULL,
  temp INT,
  humidity LONG,
  rescue VARIANT
);&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Now ingest a record with both a type mismatch and an extra field:&lt;/SPAN&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;{&lt;/SPAN&gt;&lt;/FONT&gt;&lt;BR /&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;"device_name"&lt;/SPAN&gt;&lt;SPAN&gt;: &lt;/SPAN&gt;&lt;SPAN&gt;"sensor-1"&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;/FONT&gt;&lt;BR /&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;"temp"&lt;/SPAN&gt;&lt;SPAN&gt;: &lt;/SPAN&gt;&lt;SPAN&gt;"72F"&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;/FONT&gt;&lt;BR /&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;"humidity"&lt;/SPAN&gt;&lt;SPAN&gt;: &lt;/SPAN&gt;&lt;SPAN&gt;87&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt;&lt;/FONT&gt;&lt;BR /&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;"extra_field"&lt;/SPAN&gt;&lt;SPAN&gt;: &lt;/SPAN&gt;&lt;SPAN&gt;"some value"&lt;/SPAN&gt;&lt;/FONT&gt;&lt;BR /&gt;&lt;FONT face="andale mono,times" color="#008000"&gt;&lt;SPAN&gt;}&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;SPAN&gt;The result:&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;TABLE style="border-collapse: collapse; font-family: Arial, sans-serif; font-size: 15px;"&gt;
&lt;THEAD&gt;
&lt;TR&gt;
&lt;TH style="border: 1px solid #ccc; padding: 14px 12px; text-align: left; background-color: #f2f2f2; font-size: 14px; font-weight: bold;"&gt;device_name&lt;/TH&gt;
&lt;TH style="border: 1px solid #ccc; padding: 14px 12px; text-align: left; background-color: #f2f2f2; font-size: 14px; font-weight: bold;"&gt;temp&lt;/TH&gt;
&lt;TH style="border: 1px solid #ccc; padding: 14px 12px; text-align: left; background-color: #f2f2f2; font-size: 14px; font-weight: bold;"&gt;humidity&lt;/TH&gt;
&lt;TH style="border: 1px solid #ccc; padding: 14px 12px; text-align: left; background-color: #f2f2f2; font-size: 14px; font-weight: bold;"&gt;rescue&lt;/TH&gt;
&lt;/TR&gt;
&lt;/THEAD&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD style="border: 1px solid #ccc; padding: 8px 12px;"&gt;sensor-1&lt;/TD&gt;
&lt;TD style="border: 1px solid #ccc; padding: 8px 12px;"&gt;null&lt;/TD&gt;
&lt;TD style="border: 1px solid #ccc; padding: 8px 12px;"&gt;87&lt;/TD&gt;
&lt;TD style="border: 1px solid #ccc; padding: 8px 12px;"&gt;{"extra_field": "some value", "temp": "72F"}&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN&gt;Both non-conforming fields, the mistyped &lt;/SPAN&gt;&lt;STRONG&gt;temp&lt;/STRONG&gt;&lt;SPAN&gt; and the unknown &lt;/SPAN&gt;&lt;STRONG&gt;extra_field&lt;/STRONG&gt;&lt;SPAN&gt;, 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.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Recommendations&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;A few things to keep in mind as you design tables around this:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Evolve the table, not the ingest.&lt;/STRONG&gt;&lt;SPAN&gt; Zerobus never auto-evolves your schema. When your data shape changes permanently, evolve the table first (for example, with &lt;/SPAN&gt;&lt;STRONG&gt;ALTER TABLE&lt;/STRONG&gt;&lt;SPAN&gt;), then send records against the new schema.&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Keep your schema backward compatible. &lt;/STRONG&gt;&lt;SPAN&gt;Adding a nullable column is non-breaking, so you can roll out table and producer changes on separate timelines.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;The rescue column is a landing zone, not a destination.&lt;/STRONG&gt;&lt;SPAN&gt; Once data is durable, you can transform and schematize it downstream. A common pattern is to &lt;/SPAN&gt;&lt;STRONG&gt;fan out&lt;/STRONG&gt;&lt;SPAN&gt;: read the rescue column, promote fields that have become common into real typed columns, and route the rest onward. The rescue column buys you time to make those decisions with the data safely in hand.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Combine strictness with rescue deliberately. NOT NULL&lt;/STRONG&gt;&lt;SPAN&gt; on the fields you truly require, plus a rescue column for everything else, gives you a table that both &lt;/SPAN&gt;&lt;STRONG&gt;guarantees&lt;/STRONG&gt;&lt;SPAN&gt; your core contract and &lt;/SPAN&gt;&lt;STRONG&gt;never loses&lt;/STRONG&gt;&lt;SPAN&gt; the extras. That combination is where most teams end up.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;SPAN&gt;Try it out today&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Our whole philosophy is the following: &lt;/SPAN&gt;&lt;STRONG&gt;Zerobus makes your data durable and is reliable when it comes to landing your data. You decide how much to schematize along the way.&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Your adventure, your rules.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Ready to try it? Check out the &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/ingestion/zerobus-rescue-column" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Zerobus rescue column documentation&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; to design your contract.&amp;nbsp;Be sure to &lt;A href="https://docs.databricks.com/aws/en/admin/workspace-settings/manage-previews" target="_self"&gt;enable the Beta feature&lt;/A&gt;&amp;nbsp;first.&lt;BR /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;I&gt;&lt;SPAN&gt;Have questions, or want to share how you are shaping your schemas with Zerobus? Join the discussion below!&lt;/SPAN&gt;&lt;/I&gt;&lt;/P&gt;</description>
      <pubDate>Wed, 29 Jul 2026 21:11:44 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/try-the-new-zerobus-rescue-column-choose-your-own-adventure/ba-p/164413</guid>
      <dc:creator>Vicky_Bukta_DB</dc:creator>
      <dc:date>2026-07-29T21:11:44Z</dc:date>
    </item>
  </channel>
</rss>

