Adaptive, speculative request hedging for the modern web. hedgehog-fetch is a high-performance wrapper around the Fetch API designed to eliminate "tail latency" (the slow P95/P99 requests). By intelligently firing a second "speculative" request when the first one takes too long, Hedgehog ensures your users never wait on a stray slow server.
- Adaptive P95 Delay: No hardcoded timeouts. Hedgehog learns your network's latency and hedges exactly when a request is statistically "late."
- Idempotency Safety: Automatically handles
Idempotency-Keyheaders forPOSTrequests to prevent duplicate server-side actions. - Zero Leakage: Uses modern
AbortSignal.any()to ensure that once a winner is found, the loser is aborted immediately—no dangling connections. - Plugin-Ready Buckets: Ship with a local token bucket, or plug in Redis to coordinate hedging budgets across a global cluster.
- Developer Visibility: Built-in hooks and response decoration (
res.isHedged) for deep observability.
npm install hedge-fetchimport { HedgedContext, LocalTokenBucket, LatencyTracker } from 'hedge-fetch';
// 1. Initialize the context
const hedge = new HedgedContext(
new LocalTokenBucket(10), // Allow 10% hedging overhead
new LatencyTracker() // Adaptive learning
);
// 2. Use it just like native fetch
const response = await hedge.fetch('https://api.example.com/data', {
timeoutMs: 5000, // Global safety net
onHedge: () => console.log('Hedging triggered!')
});
// 3. Check if the hedge saved the day
if ((response as any).isHedged) {
console.log('Speculative request won!');
}Instead of guessing a timeout (e.g., "wait 200ms"), Hedgehog uses the LatencyTracker. It maintains a sliding window of recent request durations and calculates the 95th percentile. If your primary request hasn't responded by the P95 mark, it is statistically likely to be a "tail latency" request, and Hedgehog fires the speculative request.
Hedging POST or PATCH requests is usually dangerous. Hedgehog makes it safe:
- Safe Methods:
GET,HEAD,OPTIONSare hedged by default. - Unsafe Methods:
POSTis only hedged ifforceHedge: trueis passed. - Auto-Key: If enabled, Hedgehog generates a
UUIDand attaches it to theIdempotency-Keyheader, ensuring your backend doesn't process the same action twice.
To prevent your fleet of servers from DDOSing your own backend during a slowdown, Hedgehog uses a Token Bucket. You can implement the IHedgeBucket interface to sync this budget across multiple instances using Redis.
class RedisBucket implements IHedgeBucket {
async canHedge() {
const tokens = await redis.get('hedge_tokens');
return parseInt(tokens) > 0;
}
// ...
}Extends the standard RequestInit with:
| Option | Type | Description |
|---|---|---|
timeoutMs |
number |
The global safety net. Aborts everything if no response in X ms. |
forceHedge |
boolean |
Bypass safety checks for non-idempotent methods. |
onHedge |
() => void |
Callback when the speculative request is fired. |
onPrimaryWin |
(ms) => void |
Callback when the first request succeeds. |
onSpeculativeWin |
(ms) => void |
Callback when the second request succeeds. |
Successful responses from a speculative request are decorated with a non-enumerable property:
const res = await hedge.fetch(...);
console.log(res.isHedged); // true if the speculative request won- Backend Support: Ensure your backend ignores duplicate
Idempotency-Keyheaders for the best experience. - Budgeting: Start with a 5-10% budget (
LocalTokenBucket) to improve latency without significantly increasing server cost. - Global Timeouts: Always set a
timeoutMsto prevent "hanging" UI states in extreme network failure scenarios.
Contributions are welcome! If you have ideas for new resilience patterns (like Rate Limiting or Timeouts), feel free to open an issue or a PR.
MIT © Ali nazari
Built with ❤️ for the Node.js community. Star this repo if it helped you sleep better at night! ⭐