This is a classic case for partitioning.
When you insert a new record, have a trigger delete the old record from the user_login_session_latest
sub-table and insert it into the user_login_session_history
sub-table. Have both of them inherit from an empty top-level user_login_session
table so you can still query the two transparently. You will need an additional trigger-maintained column like is_latest
to partition on, since you can't do constraint exclusion on a condition that refers to other rows.
A partial index will work, but not as well as you might hope; it's hard to write a partial index based on the concept of "latest" (you need an app to set a flag), and it might not boost things as much as you expect. The partial index points at a page, but if that page contains 10 old records and one new record you might not gain that much; you'd need to regularly CLUSTER
the table to see much benefit. Partitioning will be a much better option.
Alternately, you can maintain a materialized view of the "latest" users in another table using triggers or application logic. I'd generally prefer to use partitioning in preference to a materialized view for this.
Remember to tune autovacuum to vacuum your tables quite frequently if there's lots of updating/deleting going on.