| 1 | # Spec - Query planner tier selection |
| 2 | |
| 3 | ## Status |
| 4 | |
| 5 | Active. Added by PR #22495 / commit `2c44acdef`. |
| 6 | |
| 7 | ## Scope |
| 8 | |
| 9 | This spec describes automatic storage-tier selection for metric data |
| 10 | queries when the caller does not explicitly request `tier=`. |
| 11 | |
| 12 | It applies to the query-target planner used by `/api/v1/data`, |
| 13 | `/api/v2/data`, `/api/v3/data`, MCP metric queries, weights/value |
| 14 | helpers, and other callers that reach `rrd2rrdr()` without |
| 15 | `RRDR_OPTION_SELECTED_TIER`. |
| 16 | |
| 17 | Explicit `tier=` requests are outside this automatic selection rule. |
| 18 | They keep the existing behavior: the requested tier is used when valid, |
| 19 | and automatic tier switching is disabled. |
| 20 | |
| 21 | ## Contract |
| 22 | |
| 23 | Automatic tier selection is resolution-driven among tiers that overlap |
| 24 | the requested effective query window. |
| 25 | |
| 26 | 1. A tier with no overlap with the requested effective window is not a |
| 27 | candidate. |
| 28 | 2. A candidate tier is scored by point density as if it had full-window |
| 29 | coverage: |
| 30 | |
| 31 | ```text |
| 32 | candidate_points = effective_duration / tier_update_every |
| 33 | ``` |
| 34 | |
| 35 | The implementation uses fixed-point integer weights so sub-resolution |
| 36 | windows retain fractional ordering instead of collapsing to zero. |
| 37 | |
| 38 | 3. The acceptable-density threshold is 50% of the requested output point |
| 39 | count. |
| 40 | 4. If one or more candidate tiers meet the 50% threshold, select the |
| 41 | sparsest acceptable tier. This avoids reading much denser data when a |
| 42 | coarser tier can satisfy the requested output density well enough. |
| 43 | 5. If no candidate tier meets the 50% threshold, select the densest |
| 44 | candidate tier. This handles short windows below every tier's |
| 45 | resolution and preserves the best available fidelity. |
| 46 | 6. After the initial tier is selected, the existing query planner may |
| 47 | fill beginning/end coverage gaps with neighboring tiers. Tier |
| 48 | switching is a coverage-gap mechanism, not a full per-segment |
| 49 | resolution optimizer. |
| 50 | |
| 51 | ## Important Edge Cases |
| 52 | |
| 53 | - A requested window shorter than a 10-second collector cadence must not |
| 54 | make all overlapping tiers unusable. The densest overlapping tier wins |
| 55 | when no tier reaches the 50% threshold. |
| 56 | - A non-overlapping tier must never win just because every tier has poor |
| 57 | density. |
| 58 | - When all tiers cover a narrow sub-resolution window, automatic |
| 59 | selection should choose the densest tier, not the highest-numbered |
| 60 | tier. |
| 61 | - When a coarser tier can provide at least 50% of the requested point |
| 62 | density, it may be selected over a much denser tier to reduce source |
| 63 | reads while preserving acceptable output fidelity. |
| 64 | |
| 65 | ## Natural Points |
| 66 | |
| 67 | The legacy automatic aggregate-tier helper is not part of this contract. |
| 68 | Natural-points update-every selection for an explicit selected tier uses |
| 69 | the minimum `db_update_every_s` for that selected tier across the query |
| 70 | target metrics. |
| 71 | |
| 72 | ## Code References |
| 73 | |
| 74 | - `src/web/api/queries/query-plan.c` - automatic per-metric tier |
| 75 | selection and planner gap-fill. |
| 76 | - `src/web/api/queries/query-window.c` - selected-tier natural-points |
| 77 | update-every calculation. |