I am having a hard time figuring out a reliable and scalable solution for a webhook dispatch system.
The current system uses RabbitMQ with a queue for webhooks (let's call it events), which are consumed and dispatched. This system worked for some time, but now there are a few problems:
- If a system user generates too many events, it will take up the queue causing other users to not receive webhooks for a long time
- If I split all events into multiple queues (by URL hash), it reduces the possibility of the first problem, but it still happens from time to time when a very busy user hits the same queue
- If I try to put each URL into its own queue, the challenge is to dynamically create/assign consumers to those queues. As far as
RabbitMQdocumentation goes, the API is very limited in filtering for non-empty queues or for queues that do not have consumers assigned. - As far as
Kafkagoes, as I understand from reading everything about it, the situation will be the same in the scope of a single partition.
So, the question is - is there a better way/system for this purpose? Maybe I am missing a very simple solution that would allow one user to not interfere with another user?
Thanks in advance!
So, I am not sure if this is the correct way to solve this problem, but this is what I came up with.
Prerequisites: RabbitMQ with deduplication plugin
So my solution involves:
g:eventsqueue - let's call it aparentqueue. This queue will contain the names of allchildqueues that need to be processed. Probably it can be replaced with some other mechanism (like Redis sorted Set or something), but you would have to implement ack logic yourself then.g:events:<url>- there are thechildqueues. Each queue contains only events that are need to be sent out to thaturl.When posting a webhook payload to RabbitMQ, you post the actual data to the
childqueue, and then additionally post the name of thechildqueue to theparentqueue. The deduplication plugin won't allow the samechildqueue to be posted twice, meaning that only a single consumer may receive thatchildqueue for processing.All you consumers are consuming the
parentqueue, and after receiving a message, they start consuming thechildqueue specified in the message. After thechildqueue is empty, you acknowledge theparentmessage and move on.This method allows for very fine control over which
childqueues are allowed to be processed. If somechildqueue is taking too much time, justacktheparentmessage and republish the same data to the end of theparentqueue.I understand that this is probably not the most effective way (there's also a bit of overhead for constantly posting to the
parentqueue), but it is what it is.