> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streamnative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Open Catalog for Iceberg on Azure

This guide describes how to prepare a Snowflake Open Catalog (Polaris) for use with StreamNative Ursa as an Iceberg catalog on Microsoft Azure.

## Prerequisites

* A Snowflake standard account
* An Azure subscription with permissions to create storage accounts and configure trusted apps
* Access to the Snowflake Open Catalog feature

## 1. Create a Snowflake Open Catalog Account

The Snowflake Open Catalog console requires a dedicated **Open Catalog** account. From the standard Snowflake console, navigate to **Admin -> Accounts** and use the toggle to **Create Snowflake Open Catalog Account**.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-01.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=1355bca42a5809962d9ea8023e001dc4" alt="Snowflake console" width="1920" height="931" data-path="images/ursa-lakehouse/open-catalog-azure-01.webp" />

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-02.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=3038d84d1c01b470d85097a92b76e376" alt="Create Open Catalog account" width="1920" height="934" data-path="images/ursa-lakehouse/open-catalog-azure-02.webp" />

Configure the account with:

* **Cloud:** AWS or Azure (per Polaris availability)
* **Region:** the region in which your storage container resides
* **Edition:** any

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-03.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=7b11fc1f9472aea7f2775a145b5352cb" alt="Account configuration" width="1920" height="934" data-path="images/ursa-lakehouse/open-catalog-azure-03.webp" />

Provide an admin username and password.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-04.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=0d70662c12eb75fa0b8fa6b153f2726e" alt="Account credentials" width="1920" height="935" data-path="images/ursa-lakehouse/open-catalog-azure-04.webp" />

After creation, click the **Account URL** to sign in to the Open Catalog console.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-05.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=ac7725e461a2d8611bdeccd430834e31" alt="Account created" width="1920" height="1918" data-path="images/ursa-lakehouse/open-catalog-azure-05.webp" />

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-06.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=0155a13b123898e3b095ef2f97313105" alt="Open Catalog console" width="1920" height="933" data-path="images/ursa-lakehouse/open-catalog-azure-06.webp" />

## 2. Collect Azure Account Information

### 2.1 Azure Tenant ID

In the Azure portal, search for **Tenant properties** and record the **Tenant ID**.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-07.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=4ffefdbbeedff5e6a8f47d0317d42c63" alt="Search Tenant properties" width="1920" height="1016" data-path="images/ursa-lakehouse/open-catalog-azure-07.webp" />

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-08.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=602dc926a03474d08697053a188d4c43" alt="Tenant ID" width="1920" height="973" data-path="images/ursa-lakehouse/open-catalog-azure-08.webp" />

### 2.2 Storage Service Endpoint

In the Azure portal, navigate to **Storage accounts**, open the target storage account, and click **Settings -> Endpoints**. Record the **Blob service** primary endpoint (for example, `https://<account>.blob.core.windows.net/`).

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-09.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=be366c0977c703066a44a7d3b9e344cf" alt="Search Storage accounts" width="1920" height="969" data-path="images/ursa-lakehouse/open-catalog-azure-09.webp" />

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-10.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=6a2f1a07badeea6507acb56b6d65c91e" alt="Storage endpoint" width="1920" height="968" data-path="images/ursa-lakehouse/open-catalog-azure-10.webp" />

### 2.3 Create a Container

In the storage account, navigate to **Data storage -> Containers -> + Container** and create a new container.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-11.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=6c48cd75ad0f810fd65b8f57e3704ed5" alt="Create container" width="1920" height="973" data-path="images/ursa-lakehouse/open-catalog-azure-11.webp" />

## 3. Create the Polaris Catalog

In the Snowflake Open Catalog console, create a new catalog.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-12.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=7db143ef8b189472e72f8dbf56388558" alt="Create catalog" width="1920" height="935" data-path="images/ursa-lakehouse/open-catalog-azure-12.webp" />

> **Important:** The StreamNative compaction service writes data using the AzureDFS protocol, so the Polaris catalog must use the same protocol. Use `abfss://<container>@<account>.dfs.core.windows.net` (note `dfs`, not `blob`).

Configure the catalog with:

* **External:** disabled
* **Storage provider:** AZURE
* **Default base location:** `abfss://<container>@<storage-account>.dfs.core.windows.net`
* **Azure tenant ID:** the value from step 2.1

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-13.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=54359553dc8edf7ef005eea9edfb05ac" alt="Catalog configuration" width="1920" height="966" data-path="images/ursa-lakehouse/open-catalog-azure-13.webp" />

## 4. Create a Trusted App in Azure

Open the catalog details and record the values of `AZURE_CONSENT_URL` and `AZURE_MULTI_TENANT_APP_NAME`.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-14.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=bd765e166064a49121272f65c99075a7" alt="Catalog Azure values" width="1920" height="982" data-path="images/ursa-lakehouse/open-catalog-azure-14.webp" />

Open the `AZURE_CONSENT_URL` in a browser and click **Accept**. This redirects to Snowflake and creates a trusted app in Azure. The trusted app name is the portion of `AZURE_MULTI_TENANT_APP_NAME` before the underscore.

> **Note:** Provisioning the trusted app in Azure can take several minutes.

## 5. Grant Container Permissions to the Trusted App

In the Azure portal, navigate to the storage account's **Access Control (IAM) -> + Add -> Add role assignment**.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-15.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=b3d064bf3725e87c863343cd55c73332" alt="Add role assignment" width="1920" height="1014" data-path="images/ursa-lakehouse/open-catalog-azure-15.webp" />

Search for **Storage Blob Data** and select **Storage Blob Data Contributor**.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-16.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=659736f3d737a1d6bd3864d07f646895" alt="Select role" width="1920" height="966" data-path="images/ursa-lakehouse/open-catalog-azure-16.webp" />

Click **Select members**, search for the trusted app name from step 4, select it, and click **Review + assign**.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-17.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=1dc3eda2270500d1cc3d1026339fa083" alt="Select members" width="1920" height="971" data-path="images/ursa-lakehouse/open-catalog-azure-17.webp" />

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-18.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=0f5c183a4c245c95ea47c0a1a59c4b3b" alt="Role assigned" width="1920" height="969" data-path="images/ursa-lakehouse/open-catalog-azure-18.webp" />

## 6. Create a Connection (Service Principal)

In the Open Catalog console, create a new connection that StreamNative Ursa will use to authenticate.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-19.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=8148e523df76a1d7f8f36ea9f7b5f5d5" alt="Create connection" width="1920" height="931" data-path="images/ursa-lakehouse/open-catalog-azure-19.webp" />

Configure with:

* **Name:** any name
* **Create new principal role:** enabled
* **Principal Role Name:** any name

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-20.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=e97ce9efb0c3097ed600a794ac3e0eef" alt="Connection configuration" width="1920" height="934" data-path="images/ursa-lakehouse/open-catalog-azure-20.webp" />

After creation, record the **Client ID** and **Client Secret** -- the secret cannot be retrieved later.

<img src="https://mintcdn.com/streamnative/IhGBhfNZfZHreuAr/images/ursa-lakehouse/open-catalog-azure-21.webp?fit=max&auto=format&n=IhGBhfNZfZHreuAr&q=85&s=3170d42ecc9266990b9ff3966ea81b48" alt="Connection credentials" width="1920" height="936" data-path="images/ursa-lakehouse/open-catalog-azure-21.webp" />

## 7. Create a Catalog Role and Grant Privileges

Navigate to **Catalogs -> \[your catalog] -> Roles -> + Catalog Role** and create a role with the following privileges:

* `NAMESPACE_CREATE`
* `NAMESPACE_READ_PROPERTIES`
* `TABLE_CREATE`
* `TABLE_WRITE_DATA`
* `TABLE_READ_DATA`

<img src="https://mintcdn.com/streamnative/RcAw7mp1LdooFmHe/images/ursa-lakehouse/open-catalog-azure-22.webp?fit=max&auto=format&n=RcAw7mp1LdooFmHe&q=85&s=3332e314c2757590bf4de46ba5582022" alt="Create catalog role" width="1920" height="941" data-path="images/ursa-lakehouse/open-catalog-azure-22.webp" />

Click **Grant to Principals Role** and grant the catalog role to the principal role created in step 6.

<img src="https://mintcdn.com/streamnative/RcAw7mp1LdooFmHe/images/ursa-lakehouse/open-catalog-azure-23.webp?fit=max&auto=format&n=RcAw7mp1LdooFmHe&q=85&s=118a0d414694ae1319437234858da286" alt="Grant to principal role" width="1920" height="934" data-path="images/ursa-lakehouse/open-catalog-azure-23.webp" />

<img src="https://mintcdn.com/streamnative/RcAw7mp1LdooFmHe/images/ursa-lakehouse/open-catalog-azure-24.webp?fit=max&auto=format&n=RcAw7mp1LdooFmHe&q=85&s=2e374a425baf3a81aef88b7f25d0bffb" alt="Grant configuration" width="1920" height="935" data-path="images/ursa-lakehouse/open-catalog-azure-24.webp" />

## Catalog Information Summary

When the steps above are complete, collect the following values for the StreamNative Ursa compaction service:

| Value                | Description                                                                                                                                                |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `iceberg.uri`        | Polaris REST endpoint (e.g., `https://<account>.<region>.snowflakecomputing.com/polaris/api/catalog`). The format follows the URL of your Polaris console. |
| `iceberg.warehouse`  | The Polaris catalog name created in step 3                                                                                                                 |
| `iceberg.credential` | `<client-id>:<client-secret>` from step 6                                                                                                                  |
| `iceberg.scope`      | `PRINCIPAL_ROLE:ALL`                                                                                                                                       |

## Table Maintenance

Snowflake Open Catalog (Polaris) and the Hadoop catalog do **not** run table maintenance on your behalf. Streaming writes from the StreamNative Ursa compaction service produce many small Parquet files and accumulate snapshot history over time, which degrades query performance and inflates storage costs. You are responsible for scheduling and running maintenance against every Iceberg table written by Ursa.

Run the maintenance operations below on a regular schedule. They are provided as [Apache Iceberg Spark stored procedures](https://iceberg.apache.org/docs/latest/spark-procedures/) and can be triggered from any Spark cluster (Databricks, AWS EMR, AWS Glue, GCP Dataproc, or self-managed Spark) that has the Iceberg Spark runtime, catalog credentials, and IAM access to the warehouse bucket.

**Maintenance operations**

| Operation             | Purpose                                                                                                                               | Suggested cadence                                                                         |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `rewrite_data_files`  | Compact small Parquet files into fewer, larger files. Reduces file-listing overhead and improves scan performance.                    | Hourly to daily, depending on ingestion rate                                              |
| `expire_snapshots`    | Drop snapshots older than the retention window so their data and manifest files can be cleaned up.                                    | Daily; retain at least 1–7 days so in-flight readers and time-travel queries keep working |
| `remove_orphan_files` | Delete files in the table location that are no longer referenced by any snapshot (typically left behind by failed or partial writes). | Weekly                                                                                    |
| `rewrite_manifests`   | Rewrite manifest files so they align with the current partition layout. Improves query planning time.                                 | Weekly, or after large schema or partition changes                                        |

**Example: run maintenance from Spark**

The following examples assume the catalog has been registered in Spark as `<catalog>`. Replace `<catalog>`, `<namespace>`, and `<table>` with your values.

```sql theme={null}
-- Compact small files. Iceberg targets files smaller than the default 512 MB.
CALL <catalog>.system.rewrite_data_files(table => '<namespace>.<table>');

-- Expire snapshots older than 3 days; keep the 5 most recent snapshots.
CALL <catalog>.system.expire_snapshots(
  table       => '<namespace>.<table>',
  older_than  => TIMESTAMP '2026-05-20 00:00:00',
  retain_last => 5
);

-- Remove orphan files older than 7 days.
CALL <catalog>.system.remove_orphan_files(
  table      => '<namespace>.<table>',
  older_than => TIMESTAMP '2026-05-20 00:00:00'
);

-- Rewrite manifests to match the current partition layout.
CALL <catalog>.system.rewrite_manifests(table => '<namespace>.<table>');
```

**Operational guidance**

* **Credentials.** The principal that runs maintenance must have catalog privileges to read and write the target table (for example, the same `TABLE_READ_DATA`, `TABLE_WRITE_DATA`, `TABLE_READ_PROPERTIES`, and `TABLE_WRITE_PROPERTIES` privileges configured for the Ursa compaction service) and IAM access to the warehouse bucket so it can read and rewrite the underlying data files. With the Hadoop catalog there is no catalog service to authenticate against — only the bucket IAM access is required.
* **Concurrency.** Iceberg uses optimistic concurrency control. If maintenance commits race with the Ursa compaction writer, one of them retries. Schedule heavy operations (`rewrite_data_files`, `rewrite_manifests`) during low-write windows when possible.
* **Retention vs. time travel.** `expire_snapshots` and `remove_orphan_files` permanently delete files. Choose a retention window that exceeds the longest expected read query and your time-travel SLA.
* **Schedule the workload.** Most teams orchestrate these procedures from Databricks Jobs, AWS EMR steps, Airflow, Dagster, or a Kubernetes `CronJob`. Pick a scheduler that fits your existing operational stack.
* **Reference.** See the [Iceberg Spark procedures](https://iceberg.apache.org/docs/latest/spark-procedures/#metadata-management) documentation for the full parameter list, including options for partial rewrites (`where`), file-size targets, and merge-on-read delete file compaction.

For the next steps, see [Register Lakehouse Catalogs](/cloud/lakehouse/catalogs/register-catalog).
