Serve bounded archived trace API reads through Workhorse
What does this MR do and why?
A byte_offset/byte_limit request for an archived job log in object storage is read in Rails through Gitlab::HttpIO in aligned 128 KiB chunks. A 500 KiB span takes 4-5 GETs and up to 640 KiB of transfer. byte_offset alone has no upper bound and buffers the whole remainder in Rails.
Behind the beta feature flag ci_job_trace_api_range_via_workhorse (default off, project actor), Rails now clamps the span to the artifact size and hands Workhorse a send-url with UpstreamRange: bytes=<first>-<last>. Workhorse fetches exactly that span in one GET and streams it as a plain 200.
- An empty span (offset at or past the end) or a HEAD request is answered by Rails with a 200 and Content-Length. The only storage request is the existence check that
archived?makes on every path. - Logs on local disk stay on the Rails path.
Gitlab::Workhorse.send_urlgains anupstream_range:keyword. When nil, the field is omitted, so existing callers are unaffected.
This flag is separate from ci_job_trace_api_archived_log_via_workhorse, so range reads can be rolled back independently of full-log reads. Workhorse support for UpstreamRange is already merged.
Client-visible differences are the same as for full reads: with the flag on, the response carries storage's strong ETag and Last-Modified, in place of the weak ETag Rails computes from the body. Content-Length comes from storage. A HEAD request now gets Content-Length equal to the span length; today (flag off) it gets Content-Length: 0. Status, bytes, Content-Type and Content-Disposition are unchanged. Storage errors pass through with their own status instead of a Rails JSON 500. verify_workhorse_api! runs first, so non-Workhorse requests get a plain 403.
No changelog entry, because the flag is off by default.
References
- Work item: https://gitlab.com/gitlab-org/gitlab/-/work_items/629425
- Epic: https://gitlab.com/groups/gitlab-org/-/work_items/23204
- Workhorse
UpstreamRangeMR: !256441 (merged) - Full-read counterpart: !255994 (merged)
- Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/work_items/630364
Screenshots or screen recordings
Not applicable: API-only change.
How to set up and validate locally
- Enable artifacts object storage in GDK:
gdk config set object_store.enabled true && gdk reconfigure. - Run a job and wait for its log to archive.
- In a Rails console, run
Feature.enable(:ci_job_trace_api_range_via_workhorse). Puma caches feature flags for about a minute, so wait ~1 minute after each toggle. - Request the trace API with
?byte_offset=1000&byte_limit=5000using curl, with the flag on and off, andcmpthe bodies. They are identical. With the flag on, the response has storage's ETag andLast-Modified; with it off, a weak Rails ETag. Neither hasContent-Range. - In the Rails log,
external_http_countis 1 with the flag on, and grows with the span with it off (5 for a 500 KB span on a test log). Abyte_offsetpast the end gives an empty 200 both ways.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.