Share etag cache keys between scoped and unscoped URLs
What does this MR do and why?
GitLab uses ETag caching for polling endpoints. Polling means the browser asks the same URL again and again, to check for updates. Examples are the realtime_changes endpoints for issues and epics.
An ETag is a random stamp. GitLab stores it in Redis. The Redis key is the URL path of the request.
When the browser sends its old stamp, and Redis has the same stamp, the server answers "304 Not Modified". It sends no data. Rails code does not run.
When something changes, for example an epic title, GitLab writes a new stamp for that key. It builds the key with a URL helper, like realtime_changes_group_epic_path.
GitLab now has organization-scoped URLs. They look like /o/:organization_path/.... They show the same page as the URL without /o/.... URL helpers can return either form.
This caused a bug:
- The browser polls using one URL form.
- The code that writes a new stamp uses the other URL form.
- These two forms make two different Redis keys.
- The stamp the browser checks never changes.
- The browser gets "304 Not Modified" forever.
- The page never gets new data, and there is no error message.
In development and test, there is a second problem. Gitlab::EtagCaching::Store checks that each key matches a known URL pattern. The epic pattern only allows paths that start with /groups/. A scoped key does not match. This raises InvalidKeyError during GraphQL mutations, shown as a 500 error.
This MR adds a new method, Gitlab::Routing::OrganizationsHelper.unscoped_path. It removes the /o/:organization_path part from the start of a path. Gitlab::EtagCaching::Router::Rails and the store now use it. Polling and writing use the same Redis key again. GraphQL ETag keys, like /api/graphql:..., do not change.
This MR must merge before !254952 (merged). That MR makes URL helpers return organization-scoped URLs in more places, and its pipeline shows this bug.
References
How to set up and validate locally
- In a Rails console, with a non-default organization
my-organd a groupmy-groupwith epic iid 1:store = Gitlab::EtagCaching::Store.new etag = store.touch('/o/my-org/groups/my-group/-/epics/1/realtime_changes') store.get('/groups/my-group/-/epics/1/realtime_changes') == etag # => true; before this MR the touch raised InvalidKeyError - Run the specs:
bin/rspec spec/lib/gitlab/routing/organizations_helper_spec.rb spec/lib/gitlab/etag_caching ee/spec/lib/ee/gitlab/etag_caching
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.