David RomeroSoftware EngineerFor several years I have a hard goal. I want to become a great software engineer, so I work hard developing quality software every day. Fan of new technologies. I will not stop until overcome my goals.
In that post, Slack engineers speaks about how they have redesigned their queue architecture and how they uses redis, consul and kafka.
It called my attention they used consul as a locking mechanism instead of redis because I have developed several locks with redis and it’s works great. Besides, they have integrated a redis cluster and I thnik this fact should facilitate its implementation.
After I read this post, I got doubts about which locking mechanism would have better performance so I develop both strategies of locking and a kafka queue to determine which gets the best performance.
My aim was to test the performance offered by sending thousands of messages to kafka and persisting them in mongo through a listener.
First of all, I built an environment with docker consisting of consul, redis, kafka, zookeeper and mongo. I wanted to test the performance of send thousands of messages to kafka and store it in mongo.
Set up environment:
We can set up our environment with docker-compose.
docker-compose -up
Once we have our environment up, we need to create a kafka topic to channel the messages. For this we can rely on this tool, Kafka Tool.
At this point, our environment is fine, now, we have to implement a sender and a listener.
Sender:
I have created a simple spring boot web application with a rest controller that it help us to send as many messages as we want. This project has been created with the Spring Initializer Tool
Config:
We only have to set up the kafka configuration:
And this is the rest controller in charge of sending the messages to kafka:
This controller delegates in a sender the responsibility of comunicating with kafka:
Now, we can send thousands of messages to kafka easily with a http request. In the following image we can see how send 5000 messages to kafka.
Finally, we just only have to implement the listener and the lock strategies.
Listener:
I have created other simple spring boot web which it is able to listen from kafka topics too.
In the first place, I researched about libraries that implemented locks mechanism in consul so that I only have to implement the lock strategy and I had not implement lock mechanism. I found consul-rest-client a rest client for consul that has everything I need.
As for redis, I have been working with Redisson with successful results both in performance and usability so I choose this library.
Config:
I’m going to divide the config’s section in three subsections: kafka, consul and redis.
Kafka:
From this configuration, we have to take notice in three configurations:
factory.setBatchListener(true);
I’m telling to kafka that I want to receive messages in batch mode, ergo instead of receive the messages one to one, we can receive hundreds of messages from a pull of the queue.
Later, we could see the different in performance.
Each different group id receives the same messages as we can see in the following diagram. With this separation, we are achieving a better horizontal scaling.
Consul:
Consul handles the locking mechanism by sessionId, so, we have to create different sessionId in each new service.
Redis:
Redis config is very easy.
Consumer:
For the implementation of the consumer, I have used a kafka integration offered by spring.
In our consumer, I want to implement the following algorithm:
Iterate over the received messages
Deserialize json to dto object
Map dto object to domain entity.
Try to get lock
If this container has the lock
Build domain entity
Store the domain entity in the future persisted entities list.
Release lock
If I have been able to map to domain entity
Add to future persisted entities list
If the future persisted entities list is not empty
Persist the whole list
Send ack to kafka
Translated to java:
Consumer class:
Mapper class:
To determine which blocking strategy to use, we use environment variables.
Stats:
To determine the performance offered by both mechanism, I have been doing multiple benchmarcks with several executions, containers and number of messasges.
In the following pictures, we could see the results obtained by several executions in this scenarios visualized as diagrams:
Redis.
Redis with kafka batch mode.
Consul with kafka batch mode.
In the X axis, we can see the number of messages sent to kafka and in the Y axis we can see the total time spent in consuming all messages. The different colours represent the number of containers used in the benchmarcking.
Redis lock:
Redis lock with kafka batch mode:
Consul lock with kafka batch mode:
Conclusions:
In my humble opinion, we can infer kafka batch mode is faster than non batch mode since the different is incredibly big, reaching differences of more than 30 seconds as for example in 30.000 messages.
As for which is faster, we can also conclude that redis is faster than consul due to the results obtained. For example, 50.000 messages are consumed in redis in less than 20 seconds, meanwhile, Consul took about 40 seconds, double than redis. With 100.000 messages ocurrs the same. Redis wins with only 25 seconds approximately, nevertheless consul took more than 60 seconds, problematic times for real time applications.
As a curiosity, with kafka batch mode, the more container we use, the more time we took since when increasing the containers, we increase the requests to our infrastructure and therefore the latency and collisions.
However, as we persist a set of data instead of a single data, we substantially improve the times used thanks to mongo and its way of persisting large collections of data.
The full source code for this article is available over on GitHub.