The Subtle Footgun of TVar (Map _ _)

I’ve made massive improvements in concurrency performance several times recently with a really easy and mechanical change, so I figure it’s time to share this and try to popularize the idea.

Major kudos to Nikita Volkov for the wonderful stm-containers library of course

33 Likes

Good one! I somewhat recently moved our TVar (Map k v) (after moving from MVar (Map k v)) over to the StmMap.Map k v because it was getting rather large.
In the beginning, it wasn’t too bad, but with increased usage you do notice the performance degradation.

It is somewhat unfortunate you can only find stm-containers on hackage by searching for it literally, because you won’t get hits on the modules or functions :frowning:

1 Like

Great post!

There are no situations where going from StmMap.Map k v to TVar (Map k v) will make a significant improvement.

I think there are situations where TVar (Map _ _ ) will be better. If you want to atomically read all the values from the structure, then having a single TVar can lead to better performance.

Reading all the TVars in the StmMap.Map will lead to contention from touching many TVars , but also GHC’s current implementation of STM is quadratic in the amount of TVars used in a transaction. Execution time of reads/writes for TVars during transactions slows down with the number of TVars used during the Transaction. (#24410) · Issues · Glasgow Haskell Compiler / GHC · GitLab . If the map is large, this can be quite expensive!

12 Likes

That’s a great point about the quadratic slowdown from having a bunch of TVars in a transaction. Interesting! I have not needed to summarize a map atomically, and have been able to use listTNonAtomic :: Map k v -> ListT IO [(k, v)] for everything I’ve done where summary was important.

I think I’d still rather see an MVar in that case instead of a TVar, but it all really depends on the specific access patterns. Wrapping the TVar in a safer interface (ie mkUpdater :: (Map k v → IO (Map k v)) → IO (ReadOnlyMapTVar k v) can help prevent livelock problems potentially, where the bare TVar almost invites them.

EDIT: I’ve updated the post to mention this scenario and my recommendations/thoughts on managing it. Hopefully GHC will fix STM soon so stm-containers is more purely a win!

4 Likes

Livelock is when the program is repeatedly retrying transactions - working furiously and making no progress. Livelock is subtle and extremely difficult to diagnose.

I think you could diagnose this with stm-stats: retry statistics for STM transactions but I have not tried it in a real world project.

5 Likes

I see hits!

Looks like modules are only indexed by their last component, so StmContainers doesn’t work, but Map does. It’s just down the page a ways.

(Assuming you meant Hoogle)

1 Like

That’s a very interesting library. Last uploaded in 2011 is unfortunate, but honestly that may be worthwhile trying to incorporate somehow.

1 Like

stm-containers (actually low level stm-hamt which stm-containers is built upon) is used with great success in new JWT cache implementation in PostgREST: https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Cache/Sieve.hs

6 Likes

I’ve found a very simple pattern that helps with few-writers many-readers contention. Just don’t use longlived STM transactions for readers! The readers should typically just “snapshot” the map with a readTVar transaction, then proceed to do work outside the transaction. Using such short transactions does preclude some more clever approaches to STM, but i my experience I’ve found it suffices – and any needed broader atomicity invariants can be enforced elsewhere.

Of course, if many things need to both read from a map and write to it, this doesn’t work. But in that setting, it still frequently works to have processes atomically read the map, then prepare delta updates to the map, which are combined and applied in ensamble elsewhere, including perhaps with optimizations and reorderings. The appriach is similar to Uustalu and Ahman’s “Update Monads”: http://dagstuhl.sunsite.rwth-aachen.de/volltexte/2014/4623/pdf/p001-01-ahman.pdf

4 Likes