Visitar URL original
Fix query coverage and handoffs across storage-tier retention by ktsaou · Pull Request #24097 · netdata/netdata · GitHub
Skip to content

Fix query coverage and handoffs across storage-tier retention - #24097

Draft
ktsaou wants to merge 9 commits into
masterfrom
fix/query-tier-retention-coverage
Draft

ktsaou wants to merge 9 commits into
masterfrom
fix/query-tier-retention-coverage

Conversation

@ktsaou

@ktsaou ktsaou commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Summary

Fix false gaps in automatic queries when storage-tier retention does not follow tier-number order, and preserve values across the resulting handoffs. A late-enabled coarse tier can retain less history than a finer tier; wide queries must still find that older data.

Changes

  • Fill uncovered intervals from actual retention while preserving resolution-based selection, explicit tier= isolation and disconnected gaps. With selected tier 2 of 5, older coverage tries 2, 3, 4, 1, 0; newer coverage tries 2, 1, 0, 3, 4.
  • Retain the selected coarse-head probe once per metric and defer fine-prefix initialization when that prefix may be discarded. Count each physical read once and clear pending state on reuse.
  • Skip or clip coarse records against consumed fine coverage, including empty stored intervals, so seam contributions are not repeated. Keep an uncovered empty coarse interval present in read-ahead: the following real record then waits for its own row. This repairs NI-1, where MIN was 8 instead of 7.
  • Require positive interval overlap to prevent values extending through holes. Clamp expanded fine lookbehind across disconnected retention to preserve the preceding coarse island in the tested aligned fixtures.
  • Weight mixed-tier AVERAGE by represented duration while preserving single-tier interpolation/resampling. Coarse extrema and anomaly metadata retain whole-record approximations. Add five-tier fallback-order and nine-entry capacity checks.

Scope cleanup

The exclusive goal is to solve leading and trailing gaps across tiers in data and weights queries without introducing bugs absent before these changes. Tests exposing other pre-existing failures remain; independent runtime fixes for those unrelated defects are outside the scope.

Cleanup revision ff6b11b590 removes the optional boundary-array initializer and restores the original header formatting. All native tests are unchanged; recompiling the planner and relinking with the matching preserved objects passes all 46 planner checks. The full-corpus measurements below belong to the earlier recorded revisions and were not rerun for this behaviorally neutral cleanup. Remaining leading/trailing failures and introduced regressions are unfinished parts of the goal.

Validation

The same 308-contract corpus at 49efc9ee12 was run against matching before/after native source and binary snapshots. Absolute counts use TZ=UTC; datatable formatting has a separate timezone-dependent failure.

Check Before (b5934b3784) After (7fcffe55a5, #24097)
Full corpus 89/308 broken 88/308 broken
CASE-040 9/36 broken 8/36 broken

All 308 contracts and required scopes were evaluated in both sequential runs. Only CASE-040/post-gap-coarse-min changes from failing to passing; no formerly holding key or nested subtest becomes failing. Failed test paths decrease from 294 to 292, and all 80 failures outside CASE-040 are unchanged. The initial parallel post-fix run had reset-fixture failures/incomplete scopes; these final counts come from complete repeats with unchanged assertions.

  • The separately keyed NI-1 MIN reproducer fails before the fix (8 versus fixture truth 7) and passes after; its forced fine control passes in both.
  • netdata -W queryplantest: all 46 checks pass, including both five-tier orders, all nine capacity entries and pending-state reuse.
  • Young automatic tier-query budget is [2,2,0], versus [0,2,0] for forced tier 1. The fixture bounds young/forced tier-1 reads at 206 and mature reads at 250; these budgets are derived from record geometry and lookbehind. Existing boundary/cadence controls continue to hold.
  • Manifest, ledger and assertion guards, Go vet, formatting and diff checks pass. No new sanitizer or controlled timing result is claimed.

From tests/query-corpus, set QUERY_CORPUS_NETDATA and QUERY_CORPUS_SRC to matching binary/source snapshots and run TZ=UTC go test -count=1 -timeout=30m ./.... Known failures remain assertions, so the full suite exits 1.

Remaining limits

Eight CASE-040 contracts remain red: isolated-coarse-tail, late-tier-boundary-average, partial-first-record-average, partial-first-record-max, partial-first-record-metadata, partial-first-record-min, partial-first-record-sum and shifted-constant-seam.

The shifted seam now has an explicit fixture-derived coarse envelope [200, 10400/43], preserving exact fine truth while permitting coarse blending. Master and forced coarse controls pass; the automatic seam remains outside that envelope. The introduced automatic seam outlier remains unresolved under the no-new-bugs constraint. This cleanup changes no interpolation policy.

Conventional-tail controls cover aligned 4-second rows. Wider live-tail behavior is still unproven by those controls. Retained fine data at restart-hole rows 720/1320 is now independently asserted and remains lost; wholly empty gap rows are a separate passing contract.

The unaligned RAM/ALLOC island-boundary regression introduced by the earlier clamp remains unresolved in #24115 and must be repaired before the work satisfies its regression constraint. Other engine and corpus work, including the first-row island case, small-grouping overlap, coarse completeness, wider live tail and TESTS-7 ledger design, is tracked in #24116.

Plan capacity grows from five to nine entries, adding four slots (96 bytes per query metric on the tested 64-bit build); the retained probe adds per-metric ops state. Production changes and corpus tests remain separate in #24098. Both PRs remain drafts.

@coderabbitai

coderabbitai Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Your Qodo trial ends soon. Ask your workspace admin to set up billing to keep reviews running after the trial. Manage billing

@ktsaou
ktsaou requested a balanced review from Copilot September 30, 2026 23:51
@ktsaou

ktsaou commented Sep 30, 2026

Copy link
Copy Markdown
Member Author

@cubic-dev-ai please review again

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai please review again

@ktsaou cubic can't start this review because your workspace has reached its free monthly review limit. cubic has reviewed 401,632 of the 400,000 allowed lines of code this month. Reviews resume on 1 October 2026 (in 1 day). Paid plans include much higher monthly review limits. Upgrade now to resume reviews.

To help optimise your usage, you can tune cubic to get the most out of your usage limits:

Learn more →

ktsaou added 2 commits October 1, 2026 03:26
Restore higher-then-lower fallback for older coverage and lower-then-higher fallback for newer coverage. Reverts a3cfcf1.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files

Confidence score: 3/5

  • In src/web/api/queries/query-execute.c, coarser_overlap can discard valid fine-tier data when the overlapping coarse record is a stored gap. Exclude storage_point_is_gap(sp2) so only numeric coarse records can replace fine-tier data.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/web/api/queries/query-execute.c">

<violation number="1" location="src/web/api/queries/query-execute.c:357">
P1: The coarse-overlap preference can discard valid fine-tier data when the coarse record is a stored gap. Exclude `storage_point_is_gap(sp2)` from `coarser_overlap` so only numeric coarse records can replace an equal-start fine record.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Web as Web API Layer
    participant QP as Query Planner (query-plan.c)
    participant QE as Query Executor (query-execute.c)
    participant SE as Storage Engine
    participant TIER as Storage Tiers (0..n)
    participant CTX as Context Registry (rrdcontext.h)

    Note over Web,QE: Automatic Multi-Tier Query Flow

    Web->>QP: Submit query (timerange, granularity, tier constraint)
    
    QP->>QP: selectInitialTier()
    
    alt Explicit tier requested
        QP->>TIER: Query single tier only
        Note over QP: No cross-tier planning
    else Automatic tier selection
        QP->>QP: query_plan_fill_coverage()
        Note over QP: NEW: Build boundary list from actual retention
        QP->>CTX: Get per-tier retention (db_first/last_time)
        CTX-->>QP: Retention intervals per tier
        QP->>QP: Sort boundaries (after/before pairs)
        
        loop For each boundary segment
            QP->>QP: Select best tier (head/tail order)
            alt Tier covers segment
                QP->>QP: Add plan entry
            else No tier covers
                QP->>QP: Return false (fail)
            end
        end
        
        QP->>QP: Clamp boundary samples (isolated single points)
        QP->>QP: Extend isolated points to next plan start
    end
    
    QP->>QE: Initialize plans (up to 2*tiers-1)
    QE->>QE: query_planer_initialize_plans()
    
    alt Multiple plans active
        QE->>QE: query_planer_prefer_complete_head()
        Note over QE: NEW: Inspect coarse tier's first record
        QE->>QE: Check if fine prefix already covered
    end
    
    QE->>SE: Initialize storage engine query
    SE->>TIER: Open read handle for tier data
    
    loop Query execution (per output point)
        QE->>SE: Fetch next storage point
        SE-->>QE: Point (value, timestamp, tier)
        
        alt Plan switch needed
            QE->>QE: query_plan_should_switch_plan()
            QE->>QP: query_planer_next_plan()
            QP-->>QE: New plan (tier+time range)
            QE->>SE: Switch handle to new tier
            
            alt New tier is coarser
                QE->>QE: Skip already-consumed fine records
                Note over QE: CHANGED: Avoid double-reading fine prefix
            end
        end
        
        alt Overlapping SUM intervals
            QE->>QE: Trim overlapping portion
            Note over QE: CHANGED: Rate-backed sum integration
        end
        
        QE->>QE: query_add_point_to_group()
        alt Average and mixed tiers
            QE->>QE: Track seam_average (sum, duration, tiers)
        end
        
        QE->>QE: Track consumed_end_time
    end
    
    alt Mixed-tier rows for AVG
        QE->>QE: Duration-weight only if tiers differ
        Note over QE: CHANGED: Preserve single-tier interpolation
    end
    
    QE->>Web: Return results (points, timestamps)

    Note over QP: Failure path: insufficient plan capacity
    QP->>QP: Return false (> QUERY_PLANS_MAX)
    QP-->>Web: Query rejected, empty result
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/web/api/queries/query-execute.c Outdated
bool finer_total_overlap =
ops->point_mode == QUERY_POINT_MODE_TOTAL &&
sp2_tier < sp_tier && sp2.start_time_s < sp.end_time_s;
bool coarser_overlap = sp2_tier > sp_tier && !storage_point_is_unset(sp2) &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The coarse-overlap preference can discard valid fine-tier data when the coarse record is a stored gap. Exclude storage_point_is_gap(sp2) from coarser_overlap so only numeric coarse records can replace an equal-start fine record.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/web/api/queries/query-execute.c, line 357:

<comment>The coarse-overlap preference can discard valid fine-tier data when the coarse record is a stored gap. Exclude `storage_point_is_gap(sp2)` from `coarser_overlap` so only numeric coarse records can replace an equal-start fine record.</comment>

<file context>
@@ -292,28 +323,56 @@ NOT_INLINE_HOT void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY
                     bool finer_total_overlap =
                         ops->point_mode == QUERY_POINT_MODE_TOTAL &&
                         sp2_tier < sp_tier && sp2.start_time_s < sp.end_time_s;
+                    bool coarser_overlap = sp2_tier > sp_tier && !storage_point_is_unset(sp2) &&
+                                           sp2.start_time_s < sp.end_time_s;
 
</file context>

Comment thread src/web/api/queries/query-plan.c Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Confidence score: 3/5

  • src/web/api/queries/query-plan.c can accept a retained sample exactly at after_wanted, letting a tier>0 backward-expanded scan add pre-window data to the first bucket; preserve the exclusive lower-bound check in the singleton branch.
  • src/web/api/queries/query-execute.c applies fine-to-coarse overlap protection only in the read-ahead arm, so another handoff path may include already-covered or out-of-range coarse records; apply the protection across the handoff paths.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/web/api/queries/query-plan.c">

<violation number="1" location="src/web/api/queries/query-plan.c:443">
P2: This singleton branch admits a retained sample exactly at `after_wanted`, violating the query's exclusive lower boundary and allowing a tier>0 backward-expanded scan to add pre-window data to the first bucket. Keep singleton retention points only when they are strictly after `after_wanted` (while retaining the inclusive `before_wanted` endpoint).</violation>
</file>

<file name="src/web/api/queries/query-execute.c">

<violation number="1" location="src/web/api/queries/query-execute.c:326">
P2: The fine→coarse handoff protection (skip coarse records already covered by fine reads, clip surviving coarse records at `consumed_end_time`) is wired only into the read-ahead arm that runs when `query_plan_should_switch_plan(ops, sp.end_time_s)` fires mid-point. When the switch instead happens at the top-of-row check (`query_result_plan_should_switch_plan` → `query_planer_next_plan`), the coarse plan's handle begins at its `expanded_after` (several coarse records before `plan[1].after`), and those early coarse records are read raw into the new row with no `consumed` skip and no start-time clipping. Rows right after the handoff can include full out-of-row coarse values (a record spanning the interval before the row) on top of the fine rows already consumed, inflating SUM and distorting min/max/percentile rows; only the AVERAGE path is partly shielded by the seam's per-row duration clip.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


time_t after = MAX(after_wanted, qm->tiers[tier].db_first_time_s);
time_t before = MIN(before_wanted, qm->tiers[tier].db_last_time_s);
if(after != before)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This singleton branch admits a retained sample exactly at after_wanted, violating the query's exclusive lower boundary and allowing a tier>0 backward-expanded scan to add pre-window data to the first bucket. Keep singleton retention points only when they are strictly after after_wanted (while retaining the inclusive before_wanted endpoint).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/web/api/queries/query-plan.c, line 440:

<comment>This singleton branch admits a retained sample exactly at `after_wanted`, violating the query's exclusive lower boundary and allowing a tier>0 backward-expanded scan to add pre-window data to the first bucket. Keep singleton retention points only when they are strictly after `after_wanted` (while retaining the inclusive `before_wanted` endpoint).</comment>

<file context>
@@ -347,10 +362,119 @@ bool query_planer_next_plan(QUERY_ENGINE_OPS *ops, time_t now, time_t last_point
+
+        time_t after = MAX(after_wanted, qm->tiers[tier].db_first_time_s);
+        time_t before = MIN(before_wanted, qm->tiers[tier].db_last_time_s);
+        if(after != before)
+            continue;
+
</file context>
Suggested change
if(after != before)
if(after != before || after == after_wanted)

ops->db_points_read_per_tier[ops->tier]++;
ops->db_total_points_read++;

if(sp2_tier > sp_tier) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The fine→coarse handoff protection (skip coarse records already covered by fine reads, clip surviving coarse records at consumed_end_time) is wired only into the read-ahead arm that runs when query_plan_should_switch_plan(ops, sp.end_time_s) fires mid-point. When the switch instead happens at the top-of-row check (query_result_plan_should_switch_plan → query_planer_next_plan), the coarse plan's handle begins at its expanded_after (several coarse records before plan[1].after), and those early coarse records are read raw into the new row with no consumed skip and no start-time clipping. Rows right after the handoff can include full out-of-row coarse values (a record spanning the interval before the row) on top of the fine rows already consumed, inflating SUM and distorting min/max/percentile rows; only the AVERAGE path is partly shielded by the seam's per-row duration clip.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/web/api/queries/query-execute.c, line 326:

<comment>The fine→coarse handoff protection (skip coarse records already covered by fine reads, clip surviving coarse records at `consumed_end_time`) is wired only into the read-ahead arm that runs when `query_plan_should_switch_plan(ops, sp.end_time_s)` fires mid-point. When the switch instead happens at the top-of-row check (`query_result_plan_should_switch_plan` → `query_planer_next_plan`), the coarse plan's handle begins at its `expanded_after` (several coarse records before `plan[1].after`), and those early coarse records are read raw into the new row with no `consumed` skip and no start-time clipping. Rows right after the handoff can include full out-of-row coarse values (a record spanning the interval before the row) on top of the fine rows already consumed, inflating SUM and distorting min/max/percentile rows; only the AVERAGE path is partly shielded by the seam's per-row duration clip.</comment>

<file context>
@@ -292,28 +323,56 @@ NOT_INLINE_HOT void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY
                     ops->db_points_read_per_tier[ops->tier]++;
                     ops->db_total_points_read++;
 
+                    if(sp2_tier > sp_tier) {
+                        // Expanded coarse queries may begin with records already read from the fine tier.
+                        time_t covered_end = consumed_end_time;
</file context>

@ktsaou ktsaou changed the title Fix query coverage across inverted storage-tier retention Fix query coverage and handoffs across storage-tier retention Oct 1, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/web/api/queries/query-plan.c
@vkalintiris

Copy link
Copy Markdown
Contributor

Fix false gaps in automatic queries when storage-tier retention does not follow tier-number order

This caught my attention.

I think we are going to end up with a huge amount of complexity, simply because we are not introducing the invariant that retention of higher storage tiers should be >= than that of lower tiers.

Even if we don't enforce it at the storage engine layer, I think it's a reasonable enough invariant to rely upon and document it.

Keep uncovered empty coarse intervals in the read-ahead slot and add five-tier fallback and maximum-capacity planner checks.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 existing issue remains and 2 new issues found across 3 files (changes from recent commits).

Confidence score: 3/5

  • In query-execute.c, an empty fine point with bounds can be treated as covering its span, causing real coarse records inside that span to be discarded. Keep empty points from suppressing coarse data.
  • In query-plan.c, head-candidate search assumes plan tiers are numerically ordered, so it can fail with the non-monotonic retention layout this PR adds. Match the selected tier’s retention-start entry directly.
  • In query-execute.c, the fallback is unreachable for production storage points because empty intervals have count = 1, while storage_point_is_unset() only matches count = 0. Remove the dead branch; returned empty intervals already retain their values.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/web/api/queries/query-execute.c">

<violation number="1" location="src/web/api/queries/query-execute.c:333">
P3: This fallback is unreachable for production storage points: empty intervals have `count = 1`, while `storage_point_is_unset()` only matches `count = 0`. Remove the dead branch; returned empty intervals already retain their bounds and are not unset.</violation>
</file>

<file name="src/web/api/queries/query-plan.c">

<violation number="1" location="src/web/api/queries/query-plan.c:546">
P2: The head-candidate search still assumes plan tiers are numerically ordered, so it fails on the non-monotonic retention layout this PR adds. Search for the selected tier’s matching retention-start entry directly; otherwise every plan, including the unnecessary prefix and future plans, is initialized.</violation>
</file>

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

query_metric_best_tier_for_timeframe(qm, qt->window.after, qt->window.before, qt->window.points) :
qm->plan.array[0].tier;
size_t selected_plan = 0;
while(selected_plan < qm->plan.used && qm->plan.array[selected_plan].tier < selected_tier)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The head-candidate search still assumes plan tiers are numerically ordered, so it fails on the non-monotonic retention layout this PR adds. Search for the selected tier’s matching retention-start entry directly; otherwise every plan, including the unnecessary prefix and future plans, is initialized.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/web/api/queries/query-plan.c, line 546:

<comment>The head-candidate search still assumes plan tiers are numerically ordered, so it fails on the non-monotonic retention layout this PR adds. Search for the selected tier’s matching retention-start entry directly; otherwise every plan, including the unnecessary prefix and future plans, is initialized.</comment>

<file context>
@@ -524,12 +536,29 @@ static bool query_plan_build_entries(QUERY_ENGINE_OPS *ops, time_t after_wanted,
+        query_metric_best_tier_for_timeframe(qm, qt->window.after, qt->window.before, qt->window.points) :
+        qm->plan.array[0].tier;
+    size_t selected_plan = 0;
+    while(selected_plan < qm->plan.used && qm->plan.array[selected_plan].tier < selected_tier)
+        selected_plan++;
+    if(selected_plan > 0 && selected_plan < qm->plan.used &&
</file context>
Suggested change
while(selected_plan < qm->plan.used && qm->plan.array[selected_plan].tier < selected_tier)
while(selected_plan < qm->plan.used &&
!(qm->plan.array[selected_plan].tier == selected_tier &&
qm->plan.array[selected_plan].after == qm->tiers[selected_tier].db_first_time_s))
selected_plan++;

}
if(sp2.end_time_s <= covered_end)
storage_point_unset(sp2);
else if(storage_point_is_unset(sp2) && sp2.start_time_s < sp2.end_time_s)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This fallback is unreachable for production storage points: empty intervals have count = 1, while storage_point_is_unset() only matches count = 0. Remove the dead branch; returned empty intervals already retain their bounds and are not unset.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/web/api/queries/query-execute.c, line 333:

<comment>This fallback is unreachable for production storage points: empty intervals have `count = 1`, while `storage_point_is_unset()` only matches `count = 0`. Remove the dead branch; returned empty intervals already retain their bounds and are not unset.</comment>

<file context>
@@ -318,47 +316,44 @@ NOT_INLINE_HOT void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY
                         }
                         if(sp2.end_time_s <= covered_end)
                             storage_point_unset(sp2);
+                        else if(storage_point_is_unset(sp2) && sp2.start_time_s < sp2.end_time_s)
+                            // Preserve a stored empty interval in the count-based read-ahead slot.
+                            storage_point_empty(sp2, sp2.start_time_s, sp2.end_time_s);
</file context>

@sonarqubecloud

sonarqubecloud Bot commented Oct 2, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants