DEV Community

Cover image for 7 Ways to Fix PostgreSQL Database Bloat
Team Timescale for Timescale

Posted on

7 Ways to Fix PostgreSQL Database Bloat

If your queries are slowing down or your database is eating up too much storage, there are simple actions to fix it. Here are 7 ways you can help improve database performance and save on storage costs.

1. Use Columnar Compression

PostgreSQL has some compression mechanisms, but if you want to take data compression even further, especially for time-series data, you should use Timescale’s columnar compression.

đŸ’¡ How Timescale Compression Works

It allows you to dramatically compress data through a provided add_compression_policy() function. To achieve high compression rates, Timescale uses various compression techniques depending on data types to reduce your data footprint.

As an example:

Let’s say we have a hypertable with a week's worth of data. Imagine that our application generally only needs data from the last day, but we must keep historical data around for reporting purposes. We could run SELECT add_compression_policy('my_table', INTERVAL '24 hours'); which automatically compresses rows in the my_table hypertable older than 24 hours.

Timescale’s compression would combine all the rows into a single row, where each column contains an array of all the row's data in segments of 1,000 rows. Visually, this would take a table that looks like this:

| time                   | location | temperature |
|------------------------|----------|-------------|
| 2023-09-20 00:16:00.00 | garage   | 80          |
| 2023-09-21 00:10:00.00 | attic    | 92.3        |
| 2023-09-22 00:5:00.00  | basement | 73.9        |
Enter fullscreen mode Exit fullscreen mode

And compress it down to a table like this:


| time                                                                     | location                    | temperature               |
|--------------------------------------------------------------------------|-----------------------------|---------------------------|
| [2023-09-20 00:16:00.00, 2023-09-21 00:10:00.00, 2023-09-22 00:5:00.00]  | [garage, attic, basement]   | [80, 92.3, 73.9]          |
Enter fullscreen mode Exit fullscreen mode

2. Monitor dead tuples

Dead tuples waste storage space in PostgreSQL tables. Install and use the pgstattuple extension to identify table bloat and track cleanup needs:

CREATE EXTENSION IF NOT EXISTS pgstattuple;
Enter fullscreen mode Exit fullscreen mode

If you run the following query,

SELECT * FROM pgstattuple('my_table');
Enter fullscreen mode Exit fullscreen mode

Postgres would give you a table of helpful information in response:

 table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent 
-----------+-------------+-----------+---------------+-----------------+----------------+--------------------+------------+--------------
  81920000 |      500000 |  40000000 |          48.8 |           10000 |        1000000 |                1.2 |     300000 |          0.4
Enter fullscreen mode Exit fullscreen mode
  • table_len tells you how big your table is in bytes, including data, indexes, toast tables, and free space.
  • dead_tuple_len tells how much space is being occupied by dead tuples which can be reclaimed by vacuuming.
  • free_space indicates the unused space within the allocated pages of the table.. Take note that free_space will reset for every new page created.

You can also perform calculations or transformations on the result to make the information more understandable. For example, this query calculates the ratios of dead tuples and free space to the total table length, giving you a clearer perspective on the storage efficiency of your table:

SELECT
(dead_tuple_len * 100.0 / table_len) as dead_tuple_ratio,
(free_space * 100.0 / table_len) as free_space_ratio
FROM
pgstattuple('my_table');
Enter fullscreen mode Exit fullscreen mode

3. Run autovacuum for storage savings

PostgreSQL's autovacuum removes dead tuples and reclaims space. Instead of modifying global settings in postgresql.conf, configure it per table:

ALTER TABLE my_table SET (autovacuum_vacuum_scale_factor = 0, autovacuum_vacuum_threshold = 200)
Enter fullscreen mode Exit fullscreen mode

This will update my_table to have autovacuum run after 200 tuples have been updated or deleted.

More information about additional autovacuum settings are in the PostgreSQL documentation.

4. Reclaim unused pages with pg-repack

When autovacuum isn't enough, VACUUM FULL can reclaim unused pages - but it locks your tables. For production databases, use pg_repack instead. This PostgreSQL extension cleans up table bloat with minimal locking by creating a fresh copy of your table, then swapping it with the original. The result: minimum storage space used with minimal downtime.

5. Find unused indexes

As we mention in this article on indexing design, over-indexing is a frequent issue in many large PostgreSQL databases. Indexes consume disk space, so removing unused or underutilized indexes will help you keep your PostgreSQL database lean.

You can use pg_stat_user_indexes to spot opportunities:

SELECT
relname AS table_name,
indexrelname AS index_name,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,
idx_scan AS index_scan_count
FROM
pg_stat_user_indexes
WHERE
idx_scan < 50 -- Choose a threshold that makes sense for your application.
ORDER BY
index_scan_count ASC,
pg_relation_size(indexrelid) DESC;
Enter fullscreen mode Exit fullscreen mode

(This query looks for indexes with fewer than 50 scans, but this is an arbitrary number. You should adjust it based on your own usage patterns.)

6. Arrange columns by data type (from largest to smallest)

PostgreSQL aligns data types in memory based on their size, which can create unused space between columns. Ordering columns from largest to smallest data type in your table definition minimizes this wasted space.

This practical tip will help you minimize wasted space. Learn more about optimizing database schema design here.

7. Delete old data regularly

Most apps don't need to store data forever. You should always ask yourself: how long should I keep data around?

Setting up data retention policies is essential for managing storage appropriately. TimescaleDB (a PostgreSQL extension) can automatically delete old data using retention policies.

You can create a retention policy by running:

SELECT add_retention_policy('my_table', INTERVAL '24 hours');
Enter fullscreen mode Exit fullscreen mode

In this snippet, Timescale would delete chunks older than 24 hours from my_table.

Key Takeaways

Table bloat wastes storage space and slows query performance. Implement these techniques to keep your PostgreSQL database lean and your database cost low:compress historical data, set retention policies, and maintain regular cleanup routines.

For detailed setup instructions and best practices, dive into the full article.

Top comments (0)