Skip to content

Optimize notes API performance with incremental pagination

What does this MR do and why?

Replace auto_paginate with manual pagination in existing_reports_note() to avoid loading potentially thousands of system notes when searching for specific user comments. This approach allows for existing early when the interesting note is found, rather than needing to iterate over the entire set of notes.

This may help address timeout issues in CI pipelines where issues accumulate thousands of system notes.

NB - This would only help workflows where the issue in question is created early. In the worst case scenario we'd have to paginate to the final page of results to find the note of interest, but this matches the current behaviour which auto_paginates every single page of notes each single time.

Screenshots or screen recordings

These are strongly recommended to assist reviewers and reduce the time to merge your change.

How to set up and validate locally

I ran this locally against a test project in GDK using slow.json

slow.json
{
  "version": "3.13.0",
  "messages": [
    "Run options: exclude {:orchestrated=>true, :geo=>true, :skip_signup_disabled=>true, :skip_live_env=>true}"
  ],
  "seed": 32030,
  "examples": [
    {
      "id": "./qa/specs/features/browser_ui/10_software_supply_chain_security/login/log_in_spec.rb[1:1:1]",
      "description": "user logs in using basic credentials and logs out",
      "full_description": "Software Supply Chain Security basic user login user logs in using basic credentials and logs out",
      "status": "passed",
      "file_path": "./qa/specs/features/browser_ui/10_software_supply_chain_security/login/log_in_spec.rb",
      "line_number": 6,
      "run_time": 156913.342736811,
      "pending_message": null,
      "testcase": "https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347880",
      "quarantine": null,
      "screenshot": null,
      "product_group": null,
      "feature_category": "system_access",
      "ci_job_url": "https://ops.gitlab.net/gitlab-org/quality/staging-canary/-/jobs/20414134",
      "level": "E2E",
      "ignore_runtime_data": false
    }
  ],
  "summary": {
    "duration": 156913.542090628,
    "example_count": 1,
    "failure_count": 0,
    "pending_count": 0,
    "errors_outside_of_examples_count": 0
  },
  "summary_line": "1 example, 0 failures"
}
export GITLAB_BOT_USERNAME="root"
export CI_API_V4_URL="http://gdk.test:3000/api/v4"
export QA_GITLAB_CI_TOKEN=$GITLAB_QA_ACCESS_TOKEN

$ bundle exec ./exe/slow-test-issues --project group3846/project3846 --token $GITLAB_QA_ACCESS_TOKEN --input-files slow.json


# from /path/to/gdk/gitlab
$ bundle exec rails console


project = Project.find(19)
user = User.first
issues = project.issues.limit(3).to_a
puts "Found #{issues.count} issues in project #{project.name}"
issues.each { |issue| puts "- Issue ##{issue.iid}: #{issue.title}" }
require 'benchmark'
# Add 3000 description updates to each issue
issues.each_with_index do |issue, issue_index|
  puts "\n=== Processing Issue ##{issue.iid} (#{issue_index + 1}/3) ==="
  puts "Starting notes count: #{issue.notes.system.count}"
  total_time = Benchmark.measure do
    3000.times do |i|
      current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")
      new_description = "#{issue.description || 'Performance test'} - Updated at #{current_time} (iteration #{i + 1})"
      update_time = Benchmark.measure do
        Issues::UpdateService.new(
          container: project,
          current_user: user,
          params: { description: new_description }
        ).execute(issue)
      end
      # Show progress and flag slow updates
      if update_time.real > 1.0
        puts "\nSLOW UPDATE - Iteration #{i + 1}: #{update_time.real.round(3)}s"
      elsif i % 20 == 0
        print "#{i + 1}"
      else
        print "."
      end
    end
  end
  puts "\nIssue ##{issue.iid} - Final notes count: #{issue.notes.system.count}"
  puts "Total time for 100 updates: #{total_time.real.round(3)}s"
  puts "Average per update: #{(total_time.real / 100).round(3)}s"
end
puts "\n=== Summary ==="
issues.each do |issue|
  puts "Issue ##{issue.iid}: #{issue.notes.system.count} system notes"
end

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by John McDonnell

Merge request reports

Loading