Record each scheduled game-data import as a run with an explicit outcome, then alert on missing successful results only after its deadline; choose a hosted health API by whether it preserves those run boundaries and attributes telemetry costs to the responsible workload. Short answer: a green process endpoint cannot establish that a scheduled import produced usable records, and a single cumulative counter cannot establish which run failed. The dashboard needs an evidence trail, not a prettier heartbeat.
The relevant startup question is often phrased as finding a simple hosted API for a Node.js health dashboard without operating Prometheus, across EU and US deployments. For this workload, the deciding constraint is subtler: a scheduled import can execute on time, return HTTP 200, and still publish zero validated results. The health contract must distinguish a legitimately empty source window from a run that stopped making progress. Cost attribution matters because a dashboard that cannot identify which job, region, or environment generated its telemetry cannot support a defensible retention or sampling decision.
What should an app health dashboard API prove without a metrics server?
Treat the scheduler's intent, execution, and committed output as separate facts. A run identifier ties them together; a deadline specifies when absence becomes actionable. The producer should record an expected run for each scheduled window, a terminal status after validation, and the number of accepted results. Where zero results are normal, record an explicit empty_expected outcome under a documented rule. Otherwise zero is a failed business result, even if the transport and worker are healthy.
The critical invariant is that a successful outcome means the results have crossed the application's commit boundary. A response from an upstream feed is not that boundary. Neither is a successful parse before a database transaction rolls back. For retries, give a logical scheduled window one stable identity and retain attempt identifiers separately; otherwise duplicate completions can mask an absent window. This is an exactly-once accounting goal, not a claim that the network delivers messages exactly once. The durable ledger of outcomes is authoritative, and telemetry is a projection of it.
No signal, no success.
Check the window first.
For a concrete example, suppose the match-results import is scheduled every 15 minutes. Set a per-run deadline based on the actual scheduling and validation budget, and evaluate the latest due window rather than counting successful HTTP calls over a rolling period. Fifteen minutes describes the example schedule, not a universal alert threshold. The on-call view should show the due window, its last committed outcome, the latest attempt, accepted-result count, and the owning team. A separate health endpoint can report worker liveness, but it cannot substitute for these fields.
Where should the health boundary live?
The architecture decision is to derive alert state from committed import outcomes, while exporting bounded metrics for trend views. Three approaches can expose a dashboard; they do not provide equivalent evidence.
| Approach | What it establishes | Failure boundary | Attribution consequence |
|---|---|---|---|
| Process health poll | The application responds to a probe | A responsive worker can publish nothing | Cheap to label by service, weak by scheduled job |
| Instrumented metrics endpoint | Counts and durations observed by the process | Restarts, missed schedules, and uncommitted work need separate handling | Bound labels to job, region, and environment; avoid run IDs as metric labels |
| Durable run ledger with exported aggregate metrics | Whether each due window reached a committed terminal outcome | Ledger write and alert evaluation must be monitored independently | Charge ingestion and storage to a defined workload using stable dimensions |
OpenTelemetry describes metrics as aggregated measurements, and its guidance on attributes and cardinality is relevant here: a unique run ID is valuable in the audit ledger but unsuitable as an unbounded metric dimension. Export a low-cardinality import_completed_total by job and outcome; retrieve the individual run from the application store when an alert fires. This division makes the dashboard inexpensive to query without discarding the evidence needed for reconciliation. It also permits the telemetry pipeline to be replaced without redefining success.
An alert evaluator should read the expected schedule independently of the producer. Otherwise a stopped scheduler emits neither runs nor failures, and a dashboard built only from completed-run events goes quiet at precisely the wrong time. Clock skew, daylight-saving changes, retries, overlapping windows, and backfills need explicit rules. Use UTC window boundaries and compare against a configured deadline; do not allow a backfill of an old window to satisfy today's missing window. Test the missing-schedule case before relying on any hosted alert feature.
The ledger design has a real limitation: it adds transactional state and requires someone to maintain the schedule definition even if the chosen monitoring service already accepts heartbeat events. For a throwaway import whose failure has no downstream reconciliation requirement, that operational overhead may be disproportionate; a heartbeat with a defined grace period could be sufficient. A production match-results pipeline is different because a retry may insert some results before another attempt completes, a delayed source may legitimately report no matches, and an old backfill may arrive after a newer window has failed. Those three cases demand separate answers from the state model, and none can be inferred reliably from an alive worker alone. The trade-off is additional application-side correctness logic in exchange for an audit trail that survives a change in dashboard provider.
How does the critical path avoid duplicate success?
The following Go sketch shows the state transition rather than a vendor API. The store implementation must atomically commit the imported results and terminal run outcome, and its unique key must be the logical job and window. If results live in a different transaction domain, an outbox or equivalent reconciliation mechanism is needed before claiming a committed result.
package imports
import (
"context"
"time"
)
type Outcome struct {
Job string
Window time.Time
Accepted int
Status string
}
type Store interface {
CommitWindow(ctx context.Context, outcome Outcome, results []Result) error
Latest(ctx context.Context, job string, window time.Time) (Outcome, bool, error)
}
type Result struct{ ID string }
func Evaluate(ctx context.Context, store Store, job string, window, deadline, now time.Time) (bool, error) {
if now.Before(deadline) {
return false, nil
}
outcome, found, err := store.Latest(ctx, job, window)
if err != nil {
return false, err
}
return !found || outcome.Status != "committed" || outcome.Accepted == 0, nil
}
The example treats an empty result as alertable; a domain with legitimate empty windows should encode and test an explicit accepted-empty status rather than silently changing the zero test. If the ledger read fails, evaluation is unknown, not healthy: report evaluator failure on a separate channel and retain the last known assessment with its timestamp. An alert's audit record should include the evaluated window, deadline, rule version, and observed outcome so that an incident review can reconstruct why it fired.
Unknown is not green.
Which hosted API contract is sufficient?
Evaluate a service against the data flow, not a feature checklist. Can a small Node.js producer submit an idempotent outcome or export standard metrics without requiring a Prometheus server? Can the alert evaluate absence against an external schedule, or must an application-side evaluator send the alert state? Check whether its API exposes ingestion failures and delayed data, whether dashboard queries separate EU and US workload ownership, and whether retention and access controls apply to the raw event record as well as aggregates. Verify these behaviors in a trial with one normal run, one accepted-empty run, one missed run, a duplicate retry, and a failed ledger read. The last case should be visibly unknown.
Cost attribution should follow the same stable dimensions as operations: job, deployment region, environment, and accountable team. Measure event volume, metric series, alert evaluations, and retention separately against a realistic import schedule; avoid attaching player identifiers or run IDs to every time series. Regional routing deserves an explicit data-flow review rather than an assumption that a dashboard's hosting region determines where all telemetry is processed. For personal data, GDPR Article 17 describes a right to erasure subject to conditions and exceptions; it does not by itself certify a provider or dictate one universal retention period. Keep player-level payloads out of health telemetry unless a justified operational need and a deletion path have been established.
This is also a deployment decision. Pin the outcome schema and alert rule together, roll out the writer before enabling absence alerts, and rehearse an overdue window in a nonproduction environment. Track dropped submissions and evaluator lag as distinct faults. A backend team should be able to change telemetry providers without rewriting the rule that declares a game import complete.
Why reject the heartbeat-only option?
A hosted uptime poll remains useful for detecting a dead process or an unreachable endpoint. It is the wrong primary alert for silent imports: the process can answer every probe while the schedule is absent, the feed is empty, or a commit fails. Keep that poll as a separate availability signal, and make the due-window ledger the source for result alerts. The defensible choice is the API contract that can carry bounded, attributable evidence of those outcomes while leaving the application's correctness rule under application control.
Top comments (0)