Python ZeroMQ PUSH/PULL logic, set high water mark to a low end puller without losing any message
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Python's ZeroMQ (ØMQ) library stands out as a high-performance asynchronous messaging library, aimed at use in scalable distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker. The PUSH/PULL pattern is particularly useful for distributing data across multiple workers where nodes in a network can either 'push' or 'pull' messages. This article will delve into the PUSH/PULL logic and explain how to manage high water marks to prevent loss of messages when dealing with slow consumers.
Understanding ZeroMQ PUSH/PULL Pattern
In ZeroMQ, the PUSH/PULL pattern is used to distribute messages from one sender to multiple receivers. This pattern is ideal for load balancing work across workers:
- PUSH socket: It sends messages to downstream nodes in a round-robin fashion. It’s typically used in a producer or load balancer to distribute tasks among workers.
- PULL socket: It receives messages sent from a PUSH socket. It’s commonly used by worker nodes that process data/tasks.
High Water Mark and its Implications
One of the key concepts in ZeroMQ is the high water mark (HWM), which is essentially a limit on the number of outstanding messages ØMQ will queue in memory for any single peer. If this limit is surpassed, the system behavior will depend on other settings and socket types:
- PUSH sockets: When the high water mark is reached, the socket will block further sends or drop messages depending on the socket's configuration.
- PULL sockets: The high water mark dictates how many messages the socket can pull in before requiring processing or acknowledgment.
How to Avoid Losing Messages
Loss of messages can be critical in many distributed applications. Here is how you can configure high water marks and manage slow consumers to avoid message loss:
1. Adjusting the High Water Mark
The default high water mark in ZeroMQ may not suit all scenarios, particularly if you have a range of consumers with varying processing capabilities. You can set the HWM via the setsockopt method. For example:
Here, we set the high water mark for both sending (PUSH) and receiving (PULL) sockets. Lowering the HWM for PULL can be critical when dealing with slower consumers to prevent them from being overwhelmed.
2. Implementing Message Queuing
In some cases, incorporating an intermediary queue that holds messages before they are distributed to PULL sockets can help manage bursts of traffic or slow consumers. This queue can be another ZeroMQ socket that redistributes messages more intelligently or basic custom logic within your application that controls how messages are sent to consumers.
3. Monitoring and Feedback
Implement health checks and feedback mechanisms through which workers can signal back their state or capacity. This mechanism can help in making decisions about message distribution, like rerouting messages or adjusting the flow dynamically.
Best Practices and Additional Notes
- Test under load: It’s crucial to test your system under expected loads and use cases to determine the optimal HWM settings.
- Graceful handling of overflows: Decide how your application should behave when high water marks are hit — whether dropping messages, blocking senders, or buffering messages externally.
Summary Table
| Aspect | Detail |
| Pattern | PUSH distributes tasks, PULL receives and processes them. |
| High Water Mark | A limit on outstanding queued messages; crucial for back-pressure management. |
| Configuration | Set via setsockopt in code, e.g., setsockopt(zmq.SNDHWM, 10). |
| Slow Consumers | Lower HWM on PULL sockets, use intermediary queues, monitor worker health. |
| Testing | Verify behavior under load to tune HWMs appropriately. |
Conclusion
Setting a low-ending high water mark on the PULL side and efficiently managing it ensures that no messages are lost even when dealing with slow or fewer consumers. Properly understanding and leveraging ZeroMQ's socket patterns, options like HWM, and dynamic monitoring can make your distributed application robust and responsive under different operational conditions.
Related reading
- Quarkus + Kafka + Smallrye exception handling
- Query Kafka topic for specific record
- QueueingBasicConsumer is deprecated. Which consumer is better to implement RabbitMq .net client
- Rabbit - Error mnesia_unexpectedly_running
- Pythonic way to avoid if x return x statements
- Pythonic way to check if a file exists?
- Rabbit mq - Error while waiting for Mnesia tables
- Rabbit mq - Error while waiting for Mnesia tables

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.