Intermediate·Lakebase

Synced tables, from Unity Catalog into Postgres

kept in this browser

A managed, read-only copy of a Unity Catalog table in Lakebase, kept current in snapshot, triggered or continuous mode, so applications read lakehouse data with Postgres latency.

On the preview radar: LTAP Direct Writes

On this page12

What it is

A synced table is a copy of a Unity Catalog table inside a Lakebase database, kept current by Databricks and read-only on the Postgres side. The source can be a managed or external Delta table, an Iceberg table, a view or a materialized view. Creating one gives you two objects: a synced-table entry in Unity Catalog, and a real Postgres table that applications query.

This direction — lakehouse to operational database — is what used to be called reverse ETL. The opposite direction, Postgres changes into Delta, is the Lakebase change data feed, from Postgres into Delta.

Why it exists

The lakehouse computes things applications need at request time: a customer’s segment, a product’s recommended accessories, a risk score, a price. A SQL warehouse can return those, but not at the latency and concurrency of a web page, and an application should not hold a warehouse connection per request.

The usual answer was a nightly job that exported the table into an application database, with its own credentials, its own schema drift and its own failure modes. A synced table is that job made managed: you name the source, the key and the rhythm, and Databricks runs and monitors the copy.

How it works

Creating one

From Catalog in the workspace sidebar (on the source table), from the API, or from the CLI:

databricks postgres create-synced-table my_catalog.sales.orders_synced --json '{
  "spec": {
    "source_table_full_name": "main.sales.orders",
    "branch": "projects/shop/branches/production",
    "postgres_database": "shop",
    "primary_key_columns": ["order_id"],
    "scheduling_policy": "SNAPSHOT",
    "create_database_objects_if_missing": true
  }
}'

You choose a primary key. Rows whose key is null are left out. If the key is not unique in the source the pipeline fails, unless you also give a timeseries key, which decides which of the duplicate rows wins. In Postgres, the table lands in a schema named after the Unity Catalog schema.

Three sync modes

ModeHow it updatesSuits
SnapshotCopies the whole table on every runTables where more than about 10% of rows change between runs
TriggeredApplies only the changes, on demand or on a scheduleIncremental changes a few times an hour or less
ContinuousStreams changes, seconds behind the sourceData the application must see almost at once

Triggered and Continuous read the source’s change feed, so the source needs one: either delta.enableChangeDataFeed set on the table (see Change Data Feed) or the automatic change data feed, which also covers Iceberg. Triggered becomes expensive when run more often than every five minutes; at that point Continuous is the honest choice. Continuous processes changes in intervals of at least 15 seconds.

ALTER TABLE main.sales.orders
  SET TBLPROPERTIES (delta.enableChangeDataFeed = true);

What runs underneath

The copy is a managed Lakeflow pipeline, using up to 16 Postgres connections per synced table (see Lakeflow pipelines). For Snapshot and Triggered, the runs after the first are started by a Database Table Sync pipeline task in Lakeflow Jobs, fired by a table-update trigger or a schedule (see Triggers: schedule, file arrival, table update, continuous). Monitoring a sync is monitoring that job.

What Postgres lets you do with it

A synced table belongs to the sync, owned by a databricks_writer_<dbid> role. In Postgres you may:

  • run read queries;
  • create, alter and drop indexes on it;
  • drop the table.

Every other DDL is denied, and because the table is not yours, owner-only features such as row-level security cannot be added. A member of databricks_superuser can technically write to it; Databricks’ advice is to correct the data at the source instead, since the next sync would overwrite or conflict with the change.

Indexes are the part worth your attention: the sync creates the primary key, and every other access path the application uses needs an index you add.

Types

Complex types become JSON: ARRAY, MAP and STRUCT map to JSONB. TIMESTAMP becomes timestamptz and TIMESTAMP_NTZ becomes timestamp. With type_overrides, a column can be mapped to vector(n) or halfvec(n) for embeddings (the vector extension must exist first, see The Data API and Postgres extensions) or to varchar(n). A null byte in a string or nested column makes the sync fail; strip it in the source.

Limits

  • Up to 20 synced tables per source table.
  • In Triggered and Continuous, only additive schema changes on the source are carried over.
  • A synced table’s definition cannot be edited; delete it and create it again.
  • Keep tables that need full refreshes under about 1 TB. During a full refresh both the old and the new copy count against the branch’s storage.
  • As a rough guide, incremental modes apply about 150 rows per second per CU, and Snapshot loads up to about 2,000 rows per second per CU.
  • Names use letters, digits and underscores only.

Deleting the synced table in Unity Catalog also drops the Postgres table.

LTAP Direct Writes, in Beta since August 2026, speeds up initial loads and Snapshot refreshes; it needs Postgres 17, an admin to opt in, and applies to newly created synced tables only.

When not to use one

A synced table copies a table that already exists in the lakehouse. A streaming job that should write its results straight into Postgres can instead use the Lakebase sink for Structured Streaming (.format("postgresql"), Databricks Runtime 18 LTS and above), where the application owns the table and the writes.

Example

An application page shows a customer’s lifetime value, computed nightly in a gold table:

  1. Enable the change data feed on main.gold.customer_ltv.
  2. Create a Triggered synced table into the application’s database, keyed on customer_id, triggered when the gold table updates.
  3. In Postgres, grant the application’s role SELECT on the synced table and add the index its queries need:
CREATE INDEX ON gold.customer_ltv (segment, ltv DESC);
GRANT USAGE ON SCHEMA gold TO "shop-app";
GRANT SELECT ON gold.customer_ltv TO "shop-app";

The page reads one row by key in milliseconds; the nightly job writes Delta as it always did; nobody maintains an export.

Common mistakes

  • No change feed on the source. Triggered and Continuous need it; creation fails or the sync stops.
  • Duplicate keys. A key that is not unique fails the pipeline unless a timeseries key breaks the tie.
  • Writing to the synced table. It is read-only by design. Writes belong in a table the application owns, or at the source.
  • Forgetting indexes. Only the primary key comes with the table.
  • Destructive schema changes on the source. Dropping or retyping a column is not carried over incrementally; recreate the synced table.
  • Triggered every minute. Below a five-minute interval Triggered becomes expensive; that is the rhythm Continuous exists for.
Report a problem with this page
What kind of problem?

Reports about "Synced tables, from Unity Catalog into Postgres" go to the maintainer, not to a public thread.

Suggest a resource
What kind?

Nothing appears on the site automatically. A person reads every suggestion, checks the link and writes the note that goes with it.