Make the RSpec retry process cheaper
What does this MR do?
Makes the RSpec retry process (RETRY_FAILED_TESTS_IN_NEW_PROCESS) about 85s cheaper. With dynamic test balancing, the lanes that retry a failure finish about 2.5 min after the rest of the group in nearly every pipeline. The failing examples take only about 5s of that. This MR removes the two biggest avoidable steps:
-
Empty the
auto_explainlog after recording it (about 38s).AutoExplain.recordreadslog/pglog.csv, the PostgreSQL service's log for the whole job. So the retry process rescanned everything the first process had already recorded, and wrote a second file with essentially the same entries. In the example lane below, both processes wrote 1090 / 84 / 156 entries formain/ci/sec.scripts/merge-auto-explain-logsthen deduped them by fingerprint.Now, once every database is recorded,
recordempties the log withCOPY (SELECT 1 WHERE false) TO '<data_directory>/log/pglog.csv'. That needs superuser, whichrecord_auto_explain?already requires. The next RSpec process in the job then only scans and records its own queries. Nothing is lost, including queries that only a passing retry reaches.Tested against a throwaway PostgreSQL 17 started with the CI logging flags (
logging_collector,csvlog,log_filename=pglog,auto_explain.log_min_duration=0, JSON format):- After truncation, PostgreSQL keeps appending from the start of the file. It writes no NUL bytes, and
file_fdwstill parses the file. - Running
AutoExplain.setup/recordthrough Rails twice, as a first process and then as a retry: the retry's files only contained the retry's own queries.
- After truncation, PostgreSQL keeps appending from the start of the file. It writes no NUL bytes, and
-
Compare two commits in
warn_on_successfully_retried_test(about 48s).git diff --name-only $CI_MERGE_REQUEST_TARGET_BRANCH_SHAwith only one commit compares that commit with the working tree. RSpec jobs restore the checkout from theclone-gitlab-repoartifact, so the file metadata in the index no longer matches the files on disk, and git rehashes every tracked file. Comparing the target branch SHA withHEAD(the merged-results commit) only compares trees, and better matches what the check means: files changed by this MR. The old working-tree diff caught two extra things, and neither mattered:- In
FOSS_ONLYjobs, it listed everyee/spec file that the job'sbefore_scriptdeletes. Those could never match, because FOSS jobs only retryspec/files. - Uncommitted edits, of which CI has none.
In merge trains,
HEADis the train commit and the target SHA is the previous train car, so the result is still the MR's changes. Without a target SHA (pipelines without merged results), the diff is empty, as it effectively was before. - In
Locally, after making the stat data stale (git ls-files -z | xargs -0 touch -c), git diff --name-only <sha> took 29.4s and git diff --name-only <sha> HEAD took 0.04s.
Time from Queue drained to the end of the job script, across 284 rerun lanes of rspec-ee unit pg17 in 96 tier 3 MR pipelines (2026-10-13/14):
| Step | p50 | p10-p90 | After this MR |
|---|---|---|---|
| Exit the first RSpec process | 5s | 5-8s | unchanged |
| Boot the retry process and load spec files | 67s | 58-74s | unchanged |
| Run the failing examples | 5s | 2-9s | unchanged |
After the examples, mostly AutoExplain.record |
36s | 29-44s | a few seconds (only the retry's own queries) |
junit_merge (about 0.5s) and the git diff |
49s | 40-54s | about 1s |
| Total | 166s | 150-185s | about 80s |
Example lane
Lane 27/47 of rspec-ee unit pg17, https://gitlab.com/gitlab-org/gitlab/-/jobs/17160493593:
| Time | Event |
|---|---|
| 06:03:00-06:03:08 | First process: auto_explain log contains 1090 entries for main (30s), then 84 for ci and 156 for sec |
| 06:03:18 | Retrying the failing examples in a new RSpec process... |
| 06:04:33-06:04:37 | The failing example group runs in 3.97s |
| 06:05:07-06:05:15 | Retry process: the same 1090 / 84 / 156 entries again (38s) |
| 06:05:16 | Saved rspec/rspec-17160493593.json. |
| 06:06:06 | A test was flaky and succeeded after being retried... (50s later) |
Related issues
References
- #633795 (closed): separate MR to make
AutoExplain.recordfaster in every process - #606465: dynamic test balancing, which made this tail visible
Checklist
Pre-merge
Consider the effect of the changes in this merge request on the following:
- Different pipeline types. The time saving only applies to RSpec jobs that retry a failure, or that run more than one RSpec process. Every job that records
auto_explainnow ends with an emptypglog.csv, and nothing else reads that file. In merged-results pipelines, the flaky-test check compares the merge commit with the target branch. WithoutCI_MERGE_REQUEST_TARGET_BRANCH_SHA, it falls back to an empty diff, as before. - Non-canonical projects:
-
gitlab-foss: the same scripts andrecord_auto_explain?conditions apply. -
security: no difference. -
dev: no difference. - personal forks: no difference.
-
- Pipeline performance: saves about 85s per retrying lane, which shortens the tail of balanced RSpec groups by about the same.
If new jobs are added: not applicable, no new jobs.
- Change-related rules (e.g. frontend/backend/database file changes): _____
- Frequency they are running (MRs, main branch, nightly, bi-hourly): _____
- Add a duration chart to https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations if there are new jobs added to merge request pipelines
This will help keep track of expected pipeline time and cost increases.
Post-merge
- Consider communicating these changes to the broader team following the communication guideline for pipeline changes