Skip to content

Add pg_lake_polaris extension for two-way Polaris sync#392

Draft
sfc-gh-mslot wants to merge 2 commits into
marcoslot/external-writesfrom
marcoslot/polaris-extension
Draft

Add pg_lake_polaris extension for two-way Polaris sync#392
sfc-gh-mslot wants to merge 2 commits into
marcoslot/external-writesfrom
marcoslot/polaris-extension

Conversation

@sfc-gh-mslot

@sfc-gh-mslot sfc-gh-mslot commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Work in progress, opening as draft so the branch does not get lost.

Problem

pg_lake stores Iceberg metadata in lake_iceberg.tables_internal. When Apache Polaris runs co-resident in the same Postgres database (polaris_schema.entities), there is no path to keep the two catalogs in sync. Tables registered through pg_lake are invisible to Polaris clients, and metadata commits made through Polaris (external writers, REST clients) are invisible to pg_lake.

Solution

Add a new optional extension pg_lake_polaris that mirrors metadata between the two catalogs:

  • Outbound (pg_lake to Polaris) is via triggers on tables_internal.
  • Inbound (Polaris to pg_lake) piggybacks on sync_iceberg_metadata_from_external_write, which is also added in this branch as a generic external-write entrypoint in pg_lake_table.
  • Loop prevention is a process-global suppress flag, mirroring the SkipIcebergDDLProcessing pattern.

v0.1 limitations (single-instance Polaris ID allocator, scalar-only inbound CREATE, flat namespaces) are documented inline in the SQL.

The branch also pulls in supporting changes in pg_lake_table: external Iceberg write support via catalog UPDATE, a deletion queue for files unreferenced after external sync, and snapshot/SPI fixes surfaced while wiring this up.

Setup

-- Optional: override the Polaris realm_id (default 'POLARIS').
SET pg_lake_polaris.realm_id = 'POLARIS';

-- Load the extension. pg_lake_iceberg and pg_lake_table are required.
CREATE EXTENSION pg_lake_polaris;

-- Pin this database to a Polaris catalog that already exists in
-- polaris_schema.entities. Installs the inbound trigger and mirrors
-- existing pg_lake Iceberg tables out to Polaris.
SELECT lake_polaris.register_catalog('mycat');

After register_catalog, pg_lake CREATE TABLE ... USING iceberg shows up as a table entity under the named Polaris catalog, and metadata commits made through Polaris are reflected back into lake_iceberg.tables_internal. To stop syncing, call lake_polaris.unregister_catalog().

Test plan

  • pg_lake_polaris/tests/pytests/test_polaris_register.py, test_polaris_outbound.py, test_polaris_inbound.py, test_polaris_e2e.py
  • pg_lake_table/tests/pytests/test_external_write.py
  • Inbound INSERT path is not e2e-tested in v0.1; documented as a known limitation.

New optional extension that mirrors metadata between pg_lake's Iceberg
catalog (lake_iceberg.tables_internal) and an Apache Polaris metastore
(polaris_schema.entities) co-resident in the same Postgres database.
Outbound is via trigger; inbound piggybacks on
sync_iceberg_metadata_from_external_write to apply Polaris-side
commits. Loop prevention is a process-global suppress flag mirroring
the SkipIcebergDDLProcessing pattern. v0.1 limitations (single-instance
Polaris ID allocator, scalar-only inbound CREATE, flat namespaces) are
documented in the SQL.
Two pytest e2e tests exercise the outbound path through pg_lake's
real CREATE TABLE / INSERT / DROP DDL flow against the lake_polaris
extension's triggers. The inbound INSERT path is not e2e-tested in
v0.1 — the test environment lacks a way to write a real Iceberg
metadata file outside pg_lake (PyIceberg-core not installed; PyIceberg
SqlCatalog can't write to the current database's iceberg_tables);
sharing pg_lake-written metadata trips a pg_lake-internal
deletion_queue invariant about two tables referencing the same files.
Documented as a known v0.1 limitation.

In the process, fix two real bugs surfaced by the e2e attempt:

  - outbound_remove deleted entity_link AFTER deleting the polaris
    entity, so the inbound DELETE trigger queued by the entity DELETE
    still found the link and re-attempted the foreign-table drop.
    Reverse the order so self-canceling state is the load-bearing
    mechanism (the suppress flag is defense-in-depth).
  - inbound_sync_insert specified read_only 'true' (a REST-catalog-only
    option) and INSERTed into tables_internal even though pg_lake's
    CREATE FOREIGN TABLE hook already does. Drop the option and switch
    to UPDATE.

Also: skip stale tables_internal rows in outbound_sync, fix conftest
teardown order so DROP EXTENSION removes triggers before clearing
polaris_schema entities.
@sfc-gh-mslot sfc-gh-mslot changed the base branch from main to marcoslot/external-writes June 10, 2026 13:53
@sfc-gh-mslot sfc-gh-mslot changed the title Add experimental pg_lake_polaris extension for two-way Polaris sync Add pg_lake_polaris extension for two-way Polaris sync Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant