Blog

Postgres Materialized Views: Basics, Tutorial, and Optimization Tips

September 6, 2023
Gilad Kleinman

What Is a Materialized View in PostgreSQL? 

A materialized view in PostgreSQL is a database object that stores the result of a query physically on disk. Unlike a regular view, which dynamically retrieves data when queried, a materialized view saves the query output and can be refreshed manually or at scheduled intervals.

Materialized views are useful for improving query performance, especially when dealing with complex joins or aggregations on large datasets. Since the data is precomputed and stored, queries against a materialized view can be significantly faster than running the underlying query repeatedly. However, because the data is static until refreshed, materialized views may not always reflect the most up-to-date information from the base tables.

In this article:

  • Advantages of Using Materialized Views in Postgres
  • When to Use Materialized Views in Postgres
  • Quick Tutorial: Getting Started with Postgres Materialized Views
  • 5 Best Practices for Optimizing Materialized Views in Postgres

Advantages of Using Materialized Views in Postgres 

Here are some key advantages of using materialized views in PostgreSQL:

  • Performance improvement: Since materialized views store query results, they reduce query execution time, especially for complex aggregations and joins.
  • Reduced load on source tables: Instead of repeatedly executing expensive queries, applications can query the materialized view, reducing the burden on base tables.
  • Precomputed aggregations: Materialized views are useful for reporting and analytics where data does not need to be updated in real time.
  • Indexing support: Unlike regular views, materialized views can have indexes, further improving query performance.
  • Data caching: They serve as a form of caching, providing quick access to precomputed results without relying on external caching solutions.

When to Use Materialized Views in Postgres

Here are some of the main scenarios for using materialized views in Postgres databases.

Complex and Time-Consuming Queries

In PostgreSQL, some queries are inherently slow due to the complexity of their operations. This includes queries with multiple table joins, aggregations over large datasets, or recursive queries. Running such queries repeatedly can significantly impact database performance, especially in high-traffic environments.

Materialized views help by storing the precomputed results of these queries, eliminating the need to reprocess the same calculations every time the data is requested. This is valuable in applications that generate periodic reports, dashboards, or analytics, where users expect fast query responses without waiting for real-time computation. While the data in a materialized view may not be live, periodic refreshes can ensure it remains relevant.

Performance Optimization

Materialized views improve overall database efficiency by reducing the computational burden on PostgreSQL’s query planner and execution engine. When a query runs directly against base tables, PostgreSQL must scan, join, and filter the raw data in real time. This process can be slow for complex queries, especially when dealing with millions of rows or large aggregations.

By storing query results persistently, materialized views allow applications to access precomputed data instantly, avoiding expensive reprocessing. Additionally, materialized views support indexing, meaning queries that filter or sort data can benefit from indexed access paths, further reducing execution time. 

Data Warehousing

In data warehousing environments, queries often scan and aggregate vast amounts of historical data. Running such queries directly against transactional databases can be slow and inefficient. Materialized views help by precomputing and storing summaries, making queries significantly faster.

For example, a retail business might store daily sales data in a fact table with millions of rows. Instead of running a complex aggregation query every time a report is needed, a materialized view can store precomputed daily, weekly, or monthly summaries. These summaries can be refreshed at scheduled intervals, ensuring analysts access the latest processed data.

Quick Tutorial: Getting Started with Postgres Materialized Views 

In this tutorial, we'll cover how to create, refresh, and optimize materialized views with indexing and remote data access. These instructions are adapted from the PostgreSQL documentation.

Creating a Basic Materialized View

A materialized view is created using the CREATE MATERIALIZED VIEW statement. Here's an example that stores all rows from a table:

CREATE MATERIALIZED VIEW examplematerialview AS
SELECT * FROM exampletab;

This statement is similar to creating a table with CREATE TABLE AS, but with one key difference: PostgreSQL stores the query definition along with the materialized view. This allows refreshing the view later to update its contents.

Refreshing a Materialized View

Unlike regular views, materialized views do not automatically update when the underlying data changes. To keep the data current, use the REFRESH MATERIALIZED VIEW command:

REFRESH MATERIALIZED VIEW examplematerialview;

This re-executes the query used to create the materialized view, replacing the old data with fresh results. This approach is useful for workloads that do not require real-time data but still need periodic updates.

Example: Aggregating Product Data

Materialized views are helpful for summarizing data efficiently. 

1. Consider an product table that tracks inventory costs:

CREATE TABLE product (
    id SERIAL PRIMARY KEY,
    product_name VARCHAR(255) NOT NULL,
    product_description VARCHAR(500),
    product_category VARCHAR(100),
    product_cost_price FLOAT NOT NULL,
    product_price FLOAT NOT NULL,
    added_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    active BOOLEAN DEFAULT TRUE
);

2. If we need quick access to historical product costs  data without querying millions of rows each time, we can create a materialized view that precomputes purchase cost  summaries:

CREATE MATERIALIZED VIEW product_cost_summary AS
  SELECT
    id AS product_id,
    product_name,
    added_on::DATE AS inventory_date,
    product_cost_price::NUMERIC(15, 4) AS cost_amount
  FROM 
    product
  WHERE 
    added_on < CURRENT_DATE;

3. Now, instead of running expensive aggregation queries on demand, applications can retrieve precomputed summaries from product_cost_summary.

4. To keep the materialized view updated, schedule a nightly refresh:

REFRESH MATERIALIZED VIEW product_cost_summary;

Indexing a Materialized View for Faster Queries

Unlike regular views, materialized views support indexes, further improving query performance. Adding an index allows efficient lookups and sorting:

CREATE UNIQUE INDEX cost_summary_product
ON product_cost_summary (product_id, inventory_date);

5 Best Practices for Optimizing Materialized Views in Postgres 

Here are some useful practices to consider when working with materialized views in Postgres.

1. Efficient Refresh Strategies

Refreshing a materialized view can be resource-intensive, especially when dealing with large datasets. PostgreSQL offers two primary refresh methods: 

  • Complete refresh: The default method, which replaces the entire content of the materialized view. It is efficient for smaller datasets but can be slow for large views.
  • Concurrent refresh: Allows the materialized view to be refreshed without locking it, enabling queries to continue using the old data while the refresh occurs. This requires the materialized view to have a unique index.

To refresh a materialized view efficiently:

  • Use REFRESH MATERIALIZED VIEW CONCURRENTLY when high availability is required.
  • Schedule refreshes during off-peak hours to minimize performance impact.

2. Keeping Materialized Views Updated Efficiently

Since materialized views do not automatically update when underlying data changes, it is crucial to define an efficient refresh mechanism. Strategies include:

  • Trigger-based approaches: Use database triggers or event-based mechanisms to track changes and refresh the materialized view when needed.
  • Scheduled refreshes: Automate updates using cron jobs or pgAgent for periodic refreshes.

Example of automating a refresh using cron:

crontab -e
0 2 * * * psql -d mydb -c "REFRESH MATERIALIZED VIEW CONCURRENTLY cost_summary_product;"

This schedules a refresh at 2 AM daily. 

Note: Necessary permissions must be assigned to cron tab user(s) in order to execute this command. 

3. Partitioning Materialized Views

For very large datasets, partitioning a materialized view can significantly improve performance and refresh efficiency. While PostgreSQL does not support native partitioning for materialized views, it is possible t o manually partition the underlying data and create separate materialized views for each partition.

Approach:

  1. Partition the base table using table inheritance or native partitioning.
  2. Create individual materialized views for each partition.
  3. Use a UNION ALL view to combine them for querying.

Example:

CREATE MATERIALIZED VIEW cost_summary_after_apr_2024 AS
SELECT * FROM product_cost_summary WHERE inventory_date >= '2024-04-01';
CREATE MATERIALIZED VIEW cost_summary_before_apr_2024 AS
SELECT * FROM product_cost_summary WHERE inventory_date < '2024-04-01';
CREATE VIEW cost_summary_combined AS
SELECT * FROM cost_summary_before_apr_2024
UNION ALL
SELECT * FROMcost_summary_after_apr_2024;

This allows refreshing partitions independently, reducing processing overhead.

4. Monitoring and Maintenance

To ensure optimal performance, materialized views should be monitored and maintained regularly. Key maintenance tasks include:

  • Track Refresh Times: Measure how long refresh operations take. If performance degrades, consider optimizing the underlying query or indexing strategy.
  • Monitor Staleness: Depending on how often data changes in the source tables, determine acceptable staleness thresholds for each materialized view.
  • Check Disk Usage: Materialized views consume disk space. Monitor storage, especially when maintaining multiple or large views.
  • Validate Index Usage: Ensure indexes are being used effectively with EXPLAIN ANALYZE. If not, reevaluate which columns are indexed.

Challenges of Using Postgres Materialized Views

Performance Impact

Refreshing a materialized view—especially for large datasets—can be resource-intensive, potentially slowing down the database.

Stale Data

Even with automated refreshes, materialized views can become stale between refresh intervals, leading to outdated results.

Complexity in Maintenance

Managing multiple materialized views with varying refresh requirements adds to the operational complexity of the database.

How Epsio Enhances Materialized View Management

At Epsio, we’ve developed streaming SQL engine that compliments your existing PostgreSQL to simplify and optimize materialized view management. Here’s how we can help:

Incremental Materialized Views

Instead of refreshing an entire materialized view, Epsio’s Incremental Materialized Views technology updates only the changed portions of the data. This significantly reduces the refresh time and resource usage.

Real-Time Updates

Our advanced algorithms enable near-real-time updates for materialized views, ensuring your data is always current without heavy computational costs.

Optimized Resource Usage

Epsio minimizes the performance impact of refreshes, allowing you to maintain fast query performance without high infrastructure costs.

Seamless Integration with PostgreSQL

Epsio is fully compatible with PostgreSQL, making it easy to integrate into your existing database workflows without significant changes.

Learn more about Epsio

Deliver instant & up-to-date results for complex queries