Anti-Patterns: Prompt Injection II · 5 of 5
Live-Fetch Dependency
A live-fetch dependency is a runtime need to pull code, data, or configuration from somewhere else before a system can do its job.
Every working system has to get its ingredients from somewhere. Some ingredients are packed into the system before it starts; others are fetched while it is running. A live-fetch dependency is the second kind: a real-time reliance on another place being reachable, correct, fast enough, and trustworthy at the moment you need it. That simple choice can make software fresher and more flexible, but it also turns a remote delay, outage, or bad update into your problem.
What a live-fetch dependency is
A dependency is something your system needs in order to work. It might be a library of code, a pricing table, a feature flag, a machine learning model, a translation file, a certificate, a fraud score, or a page layout. If the system must fetch that thing while it is running, and cannot do its intended job without the result, it has a live-fetch dependency.
The important part is not whether the dependency is inside or outside your company. The important part is timing. If the thing is fetched during a build, packaged, tested, and shipped with the system, it is not live for the user request. If it is fetched during startup, during a scheduled job, or in the path of a user action, it can affect availability and response time.
Live fetching is not automatically bad. It is a trade: you gain freshness and central control, and you accept a runtime contract with another system.
Why systems fetch things live
Live fetching appears whenever the value of newness is higher than the value of self-containment. A mobile app may fetch a configuration file so that a risky feature can be turned off without releasing a new app. A web page may fetch a design asset from a content delivery network, a geographically distributed service that serves files closer to users. A payment service may fetch exchange rates because yesterday’s rate is not good enough.
It also appears for organizational reasons. Central teams want one place to update policy. Security teams want certificates and deny lists to change quickly. Product teams want experiments without redeploying every service. Data teams want models to improve independently of application releases.
These are valid reasons, but they do not erase the cost. They move part of the system’s correctness from compile time or release time into runtime.
- Use live fetching when the fetched value must change faster than the application can safely be released.
- Avoid live fetching when a stale but known-good value is acceptable and the cost of a failed fetch is high.
- Treat every live fetch as a small integration with its own owner, contract, timeout, fallback, and monitoring.
- Be suspicious when live fetching is chosen only because packaging, versioning, or release discipline is hard.
The hidden contract
A live fetch creates a contract even when nobody writes one down. Your system is assuming that another system will be reachable, fast, compatible, authorized to serve the data, and correct enough for your purpose. If any of those assumptions fails, your system may fail too.
This contract has more dimensions than simple uptime. Freshness matters: are you allowed to use a value from five minutes ago, five hours ago, or never? Compatibility matters: can the remote side add a field, remove a field, or change a default? Integrity matters: how do you know the thing fetched was not tampered with or accidentally corrupted?
The contract should be explicit because different dependencies deserve different answers. A bank balance, a feature flag, and a marketing image are all fetchable, but they should not have the same failure behavior.
Where live fetching breaks
The most obvious failure is that the remote system is down. A less obvious one is that it is merely slow. Slow can be worse than down because callers keep waiting, threads or workers fill up, queues grow, and the original problem spreads to systems that were healthy.
Another common failure is a bad but reachable value. A configuration service may return a malformed document. A package registry may serve a compromised library. A model service may return a version trained for a different input shape. The fetch succeeds, but the system becomes wrong.
The third class is coordinated failure. Many instances restart at once, all fetch the same dependency, and overwhelm it. A short network issue causes every client to retry at the same time. A cache expires globally on the hour. The live dependency becomes a meeting point for traffic spikes.
- Outage: the dependency cannot be reached.
- Slowness: the dependency responds, but not within the caller’s time budget.
- Bad content: the dependency returns a value that is syntactically valid but semantically wrong.
- Incompatibility: the returned value follows a different version or meaning than the caller expects.
- Stampede: many callers fetch or retry at once and create the outage they fear.
- Trust failure: the caller cannot prove the source, integrity, or authorization of the fetched content.
Designs that reduce the blast radius
The safest live-fetch design starts by asking a blunt question: what should happen if the fetch fails? If the answer is undefined, the dependency will define it for you during an incident. Better answers include using the last known good value, serving a reduced experience, delaying nonessential work, or failing closed when safety requires it.
Caching is the usual first tool. A cache is a local or nearby copy kept to avoid repeated remote work. Caching is useful only when paired with rules: how long the value may live, how it is refreshed, how it is validated, and what happens if refresh fails. The time to live, often shortened to TTL, is the allowed age of a cached value before the system tries to refresh it.
Timeouts and retry limits are equally important. A timeout is a maximum wait; without one, a caller can hang indefinitely. Retries can hide brief glitches, but too many retries can multiply load. Good clients use small timeouts, limited retries, random delay between retries, and a circuit breaker, which temporarily stops calls to a failing dependency so the caller can recover.
- Keep a last known good copy when stale data is safer than no data.
- Validate schemas, signatures, ranges, and version numbers before accepting fetched content.
- Set timeouts shorter than the user or upstream system can tolerate.
- Use retry budgets so one dependency failure does not consume all capacity.
- Stagger refreshes and add random delay so clients do not stampede.
- Make startup able to proceed in a limited mode when the dependency is noncritical.
Decisions leaders and practitioners must make
A live-fetch dependency is not only an engineering detail. It is a product, operations, security, and cost decision. Leaders need to decide which experiences must be current, which may be stale, and which must stop rather than risk using uncertain data. Practitioners need enough authority to encode those decisions in software rather than improvise them during an outage.
Service level objectives, or SLOs, are targets for how a service should behave, such as how often it succeeds or how quickly it responds. If a user-facing SLO depends on a live fetch, the dependency’s own behavior must fit inside that budget. A page with a 500 millisecond response target cannot safely wait 900 milliseconds for a personalization service.
Ownership is another decision. Someone must own the fetched content, the serving system, the client behavior, and the emergency path. If a remote configuration can take down checkout, then changing that configuration is a production change and should be reviewed, tested, logged, and reversible.
Operating live-fetch dependencies deliberately
Once a live fetch exists, it should be visible in the same way a database or message queue is visible. You should know its success rate, latency, error types, content version, cache hit rate, and fallback rate. A fallback that works is good; a fallback that is silently used for weeks may hide a broken dependency or stale business rule.
Testing should include unhappy paths. Turn the dependency off in a safe environment. Slow it down. Return malformed data. Return an older version. Expire the cache. Restart many clients at once. These tests often reveal that the documented fallback exists only in comments, or that the startup path has a hidden live fetch nobody noticed.
The mature posture is not to eliminate every live-fetch dependency. That would make many systems rigid and slow to adapt. The mature posture is to classify them, bound them, monitor them, and make their failure behavior boring.
Key takeaways
- A live-fetch dependency is any runtime fetch that a system needs in order to complete its work.
- The main trade is freshness and central control versus runtime risk in availability, latency, compatibility, and trust.
- A successful network response does not guarantee that the fetched content is correct, safe, or usable.
- Caches, timeouts, retry limits, validation, and fallbacks turn live fetching from hope into an engineered contract.
- Leaders should decide where stale data is acceptable, where failure is safer, and who owns each emergency path.
- The goal is not to ban live fetching, but to make its behavior explicit, bounded, monitored, and tested.
Glossary
- Live-fetch dependency
- A runtime reliance on fetching code, data, configuration, or another value from another place before a system can do its job.
- Dependency
- Anything a system needs in order to work, such as a service, file, library, database, or configuration value.
- Runtime
- The period when software is executing, as opposed to when it is being built, packaged, or released.
- Cache
- A stored copy of data kept close to the caller so it can be reused without fetching the original every time.
- Time to live
- The maximum age a cached value is allowed to reach before the system tries to refresh it.
- Circuit breaker
- A client-side safety mechanism that temporarily stops calls to a failing dependency so resources are not exhausted.
- Service level objective
- A measurable target for how a service should behave, such as a success rate or response time goal.
- Last known good
- The most recent fetched value that was validated and accepted as safe to use if a later fetch fails.