feat: saveUserHistory refactor - #3582
Open
stevekaplan123 wants to merge 4 commits into
Open
Conversation
stevekaplan123
marked this pull request as draft
August 4, 2026 18:45
📊 Code Quality Score: 16/100
Was this score accurate? 👍 Yes · 👎 No Scored by GitVelocity · How are scores calculated? |
stevekaplan123
marked this pull request as ready for review
August 4, 2026 19:02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When you read while logged in, your reading history is saved in the
database. When you're logged out there's no account to attach it to, so it's
stored in a browser cookie named
user_historyinstead. The browser sends thatcookie back to us on every single request to the domain.
Two separate size limits apply, and it matters that they're different:
Overrunning limit 2 returns
400 Request Header Or Cookie Too Largefrom nginx before hitting Django.The old code tried to keep the cookie small like this: write the entire history,
immediately read the cookie back, and if what came back was shorter than what was
written, conclude the browser had rejected it and retry with fewer entries.
That only ever detects limit 1.
user_historycan sit comfortably under 4kB —so the browser accepts it and the read-back looks fine — while still pushing the
combined header over nginx's 8kB limit. The check is blind to the failure we
actually hit.
Code Changes
Make the value small before writing it, instead of writing and then reacting.
A new helper walks the history newest-first and keeps the longest run that fits
inside a byte budget:
_trimUserHistoryForCookie. The write path insaveUserHistorybecomes three lines, with no read-back and noretry. Since the value can no longer exceed the budget, the browser never rejects it and
there's nothing left to detect afterward.
Notes for reviewers
Why a byte budget instead of "keep the last 20 refs". The ticket suggested
capping at a fixed number of refs, but history entries aren't a uniform size. Twenty entries is already ~8kB — double the browser's per-cookie limit and at
nginx's header limit. So a count doesn't bound the cookie at all; it only bounds
it if every entry happens to be small. A byte budget bounds it directly. At
3000 bytes you get roughly 7–11 entries in practice.
Why 3000 specifically. It leaves margin under the browser's ~4kB per-cookie
limit, and leaves about 5kB of nginx's 8kB header for every other cookie plus
User-Agent,Referer, and the request line. It's a single constant if we wantto tune it later.
This is really tricky to test in a browser so I wrote a Playwright test to save 40 history items and then checked that only the last few items were still in the history cookie and that the size of the cookie was roughly 3KB.