Back to newsletter 118 contents
My default choice of "fairly fast and scalable" architecture hasn't wavered since I first came across it over 20 years ago (and it had very likely been around for a long time before that): independent specialized components, each separately parallelizable; a common plugin bus to allow the components to communicate in a location independent way; a distributed event/data cache with push and pull capabilities; all execution between components asynchronous and pipelined.
There are two big problems with this architecture. The first is that the implementation tends to be complicated because people are really bad at understanding complex event driven design. We can easily handle the odd asynchronous event being processed, and can easily understand lots of sequential processing. But when there are many possible processing paths and many possible asynchronous events, the code becomes spaghetti code. Even where you write the cleanest, most readable code in the world, it still becomes spaghetti code - it's the nature of event-driven code. It's equivalent to having lots of "goto"s sprinkled all over the place, the jumps between segments just does our head in. Even after years of GUI event-handling, we aren't really coming any closer to making event-driven code clear and easily understandable. In fact, for many people, this drawback is so strong that it is a major reason not to use this acrhitecture. Sadly, I know of no solution to this issue - at least for now if you want the efficiency of the architecture, you have to be prepared to have complex event driven processing.
The second problem with the architecture - and of many other architectures too - is that it is perceived to be a tradeoff between latency and throughput; in this case that throughput is increased at the cost of some latency. The criticism is that because you have abstracted away a potentially ultra-low latency request-processrequest-response round trip into a sequence consisting of request, raise event, put on bus, add to cache, trigger push on component queue, process request, raise result event, put on bus, add to cache, trigger push on result component queue, send result sequence; then invariably you have lost any chance of really low latencies.
The criticism is valid. Sure you can add in priority queues and you get some ability to improve latency for a subset of requests, but the inherently longer indirect route is still there and that adds overhead. But there is a way of getting the best of both worlds, which is to build in to your frameworks the ability for the system to recognize when it can bypass the longer code path and achieve low latency where that is obtainable. We have long done this, for example, with local vs remote calls in JEE, where the application server can recognize that two beans are local to each other and can convert a potentially inefficient remote loopback packaged indirect call into a much more efficient direct local call. This technique isn't easy, and only really works where the application container manages the communication layer and interactions between components, but it is achievable.
Realistically though for most applications, you need two code paths. For those requests that need really low latency, you need a shorter, synchronous highly optimised hop-minimized path. I'll think about that and continue on this subject in the next newsletter. But back to this month's newsletter. We have all our usual Java performance tools, news, and article links. Javva The Hutt tells us about Not JavaOne; Over at fasterj we have a new cartoon about measuring service times; and, as usual, we have extracted tips from all of this month's referenced articles.
Java performance tuning related news.
Java performance tuning related tools.
Back to newsletter 118 contents