
Short answer: Redis is a good place to keep the finished XML of an RSS feed for a few minutes. Build the feed once, store the whole document under one key with an expiry, return the stored copy to every reader and keep a longer-lived stale copy for the moments when the database or the application is slow. Use a short lock so that only one request rebuilds an expired feed, delete the key when you publish if you want instant updates, send ETag and Cache-Control headers from the cached text and connect the address to an auto-poster.
A feed address is one of the most requested URLs on a busy site. Readers, search engines, aggregators and auto-posters all fetch it, often every few minutes, and each request can trigger a database query, a template and an XML build. When traffic grows, or when a crawler with a bad schedule arrives, the feed becomes a quiet source of load, and the page that suffers is often the home page, because it shares the same database. A cache in front of the feed removes most of that work. Redis fits well because it is fast, it expires keys by itself and it offers an atomic way to take a lock. This guide shows a pattern in Node.js with Express, but the same ideas apply to any language that has a Redis client.
Cache the finished document, not the rows from the database. The XML is the same for every reader, it is small, usually a few dozen kilobytes, and building it is the expensive part. A single string under a single key is easy to store, easy to delete and easy to inspect with a command line tool.
Do not cache answers that depend on the reader, such as feeds for logged-in members or private feeds that include a token. If the content differs by user, include the user or the token in the key, or skip the cache for those feeds. A shared key for private content is a data leak, and no gain in speed justifies it.
The simplest pattern reads from Redis first and builds the feed only when the key is missing. The code below uses the node-redis client and an Express route.
import express from 'express';
import { createClient } from 'redis';
import { createHash } from 'node:crypto';
import { buildFeedXml } from './feed.js';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
const app = express();
const KEY = 'feed:main';
const TTL = 300;
app.get('/feed.xml', async (req, res) => {
let xml = await redis.get(KEY);
if (!xml) {
xml = await buildFeedXml();
await redis.set(KEY, xml, { EX: TTL });
}
const etag = '"' + createHash('sha1').update(xml).digest('hex') + '"';
if (req.headers['if-none-match'] === etag) return res.status(304).end();
res.set({
'Content-Type': 'application/rss+xml; charset=utf-8',
'Cache-Control': 'public, max-age=300',
ETag: etag,
});
res.send(xml);
});The key expires after five minutes, so the feed is rebuilt at most once in that time. The ETag is computed from the text that is returned, which means that it stays the same until the content changes and tools that poll often receive a short 304 answer. Details of the validator mechanism are in the guide to ETags and conditional requests.
The basic pattern has a weakness. When the key expires, every request that arrives before the first rebuild finishes also finds the key missing, and each of them builds the feed. If the build takes two seconds and the site receives fifty requests per second, a hundred builds start at once, the database slows down, the builds take longer and more requests pile up. This is called a cache stampede, and it usually happens at the worst time, because a busy moment is also when a popular post has just been published.
There are three defences, and they work well together:
Redis can create a key only when it does not exist, with an expiry, in a single command. That command is the foundation of a simple lock. Keep a second copy of the feed with a much longer expiry, and return it when the lock is taken by someone else.
const STALE_KEY = 'feed:main:stale';
const LOCK_KEY = 'feed:main:lock';
async function getFeed() {
const fresh = await redis.get(KEY);
if (fresh) return fresh;
const gotLock = await redis.set(LOCK_KEY, '1', { NX: true, EX: 30 });
if (!gotLock) {
const stale = await redis.get(STALE_KEY);
if (stale) return stale;
await new Promise((r) => setTimeout(r, 200));
return (await redis.get(KEY)) ?? (await redis.get(STALE_KEY)) ?? buildFeedXml();
}
try {
const xml = await buildFeedXml();
const jitter = Math.floor(Math.random() * 30);
await redis.set(KEY, xml, { EX: 300 + jitter });
await redis.set(STALE_KEY, xml, { EX: 86400 });
return xml;
} finally {
await redis.del(LOCK_KEY);
}
}The lock expires after thirty seconds, so a crashed process cannot block the feed for ever. The request that gets the lock builds the document and writes both copies. The other requests return the stale copy at once, which is at most a few minutes old, or wait a moment and read the new one. If the build fails with an error, the stale copy keeps the feed alive for a day, and readers see an old but valid document and not an error page. This matters for feeds, because tools treat repeated errors as a reason to slow down or to stop.
A lifetime of five minutes is acceptable for most sites, but if you want a new article to appear in the feed immediately, delete the key when you publish.
async function onArticlePublished() {
await redis.del('feed:main', 'feed:category:news');
}The next request rebuilds the feed with the new item. Keep the stale copy, because it is only a fallback. If your publishing system runs outside the Node application, a small command that deletes the key after publication is enough. Be careful to delete the keys of every feed that contains the article, such as the category feeds and the tag feeds, or one of them will show the old state until its lifetime ends. The reasons why a post can appear late are collected in the guide to feed caching.
| Setting | Why it matters | Suggested approach |
|---|---|---|
| Key names | Clear names make deletion safe | Prefix with feed and the path |
| Expiry | Stops old feeds from living for ever | Minutes for fresh, a day for stale |
| Memory limit | Protects other data in the same instance | Set maxmemory with an LRU policy |
| Size of values | Large documents slow down transfers | Limit the feed to recent items |
| Separate database or instance | Keeps the cache away from sessions and queues | Use a different logical database or a different server |
A feed with fifteen to thirty recent items is plenty for most tools, and a smaller document is cheaper to store and to send. The recommended size is explained in the guide to the number of items in a feed. If Redis shares an instance with sessions or a job queue, set a memory limit with an eviction policy for least recently used keys, and never use an eviction policy that can remove the queue. A separate instance for cache data is the safest arrangement.
A cache must never become a single point of failure. Wrap the Redis calls so that a connection error is logged and the application builds the feed directly, perhaps with a small in-memory copy for a few seconds. Set short timeouts on the client, because a hanging connection is worse than a missing key. After the cache returns, the next request fills it again and the system recovers without any action from you. Monitor the hit rate and the number of rebuilds, since a sudden fall in hits tells you that keys are evicted too early or that the expiry is wrong.
Check three things with curl: the content type and the validators on the first request, the 304 answer on a conditional request and the time of the answer when the cache is warm.
curl -sI https://example.com/feed.xml
curl -s -o /dev/null -w '%{http_code} %{time_total}\n' https://example.com/feed.xml
curl -sI -H 'If-None-Match: "paste-the-etag-here"' https://example.com/feed.xml
redis-cli ttl feed:mainA longer routine is in the feed validation guide, and a shorter one in debugging a feed with curl.
PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed, connect your accounts and choose how each post is built from the title, the description or the page title. PostRSS checks the feed as often as every minute and shares each new entry, with its image and link, to the channels you choose among 66 supported networks, messengers, team chats and blogs, including LinkedIn, X, Bluesky, Mastodon and Telegram. A cached feed with validators makes frequent checks cheap for your server. Keyword filters, UTM parameters and posting windows let you tune each channel. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.
Redis turns a feed from a repeated database job into a cheap read. Store the finished XML under one key with an expiry of a few minutes, take a short lock so that a single request rebuilds it, keep a stale copy for the bad moments, add a little random time to the expiry and delete the keys when you publish if you need instant updates. Send validators from the cached text, handle a missing cache gracefully and then connect the address to an auto-poster, so that every article you publish reaches your social channels quickly and your server stays calm.
Five minutes is a good start. Shorter lifetimes cost more rebuilds, and lifetimes of hours make new posts reach your social channels late.
It is a burst of identical rebuilds that starts when a popular key expires and many requests find it missing at the same time. A lock and a stale copy prevent it.
Yes, if you want the new article in the feed at once. Delete every key that contains the article, including category and tag feeds.
Only if the key includes the user or the token. A shared key for private content leaks it, so skip the cache for such feeds if unsure.
Yes. PostRSS sees only the feed address and reads any valid RSS or Atom document that is reachable without a login. Paste the address, connect your channels and new articles are shared as soon as they appear.
What changed in the networks, what broke, and how to fix it before it costs you reach.
Από την Internet Solutions, την ομάδα πίσω από το PostRSS. Κάθε προϊόν σάς εξοικονομεί χρόνο με τον δικό του τρόπο.