Using CouchDB’s _changes Feed for Real‑Time Sync in Distributed Apps
Learn how CouchDB’s _changes HTTP feed provides a low‑overhead, long‑polling stream of document revisions that lets multiple clients react instantly to updates, plus how to manage connection costs.
26 Sept 2026, 20:23 UTC

The Problem: Keeping Distributed Clients in Sync
Applications that run on multiple devices or nodes often need to react when a document changes elsewhere. Polling introduces latency and waste, while building a custom messaging layer adds operational complexity.
How the _changes Feed Works
CouchDB exposes an HTTP endpoint /_changes that returns a continuous stream of document revisions. By using the feed=longpoll parameter the request hangs until a change occurs, then returns a JSON line and closes, allowing the client to immediately re‑issue the request. The feed can be filtered with a design‑document filter (e.g., ?filter=myapp/by_type) and a heartbeat can keep the connection alive.
Worked Example: Node.js Client Listening for Changes
const https = require('https');
function startChangesFeed(dbUrl, since = 'now') {
const options = {
method: 'GET',
headers: { Accept: 'application/json' },
};
const req = https.request(`${dbUrl}/_changes?feed=longpoll&since=${since}&heartbeat=10000`, options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
const change = JSON.parse(data.trim());
if (change.doc && change.doc.type === 'task') {
console.log(`Task updated: ${change.doc._id}`, change.doc);
// UI update logic would go here
}
} catch (e) {
// ignore empty heartbeat lines
}
// reconnect
startChangesFeed(dbUrl, change ? change.last_seq : since);
});
});
req.on('error', (err) => {
console.error('Feed error', err.message);
setTimeout(() => startChangesFeed(dbUrl, since), 5000);
});
req.end();
}
// Example usage
startChangesFeed('http://localhost:5984/mydb');
Replace mydb with your database name and adjust the filter or heartbeat as needed. The function reconnects automatically after each change or on error.
Trade‑Offs and Operational Considerations
- Each open _changes request holds a socket and a thread/process on the CouchDB node.
- Under thousands of simultaneous clients this can exhaust file‑descriptor limits unless the node is tuned or a proxy is used.
- The feed does not guarantee global ordering of concurrent updates across shards; applications must be prepared to handle out‑of‑order or duplicate revisions.
Actionable Steps to Deploy the Feed Safely
- Enable the _changes endpoint (it is on by default) and verify access with
curl -X GET 'http://localhost:5984/mydb/_changes?feed=longpoll&since=now'in one terminal. - In another terminal, update a document (e.g., via Fauxton or
curl -X PUT) and observe the first terminal receive a JSON line. - Add a design‑document filter if you only need certain document types, reducing payload.
- Set a heartbeat interval (e.g., 10000 ms) to keep firewalls from dropping idle connections.
- Monitor CouchDB stats (
couchdb stats) for connection count and memory usage as you scale the number of clients. - Consider placing a lightweight reverse proxy (NGINX, HAProxy) in front of CouchDB to terminate long‑polling connections and distribute load.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.