The README
in src/backend/access/nbtree
has a lot of in-depth information about this. Quotes in this answer are from there.
If you really delete all but one rows in the table, almost all pages in the index get deleted.
We consider deleting an entire page from the btree only when it's become
completely empty of items. (Merging partly-full pages would allow better
space reuse, but it seems impractical to move existing data items left or
right to make this happen --- a scan moving in the opposite direction
might miss the items if so.) Also, we never delete the rightmost page
on a tree level (this restriction simplifies the traversal algorithms, as
explained below). Page deletion always begins from an empty leaf page. An
internal page can only be deleted as part of deleting an entire subtree.
This is always a "skinny" subtree consisting of a "chain" of internal pages
plus a single leaf page. There is one page on each level of the subtree,
and each level/page covers the same key space.
The space is not released to the operating system, however:
Reclaiming a page doesn't actually change its state on disk --- we simply
record it in the shared-memory free space map, from which it will be
handed out the next time a new page is needed for a page split.
The tree will become “skinny”, because the depth of an index never shrinks. PostgreSQL has an optimization for that:
Because we never delete the rightmost page of any level (and in particular
never delete the root), it's impossible for the height of the tree to
decrease. After massive deletions we might have a scenario in which the
tree is "skinny", with several single-page levels below the root.
Operations will still be correct in this case, but we'd waste cycles
descending through the single-page levels. To handle this we use an idea
from Lanin and Shasha: we keep track of the "fast root" level, which is
the lowest single-page level. The meta-data page keeps a pointer to this
level as well as the true root. All ordinary operations initiate their
searches at the fast root not the true root.
If you run REINDEX INDEX
on the index or VACUUM (FULL)
the table, the index will be rebuilt, and the space will be freed.