1

I'm searching various alternatives for a write intensive application when it comes to selecting a Java data structure. I know that ONE data structure could not provide a single universal solution solution to a write intensive application but I'm surprised by the lack of discussion out there on the topic.

There are many people talking about read-intensive-rate-writes or concurrent-read-only applications but I cannot find any conversation around the data structures used for a write intensive application.

Based on the following requirements

  1. key/value pairs - Map
  2. Unsorted - for the sake of simplicity
  3. 1000+ writes per minute / negligible reads
  4. All data are stored in memory

I am thinking of the following approaches

  1. Simple ConcurrentHashMap: Although based on this from official Oracle docs

[...] even though all operations are thread-safe, retrieval operations do not entail locking

It must be better suited for read intensive applications

  1. Combination of a BlockingQueue and a set of ConcurrentHashMaps. In batches, the queue is drained of all its elements and then the updates are appropriately allocated in the underlying maps. In this approach though I would need an additional map to identify which maps are included to every map - acting like an orchestrator
  2. Use a HashMap and synchronize on the API level. Meaning that every write related method is going to be synchronized
synchronized void aWriteMethod(Integer aKey,String aValue) {
   thisWriteIntensiveMap.put(aKey,aValue);
}

It'd be great if this question did not just receive criticism on the aforementioned options but also suggestions about new and better solutions.

PS: Apart from the integrity of the data, order of operations and throttling issues what else needs to be taken into account into choosing the "best" approach for a write intensive.

I know that this might look a bit open ended but it'd be interesting to hear how people think on this problem.

Niko
  • 616
  • 4
  • 20
  • What is the use case? why map? If you have a write-intensive application and no need for deduping, you can use a linked list without locking the whole structure. – Burak Serdar May 23 '21 at 15:55
  • @BurakSerdar If I use a map then on every update/upsert/delete I would need to scan the entire list to find the entry I want to touch. This is why map seems like the more fitting choice – Niko May 23 '21 at 15:58
  • 1) You are talking about read and write, when the problem you are describing is more of a general nature concerning insert, update and get operations. 2) This question is language agnostic, i.e. has nothing to do with Java per se. 3) You are basically asking about time complexity. If you search for any collection type on wikipedia, it will highlight the complexity class in the summary box. For example en.wikipedia.org/wiki/Binary_search_tree. – SME_Dev May 23 '21 at 16:02
  • @SME_Dev 1) Yes but my problem is when the writes significantly outweigh the reads 2) I do not think it is language agnostic. Maybe Python has solved this problem out of the box using - have already implemented a ds focused on this use case. I am concerned about Java. 3) I am not asking about time or space complexity - this is concurrency question. I am mostly interested in the existence (or not) of a ds or common practice that caters for write intensive applications – Niko May 23 '21 at 16:04
  • @SME_Dev If I cared about complexity I would have chosen HashMap to ConcurrentHashMap. The latter is significantly slower. https://i.stack.imgur.com/XRbrk.png – Niko May 23 '21 at 16:05
  • That means your write operations do not outnumber reads, because each write will include a lookup. Now we are talking about two operations during which you have to keep the structure locked. – Burak Serdar May 23 '21 at 16:06
  • @BurakSerdar This is true. An update/upsert/remove is an implicit read. – Niko May 23 '21 at 16:07
  • @BurakSerdar but based on this realization do you know of any Java structures that have clearly provisioned for something like this? – Niko May 23 '21 at 16:10
  • 1
    Another point is, that you need to understand e.g. the speed and algorithm of an insert operation, because then you know what kind of synchronization is going to be used. E.g. does only one value get a lock, does a subset of values get locked, or does the entire collection get locked. If you think of how a tree is rebalanced, I'm sure that has a huge synchronization overhead. In the end, it will be different from application to application, so loadtests/benchmarks are a good way to find out the best alternative. – SME_Dev May 23 '21 at 16:11
  • I am not aware of an existing structure suitable for the use case. If a high level of concurrency is desired, a sharding structure could work: use a hashkey to select one of the hashmaps, and lock only that hashmap. – Burak Serdar May 23 '21 at 16:14
  • @SME_Dev " does only one value get a lock, does a subset of values get locked, or does the entire collection get locked." This is a very good point. If I'm not mistaken `ConcurrentHashMap` uses buckets in order to avoid the lock on the entire collection. "If you think of how a tree is rebalanced, I'm sure that has a huge synchronization overhead": True! That is why I wanted to avoid sorted collections - skip lists, tree maps, tree sets and so on... – Niko May 23 '21 at 16:24
  • 1
    You might prefer to use [NonBlockingHashMap](https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/maps/NonBlockingHashMap.java) for a lock-free map, which offers higher write throughput at the cost of not supporting atomic compute methods. Otherwise if you do not need to read your writes immediately, an intermediate queue (via a ring buffer) allows you to cheaply record the update, drain it on a single thread, and penalize readers by having to catch-up. This record/replay approach is used by Caffeine cache, as every LRU read is an internal write. – Ben Manes May 23 '21 at 17:31
  • @BenManes This is somehow what I was thinking with option 2. A bounded blocking queue is a kind of ring buffer right? – Niko May 23 '21 at 21:19
  • 1
    @Niko Yes, though `ArrayBlockingQueue` uses a lock, whereas JCTools' offers bounded lock-free queues. Either way there is complexity of sharding and draining the queues. Your write rate of 16+/s is lowish, as ConcurrentHashMap can do [50M writes/s](https://github.com/ben-manes/caffeine/wiki/Benchmarks#write-100-1) on a Zipfian distribution. You should benchmark via JMH, as you'll probably be fine with the stock hashmap. – Ben Manes May 23 '21 at 21:25
  • Thanks @BenManes! Very interesting info! – Niko May 23 '21 at 21:30
  • 1. 1000+ is really nothing. Even HashMap within a synchronized block meets your requirements 2. If you care about performance so much, you cannot use immutable types and auto(un)boxing. GC may eat all your efforts (see https://stackoverflow.com/questions/67440755/why-java-multi-threading-does-not-give-a-linear-speedup-to-the-number-of-threads/67447273). For type specific collections look into https://github.com/leventov/Koloboke, https://github.com/JCTools/JCTools, and https://github.com/real-logic/agrona/tree/master/agrona/src/main/java/org/agrona/collections. Test them with your load in JMH – AnatolyG May 25 '21 at 11:22
  • And, finally, think about your architecture twice (at least :)). A lot of things like GC pressure, ineffective IO, cache misses et.etc. may make your search of the right collection for the middle of your pipeline useless. – AnatolyG May 25 '21 at 11:26

1 Answers1

3

Even if you would go for the worst possible type of map like e.g. a synchronized HashMap, I don't think you will notice any performance impact with your load. A few thousand writes per minute is nothing.

I would set up a JMH benchmark and try out various map implementations. The obvious candidate is a ConcurrentHashMap because it is designed to deal with concurrent access.

So I think this is a good example of premature optimization.

pveentjer
  • 10,545
  • 3
  • 23
  • 40