Normally you don't have to worry about that at all.

However, if there has been a mass delete or update, or the sustained change rate was so high that autovacuum couldn't keep up, you may end up with a badly bloated index.

The tool to determine that id the pgstattuple extension:

CREATE EXTENSION pgstattuple;

Then you can examine index bloat like this:

SELECT * FROM pgstatindex('spatial_ref_sys_pkey');

-[ RECORD 1 ]------+-------
version            | 2
tree_level         | 1
index_size         | 196608
root_block_no      | 3
internal_pages     | 1
leaf_pages         | 22
empty_pages        | 0
deleted_pages      | 0
avg_leaf_density   | 64.48
leaf_fragmentation | 13.64

This index is in excellent shape (never used): It has only 14% bloat.

Mind that indexes are by default created with a fillfactor of 90, that is, index blocks are not filled to more than 90% by INSERT.

It is hard to say when an index is bloated, but if leaf_fragmentation exceeds 50-60, it's not so pretty.

To reorganize an index, use REINDEX.

Answer from Laurenz Albe on Stack Overflow
🌐
MinervaDB
minervadb.xyz › home › how to troubleshoot index fragmentation in postgresql?
Troubleshooting Index Fragmentation in PostgreSQL: A Practical Guide
March 12, 2023 - MinervaDB, PostgreSQL, PostgreSQL DBA, PostgreSQL Performance · Index fragmentation in PostgreSQL can occur when data is inserted, updated, or deleted in a table. As new data is added, the index can become fragmented, meaning that the data ...
🌐
PostgreSQL Wiki
wiki.postgresql.org › wiki › Index_Maintenance
Index Maintenance - PostgreSQL wiki
*/ CASE WHEN btpo_next != 0 THEN first_item END AS highkey, /* * distinct_block_pointers is table blocks that are pointed to by items on * the page (not including high key, which doesn't point anywhere). * * This is interesting on leaf pages, because it indicates how fragmented the * index is with respect to table accesses, which is important for range * queries.
🌐
Hashnode
postgresqlblog.hashnode.dev › how-to-fix-fragmented-indexes-in-postgresql
Fixing Fragmented Indexes in PostgreSQL
May 20, 2024 - To troubleshoot fragmented indexes in PostgreSQL, you can utilize the built-in functionality provided by the database. One approach is to use the pgstattuple extension, which provides statistical information about tables and indexes. By running the pgstattuple function on the table with fragmented indexes, you can gather information about the level of fragmentation.
🌐
DEV Community
dev.to › dm8ry › how-to-check-fragmentation-of-tables-and-indexes-in-postgresql-fg7
How to check fragmentation of tables and indexes in PostgreSQL? - DEV Community
April 10, 2023 - postgres=# SELECT * FROM pgstatindex('cities_pkey'); version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation ---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+-------------------- 4 | 0 | 16384 | 1 | 0 | 1 | 0 | 0 | 1.77 | 0 (1 row) postgres=#
Top answer
1 of 2
1

I think leaf_fragmentation is the percentage of leaf pages where the following leaf page has a lower block number. This is relevant for performance, because for an index range scan on a fragmented index, the kernel won't detect a sequential scan, and its read-ahead mechanism is defied.

But for me, the more important column is avg_leaf_density. If that is too low, your index is bloated and inefficient.

The question what values are OK is a difficult one. I'd say that anything over 30% is fine, but 20% is not terrible either.

If you are unsure whether to rebuild an index or not, ask yourself the following:

  • If you observe the bloat over time, does it continuously increase? If yes, that is bad, and a REINDEX is probably necessary.

  • If the bloat stays high, but does not increase, REINDEX once and observe

    • Does that improve the performance of your application?

    • Does the index get bloated again until it is as bloated as before?

    "No" to the second question would suggest to leave the index alone.

If you decide to REINDEX, explore if more aggressive autovacuum settings on the affected table can keep the bloat down.

2 of 2
2

It Depends™. Fragmentation by itself isn't good or bad, it's just a property of the index. If all your queries scan the index for a unique value, they will read a handful of pages, at most, in any case, so it doesn't matter if the index is fragmented. If, on the other hand, you routinely scan wide ranges of pages, lots of empty pages would probably hurt. You'll need to look at the I/O statistics to decide what's acceptable.

🌐
SingleStore
singlestore.com › blog › product › understanding postgresql’s data fragmentation problem, and how singlestoredb is better
Understanding PostgreSQL’s Data Fragmentation Problem, and How SingleStoreDB Is Better
April 28, 2023 - Let's talk about PostgreSQL first, ... stored at the table level. Fragmentation enables you to define groups of rows or index keys within a table according to some algorithm or scheme....
🌐
SmartTechWays
smarttechways.com › home › how to check index fragmentation in postgresql
How to Check Index Fragmentation in PostgreSQL - SmartTechWays - Innovative Solutions for Smart Businesses
3 weeks ago - Index fragmentation in PostgreSQL refers to the situation where the physical storage of the index entries is not contiguous, leading to inefficient access patterns and increased I/O operations. This fragmentation can hinder performance by requiring more time to traverse the index.
Find elsewhere
🌐
Medium
medium.com › @jramcloud1 › reindexing-in-postgresql-17-the-complete-dba-guide-to-keeping-your-indexes-healthy-20d0cd8e828f
Reindexing in PostgreSQL 17: The Complete DBA Guide to Keeping Your Indexes Healthy | by Jeyaram Ayyalusamy | Medium
July 2, 2025 - Over time, this leads to a problem known as index fragmentation — a state where the index structure becomes cluttered with obsolete entries and inefficient page splits. ... UPDATEs in PostgreSQL do not overwrite existing rows; instead, they ...
🌐
Stack Overflow
stackoverflow.com › questions › 71020640 › index-fragmentation-in-posgresql
postgresql - index fragmentation in posgresql - Stack Overflow
Btree leaf pages form a logical chain (a doubly linked list) and if you follow that chain in the forward direction you encounter the key values in sorted order. They also have a "physical" order, their block number within the file. That extension considers it fragmentation if the next page following the linked list is to a "physically" earlier page in the file.
Top answer
1 of 1
5

The newly created index is essentially optimally packed sorted data. To put some more data somewhere in the middle, while still maintaining the optimal packed sorted data you'd have to rewrite half of the index with every insert on average.

This is not acceptable, so the database uses some complicated and clever format for indexes (based on a b-tree data structure) that allow for changing the order of index blocks without moving them on disk. But the consequence of this is that after inserting some data in the middle some of the index data blocks are not 100% packed. The space left can be used in the future but only if the values inserted match to the block with regards of ordering.

So, depending on your usage pattern, you can easily have index blocks only 10% packed on average.

This is compounded by the fact that when you update a row both old and new version have to be present in the index at the same time. And if you do a bulk update of the whole table then the index will have to expand to contain twice the number of rows, although briefly. But it will not shrink back as easily, as this requires basically a rewrite of it all.

The index size tend to grow first and then stabilize after some usage. But the stable size is often nowhere near the size of a newly created one.

You might want to tune the autovacuum to be more aggressive - so the not needed anymore space in table and indexes is recovered faster and therefore can be reused faster. This can make your index stabilize faster and smaller. Also try to avoid too big bulk updates or do a vacuum full tablename after a huge update.

🌐
Medium
medium.com › @dmitry.romanoff › how-to-check-the-fragmentation-of-tables-and-indexes-in-postgresql-4da59dcb1704
How to check the fragmentation of tables and indexes in PostgreSQL? | by Dmitry Romanoff | Medium
November 9, 2023 - postgres=# SELECT * FROM pgstatindex('cities_pkey'); version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation ---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+-------------------- 4 | 0 | 16384 | 1 | 0 | 1 | 0 | 0 | 1.77 | 0 (1 row) postgres=#
🌐
Slideshare
slideshare.net › home › software › bloat and fragmentation in postgresql
Bloat and Fragmentation in PostgreSQL | PDF
December 12, 2018 - • One of the most popular type of index • PostgreSQL has B+Tree • One B+tree node is one page • Leaf nodes have TIDs pointing to the heap  Need new version index tuple when a new version is created B-tree ... 13Copyright©2018 NTT Corp.All Rights Reserved. • Fragmentation • As pages split to make room to added to a page, there might be excessive free space left on the pages • Bloat • Tables or indexes gets bigger than its actual size • Less utilization efficiency of each pages Fragmentation and Bloat
🌐
CYBERTEC PostgreSQL
cybertec-postgresql.com › home › should i rebuild my postgresql index?
Should I rebuild my PostgreSQL index? | CYBERTEC PostgreSQL | Services & Support
September 29, 2025 - avg_leaf_density is the average percentage to which index pages are filled with entries, or 100 minus the index bloat. leaf_fragmentation is the percentage of index pages that are physically after the logically next page on the same index level (the ...
🌐
DEV Community
dev.to › shiviyer › troubleshooting-fragmented-indexes-in-postgresql-ho3
Troubleshooting Fragmented Indexes in PostgreSQL - DEV Community
January 26, 2024 - To troubleshoot fragmented indexes in PostgreSQL, you can utilize the built-in functionality provided by the database. One approach is to use the pgstattuple extension, which provides statistical information about tables and indexes. By running the pgstattuple function on the table with fragmented indexes, you can gather information about the level of fragmentation.
Top answer
1 of 1
9

Fragmentation is often called bloat in PostgreSQL. It relates to its implementation of MVCC where rows are not updated in place or directly deleted, but are copied with a different ID. Those rows are then made visible or invisible depending on the transaction looking at the data. You can start looking at the wiki Show database bloat for more information on fragmentation issues. Bearing in mind that this query will not give you exact numbers, it's a useful tool to have:

SELECT
  current_database(), schemaname, tablename, /*reltuples::bigint, relpages::bigint, otta,*/
  ROUND((CASE WHEN otta=0 THEN 0.0 ELSE sml.relpages::FLOAT/otta END)::NUMERIC,1) AS tbloat,
  CASE WHEN relpages < otta THEN 0 ELSE bs*(sml.relpages-otta)::BIGINT END AS wastedbytes,
  iname, /*ituples::bigint, ipages::bigint, iotta,*/
  ROUND((CASE WHEN iotta=0 OR ipages=0 THEN 0.0 ELSE ipages::FLOAT/iotta END)::NUMERIC,1) AS ibloat,
  CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta) END AS wastedibytes
FROM (
  SELECT
    schemaname, tablename, cc.reltuples, cc.relpages, bs,
    CEIL((cc.reltuples*((datahdr+ma-
      (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::FLOAT)) AS otta,
    COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
    COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::FLOAT)),0) AS iotta -- very rough approximation, assumes all cols
  FROM (
    SELECT
      ma,bs,schemaname,tablename,
      (datawidth+(hdr+ma-(CASE WHEN hdr%ma=0 THEN ma ELSE hdr%ma END)))::NUMERIC AS datahdr,
      (maxfracsum*(nullhdr+ma-(CASE WHEN nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
    FROM (
      SELECT
        schemaname, tablename, hdr, ma, bs,
        SUM((1-null_frac)*avg_width) AS datawidth,
        MAX(null_frac) AS maxfracsum,
        hdr+(
          SELECT 1+COUNT(*)/8
          FROM pg_stats s2
          WHERE null_frac<>0 AND s2.schemaname = s.schemaname AND s2.tablename = s.tablename
        ) AS nullhdr
      FROM pg_stats s, (
        SELECT
          (SELECT current_setting('block_size')::NUMERIC) AS bs,
          CASE WHEN SUBSTRING(v,12,3) IN ('8.0','8.1','8.2') THEN 27 ELSE 23 END AS hdr,
          CASE WHEN v ~ 'mingw32' THEN 8 ELSE 4 END AS ma
        FROM (SELECT version() AS v) AS foo
      ) AS constants
      GROUP BY 1,2,3,4,5
    ) AS foo
  ) AS rs
  JOIN pg_class cc ON cc.relname = rs.tablename
  JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = rs.schemaname AND nn.nspname <> 'information_schema'
  LEFT JOIN pg_index i ON indrelid = cc.oid
  LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid
) AS sml
ORDER BY wastedbytes DESC;

Once you know where bloat is happening, you can take measures to clean the table. The operation is called VACUUMing and can be launched on demand. It is also usually done in the background with the autovacuum worker, so please do not disable it.

As noted in https://www.postgresql.org/support/versioning/, PostgreSQL 9.1 has been unsupported for one year, consider upgrading.

🌐
Ktexperts
ktexperts.com › data-fregmentation-in-postgresql
Data Fragmentation in PostgreSQL – KTEXPERTS
December 28, 2021 - Fragmentation is a database server feature that allows you to control where data is stored at the table level. Fragmentation enables you to define groups of rows or index keys within a table according to some algorithm or scheme.
🌐
Fatdba
fatdba.com › 2018 › 09 › 04 › fragmentation-or-bloating-in-postgresql-how-to-identify-and-fix-it-using-db-vacuuming
Fragmentation Or Bloating in PostgreSQL – How to identify and fix it using DB Vacuuming
July 1, 2020 - Hey Folks, Back with another post on PostgreSQL. This time related with table fragmentation (Bloating in PG) on how to identify it and fix it using Vacuuming. Okay, so we have this table of size 995 MBs with close to 20000000 rows and the DB (postgres default db) size is of 2855 MBs. postgres=# postgres=#…
🌐
CYBERTEC PostgreSQL
cybertec-postgresql.com › home › index bloat reduced in postgresql v14
Index bloat reduced in PostgreSQL v14 | CYBERTEC PostgreSQL | Services & Support
March 5, 2024 - This is particularly likely to happen if you update the same row frequently. Until VACUUM can clean up old tuples, the table and the index will contain many versions of the same row.