A wave of blocked database commands stacking up during an aggressive table lock migration.
Possible Solutions
Since we needed a way to buffer data, we considered various techniques that could accomplish the task. One idea was to abstract each backend payload into a ‘message’ to be put into a buffer and consumed by a worker. We had a few considerations that were driven by details about our data:- Our task data can range from ~10KB to ~100MB
- Tasks must be processed in order and serially for a given recording.
- Tasks across different recordings can be processed in parallel.
- We can’t lose any messages for a recording as that would invalidate the entire recording.
Apache Cassandra/MongoDB/DynamoDB/ Other NoSQL Databases
- Has row size limits that vary but generally do not support our ~100MB messages.
- Would require much more application-level logic to write and consume data.
Redis
- Data may expire so messages may be lost.
- Message ordering is harder to maintain without performance constraints.
RabbitMQ
- Fully ordered so scalability without application-level re-architecture may be a challenge.
- Brokers are replicated but do not scale well horizontally.
Apache Kafka
- Messages can be as large as ~500MB.
- Messages are ordered within a partition.
- One consumer per partition allows for consistent ordering with linear parallel scaling.
- Messages only expire after a time-based or space-based retention policy.
Building a Scalable Task Queue
As we evaluated our options, we settled on a producer-consumer message strategy. Our architecture would have our backend HTTP request handlers produce ‘task’ messages that would block until the data was sent to our chosen broker. The messages would arrive at the broker quickly and reside until a worker machine could consume them and process their data. This effectively decouples our incoming data stream from the processing, allowing us to buffer data for as long as we want (space permitting based on the configuration of our broker).Architecture diagram showing the public API handlers transmitting data to Apache Kafka, which is then ingested and processed by a set of workers.
Diagram showing messages routed to partitions based on the session ID key, with each partition having its own dedicated consumer.