Incremental materialized views

Updated at:

PolarDB for PostgreSQL provides the incremental materialized view (also called on-demand incremental materialized view, or DIMV) feature. DIMV asynchronously tracks changes on base tables through a materialized view log (mlog) and, at refresh time, applies only the delta instead of recomputing the entire view — significantly improving refresh efficiency.

Background

A materialized view precomputes and stores the result of a query Q. To refresh a standard materialized view, REFRESH MATERIALIZED VIEW re-executes Q in full: it scans every base table, redoes the joins and aggregations, and replaces the old data with the new result. When base tables are large but only a small number of rows change between refreshes, this "full recomputation" spends most of the time on data that has not changed.

Incremental materialized views take a different approach. The materialized view result is a function of its base tables, denoted as V = Q(base tables). If you know the change set Δ on the base tables since the last refresh (which rows were inserted, deleted, or updated), then the change in the view result ΔV depends only on Δ, not on the vast unchanged data. The refresh then becomes "compute ΔV and merge it into the existing V" instead of "recompute the entire V" — the compute cost is proportional to the change volume, not to the base-table size.

Implementing this idea requires solving three problems, corresponding to the three core concepts below:

  1. How to capture base-table changes: Create triggers on the base tables that record every INSERT/UPDATE/DELETE into a log table — the materialized view log (mlog) — which carries the Δ described above.

  2. How to apply changes to the view: At refresh time, read the mlog entries accumulated since the last refresh (the Δ for that window), compute ΔV, and merge it into the materialized view. Because this runs only when you explicitly refresh and processes only the delta, it is called on-demand incremental refresh.

  3. How to reclaim consumed logs: mlog keeps growing as base tables change. Once a record has been consumed by every materialized view that depends on it, the record can be deleted. This process, called log purge, prevents mlog from growing without bound.

Feature overview

  • Materialized view log (mlog): An mlog table is created on the base table; triggers record changes from INSERT, UPDATE, and DELETE.

  • On-demand incremental refresh: At refresh time, DIMV reads the mlog entries accumulated since the last refresh and applies them to the materialized view.

  • Log purge: DIMV cleans up mlog entries that have been consumed by every dependent materialized view, preventing the log from growing indefinitely.

Scope of application

Incremental materialized views (DIMV) require the polar_ivm extension. Before using DIMV, confirm that the cluster kernel version and the extension meet the following requirements.

  • Kernel version: PostgreSQL 14, and the kernel minor version must be 2.0.14.20.46.0 or later.

    Note

    You can view the minor engine version in the console or by running the SHOW polardb_version; statement. If the minor engine version does not meet the requirements, upgrade the minor engine version

  • Install the polar_ivm extension: In the database where you want to use DIMV, install the polar_ivm extension by running the following SQL:

    CREATE EXTENSION IF NOT EXISTS polar_ivm;
  • Base-table requirement: Every base table involved in a DIMV must have a column set that uniquely identifies each row. DIMV relies on this to precisely map changes recorded in mlog to the corresponding view rows during incremental refresh.

    • The base table must explicitly define a PRIMARY KEY. Both single-column and composite primary keys are supported. Tables without a primary key cannot serve as DIMV base tables.

Usage

Create a DIMV and its mlog

When you create a DIMV with CREATE MATERIALIZED VIEW ... REFRESH FAST ON DEMAND ... WITH NO DATA, the system automatically creates an mlog for each involved base table and adds the columns required by the query. Always use WITH NO DATA so that the system creates and augments the mlog for you.

CREATE MATERIALIZED VIEW dimv_name
REFRESH FAST ON DEMAND
AS query
WITH NO DATA;

Each base table needs only one mlog, which can be shared by multiple DIMVs. If an existing mlog is missing columns that a new DIMV depends on, creating the new DIMV with WITH NO DATA lets the system fill in the missing columns automatically. Without WITH NO DATA, you must add the columns beforehand; otherwise, DIMV creation fails.

Build the baseline with the first full refresh

After you create a DIMV with WITH NO DATA, the DIMV has no data and no "incremental refresh baseline" (that is, no trusted starting transaction ID has been recorded). If you call polar_matview.incremental_refresh_mv() directly for an incremental refresh at this point, the following error is returned:

ERROR:  No refresh snapshot found for incremental materialized view mv_name
ERROR:  materialized view "mv_name" has not been populated
HINT:  Use the REFRESH MATERIALIZED VIEW command.

The correct startup sequence is to first run a full REFRESH MATERIALIZED VIEW to build the incremental baseline. After that, you can call polar_matview.incremental_refresh_mv() for on-demand incremental refreshes.

-- 1. Create the base table and insert initial data
CREATE TABLE t_dimv(id INT PRIMARY KEY, cat TEXT, amount NUMERIC);
INSERT INTO t_dimv VALUES(1,'a',10),(2,'a',20),(3,'b',30);

-- 2. Create the DIMV with WITH NO DATA (the system auto-creates mlog on t_dimv)
CREATE MATERIALIZED VIEW mv_dimv
REFRESH FAST ON DEMAND
AS SELECT cat, SUM(amount) FROM t_dimv GROUP BY cat
WITH NO DATA;

-- 3. First full refresh — build the incremental baseline
REFRESH MATERIALIZED VIEW mv_dimv;

SELECT * FROM mv_dimv ORDER BY cat;
-- Output:
-- cat | sum
-- ----+-----
-- a   |  30
-- b   |  30

-- 4. Append more data to the base table
INSERT INTO t_dimv VALUES(4,'b',40),(5,'a',50);

-- 5. On-demand incremental refresh (applies delta only, not a full recompute)
SELECT polar_matview.incremental_refresh_mv('mv_dimv'::regclass::oid);
-- Returns t on success

SELECT * FROM mv_dimv ORDER BY cat;
-- Output:
-- cat | sum
-- ----+-----
-- a   |  80
-- b   |  70
Note
  • Every newly created DIMV must first run a full REFRESH MATERIALIZED VIEW to build the baseline before it can enter the on-demand incremental refresh phase.

  • Once the first full refresh completes, the DIMV is registered in the metadata table polar_ivm.imatview_metadata. All subsequent incremental refreshes — whether triggered manually by polar_matview.incremental_refresh_mv() or by background workers — are computed against that baseline.

  • If a DIMV becomes invalid (for example, after TRUNCATE on a base table), you must run REFRESH MATERIALIZED VIEW again to rebuild the baseline. See Invalidation and recovery.

Configure automatic refresh and purge

DIMVs are not refreshed automatically by default. Use the pg_cron extension to launch one or more long-running DIMV workers when the database starts. Then configure a refresh interval on the DIMVs that need automatic refresh — the workers will perform incremental refresh and mlog purge in the background.

Background worker automatic refresh

polar_matview.auto_refresh_worker_main() is a non-terminating loop procedure: the worker pulls due tasks from the scheduling queue and performs incremental refresh on a single DIMV or purge on a single mlog. Because this procedure must run continuously in the background, running it directly in a foreground session would block the session. In practice, use the pg_cron extension with the @restart trigger to bring workers up automatically when the cluster starts:

-- 1. Install pg_cron if it is not already installed
CREATE EXTENSION IF NOT EXISTS pg_cron;

-- 2. Use pg_cron to bring up DIMV refresh workers in the background when the cluster starts
-- Each cron.schedule call corresponds to one worker. Scale the worker count with the refresh workload.
SELECT cron.schedule(
    'dimv-auto-refresh-worker-1',
    '@restart',
    $$CALL polar_matview.auto_refresh_worker_main()$$
);

SELECT cron.schedule(
    'dimv-auto-refresh-worker-2',
    '@restart',
    $$CALL polar_matview.auto_refresh_worker_main()$$
);

Each call corresponds to one worker; size the worker count to your refresh workload. These workers handle both automatic refresh and automatic mlog purge.

Note

The worker session's statement_timeout and polar_transaction_timeout must both be 0; otherwise the worker is interrupted by the timeout and repeatedly restarted. If both are 0 by default, no extra configuration is needed. If either is non-zero, create a dedicated role, set both to 0 on that role, and schedule workers under that role:

-- Create a dedicated role and set both timeout parameters to 0
CREATE ROLE dimv_worker LOGIN;
ALTER ROLE dimv_worker SET statement_timeout = 0;
ALTER ROLE dimv_worker SET polar_transaction_timeout = 0;

-- Schedule workers under that role (via pg_cron background execution)
SELECT cron.schedule_in_database(
    'dimv-auto-refresh-worker-1',
    '@restart',
    $$CALL polar_matview.auto_refresh_worker_main()$$,
    'postgres',       -- Target database
    'dimv_worker'     -- Execute as this dedicated role
);

Set the refresh interval

Once a refresh interval is configured on a DIMV, the worker performs incremental refresh at that interval. The system also maintains the purge interval of the related mlog automatically, so you do not need to configure purge separately. Use polar_matview.set_refresh_interval() and polar_matview.reset_refresh_interval() to set or clear the refresh interval on a specific DIMV (the argument is the materialized view OID):

-- Set the automatic refresh interval of a DIMV to 1 second
SELECT polar_matview.set_refresh_interval('dimv_name'::regclass::oid, '1 seconds'::interval);

-- Disable automatic refresh for a DIMV
SELECT polar_matview.reset_refresh_interval('dimv_name'::regclass::oid);

The scheduler scans every DIMV in polar_ivm.imatview_metadata that is registered and has a refresh interval greater than or equal to 0, and refreshes each one independently at its own interval. Refresh is per-object; refreshing one DIMV does not automatically drive its dependent DIMVs. In nested scenarios, set the interval on each level separately.

Unless you have a specific reason, set the refresh interval in seconds rather than minutes. The CPU cost of incremental refresh depends on the delta volume, not on the refresh frequency: stretching the interval does not reduce total cost — it only lets changes pile up between refreshes and produces resource spikes at refresh time. A smaller interval flattens the load and smooths out the peaks.

Note that each refresh has fixed overhead (such as locking), so an interval that is too small increases the share of that fixed cost. Setting the interval to 0 (refresh as soon as possible) can actually increase total resource cost, and is recommended only when you need the lowest possible refresh latency.

When a base table is directly depended on by multiple DIMVs, the automatic purge interval of that base table's mlog takes the largest refresh interval among those DIMVs. If all direct dependents disable automatic refresh, automatic purge on that mlog is also disabled.

The scheduler only processes DIMVs that are still incrementally updatable and mlogs that are still valid. If a DIMV or mlog becomes invalid, run the recovery procedure first.

End-to-end example

The following end-to-end example covers the entire DIMV lifecycle, from installing the extension to configuring automatic refresh.

-- 1. Install extensions
CREATE EXTENSION IF NOT EXISTS polar_ivm;
CREATE EXTENSION IF NOT EXISTS pg_cron;

-- 2. Launch background workers (auto-started after each database restart)
SELECT cron.schedule(
    'dimv-auto-refresh-worker-1',
    '@restart',
    $$CALL polar_matview.auto_refresh_worker_main()$$
);

-- 3. Create base tables and insert initial data
CREATE TABLE products (
    product_id  INT PRIMARY KEY,
    name        TEXT,
    category    TEXT,
    price       NUMERIC
);

CREATE TABLE order_items (
    item_id     BIGSERIAL PRIMARY KEY,
    product_id  INT,
    order_date  DATE,
    quantity    INT
);

INSERT INTO products VALUES
    (1, 'Widget A',  'Electronics', 100),
    (2, 'Widget B',  'Electronics', 150),
    (3, 'Gadget C',  'Home',         80);

INSERT INTO order_items (product_id, order_date, quantity) VALUES
    (1, '2024-01-01', 10),
    (2, '2024-01-02',  5),
    (1, '2024-01-03',  8);

-- 4. Create the DIMV with WITH NO DATA; the system auto-creates and augments the mlog
CREATE MATERIALIZED VIEW mv_product_sales
REFRESH FAST ON DEMAND
AS
SELECT p.product_id,
       p.category,
       SUM(oi.quantity) AS total_qty,
       COUNT(*)         AS order_count,
       MAX(oi.order_date) AS latest_order
FROM products p
JOIN order_items oi ON p.product_id = oi.product_id
GROUP BY p.product_id, p.category
WITH NO DATA;

-- 5. First full refresh — build the incremental baseline
REFRESH MATERIALIZED VIEW mv_product_sales;

-- 6. Configure a 1-second automatic refresh; the system manages the corresponding mlog purge interval
SELECT polar_matview.set_refresh_interval(
    'mv_product_sales'::regclass::oid,
    '1 second'::interval
);

-- 7. Subsequent base-table changes are incrementally refreshed into the materialized view by the background worker, and mlog is purged automatically
INSERT INTO order_items (product_id, order_date, quantity) VALUES (3, '2024-01-04', 20);
UPDATE order_items SET quantity = quantity + 1 WHERE item_id = 1;
DELETE FROM order_items WHERE item_id = 2;

-- Wait one refresh cycle and query to see the latest results
SELECT * FROM mv_product_sales ORDER BY product_id;
Note

Best practices:

  • Always create DIMVs with WITH NO DATA and let the system create and augment mlog for you.

  • Prefer background workers for automatic refresh and purge, and scale the worker count with the workload. Independent DIMVs can be processed in parallel by multiple workers.

  • Set timeout parameters — including statement_timeout and polar_transaction_timeout — to 0 on the role that schedules workers, so that workers are not interrupted.

  • Regularly monitor the valid status of DIMVs and mlogs through the monitoring views (polar_matview_monitor.dimv_stat_refresh, polar_matview_monitor.mlog_stat_purge) and recover invalid objects promptly.

Manual management

The following are manual operations for when you need precise control over mlog, refresh, or purge timing. For most cases, use WITH NO DATA and let the system manage them automatically.

Manually create and adjust mlog

The recommended approach is to create DIMVs with WITH NO DATA and let the system manage mlog. If you need to create mlog before creating the DIMV, or add and drop columns on an existing mlog, use the following functions provided by the polar_ivm schema.

Create an mlog

If you do not use WITH NO DATA when creating a DIMV, you must manually create an mlog on each base table before creating the DIMV:

SELECT polar_ivm.create_matview_log('base_table'::regclass);

Add and drop columns

If a column required by a new DIMV is missing from the existing mlog, use matview_log_add_column to add it. Columns that are no longer needed can be removed with matview_log_drop_column:

-- Add a column to mlog
SELECT polar_ivm.matview_log_add_column('base_table'::regclass, 'new_col');

-- Drop a column from mlog
SELECT polar_ivm.matview_log_drop_column('base_table'::regclass, 'old_col');

Drop an mlog

Once all DIMVs that depend on an mlog have been dropped, you can remove the mlog on that base table with drop_matview_log:

SELECT polar_ivm.drop_matview_log('base_table'::regclass);
Note

Dropping an mlog fails while any DIMV still depends on it. Drop the dependent DIMVs first.

Manual refresh

After changes are made to base tables, call polar_matview.incremental_refresh_mv() to perform an on-demand incremental refresh (the argument is the materialized view OID):

-- DML on base tables
INSERT INTO users VALUES (1, 'Alice');
UPDATE users SET name = 'Alice A.' WHERE userid = 1;
INSERT INTO sales VALUES (100, 1, '2023-01-01', 500);

-- Incrementally refresh the DIMV that depends on both base tables
SELECT polar_matview.incremental_refresh_mv('mv_sales_summary'::regclass::oid);

Incremental refresh has the following transaction constraints:

  • Must run at the READ COMMITTED isolation level.

  • The finest constraint unit is the transaction. Within a single transaction, if a base table has already executed DML that writes to mlog, you cannot then refresh a DIMV that depends on that base table, and vice versa.

  • The constraint is evaluated per base table. Modifying unrelated tables or refreshing DIMVs that do not depend on that base table is not affected.

  • You can refresh the same DIMV or different DIMVs multiple times, and modify the same base table multiple times.

You can also run a full refresh. A full refresh updates the incremental baseline; subsequent incremental refreshes are computed from the new baseline:

REFRESH MATERIALIZED VIEW mv_sales_summary;

Manual purge

Use polar_matview.matview_log_purge() to manually purge the mlog of a specific base table:

SELECT polar_matview.matview_log_purge('base_table'::regclass);
Note

Purge only removes entries that have already been consumed by every dependent DIMV. It is a good practice to refresh the related DIMVs first and then purge the mlog. Otherwise unconsumed entries are retained.

Nested DIMVs

A DIMV can reference another DIMV to form nesting. There are two forms: manual nesting and automatic nesting.

Manual nesting

You explicitly reference one DIMV from another. Example: first aggregate sales by user, then filter to VIP customers:

-- Inner DIMV: aggregate sales by user
CREATE MATERIALIZED VIEW mv_sales_by_user
REFRESH FAST ON DEMAND
AS
SELECT userid,
       SUM(amount) AS total_amount,
       COUNT(*) AS order_count
FROM sales
GROUP BY userid
WITH NO DATA;

-- Outer DIMV: reference the inner DIMV to filter VIPs
CREATE MATERIALIZED VIEW mv_vip_sales
REFRESH FAST ON DEMAND
AS
SELECT u.userid, u.name, s.total_amount
FROM users u
JOIN mv_sales_by_user s ON u.userid = s.userid
WHERE s.total_amount > 10000
WITH NO DATA;

-- First full refresh: inner first, then outer
REFRESH MATERIALIZED VIEW mv_sales_by_user;
REFRESH MATERIALIZED VIEW mv_vip_sales;

-- After base-table changes, still refresh the inner view first, then the outer view
SELECT polar_matview.incremental_refresh_mv('mv_sales_by_user'::regclass::oid);
SELECT polar_matview.incremental_refresh_mv('mv_vip_sales'::regclass::oid);
Note

When using automatic refresh, you must set the refresh interval on the inner and outer DIMVs separately. Refreshing the outer view does not automatically drive the inner view; setting the interval only on the outer view causes it to read stale data from the inner view.

Automatic nesting

When you create a DIMV with WITH NO DATA, the system can split certain complex queries into internal DIMVs, and let the top-level DIMV reference the internal results. Typical scenarios include:

  • Outer joins combined with aggregation.

  • An aggregate result used inside a function expression, for example md5(sum(amount)).

  • Certain subqueries containing aggregation, DISTINCT, GROUP BY, or window functions.

Internal DIMVs live in the polar_autonest_dimv schema, are maintained by the system, and should not be modified directly. Automatic nesting does not cover all complex SQL; LATERAL outer references, HAVING, and some combinations of DISTINCT with complex aggregate expressions are rejected.

For the first full refresh, you must first run REFRESH MATERIALIZED VIEW on the internal objects of the top-level DIMV in the polar_autonest_dimv schema, and then refresh the top-level object. Only after this initial full refresh are the inner and top-level objects registered in polar_ivm.imatview_metadata.

The automatic refresh scheduler scans every DIMV in polar_ivm.imatview_metadata that is registered and has a refresh interval greater than or equal to 0, and refreshes each one independently at its own interval — refreshing the top-level DIMV does not automatically drive the inner ones, and set_refresh_interval only applies to the specified object without cascading to inner objects. Therefore, the internal objects produced by automatic nesting also need their own refresh interval; setting the interval only on the top-level DIMV leaves the inner objects unrefreshed and the top-level DIMV reading stale data.

Partitioned tables as base tables

DML on the parent table, new partitions, and DML after ATTACH are all tracked through the parent-table mlog. After a partition is DETACH-ed, it no longer writes to the parent-table mlog.

Note that ATTACH and DETACH PARTITION themselves do not write to mlog and therefore do not update DIMV data automatically:

  • When you DETACH a non-empty partition, its existing contribution to the DIMV result is not automatically removed.

  • When you ATTACH a non-empty partition, its existing data is not automatically included in the DIMV result.

Therefore:

  • Use an empty table when running ATTACH. Any data inserted after ATTACH is tracked normally through mlog.

  • After DETACH, run a full refresh on the related DIMVs to remove the contribution of the detached partition. If the business can tolerate keeping the contribution of the DETACH-ed partition, or if that is desired, no action is required.

Monitoring views

DIMV refresh statistics

Use the monitoring view polar_matview_monitor.dimv_stat_refresh to inspect DIMV refresh statistics:

SELECT * FROM polar_matview_monitor.dimv_stat_refresh;

The columns are:

Column

Description

dimv_name

DIMV name.

dimv_oid

DIMV OID.

calls

Total number of incremental refreshes.

mean_time

Average duration of incremental refresh.

max_time

Maximum duration of incremental refresh.

min_time

Minimum duration of incremental refresh.

total_time

Total duration of incremental refresh.

last_start_time

Start time of the most recent incremental refresh.

last_end_time

End time of the most recent incremental refresh.

data_delay

Data lag — the difference between the current time and the last refresh time.

xid_delay

Transaction lag — the age of the transaction ID at the last refresh baseline.

valid

Whether the DIMV can still be incrementally refreshed. When it is false, follow Invalidation and recovery.

mlog purge statistics

Use the monitoring view polar_matview_monitor.mlog_stat_purge to inspect mlog purge statistics:

SELECT * FROM polar_matview_monitor.mlog_stat_purge;

The columns are:

Column

Description

relname

Name of the base table associated with the mlog.

relid

Base table OID.

calls

Total number of purges.

mean_time

Average duration of purge.

max_time

Maximum duration of purge.

min_time

Minimum duration of purge.

total_time

Total duration of purge.

mean_rows

Average number of rows purged per run.

max_rows

Maximum number of rows purged per run.

min_rows

Minimum number of rows purged per run.

total_rows

Total number of rows purged.

last_remain_rows

Remaining mlog rows after the most recent purge.

last_start_time

Start time of the most recent purge.

last_end_time

End time of the most recent purge.

age

Age of the oldest transaction ID in the mlog. As it approaches polar_mlog_xid_max_limit, the mlog is approaching invalidation.

valid_lifecycle

Time remaining before the mlog is invalidated due to a purge timeout.

valid

Whether the mlog is valid.

Invalidation and recovery

mlog invalidation and recovery

An mlog is marked invalid when either of the following conditions is met. Dependent DIMVs receive a warning and get false as the return value from incremental refresh (polar_matview.incremental_refresh_mv()):

  • The mlog's transaction ID span exceeds polar_mlog_xid_max_limit (default 2 billion). As transaction IDs approach wraparound, the relative order of log entries can no longer be determined reliably.

  • The time since the last purge exceeds polar_mlog_purge_max_interval (default 18,000 seconds). After long periods without purge, even the timestamp-based fallback cannot guarantee that log entries remain identifiable.

These two conditions are not triggered under normal operation. As long as the mlog is continuously purged by workers, its transaction ID span keeps shrinking and never approaches 2 billion. They are only a safety net — typically only hit when you have disabled automatic purge, switched to manual purge, and let the mlog go without purge for a long time (long enough for over 2 billion transaction IDs to be consumed).

Detect mlog state through the valid, age, and valid_lifecycle columns of the polar_matview_monitor.mlog_stat_purge monitoring view.

To recover, truncate and revalidate the mlog, and then rebuild the baseline on the dependent DIMVs:

SELECT polar_ivm.matview_log_truncate_and_validate('base_table_name'::regclass);

DIMV invalidation and recovery

A DIMV becomes invalid when the materialized view itself can no longer be incrementally refreshed (the metadata field polar_ivm.imatview_metadata.is_incrementally_updatable is set to false). This is triggered by the following operations:

  • TRUNCATE on a base table: all populated DIMVs that depend on that base table become invalid.

  • A non-concurrent full REFRESH MATERIALIZED VIEW on a nested DIMV that serves as a base table for other DIMVs: it resets that DIMV's mlog and cascades invalidation to the downstream DIMVs.

  • The mlog of a base table is reset (for example, by polar_ivm.matview_log_truncate_and_validate()): DIMVs dependent on that base table are cascaded to invalid.

Other incompatible DDL that would break the incremental baseline (for example, dropping or altering a depended-on column) is blocked by default, rather than silently invalidating the DIMV. See Impact of DDL on base tables.

Impact of DDL on base tables

DDL on DIMV base tables is constrained to protect the incremental baseline. The behavior of common operations is as follows:

DDL operation

Behavior

ALTER TABLE ADD COLUMN

Allowed. New DIMVs that use the column require the column to be added to mlog; WITH NO DATA can add it automatically.

ALTER TABLE DROP COLUMN

Blocked when the column is depended on by a DIMV or mlog. Use CASCADE to drop dependents transitively.

ALTER TABLE RENAME COLUMN

Allowed; dependent definitions are updated automatically.

ALTER TABLE ALTER COLUMN TYPE

Blocked when the column is depended on by a DIMV or mlog.

ALTER TABLE RENAME

Allowed; dependent definitions are updated automatically.

TRUNCATE

Allowed; the DIMV becomes not incrementally updatable and requires a full refresh to recover.

Parameter reference

Parameter

Type

Default value

Description

polar_auto_create_mlog_index

bool

on

Whether to automatically copy base-table indexes to mlog.

polar_ivm_stat_track

enum

enable

Whether to enable statistics for DIMV refresh and mlog purge. Valid values: enable or disable.

polar_mlog_xid_max_limit

int

2000000000

Upper limit on the mlog transaction ID span. The mlog is invalidated when it is exceeded. Default 2 billion.

polar_mlog_purge_max_interval

int

18000

Maximum time (in seconds) since the mlog was last purged. The mlog is invalidated when it is exceeded.

Appendix: Supported query types

Feature

Supported

Notes

Simple SELECT

Supported

Projection, filtering, and expressions are supported.

Inner join

Supported

Multi-table joins and self-joins are supported.

Outer join

Partially supported

Some scenarios are supported through automatic nesting; complex combinations have constraints.

WHERE

Supported

Common expressions and some subquery scenarios are supported.

GROUP BY

Supported

Single-column, multi-column, and NULL grouping keys are supported.

Common aggregates

Supported

SUM, AVG, COUNT, MIN, MAX.

Arbitrary aggregates

Supported

Must be maintained per GROUP BY group.

Top-level DISTINCT

Supported

DISTINCT inside a subquery is only supported in some scenarios through automatic nesting.

Subqueries

Partially supported

Some simple subqueries, EXISTS, IN, and simple CTEs are supported.

Partitioned tables as base tables

Supported

The mlog is created on the partition parent table.

Partitioned materialized view

Supported

Helper functions are provided to create the parent table and partitioned materialized view.

Window functions

Partially supported

Must include PARTITION BY, and additional constraints listed below apply.

HAVING

Not supported

DIMV definitions cannot include HAVING.

UNION / INTERSECT / EXCEPT

Not supported

Set operations are not supported.

LIMIT / OFFSET

Not supported

Result trimming is not supported.

Volatile functions

Not supported

For example, random() and now().

Complex CTEs, nested EXISTS, LATERAL outer references, and certain combinations of aggregates, GROUP BY, DISTINCT, and EXISTS have constraints. Validate specific SQL in a test database first.

Supported aggregate functions

In addition to the common aggregates (SUM, AVG, COUNT, MIN, MAX), DIMV also supports the following aggregates:

  • String and array aggregates: string_agg, array_agg.

  • Ordered-set aggregates: such as percentile_disc and percentile_cont.

  • User-defined aggregates: aggregate functions that you define with CREATE AGGREGATE.

Example using string_agg and array_agg:

CREATE MATERIALIZED VIEW mv_agg
REFRESH FAST ON DEMAND
AS
SELECT cat,
       string_agg(name, ',') AS names,
       array_agg(id ORDER BY id) AS ids
FROM t_dimv
GROUP BY cat
WITH NO DATA;

Constraints on arbitrary aggregates

  • Requires GROUP BY. Whole-table arbitrary aggregates without GROUP BY are not supported.

  • NULL grouping keys and multi-column grouping keys are supported.

  • Groups that change are recomputed in full; refresh cost is approximately O(delta group).

  • Common aggregates can be maintained at the base-table change granularity and are usually cheaper than arbitrary aggregates.

Constraints on window functions

PolarDB for PostgreSQL currently supports the following window functions: ROW_NUMBER(), RANK(), DENSE_RANK(), LEAD(), LAG(), FIRST_VALUE(), LAST_VALUE(), NTH_VALUE(), and window aggregates. Constraints:

  • Every window function must specify PARTITION BY. Whole-table windows are not supported.

  • Multiple window functions must have PARTITION BY keys that form a compatible subset chain.

  • PARTITION BY can use columns or immutable function expressions.

  • Window functions cannot be combined with GROUP BY or aggregate functions.

  • Window functions cannot be combined with outer joins or EXISTS sublinks.

  • Window functions cannot appear inside a subquery.