Skip code navigation lookups when a project has no LSIF artifacts

What does this MR do and why?

Gitlab::CodeNavigationPath#build runs on the blob page and on every merge request diff render. Today it always shells out to Gitaly for the latest commits and then queries every pipeline of those shas — even for projects that never produced an LSIF artifact and can therefore never render code navigation.

This MR adds an early guard: when the project has no LSIF artifacts, skip the Gitaly commit lookup and the pipeline lookup entirely and return nil. The existence check is an index-only LIMIT 1 query, far cheaper than the lookups it avoids, and the result is memoized for the rest of the request.

Split out of !263784.

Database

LSIF artifact existence check:

SELECT 1 AS one FROM "p_ci_job_artifacts"
WHERE "p_ci_job_artifacts"."project_id" = ? AND "p_ci_job_artifacts"."file_type" = 15
LIMIT 1
Plan
execution plan (EXPLAIN ANALYZE):
 Limit  (cost=0.70..4.06 rows=1 width=4) (actual time=162.598..162.607 rows=0 loops=1)
   Buffers: shared read=65
   I/O Timings: read=161.188 write=0.000
   ->  Append  (cost=0.70..57.70 rows=17 width=4) (actual time=162.595..162.603 rows=0 loops=1)
         Buffers: shared read=65
         I/O Timings: read=161.188 write=0.000
         ->  Index Only Scan using index_ci_job_artifacts_on_id_project_id_and_file_type on gitlab_partitions_dynamic.ci_job_artifacts p_ci_job_artifacts_1  (cost=0.70..3.72 rows=1 width=4) (actual time=17.080..17.081 rows=0 loops=1)
               Index Cond: ((p_ci_job_artifacts_1.project_id = ?) AND (p_ci_job_artifacts_1.file_type = 15))
               Heap Fetches: 0
               Buffers: shared read=5
               I/O Timings: read=16.965 write=0.000
         ->  Index Only Scan using ci_job_artifacts_102_project_id_file_type_id_idx on gitlab_partitions_dynamic.ci_job_artifacts_102 p_ci_job_artifacts_2  (cost=0.58..3.60 rows=1 width=4) (actual time=20.238..20.238 rows=0 loops=1)
               Index Cond: ((p_ci_job_artifacts_2.project_id = ?) AND (p_ci_job_artifacts_2.file_type = 15))
               Heap Fetches: 0
               Buffers: shared read=4
               I/O Timings: read=20.148 write=0.000
         ->  Index Only Scan using ci_job_artifacts_103_project_id_file_type_id_idx on gitlab_partitions_dynamic.ci_job_artifacts_103 p_ci_job_artifacts_3  (cost=0.57..3.59 rows=1 width=4) (actual time=10.843..10.843 rows=0 loops=1)
               Index Cond: ((p_ci_job_artifacts_3.project_id = ?) AND (p_ci_job_artifacts_3.file_type = 15))
               Heap Fetches: 0
               Buffers: shared read=4
               I/O Timings: read=10.753 write=0.000
         ->  Index Only Scan using ci_job_artifacts_104_project_id_file_type_id_idx on gitlab_partitions_dynamic.ci_job_artifacts_104 p_ci_job_artifacts_4  (cost=0.57..3.59 rows=1 width=4) (actual time=15.301..15.301 rows=0 loops=1)
               Index Cond: ((p_ci_job_artifacts_4.project_id = ?) AND (p_ci_job_artifacts_4.file_type = 15))
               Heap Fetches: 0
               Buffers: shared read=4
               I/O Timings: read=15.198 write=0.000
         ->  Index Only Scan using ci_job_artifacts_105_project_id_file_type_id_idx on gitlab_partitions_dynamic.ci_job_artifacts_105 p_ci_job_artifacts_5  (cost=0.57..3.59 rows=1 width=4) (actual time=11.853..11.854 rows=0 loops=1)
               Index Cond: ((p_ci_job_artifacts_5.project_id = ?) AND (p_ci_job_artifacts_5.file_type = 15))
               Heap Fetches: 0
               Buffers: shared read=4
               I/O Timings: read=11.772 write=0.000
         ->  Seq Scan on gitlab_partitions_dynamic.ci_job_artifacts_117 p_ci_job_artifacts_17  (cost=0.00..0.00 rows=1 width=4) (actual time=0.025..0.025 rows=0 loops=1)
               Filter: ((p_ci_job_artifacts_17.project_id = ?) AND (p_ci_job_artifacts_17.file_type = 15))
               I/O Timings: read=0.000 write=0.000


stats:

Time: 204.444 ms
  - planning: 41.623 ms
  - execution: 162.821 ms
    - I/O read: 161.188 ms
    - I/O write: 0.000 ms

Shared buffers:
  - hits: 0 from the buffer pool
  - reads: 65 (~520.00 KiB) from the OS file cache, including disk I/O
  - dirtied: 0
  - writes: 0

References

  • !263784 (parent MR this is split out of)

Screenshots or screen recordings

No UI change. When a project has no LSIF artifacts, code navigation is absent exactly as before; the lookups that would have produced that same nil are simply skipped.

How to set up and validate locally

  1. In a Rails console, pick a project with no LSIF artifacts and confirm the lookups are skipped:

    project = Project.find_by_full_path('<some-project>')
    project.job_artifacts.with_file_types(['lsif']).exists? # => false
    
    # Spy on the Gitaly lookup that should no longer happen:
    allow = -> { raise 'Gitaly lookup was not skipped' }
    project.repository.define_singleton_method(:commits) { |*| allow.call }
    
    Gitlab::CodeNavigationPath.new(project, project.commit.sha)
      .full_json_path_for('README.md') # => nil, without Gitaly or a pipeline query
  2. For a project that does have an LSIF artifact, code navigation links still render on blob pages as before.

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.

Edited by Tiger Watson

Merge request reports

Loading
Loading