Limits of NHibernate Second-Level Cache Concurrency Strategies for Mutable Data
0 reputation · 25 Mar 2024, 13:08 UTC
0 reputation · 25 Mar 2024, 13:08 UTC
The goal is to select a second‑level cache concurrency strategy for mutable entities that provides acceptable consistency while maximizing concurrent reads and writes in NHibernate. The documented strategies—read‑only, nonstrict‑read‑write, read‑write, and transactional—offer different locking and expiration guarantees, but the framework leaves the choice between read‑write and nonstrict‑read‑write to the developer without prescribing a rule.
Constraints include the risk of serving stale data when cache expiration is not aligned with transaction boundaries, and the fact that underlying cache providers (e.g., Redis, SysCache) may implement sliding or absolute expiration differently, affecting the observable behavior of each strategy. These uncertainties raise the following questions: What workload characteristics indicate a preference for read‑write over nonstrict‑read‑write? How should cache expiration policies be tuned to reduce stale‑data windows for each strategy? Are there provider‑specific features that make one strategy more suitable than the other?
26525 reputation · 25 Mar 2024, 19:47 UTC
Choose read-write when concurrent transactions may update the same entity and you cannot tolerate one transaction reading a value another transaction is mid-flight on changing. Choose nonstrict-read-write when updates are rare, rarely concurrent, and a brief stale-read window after commit is acceptable. If neither description fits — high write concurrency with strict consistency needs — the correct answer is to leave that entity out of the second-level cache entirely.
The likely explanation for the confusion is that both strategies sound like they "handle" mutable data, but they solve different problems:
Signals for nonstrict-read-write: single-writer or batch-window updates, reference-like data that changes occasionally, and consumers that tolerate a few seconds of staleness.
Expiration is a backstop, not a correctness mechanism — neither strategy relies on TTL for consistency, since invalidation happens on update. That said:
No strategy makes the cache a source of truth. In a multi-node deployment without a distributed, coherent provider, each node's cache can diverge, and read-write's soft locks do not coordinate across nodes. If you run multiple application servers, verify that your provider supports distributed invalidation; otherwise prefer nonstrict-read-write with short expirations, or bypass the cache for mutable entities. Also note that the query cache amplifies staleness, since query results are invalidated only at table-timestamp granularity.
Behavior is provider- and version-sensitive, so confirm on your stack:
One detail that would sharpen the recommendation: whether you run a single application node or several — that answer determines whether read-write's guarantees hold at all.
Use comments to ask for clarification. Post a solution as an answer.
26,525 reputation · 25 Mar 2024, 22:20 UTC
For mutable entities, the read‑write strategy adds a version check (or database lock) at commit time, which prevents two transactions from overwriting each other's changes and guarantees that a cached entry is either up‑to‑date or removed before the next read. Nonstrict‑read‑write only timestamps the entry and relies on the underlying cache’s expiration, so a stale read can occur if an update commits after the cache entry has been refreshed but before it expires. Therefore, choose read‑write when write frequency is high or when business logic cannot tolerate occasional stale reads; otherwise, nonstrict‑read‑write offers higher read throughput for mostly‑read workloads.