NATS JetStream Stream ↔ Consumer: Stale Message Retrieval with max_age Retention
0 reputation · 04 Mar 2020, 08:31 UTC
0 reputation · 04 Mar 2020, 08:31 UTC
When a JetStream stream is configured with a max_age retention policy, messages older than the specified duration become eligible for removal. However, JetStream does not purge them instantly; removal occurs during periodic housekeeping cycles performed by the stream leader. The default housekeeping interval is roughly 30 seconds in recent NATS releases, and this value can be tuned via server configuration. In a clustered environment, replicas may lag behind the leader’s housekeeping, further extending the window during which expired messages remain accessible.
This nondeterministic delay raises questions for applications that rely on consumer queries or cached results to reflect the true state of the stream. The uncertainty lies in whether a consumer might still receive a message that has technically exceeded its max_age but has not yet been purged by housekeeping.
Key questions for investigation:
max_age and its removal from the stream, and can this window be reliably configured?max_age has already been purged, or must they implement additional checks?35275 reputation · 04 Mar 2020, 17:32 UTC
max_age limit. This occurs because JetStream does not delete messages the instant they expire; instead, it marks them for removal and purges them during periodic housekeeping cycles.The max_age retention policy defines the maximum time a message can stay in a stream. Once a message passes this age, it becomes eligible for deletion. However, the actual removal is performed by the stream leader during a background task. In recent NATS releases, this cycle occurs approximately every 30 seconds by default. Until this cycle completes, the message remains in the store and is visible to all active consumers.
In a clustered environment, the situation is further complicated by replication lag. While the leader manages the housekeeping, replicas must synchronize these state changes. If a consumer is connected to a replica that has not yet processed the leader's purge command, the consumer may still see and fetch the expired message. This creates a nondeterministic window of visibility that extends beyond the leader's own housekeeping interval.
Consumers cannot rely on the JetStream API to guarantee that a message has been purged based on max_age. If your application requires strict adherence to message age, you must implement the following strategies:
To verify the specific window in your environment, you can monitor a stream with a short max_age and observe the time between the expiration timestamp and the moment the message returns a 404 Not Found or disappears from a pull query.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.