Is there any way to do this correctly without SERIALIZABLE transactions? It would be nice to avoid having to retry transactions. Ideally I'd like to avoid explicit locking as well.
Given this limited example I'd probably choose to model notifications as an array on the user table. Then just "UPDATE user SET notifications = array['a','b']::text WHERE user_id = 1;