Kryonet: simple but super-fast TCP communication in Java + performance benchmarks

We have designed cluster-aware and cloud-based solution of service virtualization for some time. Incredible work as distributed system is next dimension of thinking – and learning as well 🙂

We was deciding to split atomic piece of our domain work to multiple nodes. Such step would hopefully provide us to point certain messages to certain nodes. We can than store data in main memory and avoid distributed locking above some remote store, probably Redis. This is usual approach how to achieve better response time of the whole solution to avoid database search for all calls. Obviously with well designed replication.

I was really worried about latency. We need really low response time in couple of milliseconds at most under heavy load. I’m talking about the whole solution which consumes CPU a lot because of the calculations of simulated response under the hood, so no space for any wasting.

What is the best latency of such transmission between two nodes? What is the throughput of one or multiple connections? Except theoretical analysis I also did our own proof of concept to verify our thoughts in the reality.

I like kryo serialization framework. It’s very fast, very simple and the boot of anybody is in level of minutes. Hopefully I discovered that guys from Esoteric Software think about remote communication and they did very logic step. They developed framework allowing TCP communication using kryo serialization – kryonet.

Well, I believe that nobody is surprised that the solution is very fast and super easy as well. Like kryo itself.

There are some benchmarks how TCP communication can be fast, in level o microseconds, but what is the real response time, performance, when I need to transfer some domain object to another node in real environment using gigabit network. I mean no laboratory environment but just real one where our app will probably run in couple of months?

Well, in short it’s hundred of microseconds.

Kryonet via Kryo

It is built above java nio, kryo serialization and asynchronous approach when the response is being received.
It’s better to read the manual on github as coding around the sending and the receiving is very easy.

Kryo preserves the way that the serializators are separated classes, later registered to the system, so you do no have to affect your domain classes. But obviously, you have to open them to support injections of fields from outside. Or you can use the reflection too.

The response is received asynchronously to the processing of the original request. Kryonet exposes TCP port on the server side and uses TCP under the hood. But don’t worry, you almost can’t touch complexity of TCP messaging.

Number meaning and setup

What was my message? It contains identifier, uuid, and map of elements. The element contains the data but the important thing is the overall size of the message.

Response time is the whole round-trip:

  1. Serialization on the client side
  2. To send the request to the server
  3. Deserialization of the message on server side
  4. To respond the message identifier back to the client
  5. The deserialization of received message on the client size

Well, the response time is just the number you can expect when you want to push some message to remote component and wait for the acknowledgment – ACK.

Both client and server are HP Proliant, 24 and 32 cores, >1 gigabit network.

Performance Benchmarks

One TCP Connection

First of all, I used one TCP connection.

Message Size [bytes]/Number of elements Average RT [micro-seconds] Median RT [micro-seconds] Buffer size Throughput [msgs/s] Rough Load [msgs/s]
48 / 2 190 160 256 B 5400 100k
48 / 2 202 170 16 kB 5400 100k
48 / 2 672 157 1kB 18300 1M
48 / 2 418 130 16kB 19300 4M
3216 / 200 316 313 16 kB 650 1k
3216 / 200 332 331 256 B 1k

Message size: size of serialized message into raw byte[].

Average vs. median: as my test keeps all response time value, it was very interesting to analyze them. Average value reflects all edge cases, mostly the divergence during the flushing of buffers etc., but median value does not. What does it mean in real? The difference between average and median value indicates growing number of slower responses.

Buffer: kryo uses serialization and object buffer, see the documentation.

Rough load: all calls are asynchronous which means that I had to limit incoming load generated by my generator. The approach I chose was to call standard java thread sleep after certain messages. The operation in milliseconds is not quite accurate but it was fine for rough limitation of input load. 4M means that the generator thread sleeps for 1 milliseconds once it generates four thousand of messages.

The discussion:

  • there is only two times slower response time for 100 times larger message
  • there is almost no different response time for various buffer sizes
  • increasing load means almost the same median value but highly increased average response time. Well, the response time is not stable at all which indicates that you can’t rely on response time in the case of massive load

Multiple Client Connections to One TCP Server Port

The approach having multiple clients using one TCP port usually scales well, but not for kryonet as it uses limited buffer size, see following table. I used 10 concurrent clients for the measure.

Message Size [bytes]/Number of elements Average RT [micro-seconds] Median RT [micro-seconds] Buffer size (client/server) Throughput [msgs/s] Rough Load [msgs/s]
48 / 2 256 254 16k/16k 9000 1k
48 / 2 580 535 16k/160k 42000 5k
48 / 2 33000 23000 16k/300k 80000 10k

The discussion:

  • kryo itself is very fast but single threaded deserialization brings certains limits so huge throughput was achieved for the cost of really high latency
  • the increasing of the buffer size within the server allows to serve much more requests as they just fall into buffer and were not rejected as before for lower buffer size

Multiple Connections to Multiple Ports

The last includes multiple clients connected to the multiple server ports. It brought the best performance as expected.

Message Size [bytes]/Number of elements Connections Average RT [micro-seconds] Median RT [micro-seconds] Buffer size (client/server) Throughput [msgs/s] Rough Load [msgs/s]
48 / 2 10 542 486 256/256 54000 100k
48 / 2 10 5000 518 16k/16k 155000 500k
48 / 2 20 2008 1423 16k/32k 210000 500k
48 / 2 50 1460 404 16k/32k 182000 100k
48 / 2 50 2024 378 256k/256k 220000 100m
48 / 2 100 3120 404 256k/256k 231000 100m
3216 / 200 10 405 368 8k/32k 21000 3k
3216 / 200 10 415 353 16k/32k 21000 3k
3216 / 200 20 399 350 16k/32k 32300 2k
3216 / 200 50 380 327 16k/32k 43700 1k
3216 / 200 50 410 344 64k/128k 78000 2k

The discussion:

  • incredible load for 100/50 clients per second in no bad time as I would originally expected
  • as I’ve already discuss the stuff above, there is huge different between median and average RT as well
  • there is no such difference between 20 and 100 clients in throughtput but in average/median RT

Resources

Well, usually the transport of a message from one to another node is not your business case. It’s important that both client and server is able to do it’s core logic – message processing of received throughput is always the core.
One client does not almost burden the server, see htop screenshot for the most approach having the best throughput:
On the other hand, 10 concurrent clients to one server side port is almost the same even if the number of transported messages is 5x time higher:
As you can see, 50 concurrent clients and ports is more likely theoretical stuff for this measure than usable solution as the server do not any other thing then deserialize incoming throughtput 🙂

Conclusion

It’s fast and easy. Almost 20k transmitted small messages for one client per second is incredible number for me. On the other hand, 150-200 micro seconds response time is surprise for me as well.
The only thing to consider is to carefully define the size of all buffers. There is no queuing and just the rejection of a message can happen if the connection is under heavy load.

Data Hackaton – motivační víkend

Poslední únorový víkend patříl díky Petru Ocáskovi v Node5 eventu jménem Data Hackaton. Akce to byla početná, celkem se přihlásilo přes 70 lidí. Každý s trošku jiným očekáváním. Jelikož jsme se něčeho podobného účastnili poprvé, vyrazili jsme spíše poznat komunitu lidí, networking, než si zahackovat a udělat nějaký reálný výsledek v podobě zanalyzovaných dat. Ve finále jsme se o to do poslední chvíle snažili, bohužel, řečeno sportovní hantýrkou, jsme zaplatili nováčkovskou daň a padli 🙂

Ale od začátku.

Pátek

Ten patřil úvodu akce, pár přednáškám. Po nezbytném přivítání od Petra, následovalo pár mini-přednášek. Jelikož tématem hackatonu byla data, jednalo se převážně o představení problémů, které chceme na eventu řešit, popř. co vlastně nabízíme a nebo které problémy vám vyřešíme/pomůžeme řešit.

Zdroje dat byly ruzné. Akcie a tweety, hluk na D1čce, vývoj kriminality popř. data o firmách. Všecho znělo docela lákavě. Tady jsme udělali první chybu 🙂 Přestože networking, chtěli jsme vlastní téma. Náležitě jsme rozhodili sítě a sešlo se nám pár kandidátů, které bychom za víkend mohli zanalyzovat. Výběr pokračoval do soboty.

Během pátečního večera jsme potkali především kluky z keboole. Petr Šimeček nám dal úvodní intro jak do keboole, tak gooddata tak nakonec i do HP Vertica. Za posledně jmenovanou věc nám bylo trošku stydno, protože jako zaměstnanci HP jsme Verticu neměli nikdy v ruce. Nakonec se ukázalo, že po dlouhé instalaci by nás asi čekalo zklamaní. Slovy klasika: prostě někdo musel ukázat, že vyfukováním cigaretového kouře do vody zlato nevznikne.

Po přečtení Petrova blogpostu si vlastně vzpomínám ještě na pár zajímavých věcí prvního dne. Jednak o excelovském power query jsem neměl ani páru. Po prvotním failu Stěpán Bechynský ukázal sílu excelu a já se nestačil divit. “Big data” excel asi nedá, ale na něco menšího by to nemuselo být vůbec špatné.

Kluci z attacama.com ukázali trošku promo svojí firmě. Zaujalo mě jejich dost profesionální vystupování před kamerou (i ta tam byla), na jejich věk tleskám 🙂

Jelo se v pátek i ve 22:30.

Sobota

Plni velmi pozitivních zkušeností z prvního dne jsme ráno natěšeně dorazili na devátou do node5. Petr nám představil Radovana, který nás ubezpečil, že obě vyprofilovaná témata jsou dobrý nápad. Bohužel zpětně se ukázalo, že to nebyla pravda, ale po bitvě …

Pro jistotu jsme udělali rychlý research, jak de-mixing bitcoin servisy, která měla neutralizovat spletení proudu bitcoinů, tak i analýzy jsonu, který produkuje flightradar24.com. Na obědě jsme udělali druhou a asi nejvíc podstatnou chybu – nad pizzou jsme si plácli na to, že rozemeleme bitcoiny.

Po návratu z oběda jsme to commitnuli na standup meetingu. Během odpoledne začalo studium a naše nervozita exponencielně rostla. Zhruba v šest večer jsme měli mavenem staženou bitcoinj knihovnu připojenou do lokální peněženky, ze které to četlo bloky dat.

Během odpoledne nás Petr ještě provedl Node5. Nádhera. Velmi inspirativní prostředí, pěkný prorůstový koncept. Speciálně sekci startupistů, kde se několik měsíců žere rýže a kečup by mělo navštívit hodně lidí.

Tou dobou kluci odvedle, kterým Petr a jeho kolegové z keboole pomáhal dostat data do gooddaty – tady bych udělal menší vsuvku, protože mi přijde divné to tohle skloňovat, na hackaton zaznělo třeba i v gooddatě, já bych to prostě neskloňoval – skončili a šli hotovy domů.

My jsme měli pár řádků kódu, nic moc networking, protože s tím nám nědokázal nikdo pomoct a plnou hlavu toho, jak ten doménový model asi funguje. Kluci z keboola nás ještě obešli, zda něco máme a nechtěli bychom to dostat do gooddata platformy, popř. Verticy. V koutku duše jsme si ještě mysleli, že možná něco v neděli dáme.

Nicméně v osm jsme to vzdali. Kluci z click2streamu dřeli na gooddata via keboole ještě při našem odchodu.

Neděle

Celou noc jsem nemohl spát. Snažím se být co nejvíc produktivní a nedokázal jsem si představit, že nemáme vlastně vůbec nic – protože sen o tom, že se naučíme strukturu bitcoinů, knihovnu a datový model plus ještě na papír vymyslíme demixer a to vše během jednoho a půl dne byla naprosto bláhová 🙂

V záchvěvu ranní inspirace jsme ještě něco zkusili. Nevyšlo to. Tak jsme si řekli, že je čas uznat porážku a hecnout se něco udělat neboť až ve 14:30 je deadline.

Štefan s toptopicem a bagetou ze včerejšího dne a já se starou houskou z Lídlu v ruce jsme započali straighforward plán, jak skončit se ctí. V jedenáct jsme se rozhodli, že dodáme analyzer flighradaru, ze kterého si vybereme nějakou stats a tu spočítáme v čase.

Java, maven, Redis = to byl náš plán. Redis má tak jednoduché a geniální API, že volit cokoliv jiného byl nesmysl. Jedna servisa parsovala kontinuálně json a plnila data do Redisu, ta druhá kontinuálně přepočítávala průměrnou letovou hladinu. Já programoval, Štefan dělal ops, protože Redis jede na linuxu a v Node5 VPN zase nejede k nám do firmy. Nakonec se tam dostal.

Začali jsme plnit data a pár minut před deadlinou jsme měli spočtenou první průměrnou letovou hladinu. Bohužel na graf se už nedostalo, stejně tak jako na nějakou více smysluplnou analýzu dat. Jedna či dvě hodiny navíc a něco presentovatelného by z toho vypadlo.

Tak jsme si poslechli ostatní. Výsledky byly opravdu povznášející, protože ostatní týmy do toho opravdu šláply a ukázaly, jak se chopit dvoudenní akce. V hlasování všech aktérů zvítězil bluetooth monitoring lidí via sinfin.cz, druhý agilní Adam Kurzok s analýzou firem via daty.cz v závěsu za futurelytics s analýzou kriminality.

Zajímé byly i výsledky analýzy akcií a tweetu, především Štěpán měl velmi pěknou presentaci i pro nezasvěcené.

Na závěr dorazil i Zdeněk Farana a tak jsme se mohli pobavit s někým, kdo dělá realně ve scale.

Poděkování

  • Petr Ocásek uspořádal parádní event, díky!
  • Gazdinka parádně navařila a nemohli jsme neochutnat, díky!
  • Kluci z keboole nám dali čichnout jak funguje jejich BI via goodata a tak všechno okolo, díky!
  • Všichni ostatní vytvořili velmi inspirativní atmosféru, díky!
  • Node5 sám za sebe, protože myšlenka i provedení je úctyhodné, díky!

Postřehy

  • přijít nepřipravení byl kámen úrazu – měli jsme přijít s tématem a využít nabízených platforem, gooddata či google big query, a něco do toho nalít a prostě si to vyzkoušet + někoho k tomu strhnout
  • ikdyž jsme něpřišli s tématem, měli jsme alespoň vsadit na networking a seznámit se s lidmi – dát s nimi něco dohromady
  • potkat se s namotivovanými lidmi je nová krev do žil
  • Node5 je dechberoucí místo, tiše doufáme, že tam někdy uspořádáme podobný event v budoucnu okolo Service Virtualizací
  • když má člověk nosnou myšlenku, lze za víkend udělat dost práce
  • sušený ibišek je neskutečně dobrý
  • po nakládaných hermelínech se těžko běhá 🙂

Redis messaging using Spring and Kryo serialization

Redis is famous enterprise key/value data store which provides simple messaging using publisher/subscriber. I’ve decided to use the system to notify remote nodes (components) about a change of state between those components.

Spring among the others allows to use it’s templating system to decouple business logic from messaging system used under the hood. The use of spring data for Redis guarantees a solution which does not utilize any redis command in the code.

Redis contains serialized content, either byte[] or string. So the last thing to reveal is domain model serialization. I’ve decided to fast binary serialization using Kryo framework as a winner of battle of serializators.

Maven

First of all, it’s necessary to define all dependant components. Obviously, usual component like spring itself missing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

com.esotericsoftware.kryo
kryo
2.22


org.springframework.data
spring-data-redis
1.1.0.RELEASE


redis.clients
jedis
2.1.0

Kryo Serializator

I used domain model’s entity which hold identifier of our internal service, this is just UUID. The point is to setup kryo serializator which get the entity and returns byte[]. The entity itself is not important.

1
2
3
4
5
6
7
8
9
10
11
12
public class MyEntitySerializer extends Serializer<MyEntity> {

@Override
public void write(Kryo kryo, Output output, MyEntity object) {
return ...;
}

@Override
public MyEntity read(Kryo kryo, Input input, Class<MyEntity> type) {
return ...;
}
}

Redis Message Handler

The beautiful part is the complete definition of async message handling within xml configuration of redis message container.

Note that I used Jedis client as java redis client.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redisHost}" p:use-pool="true"/>

<bean id="myEntitySerializer" class="MyEntitySerializer" />

<bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<property name="messageListeners">




<bean id="messageListener"
class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">

<bean class="MyEntityListener"
autowire="constructor"/>

<property name="serializer" ref="myEntitySerializer"/>


<bean class="org.springframework.data.redis.listener.ChannelTopic">
<constructor-arg value="${channelName}"/>





What is going on?

Message container references both redis factory and given list of message listeners. It’s a class (or set of classes) having registered method as a callback when a new message arrives.

The last property is channel with important channel name. The code uses two variables, first is redis host and already mentioned channel name.

Message Listener

The last thing to do is to define message listener containing callback method, MyEntityListener. The class instance is called always once new message arrive using channel topic.

Crucial point was to discover the signature of callback’s method because spring’s documentation is little bit sloopy. Quick look into org.springframework.data.redis.listener.adapter.MessageListenerAdapter’s onMessage shows the correct way.

1
2
3
4
5
6
public class MyEntityListener {

public void handleMessage(MyEntity entity, String channel) {
// provide handling code
}
}

Incoming message is automatically deserialized so the method accepts entity itself.

Conclusion

Look at previous code. Every redis related code is defined in spring’s context and container class. No boilerplate code. Pretty nice.

Architectural note should explicitly show that the entity serves as a notification only. This is very important as such messaging is not reliable. Although the entity holds some information all the persistent data is defined within another place, e.g. as key/value pairs. Approaching message just notifies subscriber that new content is available to refresh and it’s supposed to GET key(s).

Constructor dependency injection over property autowiring

I use dependency injection pattern on all projects where it makes any sense, especially spring is following me all my life. Once I’ve discovered domain driven design, I’ve realized that model should be rich, clear, reusable, no meaningless dependencies.

Combining of clear model along with spring annotation can bring few issues except model dependency on spring jar files.

See following example what’s going on:

https://gist.github.com/MartinPodval/5921729.js

And relevant unit tests.

https://gist.github.com/MartinPodval/5921925.js

Once you decided to write reboust component, you can’t be absolutely sure that someone will use spring (or other DI framework) to push all dependencies.

Well, you need them check. Those are the reasons why I decided to use dependency injection via constructor. The componets just use fully supplied constructor requiring all mandatory dependencies.

See the code:

https://gist.github.com/MartinPodval/5935592.js

Advantages

Such code brings couple of other advantages which you must not see at a glance:

  1. Obviously clearer code as there is just only one place where all dependencies are injected and they are final.
  2. Simple tests
  3. No more @components with tons of dependencies. This is key point. Do you remember classes which grew and grew and when you opened them last time in your favorite IDE, they had more than 15 dependecies? Well, this is not possible any more. Who would maintain constructor using 15 parameters?
  4. No circular dependencies. Spring allows to define bean references which leads into closed circuit. I’m surely not alone who do not like it because it’s antipattern which hide some wrong design. Once you use dependency using constructor, you will not be able to write such relation anymore.

You can browse the code in GitHub.

Great reading: studying material for Sun’ SCJP by Sybex

I have started to read book SCJP: Sun Certified Programmer for Java Platform Study Guide: SE6 (Exam CX-310-065). This reading is great for huge spectrum of programmers, even senior java one has to find places where he can gain new knowledge. I had to continue reading SCJP book despite I’m currently working on project having .NET behind. It helped me to understand stuff “behind the wall”, what compiler allows, how works inheritance. The book covers all materials likely using within technical interviews 🙂 You couldn’t be caught so easy 🙂