NHibernate second-level cache invalidation behavior with async sessions in version 5.3+
0 reputation · 15 Oct 2022, 04:30 UTC
0 reputation · 15 Oct 2022, 04:30 UTC
The goal is to decide whether NHibernate’s second‑level cache can be trusted to provide repeatable reads when multiple async ISession instances share the same session factory in a repeatable development or test environment.
In NHibernate 5.3+ the async ISession uses FlushMode.Auto, which does not automatically clear the second‑level cache when another thread commits a change. Consequently, a subsequent async read may return a stale cached value unless the cache provider is transactional (e.g., RedisCache) or the application manually evicts the affected entries. This leaves developers with an unresolved choice: rely on the cache and risk stale data, implement manual eviction after each async write, or adopt a transactional cache provider. Should the second‑level cache be considered stale‑safe for async reads? Is manual eviction required after every async write to guarantee consistency? Does switching to a transactional cache provider eliminate the need for application‑level cache management?
33775 reputation · 15 Oct 2022, 04:59 UTC
NHibernate’s second‑level cache is not automatically stale‑safe for reads that occur on an async ISession unless the write transaction is flushed and committed (or the cache provider participates in the transaction). If the transaction is properly committed, the cache is invalidated and a subsequent async read will see the updated value; no manual eviction is required. Switching to a transactional cache provider (e.g., Redis with transactions) does not remove the need to commit the NHibernate transaction – the provider only guarantees that its own remove/update calls are executed within the same transaction.
If you observe stale data after an async write, the most common cause is that the transaction never reached the flush/commit stage (e.g., the using block disposed before Commit, or FlushMode never fired). In that case the second‑level cache was not updated, so the async read returns the cached old value.
var cfg = new Configuration();
cfg.SetProperty("hibernate.generate_statistics", "true");
await using var session = factory.OpenSessionAsync();
await using var tx = session.BeginTransactionAsync();
// … modify entities …
await tx.CommitAsync(); // ensures flush and cache invalidation
Enable NHibernate statistics and log second‑level cache activity. After an async write and commit, look for log lines such as:
Updating second-level cache for …Removing from second-level cache for …If those messages are absent, the transaction likely did not reach the flush/commit point.
To confirm whether the transaction is being committed, please verify that you are calling Transaction.CommitAsync() (or Commit()) and that no exception is swallowed before the commit. If you can confirm the commit occurs, the recommendation is to rely on the cache; otherwise, ensure explicit commit or manual eviction.
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 15 Oct 2022, 15:04 UTC
One clarification worth adding to the existing answer: the premise that FlushMode.Auto on async sessions "does not automatically clear the second-level cache" conflates two separate mechanisms. FlushMode only governs when this session flushes its own pending changes before executing a query; it has no bearing on when other sessions' writes invalidate shared cache entries. Invalidation is driven by the committing session's transaction completion, and the async API in NHibernate 5.3+ is a wrapper over the same session state and cache interaction logic as the synchronous path — there is no distinct "async invalidation" behavior to reason about.
The practical consequence: the stale-read risk you describe is identical for synchronous sessions sharing the same factory and provider. Framing it as an async problem can send you debugging in the wrong direction.
For a repeatable test, enable hibernate.generate_statistics and watch the second-level cache hit/miss/put counters around an async commit. If hits continue after a successful CommitAsync(), inspect the provider's invalidation latency rather than session configuration. Also note NHibernate core ships no built-in Redis provider, so any "transactional Redis cache" behavior is provider-specific and needs verification against the exact library and version you deploy.