Skip to content

Fix Backup Inventory 503 from unsupported Cosmos GROUP BY aggregate - #1295

Merged
Paul Lizer (paullizer) merged 1 commit into
microsoft:Developmentfrom
paullizer:fix/backup-inventory-groupby-aggregate
Aug 19, 2026
Merged

Fix Backup Inventory 503 from unsupported Cosmos GROUP BY aggregate#1295
Paul Lizer (paullizer) merged 1 commit into
microsoft:Developmentfrom
paullizer:fix/backup-inventory-groupby-aggregate

Conversation

@paullizer

Copy link
Copy Markdown
Collaborator

Fixes #1294

Problem

Backup Inventory in Admin Settings → Data Management always failed to load. GET /api/admin/data-management/backups returned 503, the panel showed Backup history is temporarily unavailable. Please try again., and every tile rendered 0 — even with successfully completed backups.

Root Cause

_get_data_management_backup_global_summary() computed its counts with:

SELECT c.backup_type, c.status, COUNT(1) AS count FROM c
WHERE c.type = @type AND c.operation = @operation
GROUP BY c.backup_type, c.status

The azure-cosmos Python client does not advertise support for GroupBy combined with a non-VALUE aggregate (COUNT(1) AS count). Cosmos rejects the request during query plan negotiation:

(BadRequest) {"code":"BadRequest","message":"Query contains the following features,
which the calling client does not support:\nNone GroupBy NonValueAggregate ..."}

That BadRequest propagated out of _query_data_management_history_items and surfaced as a generic 503.

This was not throttling, indexing, or capacity — Cosmos Maintenance consistently reported the composite index as Aligned / 0 missing / 7 containers checked, and the 400 entries with requestCharge = 0 in AzureDiagnostics are the benign cross-partition query-plan negotiation. Because the aggregate shipped with the summary feature, Backup Inventory had never worked in any deployment.

Why tests did not catch it

FakeHistoryContainer implemented GROUP BY c.backup_type, c.status in Python and returned grouped rows. The test double supported a query shape the real client cannot serve, so the suite passed against a query that always failed in production.

Changes

  • functions_data_management.py — added _count_data_management_backups() and rewrote the summary to issue six bounded SELECT VALUE COUNT(1) queries (total, available, running, failed, full, partial). VALUE aggregates are supported by the client and already used elsewhere in this module.
    • Deliberately avoided projecting every backup row and aggregating client-side, which would grow unbounded as history accumulates.
    • The two SELECT TOP 1 * latest-backup queries were already valid and are unchanged.
  • admin_settings.html — added a hover tooltip and an (i) toggle that expands inline guidance next to Run Retention Cleanup, covering what gets deleted, what is protected, the 25-per-run cap, and that found no expired backups to delete is expected rather than an error.
  • test_data_management_history_pagination.pyFakeHistoryContainer now raises the real BadRequest ... GroupBy NonValueAggregate error instead of emulating GROUP BY, plus two new tests.
  • Version bump, fix doc, and release notes.

Validation

  • functional_tests/test_data_management_history_pagination.py14 passed
  • Data Management suite (-k data_management) — 159 passed, only the known pre-existing test_backup_recovery_and_admin_progress_are_bounded_and_sanitized failure

Regression probe: restoring a grouped aggregate fails two tests with the exact production error, confirming they fail for the right reason:

RuntimeError: (BadRequest) Query contains the following features, which the calling client
does not support: GroupBy NonValueAggregate.

Impact

Behavior Before After
GET /api/admin/data-management/backups 503 200
Backup Inventory tiles Always 0 Actual counts
Backup Inventory table Error banner Backup rows
Retention cleanup purpose Undocumented in UI Tooltip + expandable help

Backup Inventory always failed to load, returning 503 and rendering 0 for
available, full, and partial backups even when backups had completed.

_get_data_management_backup_global_summary() computed its counts with
"GROUP BY c.backup_type, c.status" combined with a non-VALUE aggregate
(COUNT(1) AS count). The azure-cosmos Python client does not advertise
support for that combination, so Cosmos rejects it during query plan
negotiation with BadRequest "GroupBy NonValueAggregate", which surfaced
to admins as a generic 503.

This was not throttling or a missing index; the composite index was
already aligned. The aggregate shipped with the summary feature, so
Backup Inventory had never worked in any deployment.

Replace the grouped aggregate with six bounded SELECT VALUE COUNT(1)
queries via a new _count_data_management_backups() helper. VALUE
aggregates are supported by the client and already used elsewhere in
this module. This avoids projecting every backup row and aggregating
client-side, which would grow unbounded as history accumulates. The
SELECT TOP 1 latest-backup queries were already valid and are unchanged.

FakeHistoryContainer previously implemented GROUP BY in Python, so the
suite passed against a query that always failed in production. It now
raises the real BadRequest error instead, and a new test asserts no
emitted history query uses GROUP BY or a non-VALUE aggregate.

Also add a hover tooltip and an (i) toggle explaining what Run Retention
Cleanup does, including that "found no expired backups to delete" means
every backup is still inside the retention window rather than an error.

Validation: 14 passed in the history pagination suite; 159 passed across
Data Management with only the known pre-existing durability failure.
Regression probe restoring a grouped aggregate fails two tests with the
exact production error.

Fixes microsoft#1294
@paullizer
Paul Lizer (paullizer) merged commit 4850877 into microsoft:Development Aug 19, 2026
11 of 12 checks passed
Paul Lizer (paullizer) added a commit to paullizer/simplechat that referenced this pull request Aug 19, 2026
Resolves conflicts introduced by PR microsoft#1295 (Backup Inventory GROUP BY fix)
landing on Development first.

- config.py: keep 0.260.003, which supersedes the 0.260.002 from microsoft#1295.
- release_notes.md: keep both sections, newest first (0.260.003 above
  0.260.002). Neither entry replaces the other.

functions_data_management.py auto-merged cleanly; the two changes touch
different regions (_count_data_management_backups vs _set_job_progress /
_complete_job_step) and both are present after the merge.

Validation: 177 passed across the Data Management suite, with only the two
known pre-existing issues (swagger_wrapper collection error in
test_admin_endpoint.py, and
test_backup_recovery_and_admin_progress_are_bounded_and_sanitized).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant