evmap trades immediate visibility for lock-free reads
A lock-free, eventually consistent, concurrent multi-value map.
At a glance
- What is it?
- evmap is a Rust crate for a lock-free, eventually consistent, concurrent multi-value map, where readers never take a lock and writers decide how stale a read may be. It trades the guarantee of a plain HashMap for throughput on read-heavy workloads, and it is version 11.0.0.
- Who is it for?
- Adopt evmap when your workload is read-heavy, you can tolerate reads that lag behind writes, and you want the map to be multi-value. Skip it if any reader must see the most recent write immediately, because that guarantee is the one the design gives up, and skip it if a single-threaded HashMap would serve you.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 22 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A map where the reader never blocks
evmap is a lock-free, eventually consistent, concurrent multi-value map, and the way it achieves that is worth understanding before you consider it. The map allows reads and writes to execute entirely in parallel, with no implicit synchronization overhead. Reads never take locks on their critical path, and neither do writes, assuming there is a single writer. Multi-writer is possible using a `Mutex`, but the design assumes one. The concurrency primitive underneath is the `left-right` crate, and the README credits the implementation to that foundation. For a read-heavy workload, the shape is favourable: writers can afford to refresh after every write, which keeps reads up to date, while readers stay fast because they never acquire a lock. That is a different trade from a `RwLock`-guarded `HashMap`, where the writer eventually has to wait for the readers to drain.
The visibility rule is the whole contract
The trade-off the module exposes is one of eventual consistency, and the README is precise about what that means. Writes are not visible to readers except following explicit synchronization. Specifically, readers only see the operations that preceded the last call to `WriteHandle::flush` by a writer. That single sentence is the API contract, and everything else follows from it. Writers choose how stale they are willing to let reads get. They can refresh the map after every write, which emulates a regular concurrent `HashMap` for anyone who needs that, or they can refresh only occasionally to reduce synchronization overhead at the cost of stale reads. The word occasionally is doing real work in that sentence, because a reader in between refreshes sees the map as it was at the last flush and nothing since. If your application has an invariant that a read must observe a preceding write, you have to either flush on every write or keep that state somewhere else.
Why every key holds a collection
The map is multi-value, meaning every key maps to a collection of values, and the README is upfront that this introduces memory cost by adding a layer of indirection through a `Vec` for each value. The justification given is that it would not be possible to emulate this functionality on top of the semantics of the map. The parenthetical example is the operational log case, where a key identifies an event type and the value is the set of times it occurred, and a plain map would have to overwrite rather than accumulate. That reasoning generalises. If your use case is one value per key, this crate is paying for a `Vec` per entry to get nothing, and a concurrent map with single values would be a better fit. If your use case is naturally a set, the indirection stops being overhead and becomes the data model you wanted anyway.
Metadata travels with the refresh
There is a second, smaller feature that is easy to miss. Each of the two maps carries some customizable meta-information, and the writers may update it at will. When a refresh happens, the current meta also becomes visible to readers. The suggested use in the README is to indicate what time the refresh happened, which turns the meta into a cheap way for a reader to know how stale its view is. That is a genuinely useful property, because with eventual consistency the usual question a reader asks is how old is this, and the answer is otherwise not available. It also means the meta is only as fresh as the last flush, so it is a lower bound on staleness rather than a live clock. The repository carries two licence files, LICENSE-APACHE and LICENSE-MIT, and the manifest states the licence as MIT OR Apache-2.0, so you can take either.
The benchmark caveat comes before the numbers
The performance section opens with a warning that is worth reading before the charts: the benchmarks are outdated at this point, but communicate the right point, with the author hoping to update them again some time soon. The comparison was evmap against a standard Rust `HashMap` protected by a reader-writer lock, and against `chashmap`, a crate providing concurrent hash maps based on bucket-level multi-reader locks. The benchmark binary is in `benchmark/src/main.rs`, run on a 40-core machine with Intel Xeon E5-2660 v3 CPUs at 2.60GHz. It spawns reader and writer threads in tight loops, each doing a read or a write to a random key, and reports average reads and writes per second as the thread count grows, for both uniform and skewed key distributions. The stated conclusion is that evmap performs well under contention, especially on the read side, and the setup is explicitly the worst case for evmap, since every write also does a refresh. Refresh less often and performance goes up, which is the point of the design.
Feature flags for indexing, eviction and amortisation
The crate is a normal Cargo package, published as `evmap` on crates.io with documentation on docs.rs, at version 11.0.0 and edition 2018. Its dependencies are small and each one is there for a reason: `left-right` for the concurrency scheme, `smallvec` and `hashbag` for the collection values, and `indexmap` for insertion-ordered iteration when the indexed feature is on. The feature set is three optional switches, all off by default.
[features]
default = []
indexed = ["indexmap"]
eviction = ["indexed", "rand"]
amortize = ["indexmap-amortized", "hashbag/amortize"]`indexed` turns on insertion-ordered iteration by pulling in indexmap. `eviction` builds on that and adds `rand`, so the map can drop entries. `amortize` is the odd one out, wiring the amortized variant of indexmap and the amortize feature of hashbag. The default is no features at all, so the crate you get by default is the smallest one. The docs.rs metadata sets `all-features = true` and passes a `docsrs` cfg, so the published documentation shows every configuration rather than the default one. Testing uses quickcheck with the macros extension, which is a sensible choice for a data structure whose whole appeal is that its concurrency behaviour is subtle.
Editorial conclusion
Adopt evmap when your workload is read-heavy, you can tolerate reads that lag behind writes, and you want the map to be multi-value. Skip it if any reader must see the most recent write immediately, because that guarantee is the one the design gives up, and skip it if a single-threaded HashMap would serve you. Before you commit, read the flush semantics carefully, since the visibility of a write depends on a writer calling `WriteHandle::flush`, and check the benchmark caveat, because the published numbers are described as outdated.
Frequently asked questions
What is evmap and when would I use it?
It is a Rust crate providing a lock-free, eventually consistent, concurrent multi-value map. It suits read-heavy workloads where a reader must not block, and where it is acceptable for a read to observe the state as of the last flush rather than the very latest write.
When does a write to evmap become visible to readers?
Only after the writer calls `WriteHandle::flush`. Readers see the operations that preceded the last flush by a writer, and writers choose how often to flush, either after every write to emulate a concurrent `HashMap`, or less often to cut synchronization overhead at the cost of stale reads.
Is evmap multi-writer safe?
Multi-writer is possible using a `Mutex`, but the design assumes a single writer. The README states that neither reads nor writes take locks on their critical path, assuming there is a single writer.
Which optional features does the evmap crate have?
Three, all disabled by default. `indexed` enables insertion-ordered iteration through indexmap, `eviction` builds on `indexed` and adds `rand` so entries can be dropped, and `amortize` uses the amortized indexmap variant along with the amortize feature of hashbag.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jonhoo-evmap)Was this useful?