HarperFast / HarperFast/studio

[RUM] Cluster /operation 404s 71 → 1,399 (31% of the endpoint) — the ChallengeCertificate 5s poll can never stop because its 404 is treated as success

Open
#1,630 0 comments 0 reactions 0 assignees View on GitHub
rum
Dominant language
TypeScript
Stars
5
Forks
4
Avg merge
1d 8h
Merged PRs (30d)
40

Description

## Summary

`POST /Cluster/{id}/operation/` is now **31% failures (1,443 of 4,627 calls in 7d)**, up from **10% (135 of 1,379)** over the preceding 23 days. Essentially all of the increase is **404s: 71 → 1,399**.

These 404s raise no error and produce no toast, because `getSearchByValue` deliberately treats 404 as an empty result. That is why none of the existing poll guards (#1527, #1546, #1569, #1602, #1603) can ever engage on them — and why this has been invisible in Error Tracking while still costing ~1,400 doomed control-plane round-trips a week.

## The signature

Worst single session in the window: **993 × 404 over 112 unbroken minutes**, inter-request gaps pinned at **5.1s**, all against **one** cluster, **100% 404 across 335+ consecutive calls** — while other sessions in the same window got clean `200`s on other clusters. So this is a stable per-cluster condition, not a transient.

```
window calls 404 403 500 400 200 fail%
168h..now 4,627 1,399 19 8 15 3,149 31%
720h..168h 1,379 71 25 27 1 1,237 10%
```

Breadth is **flat** — 26 sessions / 83 views in 7d vs 23 sessions / 69 views in the prior 23d. What changed is volume *per affected session*: **3 → 54**. This is a latent defect whose cost scales with how long a tab stays open, not a code regression. One long-lived tab produced ~71% of the week's total.

Note `POST /HDBInstance/{id}/operation` has **zero** 404s across the same 30 days (2% failures, all 500/400/403/401). The 404s are cluster-path-only, which is what pins the source below.

## Mechanism

`useChallengeCertificates(cluster.id)` → `getChallengeCertificatesQueryOptions` polls every **5s**, unconditionally, whenever the cluster Domains table is mounted:

- `src/features/cluster/domains/queries/getChallengeCertificates.ts` — `refetchInterval: pollUnlessForbidden(5000)`, `enabled: !!clusterId`
- it calls `getSearchByValue` with `forceFabricConnect: true` on a `clu-` id, so `getInstanceClient` routes it to `apiClient.baseURL + /Cluster/{id}/operation` (`src/config/getInstanceClient.ts:58-59`)
- `getSearchByValue` POSTs `search_by_value` for `data.ChallengeCertificate` to `/` → the observed `POST /Cluster/{id}/operation/`

The 404 is swallowed on purpose (`src/integrations/api/instance/database/getSearchByValue.ts`):

```ts
{ validateStatus: (status) => status >= 200 && status < 400 || status === 404 }
if (response.status === 404) { return { data: [] }; }
```

A cluster with no `data.ChallengeCertificate` table therefore answers 404 on **every** tick, the query **succeeds** with `[]`, `query.state.error` is never set, and `pollUnlessForbidden` cannot stop the timer. The poll re-fires every 5 seconds for as long as the tab is open.

The only consumer of the data is `isGeneratingCert` in `src/features/cluster/domains/constants/tableDefinition.tsx:25-29`:

```ts
const isGeneratingCert = !!challengeCertificates?.some((cert) => !cert.issueDate || cert.inProgress);
```

So a 5s cadence exists solely to watch an in-progress certificate — and it runs at full rate precisely when there is **nothing to watch**.

## Why this needs a design decision, not a one-liner

Nothing invalidates the `[clusterId, 'ChallengeCertificate']` query key anywhere in the codebase. The 5s poll is currently the *only* way a newly-started challenge is discovered, so simply stopping or slowing the idle poll would delay the certificate-progress UI in the one flow that matters (bind domain with `generateDomainCerts`, via `setDomainIdsOnCluster`).

Options, roughly in increasing order of correctness:

1. **Idle backoff** — keep 5s while `isGeneratingCert`, drop to 30–60s otherwise. ~6–12× fewer doomed requests, but adds up to 60s discovery latency after starting a challenge.
2. **Invalidate on mutation, then idle backoff** — have `setDomainIdsOnCluster` invalidate `[clusterId, 'ChallengeCertificate']` on success so discovery is immediate, which makes an aggressive idle interval (or `false`) safe. Preferred.
3. **Distinguish "table absent" from "no rows"** — a 404 here means the table has never existed on that cluster; that cannot change until a challenge is created, which option 2 already signals. Treating it as a terminal state would let the poll stop outright.

Option 2 is the smallest change that removes the waste without regressing the cert flow.

## Verification notes

- All figures from RUM app `f590deee-…`, `@type:resource` aggregates (not event samples, so no group-by truncation).
- Attribution to `getChallengeCertificates` is by elimination: it is the only cluster-scoped 5s `POST` to `/Cluster/{id}/operation/`, and `getSearchByValue` is the only caller that tolerates a 404 instead of throwing. The 5.1s observed cadence and the cluster-only 404 distribution both match. Not confirmed against a live reproduction — the Domains page needs a cluster with a missing `ChallengeCertificate` table to exercise.
- No customer, org, cluster, instance or session identifiers are included above.

*Filed by the daily automated Datadog RUM review (2026-08-17).*

Contributor guide

Open the contributing guide

Research direction

Start with src/features/cluster/domains/queries/getChallengeCertificates.ts and trace useChallengeCertificates, then inspect setDomainIdsOnCluster and src/integrations/api/instance/database/getSearchByValue.ts. Compare the polling and query-key behavior with tableDefinition.tsx; done means missing ChallengeCertificate tables no longer generate unbounded 5-second requests while certificate-progress updates remain discoverable.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.