Reading Blobs Right After Upload
Walrus itself is strongly consistent. Once a blob is certified, any aggregator reading directly from storage nodes returns it immediately. Public aggregators often sit behind a content delivery network (CDN), which might cache a 404 Not Found response from before the aggregator sees the blob.
If your app reads blobs through a cached aggregator immediately after upload, plan for this short window.
When this applies
This propagation window only affects you when both conditions are true:
- You read through a public aggregator with a CDN in front, such as the Mainnet or Testnet public aggregators.
- You read a blob within seconds of its certification.
The window does not apply in these cases:
- You read from a self-hosted aggregator with no CDN in front. Reads are strongly consistent.
- The blob does not exist. A
404in that case is correct and should not be retried.
Retry only when you know the blob should exist
Because Walrus is strongly consistent, a 404 from an uncached aggregator means the blob is not on the network. Retrying every 404 adds latency for missing blobs.
Retry with backoff only when your app knows the blob has just been certified. Treat other 404 responses as final.
Example: retry after a known upload
The following helper is for the post-upload path: your app receives a successful certification, then immediately fetches the blob through a cached aggregator. It surfaces non-404 errors immediately and only retries 404.
// Copyright (c) Walrus Foundation
// SPDX-License-Identifier: Apache-2.0
/**
* Fetch a blob immediately after upload from a CDN-fronted aggregator.
*
* Walrus is strongly consistent, but CDNs in front of public aggregators might
* briefly cache a 404 from before the blob was visible. Use this helper only
* when your app knows the blob has just been certified. For general reads,
* treat 404 as final instead of retrying.
*/
async function fetchBlobWithRetry(
blobId: string,
aggregatorUrl: string,
opts = { maxAttempts: 6, baseDelayMs: 500 },
): Promise<ArrayBuffer> {
let lastError: unknown;
for (let attempt = 0; attempt < opts.maxAttempts; attempt++) {
try {
const res = await fetch(`${aggregatorUrl}/v1/blobs/${blobId}`, { cache: "no-store" });
if (res.ok) return await res.arrayBuffer();
if (res.status !== 404) throw new Error(`Unexpected status ${res.status}`);
} catch (err) {
lastError = err;
}
const delay = opts.baseDelayMs * 2 ** attempt;
await new Promise((r) => setTimeout(r, delay));
}
throw lastError ?? new Error(`Blob ${blobId} not available after ${opts.maxAttempts} attempts`);
}
Tune maxAttempts and baseDelayMs to your latency budget. A few seconds of total retry time is usually sufficient. Do not use this pattern for general reads. For those reads, treat 404 as final.
Bypass the CDN cache if needed
Some CDNs might ignore cache control headers and cache the 404 response. If retries keep returning the cached 404, append a unique query parameter:
fetch(`${aggregatorUrl}/v1/blobs/${blobId}?cb=${Date.now()}`);
Once the blob is reliably reachable, you can remove the query parameter.
Use multiple aggregators
If a single aggregator is slow to surface the blob, try another aggregator. If one aggregator returns 404 but another returns the blob, the blob is on the network.
Pre-warm the read path before demos
Before showing a freshly uploaded blob to an audience, retrieve it once from the aggregator you plan to use. This populates the aggregator and CDN caches before you need them.