I deleted 12M rows and the index is still the same size. Why?
Deleting rows marks them dead but leaves index entries until vacuum reclaims the pages, and even then the pages stay allocated to that index. REINDEX INDEX CONCURRENTLY is what actually returns the space.
How many database connections should a service with 4 workers open?
Start from cores, not workers. A pool of about twice the database core count is the usual ceiling, so 8 to 16 for a small instance. Beyond that throughput flattens and latency grows, because the connections queue inside postgres instead of in your pool.
Should I store user settings as JSONB or as real columns?
Columns for anything you filter or join on, JSONB for the long tail nobody queries. The moment you write a WHERE settings->>'plan' = ... that runs often, that key has earned a column and an index.
Any downside worth knowing before I commit?
It commits you to a format that is tedious to migrate away from later. The first weeks also look worse than doing nothing, which is when most people abandon it.