On this page
Quarantine (DLQ) configuration¶
Quarantine mode on a data_quality transform routes rows that fail
expectations into an external Dead Letter Queue (DLQ) table instead of dropping
them. It is enabled per action, and both DLQ tables are user-managed DDL:
- name: <action_name>
type: transform
transform_type: data_quality
source: <view>
target: <view>
expectations_file: <path>
mode: quarantine
readMode: stream
quarantine:
dlq_table: <fully.qualified.name>
source_table: <fully.qualified.name>
See also
How-to guide and recovery-path diagram: Quarantine bad rows instead of dropping them.
Action fields¶
Fields on the data_quality transform that select and configure quarantine.
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
string |
No |
|
|
|
mapping |
When |
— |
DLQ configuration block (below). Required when |
The transform must also set readMode: stream (the default; other values are
rejected) and reference an expectations_file containing at least one rule.
quarantine block¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
string |
Yes |
— |
Fully qualified name of the pre-created DLQ inbox table. |
|
string |
Yes |
— |
Fully qualified name of the logical source table. Tags DLQ rows ( |
Both values accept substitution tokens (${catalog}, ${secret:scope/key})
and are resolved at generation time. The outbox table name is derived by
appending _outbox to dlq_table (for example
main.ops.orders_dlq → main.ops.orders_dlq_outbox); it is not configured
separately.
DLQ inbox table schema¶
The inbox table receives quarantined rows. Create it before the pipeline runs.
Column |
Type |
Description |
|---|---|---|
|
STRING |
Surrogate key: |
|
STRING |
Fully qualified source table name (from |
|
STRING |
Row status: |
|
TIMESTAMP |
Time the row was written to the DLQ ( |
|
ARRAY<STRUCT<name: STRING, rule: STRING>> |
The expectations the row violated, each as a |
|
STRING |
Rescued-data JSON from the source |
|
VARIANT |
Full row payload stored as a Databricks |
CREATE TABLE IF NOT EXISTS <dlq_table> (
_dlq_sk STRING NOT NULL,
_dlq_source_table STRING NOT NULL,
_dlq_status STRING NOT NULL,
_dlq_timestamp TIMESTAMP NOT NULL,
_dlq_failed_rules ARRAY<STRUCT<name: STRING, rule: STRING>>,
_dlq_rescued_data STRING,
_row_data VARIANT NOT NULL
)
CLUSTER BY (_dlq_source_table)
TBLPROPERTIES (
'delta.enableChangeDataFeed' = 'true',
'delta.enableRowTracking' = 'true',
'delta.enableDeletionVectors' = 'true'
);
The inbox table must have Change Data Feed and row tracking enabled: the recycle
stream reads it with readChangeFeed and consumes update_postimage events,
and fails at runtime when either property is off. Both the inbox and the outbox
must also have deletion vectors enabled: without them, the inbox MERGEs of
quarantine actions that share the inbox can fail with
ConcurrentAppendException when their commits overlap (see Concurrent
writers and shared DLQ tables).
DLQ outbox table schema¶
The outbox table is the deduplicated “already recycled” ledger. Its name is
<dlq_table>_outbox. Create it before the pipeline runs.
Column |
Type |
Description |
|---|---|---|
|
STRING |
Surrogate key, carried over from the inbox. MERGE key. |
|
STRING |
Fully qualified source table name. Clustering column; every generated MERGE filters on it. |
|
VARIANT |
Full row payload. |
|
TIMESTAMP |
Time the row was written to the outbox ( |
CREATE TABLE IF NOT EXISTS <dlq_table>_outbox (
_dlq_sk STRING NOT NULL,
_dlq_source_table STRING NOT NULL,
_row_data VARIANT NOT NULL,
_dlq_recycled_at TIMESTAMP NOT NULL
)
CLUSTER BY (_dlq_source_table)
TBLPROPERTIES (
'delta.enableRowTracking' = 'true',
'delta.enableDeletionVectors' = 'true'
);
Operating the queue safely¶
To recycle a corrected row, an operator rewrites its payload and marks it
fixed in the DLQ inbox:
UPDATE <dlq_table>
SET _dlq_status = 'fixed',
_row_data = <corrected_row_data>
WHERE _dlq_source_table = '<source_table>'
AND _dlq_sk IN (<dlq_sk_values>);
<corrected_row_data> is a VARIANT value, for example parse_json('{...}'),
whose field names match the source columns: recycled rows are read back by
column name.
Always include the
_dlq_source_tablepredicate. Without it, the UPDATE is not scoped to one source and can conflict with other sources’ sinks.Mark rows
fixedwhile the pipeline is idle (quarantine requires a triggered pipeline; see Limits). An UPDATE that races the same source’s sink can fail either the UPDATE or the pipeline’s MERGE.
Limits¶
Every expectation is coerced to
dropat runtime regardless of its configuredaction;warn/failrules emit a validation warning.The expectations file must contain at least one rule.
The inbox and outbox tables are user-managed DDL and must exist before the pipeline runs.
Recycling uses Change Data Feed and a
foreachBatchsink, so it requires a triggered (non-continuous) pipeline.Deletion vectors must stay enabled on a DLQ (and its outbox) shared by concurrently running quarantine actions.
Minimal example¶
- name: dq_orders
type: transform
transform_type: data_quality
source: v_orders
target: v_orders_validated
expectations_file: "expectations/orders.yaml"
mode: quarantine
readMode: stream
quarantine:
dlq_table: "${catalog}.${ops_schema}.orders_dlq"
source_table: "${catalog}.${bronze_schema}.orders"