What accounts for a large single index size (other than bloat?)
There's almost no deletion taking place on the foos table, so the bloat is non-existent.


You don't need deletions to get bloat.  updates will do so as well.  A freshly made index on 10,000,000 ints gives me a size of 214MB, so you do have bloat (or are using weird hardware, perhaps).  You can't detect bloat just by thinking about to.  Rebuild the index and see if it gets smaller, or use pgstattuple.  Of course having a tightly packed index is not very efficient, as then nearly every operation needs to do page splits.

Your mental model of an index does not match how PostgreSQL does things.

An index does not point to a primary key value, it points directly into the table.  This is called a "tid", and generally takes 6 bytes.  

The data value itself is stored to 8 byte alignment, so even if your data is an int4, it still takes 8 bytes of disk space.  Due to this alignment, You could build an index on two int4 columns, and it will still take the same size as one int4 column.

PostgreSQL is always prepared to handle NULL values on each row, as well as variable length data, even if the definition of the table/index precludes such things.  So each index tuple has a header which tells us whether that row has any NULL or variable length data, as well as the overall length of that tuple.  This takes up some space.

PostgreSQL uses page-organized storage, not just a huge malloc chunk.  Each page contains some more overhead, both per-block and per-entry within the block (see typedef struct ItemIdData).  

So you get 8 bytes for the data itself (bar_id, aligned), 8 bytes of the tuple header which includes the tid pointer, and 4 bytes for the line pointer within each block, that comes up to 20 bytes.  With some overhead of non-leaf pages plus per-block overhead and some space within blocks which is not usable due to alignment or because they are at the ragged end of the data, I get an actual value of 22.4 bytes.  The other 4.5 fold would be bloat (although some of the "bloat" may be useful free space, unless your index is going to be absolutely static from now on)