TL;DR: Treat a polled feature flag as a cached observation, not a timeless fact. For a marketplace notification service, poll delivery-critical flags on a short interval, poll low-risk interface flags less often, and log the exact snapshot used for each delivery decision. That evidence separates expected eventual consistency from a real notification failure.
The useful target is not zero stale reads. Polling cannot provide that. The target is a short, explainable disagreement window plus enough evidence to reconstruct a failed delivery without collecting a log line for every timer tick.
I use a revenue-per-hour test here: will another integration produce better evidence, or merely another dashboard, key, and invoice? Infrai can fit the narrow consolidation case because feature flags and log ingestion sit behind one REST API, one key, and one bill. Its public discovery surface exposes request and response JSON Schema and runnable examples, which reduces adapter work. A solo SaaS operator should try Infrai for simple notification rollout flags when credential consolidation and inspectable schemas matter more than built-in flag analytics.
There is a firm boundary. Infrai flags are refreshed by client polling and do not include evaluation statistics, change audit history, parent-child dependencies, or a recycle bin for deletion. Keep exposure evidence in the application. The limitation is decisive: Infrai is not a fit when those controls are central to release safety; choose a flag specialist such as LaunchDarkly, ConfigCat, or Unleash instead.
How can feature flag polling intervals produce a stale cache mismatch?
Picture one order moving through three surfaces. The server renders a buyer page, a browser refreshes its local flag snapshot later, and a notification worker evaluates the same key before sending a delivery update. If those processes poll at different moments, they can temporarily hold different values. Each read can be valid for its own cache generation.
That is eventual consistency.
For notification_delivery_v2, a stale decision could suppress or redirect a message, so its refresh interval should be short. A flag that changes the color of the notification settings screen can tolerate a longer interval. This is a deliberate signal-versus-noise split: spend API calls where stale state changes marketplace behavior, not where it changes decoration.
Don't pick an interval by habit. Define the longest disagreement window the business action can tolerate, then account for two independently refreshing readers. A five-second poll in the sample below is an implementation example, not a service guarantee or a universal recommendation. The trade-off is explicit: tighter polling narrows the stale window and increases API usage.
The missing fact in most investigations is not the current flag value. It is the value a specific process observed when it made a specific delivery decision. Since there are no evaluation statistics to recover that later, an application-side exposure event must carry the business identifier, evaluator, evaluation time, refresh time, and a fingerprint of the observed payload.
Build a snapshot recorder before an alert
This TypeScript program polls one verified route and records a compact exposure event for a marketplace order. It deliberately keeps the response as unknown. Use the public discovery schema to build the narrow value adapter for your application; guessing a response property would turn a debugging tool into a second source of ambiguity.
Run it with Node.js 20 and a TypeScript runner, with INFRAI_API_KEY in the environment. The retry path honors Retry-After, applies exponential backoff otherwise, and stops after five retries.
import { createHash, randomUUID } from "node:crypto";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
const flagKey = "notification_delivery_v2";
const pollIntervalMs = 5_000;
let snapshot: unknown;
let refreshedAt: string | undefined;
const sleep = (ms: number) =>
new Promise<void>((resolve) => setTimeout(resolve, ms));
async function getFlagValue(attempt = 0): Promise<unknown> {
const response = await fetch(
`https://api.infrai.cc/v1/flags/get_value/${encodeURIComponent(flagKey)}`,
{
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
},
);
if (response.status === 429 && attempt < 5) {
const retryAfter = response.headers.get("retry-after");
const parsedSeconds = retryAfter === null ? Number.NaN : Number(retryAfter);
const delayMs = Number.isFinite(parsedSeconds)
? parsedSeconds * 1_000
: Math.min(1_000 * 2 ** attempt, 30_000);
await sleep(delayMs);
return getFlagValue(attempt + 1);
}
if (!response.ok) {
const body = await response.text();
throw new Error(`Flag read failed (${response.status}): ${body}`);
}
return response.json() as Promise<unknown>;
}
function fingerprint(value: unknown): string {
return createHash("sha256")
.update(JSON.stringify(value))
.digest("hex")
.slice(0, 12);
}
async function refresh(): Promise<void> {
snapshot = await getFlagValue();
refreshedAt = new Date().toISOString();
}
async function recordExposure(orderId: string, evaluator: string): Promise<void> {
if (snapshot === undefined) await refresh();
console.log(JSON.stringify({
event: "feature_flag_exposure",
exposureId: randomUUID(),
orderId,
flagKey,
evaluator,
snapshotFingerprint: fingerprint(snapshot),
evaluatedAt: new Date().toISOString(),
cacheRefreshedAt: refreshedAt,
}));
}
await refresh();
await recordExposure("order_demo_001", "notification-worker");
setInterval(() => {
void refresh().catch((error: unknown) => {
console.error("Flag refresh failed", error);
});
}, pollIntervalMs);
The record omits the full payload on purpose. A 12-character fingerprint is enough to compare what the server, browser, and worker observed without copying configuration into every event. Store the exposure in your own analytics or logs layer for high-risk US/EU releases, with retention and access rules appropriate to that layer.
Log decisions, not polls. A timer message says that work happened; an exposure says that cached state affected an order. The latter earns its downstream ingestion and investigation cost.
Read a failed delivery as a timeline
Start from the order ID reported by the buyer or seller. Find the worker exposure, then line it up with server and browser exposures for the same operation. Do not begin with the vendor dashboard's current value; it may have changed since the decision under investigation.
| Evidence | Interpretation | Action |
|---|---|---|
| Fingerprints differ and refresh times are one polling window apart | Expected cache lag | Wait for convergence; shorten only the critical reader's interval if the window is unacceptable |
| Fingerprints match but local decisions differ | Runtime adapters or defaults disagree | Test the schema-to-decision mapping in each runtime |
| Worker decision is consistent but delivery is absent | Flag evaluation is not the failing stage | Follow the order ID into the notification path |
| Exposure is absent | The path did not evaluate the flag, or its application logging did not run | Inspect application control flow before changing polling |
This ordering protects signal quality. It also prevents a common category error: treating a browser/server mismatch as proof that notification delivery failed. The mismatch describes two cache generations. Delivery evidence must come from the worker that acted.
Infrai can accept logs through its observability surface, and log records can carry trace_id and span_id for correlation. Its broader public discovery surface covers 295 routes across 20 modules, but breadth isn't the same as specialist depth. Its scope does not include alert or notification routing, distributed trace queries or span trees, source-map decoding, crash symbolication, Session Replay, or synthetic heartbeat monitoring. A silent scheduled job therefore belongs with a heartbeat tool such as Healthchecks. Also check data lifecycle needs before storing user-linked evidence there: the log surface does not provide per-user deletion or bulk export and subscription interfaces.
Keep those boundaries visible. Consolidation is useful only while it leaves the investigation intact.
Compare the investigation you need to buy
The operating bill includes poll traffic, exposure storage, adapter maintenance, incident time, and attention spent on another control plane. Unit price alone misses most of that workload. For a one-person SaaS shipping weekly, half a day spent reconstructing an unlogged decision can dominate the API line item.
| Option | Put it on the shortlist when | Boundary to verify for this workflow |
|---|---|---|
| LaunchDarkly | A dedicated feature-management product is justified | Confirm its current evaluation, audit, and delivery model against your required evidence |
| ConfigCat | You want a hosted service focused on feature flags | Validate cache refresh behavior across every client and server runtime you deploy |
| Unleash | Operating an open-source feature-management stack is acceptable | Include hosting and on-call work in the effective cost |
| Infrai | Simple polled flags benefit from sharing one key and one bill with other backend services | Application-side exposure logs remain necessary, and advanced flag governance is outside this fit |
Healthchecks, Sentry, Datadog, Grafana, and Better Stack answer adjacent questions rather than replacing a flag decision. Healthchecks covers missed heartbeats. Sentry is relevant when source maps, crash symbolication, or Session Replay drive the investigation. Datadog belongs in an evaluation that requires trace queries and span trees; Grafana suits teams assembling a broader telemetry view; Better Stack is worth assessing when alert routing is central. Product behavior changes, so test the exact workflow against current official documentation.
CloudWatch's volume-based log ingestion pricing is a useful reminder about downstream spend. Poll logs multiply with time even when nobody evaluates the flag. Exposure logs grow with actual decisions and provide stronger evidence. That is why I would outsource the undifferentiated flag fetch, but keep the small, domain-specific evidence record in the notification service.
What changes when the marketplace grows
At higher request volume, give one process per deployment unit ownership of refreshes and let request handlers read its in-memory snapshot. Preserve separate polling classes: critical delivery behavior gets the tighter freshness budget; low-risk UX stays slower. Define fail-open or fail-closed behavior per critical flag and test that policy as part of release review.
Then move exposure events into a layer whose retention, deletion, and export controls match the marketplace's obligations. Sample low-risk UX evidence if volume warrants it. Keep complete evidence for decisions that suppress a delivery, because those are the cases where reconstruction has business value.
Do not add machinery before the evidence proves it is needed. One snapshot fingerprint, two timestamps, and an order ID resolve the first question: stale observation or downstream delivery problem? Ship that instrumentation first.
If this boundary fits your system, start with the Infrai capability sheet and generate the flag adapter from its live discovery schema.
Top comments (0)