I know this question has been asked here a couple of times, but none of the answers had pleased me. This is because almost all of them involve a huge read / write process re
A quick answer on how (I think) IPB does it:
All posts older than the config amount (default 30 days) are automatically marked as read. A cronjob prunes these from each user to keep the size manageable.
All posts less than 30 days old are tracked as a JSON entry for each user ID + category. Ex: 12 categories with 1000 active users = maximum of 12,000 rows.
There is an "unread count" field for quick lookups for, say, the Forum Home, or anywhere else just a number is needed.
I could be completely off on the actual MySQL storage. I couldn't find documentation on this, but I dug through the database and saw a table that /looked/ like read/unread threads (table: core_item_markers, for reference). But I am positive on the hybrid age/mysql model.