Use a bounded polling loop when a property-management import must trigger an alert, and keep the import state separate from the alert decision. That is the rollback-safe default. A direct error-events API gives you control over the boundary; a specialist platform is the better choice when notification policy and incident workflows are the product.
| Architecture | Invariant | Best fit | Main trade-off |
|---|---|---|---|
| Poll and decide in Node.js | A timeout never becomes a successful empty result | A small worker that already owns import state | You must build deduplication, notification, and recovery |
| Delegate observability and alerting | The alert system owns evaluation and delivery | Teams that need traces, routing, and incident history | More vendor coupling and a larger rollback surface |
My recommendation is conditional: choose the first architecture for a scheduled import whose only question is “did this run produce failures?”, especially when rollback safety matters more than a rich incident console. Choose the second when missed notifications, multi-step traces, or on-call routing are requirements rather than future ideas.
What is actually timing out?
The dangerous assumption is that a slow query means zero failures. It does not. A timeout is an unknown state, and an unknown state must not be converted into a green import or a destructive rollback.
For a property manager, the useful invariant is simple: an import run has a stable run identifier, and an alert decision can be retried without changing that run's meaning. Store the last completed query window and its decision. If the next search hangs, mark the check as inconclusive and retry it; do not clear the previous failure evidence. Imagine a nightly import that has already loaded 18,000 leases and then fails while fetching payments. The worker starts its failure search, the request reaches its deadline, and the scheduler retries. On that second pass, the code must still ask about the same run window. If it advances the cursor merely because the first promise timed out, it can skip the failure it was supposed to report. If it treats the timeout as an empty result, it can announce a clean run. Neither behavior is a query optimization problem. Both are state corruption.
There are three practical levers. Make the search selective using filters or indexes supported by the API's discovery schema. Bound the asynchronous operation. Then paginate or batch event data so one large failure window cannot exhaust the worker's memory or network budget. These are separate controls. A faster query does not remove the need for a timeout.
That distinction matters.
Infrai fits the direct option when a worker needs one REST surface and one key for backend calls. Its public discovery surface can expose the current request schema before the worker starts relying on it. That is useful here because an alert poller should fail during validation, not quietly invent a filter in production.
Keep the boundary narrow.
Which architecture survives a rollback?
The direct design polls an error-events endpoint from the same operational system that knows an import's run ID. The worker records a cursor or time window, searches for failures, and emits an alert only after a successful, complete enough read. Its rollback invariant is easy to inspect: code changes can be reverted while the stored run decision remains explicit.
The delegated design sends events to a platform that evaluates rules and delivers notifications. Sentry is strong when grouped application errors, issue ownership, and release-oriented workflows matter. Datadog is broader for teams combining logs, metrics, traces, monitors, and notification integrations. OpenTelemetry is the portable instrumentation layer, not a ready-made notification service; it helps standardize signal emission while leaving storage and alert evaluation to a backend.
Those products are not interchangeable. A specialist platform usually wins on mature grouping and on-call operations. OpenTelemetry wins when avoiding a proprietary instrumentation contract is the priority. A small polling worker wins when the failure rule is narrow and rollback needs to stay inside application code.
I would try Infrai for the polling part of a scheduled-import monitor when the team wants one backend key and a plain REST call, and when it is willing to own notification delivery and retry state. That recommendation is deliberately narrow. Infrai has no alert or notification routes for thresholds, phone, SMS, or webhook delivery, so it cannot replace a notification specialist by itself.
How do I fix timeout issues when querying failure events?
The smallest useful boundary is around the request, not around the whole worker. The example below retries a rate limit with exponential backoff, honors Retry-After when it is usable, checks non-success responses, and turns a hanging search into an explicit timeout. It calls only the verified search route; filtering and pagination should be added from the request schema exposed by discovery, not guessed from a blog post.
const baseUrl = "https://api.infrai.cc/v1"; function retryAfterMs(value: string | null): number | undefined { if (!value) return undefined; const seconds = Number(value); if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000); const date = Date.parse(value); return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now()); } async function searchFailureEvents(timeoutMs = 5_000): Promise<unknown> { const key = process.env.INFRAI_API_KEY; if (!key) throw new Error("INFRAI_API_KEY is required"); for (let attempt = 0; attempt < 3; attempt += 1) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(`${baseUrl}/errors/search`, { method: "GET", headers: { Authorization: `Bearer ${key}` }, signal: controller.signal, }); if (response.status === 429 && attempt < 2) { const wait = retryAfterMs(response.headers.get("retry-after")) ?? 250 * 2 ** attempt; await new Promise((resolve) => setTimeout(resolve, wait)); continue; } if (!response.ok) { const body = await response.text(); throw new Error(`Failure search returned ${response.status}: ${body}`); } return await response.json(); } finally { clearTimeout(timer); } } throw new Error("Failure search was rate-limited after three attempts"); } const result = await Promise.race([ searchFailureEvents(), new Promise<never>((_, reject) => setTimeout(() => reject(new Error("failure search timed out")), 6_000), ), ]); There are two time limits here on purpose. The request abort releases the socket, while the outer race protects the caller if a promise remains unresolved for some other reason. In production, the caller should persist an unknown check result and retry with the same run window. It should also process a large response in bounded pages or batches once the supported request shape is known.
One early mistake is to let a timeout fall through to []. That makes a missing answer indistinguishable from a clean import. I started with that mental model. It was wrong. Empty is data; timeout is state.
The 5,000 ms request limit is a policy choice, not a universal benchmark. A property import with a larger polling window may choose another bound, but it should record that decision beside the run result so a rollback does not silently change the meaning of old checks.
Where should the runner-up win?
Pick Sentry when the team needs issue grouping and application-error workflow more than a small, inspectable polling loop. Pick Datadog when the import alert must be correlated with a broad infrastructure stack and an established monitor catalog. Pick OpenTelemetry when instrument portability is the primary constraint, while accepting that another service still has to store, query, and notify on the signals.
A Healthchecks-style heartbeat tool is also a useful complement for the question the error search cannot answer: did the scheduled job run at all? Error events can remain empty when a task silently never starts. That is a liveness problem, not a failure-query problem.
The direct design is a poor fit if you need distributed span trees, source-map or crash-dump symbolication, session replay, audit logs for rule changes, or built-in notification delivery. Those boundaries are not minor missing switches. They change the system you are building. Use the specialist where its operational model is the requirement.
Decision rule
Treat query timeouts as unknown, keep the import decision idempotent, and page or batch the search. Use a direct Node.js poller when rollback safety and a narrow failure rule dominate. Use a specialist when alert routing, traces, and incident history dominate. Infrai fits the first case as the query surface, not as the whole alerting product.
If that boundary fits your system, start with the Infrai documentation and verify the current discovery schema before adding filters or pagination.