Detect database writes on HTTP GET requests

What does this MR do and why?

In incident INC-12449, a GET endpoint wrote to the database on every call and overloaded the primary under burst traffic. Nothing currently stops a GET endpoint from writing. This MR adds a query analyzer to detect writes on GET requests, following the PreventCrossDatabaseModification playbook. It is a detection step. Enforcement is a later step.

How it works:

  1. Gitlab::Middleware::QueryAnalyzer now records the HTTP method in the request store before analyzers run. The new analyzer's enabled? reads this once per request. Zero cost for non-GET requests and Sidekiq.
  2. New PreventWritesOnGet analyzer detects INSERT/UPDATE/DELETE (and SELECT FOR UPDATE) on GET/HEAD requests, using a regex pre-filter plus pg_query. It only logs write_on_get_detected, behind the detect_writes_on_get ops flag, in every environment. It never raises. This was a deliberate choice after review feedback, to avoid breaking pipelines of unrelated merge requests.
  3. Logging goes through a dedicated logger, PreventWritesOnGet::Logger, a Gitlab::JsonLogger with exclude_context!, writing to database_writes_on_get. The dedicated logger avoids Gitlab::AppLogger, which merges the lazy Labkit context into every line and memoizes attributes like meta.user before the controller assigns them. The caller_id in the payload already identifies the endpoint.
  4. An endpoint allowlist is seeded with the known production violators, roughly 6 million writes-on-GET requests per week, led by the merge request view, repository archive download, and dependency proxy cache miss. The analyzer returns early for these endpoints and does not log them at all. This way the log only shows writes on GET that are not yet known, so it works as a detector of new cases. Fixing the known violators is out of scope, burn-down issues per owning group will follow.
  5. Two sanctioned throttled writes (Users::ActivityService, PersonalAccessTokens::LastUsedService) use a new allow_write_on_get(url:) helper.

References

How to set up and validate locally

Run:

bundle exec rspec spec/lib/gitlab/database/query_analyzers/prevent_writes_on_get_spec.rb spec/lib/gitlab/middleware/query_analyzer_spec.rb

In GDK, a GET request that writes to the database is logged to database_writes_on_get when the detect_writes_on_get ops flag is enabled.

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 Leonardo da Rosa

Merge request reports

Loading
Loading