Provide a sensible default analyze_interval for partitioned tables (or guard against omitting it)
Problem
Partitioned models declare partitioned_by, and the partition manager will run a post-rotation ANALYZE on newly-created leaf partitions — but only if analyze_interval is set. It defaults to nil, and when nil the ANALYZE is silently skipped:
lib/gitlab/database/partitioning/partition_manager.rb:177-190—ineligible_for_analyzing?is true whenanalyze_interval.blank?, sorun_analyze_on_partitioned_tablereturns early.- Strategy constructors all default
analyze_interval: nil(e.g.lib/gitlab/database/partitioning/sliding_list_strategy.rb,int_range_strategy.rb,time/base_strategy.rb).
The result is a silent footgun: a new partition starts with reltuples = -1 (no planner stats) and stays that way until an ordinary autovacuum-analyze happens to run. If autovacuum is behind (or, as in INC-13566, livelocked on anti-wraparound), the planner runs on garbage estimates and can pick pathologically bad plans — e.g. a ~483M-row parallel seq scan instead of an index scan.
Blast radius
Of ~52 partitioned_by call sites in the repo, only one (Ci::Partitionable, analyze_interval: 3.days) sets it. The other ~51 partitioned models do not, including high-churn tables such as audit_event, web_hook_log, sent_notification, merge_request_diff_file, Security::Finding, and the vulnerabilities/backups/* and vulnerabilities/archive* families. Every one of these currently gets no post-rotation ANALYZE and is exposed to the same class of stale-stats plan regression.
Proposed options (to discuss)
- Sensible default: give
analyze_intervala non-nil default in the partitioning strategies (e.g.1.dayfor high-churn sliding-list/int-range strategies), so new partitions are analyzed after rotation unless explicitly overridden. Simplest way to fix the whole class at once. - Explicit-or-guard: require
analyze_intervalto be specified on partitioned models via a spec/RuboCop cop (fail if apartitioned_bydeclaration omits it), forcing an intentional choice rather than a silent nil. - Hybrid: default to a safe value, and add an override to disable it explicitly (
analyze_interval: nilwith a documented reason) for the rare tables where post-rotation ANALYZE is genuinely undesirable.
Needs Database group input on the right default and whether a per-strategy default vs. an enforced-explicit approach is preferable.
Related
- Immediate fix for
Security::Finding: filed separately. - INC-13566 (patroni-sec CPU saturation) — the incident that surfaced this class of bug.