I think about update propagation whenever one fact appears in many places: database row, cache, search index, report, and notification view. I recommend designing how copies catch up before users find stale truth.
Copies need a delivery path and a lag budget
The important behavior starts when timing gets ugly and the system still has to choose.
The design should name the source of truth, the propagation mechanism, and the expected delay. Without that, stale reads look like random bugs.
Cache update example
The point here is to move from concept to operation: who uses it, what breaks, and what decision changes.
A price manager changes a product price. The catalog database updates first. Cache, search, and recommendation views receive events and refresh their copies.
Versioned update code
The code needs to answer the operational question: do we allow it, block it, retry it, or ask a human?
The outbox keeps the data write and the propagation signal together:
database.transaction(() -> {
catalog.save(newPrice);
outbox.add(new ProductPriceChanged(productId, newPrice));
});
The database update and outbox event are saved in the same transaction. That prevents a price change from being committed without a matching propagation event.
Sequence diagram: one update, several copies
The update is not complete everywhere at once. The propagation path makes the delay visible and manageable.
The freshness rule
- Name the source of truth for each field.
- Monitor propagation delay and failed consumers.
- Design cache invalidation as part of the write path.