3

Given 2 applications where application A is using a publisher client to contentiously stream data to application B which has a sub server socket to accept that data, how can we configure pub client socket in application A such that when B is being unavailable (like its being redeployed, restarted) A buffers all the pending messages and when B becomes available buffered messages go trough and socket catches up with real time stream?

In a nutshell, how do we make PUB CLIENT socket buffer messages with some limit while SUB SERVER is unavailable?

The default behaviour for PUB client is to drop in mute state, but it would be great if we could change that to a limit sized buffer, is it possible with zmq? or do i need to do it on application level...

I've tried setting HWM and LINGER in my sockets, but if i'm not wrong they are only responsible for slow consumer case, where my publisher is connected to subscriber, but subscriber is so slow that publisher starts to buffer messages (hwm will limit number of those messages)...

I'm using jeromq since i'm targeting jvm platform.

vach
  • 10,571
  • 12
  • 68
  • 106
  • A better strategy might be to tell the publisher to stop publishing once it detects that the subscriber has "disconnected". Have you considered using `socket.monitor` and listening for the `ZMQ_EVENT_DISCONNECTED` event? – smac89 Oct 18 '19 at 04:20
  • Actually when I think about this, I realize that this is already the default behaviour. A publisher socket will simply drop messages if there is nothing connected to it. I guess my idea would be that if you have a stream of data going to the publisher, then this stream should stop once you detect the event – smac89 Oct 18 '19 at 04:22
  • the default behaviour is yes to drop messages, but i think its a very common thing, if you are publisher and you're client socket you know exactly where you messages need to be delivered, so you know if that destination is anavailable you need to buffer to avoid message loss – vach Oct 18 '19 at 04:23
  • if you're publisher server socket, then you dont care, its up to others to subscribe and listen to you, if they didnt catch the broadcast its their problem... so my question is how do i make pub CLIENT buffer when destination is not available? – vach Oct 18 '19 at 04:24
  • regarding your first comment, i cant agree that making apps aware of downtimes of other services is good idea, my strategy is generally to develop each service with consideration that any other external service/db can have downtime, so it needs to use acks if something must be commited... and it must do retries if something is not acked – vach Oct 18 '19 at 04:25
  • the reason i want pub socket to do this is because its an extra bonus, where i could restart services and not even have a single message being lost... (its a bonus but i totally can do without it, its just very easy win so i want to enable it) – vach Oct 18 '19 at 04:26
  • Just looking at `zeromq` [HWM docs](http://zguide.zeromq.org/page:all#High-Water-Marks), it says "When your socket reaches its HWM, *it will either block or drop data depending on the socket type*. **PUB and ROUTER sockets will drop data if they reach their HWM, while other socket types will block**. Over the inproc transport, the sender and receiver share the same buffers, so the real HWM is the sum of the HWM set by both sides." So my question is, are you looking to an external buffer outside memory as a solution? Apart from my suggestion, using large HWM is the only other solution – smac89 Oct 18 '19 at 04:36
  • The problem is HWM only applies to connected state... if i restart service B, A disconnects from it and all messages are dropped. HWM is useful when you have slow/fast pub/sub case... so unrelated to my problem... What i want is quite simply, if you're the CLIENT socket you can act on your DESTINATIONs unavailability and you can buffer things for a little while... – vach Oct 18 '19 at 04:41
  • I can see how you think it would be easy to have a way to buffer ALL the messages while the subscriber connects, but you seem to be missing the part where you have limited memory and still need to guess how much of this memory to give to your publisher. If a subscriber continues to fail, what then? My initial suggestion of detecting a disconnected subscriber will work exactly the same as your idea of buffering everything. Only difference is that your idea has a chance of using up memory and is not very reliable if subscriber fails to ever keep up – smac89 Oct 18 '19 at 04:46
  • Very simple, you have limited buffer, if i'm pub client, i buffer in a ring buffer lets say 1000 messages, if i connect i will start pushing from the oldest message... if you restart B you'll see this buffer filling up lets say 300, 400, if B comes online soon enough you dont reach the limit and you dont loose messages – vach Oct 18 '19 at 04:51
  • Exactly like HWM but also keeps messages while i'm disconnected. Implying that my pub client absolutely wants to deliver all messages not just whenever connected... – vach Oct 18 '19 at 04:52
  • You could checkout [dafka](https://github.com/zeromq/dafka) – bunkerdive Feb 17 '21 at 23:50

3 Answers3

3

First of all, welcome to the world of Zen-of-Zero, where latency matters most

PROLOGUE :

ZeroMQ was designed by a Pieter HINTJENS' team of ultimately experienced masters - Martin SUSTRIK to be named first. The design was professionally crafted so as to avoid any unnecessary latency. So asking about having a (limited) persistence? No, sir, not confirmed - PUB/SUB Scalable Formal Communication Pattern Archetype will not have it built-in, right because of the added problems and decreased performance and scalability ( add-on latency, add-on processing, add-on memory-management ).

If one needs a (limited) persistence (for absent remote-SUB-side agent(s)' connections ), feel free to implement it on the app-side, or one may design and implement a new ZMTP-compliant such behaviour-pattern Archetype, extending the ZeroMQ framework, if such work goes into stable and publicly accepted state, but do not request the high-performance, latency-shaved standard PUB/SUB having polished the almost linear scalability ad astra, to get modified in this direction. It is definitely not a way to go.

Solution ?

App-side may easily implement your added logic, using dual-pointer circular buffers, working in a sort-of (app-side-managed)-Persistence-PROXY, yet in-front-of the PUB-sender.

Your design may get successful in squeezing some additional sauce from the ZeroMQ internal details in case your design also enjoys to use the recently made available built-in ZeroMQ-socket_monitor-component to setup an additional control-layer and receive there a stream of events as seen from "inside" the PUB-side Context-instance, where some additional network and connection-management related events may bring more light into your (app-side-managed)-Persistence-PROXY

Yet, be warned that

The _zmq_socket_monitor()_ method supports only connection-oriented transports, that is, TCP, IPC, and TIPC.

so one may straight forget about this in case any of the ultimately interesting transport-classes was planned to be used { inproc:// | norm:// | pgm:// | epgm:// | vmci:// }


Heads up !

There are inaccurate, if not wrong, pieces of information from our Community honorable member smac89, who tried his best to address your additional interest expressed in the comment:

"...zmq optimizes publishing on topics? like if you keep publishing on some 100char long topic rapidly, is it actually sending the topic every time or it maps to some int and sends the int subsequently...?"

telling you:

"It will always publish the topic. When I use the pub-sub pattern, I usually publish the topic first and then the actual message, so in the subscriber I just read the first frame and ignore it and then read the actual message"

ZeroMQ does not work this way. There is nothing as a "separate" <topic> followed by a <message-body>, but rather the opposite

The TOPIC and the mechanisation of topic-filtering works in a very different way.

1) you never know, who .connect()-s:
i.e. one can be almost sure the version 2.x till version 4.2+ will handle the topic-filtering in different manner ( ZMTP:RFC defines intial capability-version handshaking, to let the Context-instance decide, which version of topic-filtering will have to be used:
ver 2.x used to move all messages to all peers, and let all the SUB-sides ( of ver 2.x+ ) be delivered the message ( and let the SUB-side Context-instance process the local topic-list filter processing )

whereas
ver 4.2+ are sure to perform the topic-list filter processing on **the PUB-side Context-instance (CPU-usage grows, network-transport the opposite ), so your SUB-side will never be delivered a byte of "useless" read "not-subscribed" to messages.

2) (you may, but) there is no need to separate a "topic" into a first-frame of a thus-implied multi-frame message. Perhaps just the opposite ( it is a rather anti-pattern to do this in high performance, low-latecy distributed system design.

Topic filtering process is defined and works byte-wise, from left-to-right, pattern matching for each of the topic-list member value agains the delivered message payload.

Adding extra data, extra frame-management processing just and only does increase the end-to-end latency and processing overhead. Never a good idea to do this instead of proper design work.


EPILOGUE :

There are no easy wins nor any low-hanging fruit in professional design, the less if or ultra-low-latency are the design targets.

On the other hand, be sure that ZeroMQ framework was made with this in mind and these efforts were crowned with stable, ultimately performant well-balanced set of tools for smart (by design), fast (in operation) and scalable (as hell may envy) signaling/messaging services people love to use right because of this design wisdom.

Wish you live happy with ZeroMQ as it is and feel free to add any additional set of features "in front" of the ZeroMQ layer, inside your application suite of choice.

user3666197
  • 1
  • 6
  • 50
  • 92
  • 1
    While appreciate the answer, and especially the style, i don't agree that this in any way requires to compromise on performance or latency. Honestly i was surprised this is not possible to do and is not the default behavior in zeromq... buffering your undelivered messages to deliver them when you get chance shouldn't be much to ask for... – vach Oct 19 '19 at 14:31
  • While I understand the commented view, I cannot confirm that this functionality, if it were added, would ever justify to be included into the pipeline. Many services process the most recent state of the message-flow mediated sequence of updates. If there is a Computer-Vision process, consuming the most actual CCTV-snapshot to detect a potential perimeter violation, it has its focus set on the most recent 1 msg, not the "*(temporaly)*-archived-*(part)*" of the recent history - just the opposite. Even the **zmq.CONFLATE** option goes farther in this, skipping all but 1 already delivered messages – user3666197 Oct 19 '19 at 15:00
  • ZeroMQ is not aiming at creating a digital-copy-of-The-History-of-Events, but rather to provide smart, high-performance, low-latency, almost-linearly scalable ( this will be expensive part, in case hundreds, thousands and tens of thousands dropping-in/failing-out SUB-s were to get served as your comment has proposed - just the GB of volume of data + large [us ~ ms] of data-flow into/from RAM via a few of hardware available contentious mem-I/O-channels ). No, sir, this would be a killer idea to have this hardwired into PUB/SUB. Just test a broker-based persistence & compare increased latencies – user3666197 Oct 19 '19 at 15:06
  • I'll try it out, i'm going to implement pub/sub in grpc, add this buffering feature and compare the performance... i don't think i'm going to be disappointed... original reason i picked zmq was simplicity of the idea, dedicated smart sockets (not performance and all that jazz which is questionable in jvm version tbh). – vach Oct 20 '19 at 11:58
3

I'm posting a quick update since the other two answers (though very informative were actually wrong), and i dont want others to be misinformed from my accepted answer. Not only you can do this with zmq, it is actually the default behaviour.

The trick is that if you publisher client never connected to the subscriber server before it keeps dropping messages (and that is why i was thinking it does not buffer messages), but if your publisher connects to subscriber and you restart subscriber, publisher will buffer messages until HWM is reached which is exactly what i asked for... so in short publisher wants to know there is someone on the other end accepting messages only after that it will buffer messages...

Here is some sample code which demonstrates this (you might need to do some basic edits to compile it).

I used this dependency only org.zeromq:jeromq:0.5.1.

zmq-publisher.kt

fun main() {
   val uri = "tcp://localhost:3006"
   val context = ZContext(1)
   val socket = context.createSocket(SocketType.PUB)

   socket.hwm = 10000
   socket.linger = 0
   "connecting to $uri".log()
   socket.connect(uri)

   fun publish(path: String, msg: Msg) {
      ">> $path | ${msg.json()}".log()
      socket.sendMore(path)
      socket.send(msg.toByteArray())
   }

   var count = 0

   while (notInterrupted()) {
      val msg = telegramMessage("message : ${++count}")
      publish("/some/feed", msg)
      println()

      sleepInterruptible(1.second)
   }
}

and of course zmq-subscriber.kt


fun main() {
   val uri = "tcp://localhost:3006"
   val context = ZContext(1)
   val socket = context.createSocket(SocketType.SUB)

   socket.hwm = 10000
   socket.receiveTimeOut = 250

   "connecting to $uri".log()
   socket.bind(uri)

   socket.subscribe("/some/feed")

   while (true) {
      val path = socket.recvStr() ?: continue
      val bytes = socket.recv()
      val msg = Msg.parseFrom(bytes)
      "<< $path | ${msg.json()}".log()
   }
}

Try running publisher first without subscriber, then when you launch subscriber you missed all the messages so far... now without restarting publisher, stop subscriber wait for some time and start it again.

Here is an example of one of my services actually benefiting from this... This is the structure [current service]sub:server <= pub:client[service being restarted]sub:server <=* pub:client[multiple publishers]

Because i restart the service in the middle, all the publishers start buffering their messages, the final service that was observing ~200 messages per second observes drop to 0 (those 1 or 2 are heartbeats) then sudden burst of 1000+ messages come in, because all publishers flushed their buffers (restart took about 5 seconds)... I am actually not loosing a single message here...

enter image description here

Note that you must have subscriber:server <= publisher:client pair (this way publisher knows "there is only one place i need to deliver these messages to" (you can try binding on publisher and connecting on subscriber but you will not see publisher buffering messages anymore simply because its questionable if subscriber that just disconnected did it because it no longer needs the data or because it failed)

vach
  • 10,571
  • 12
  • 68
  • 106
2

As we've discussed in the comments there is no way for the publisher to buffer messages while having nothing connected to it, it will simply drop any new messages:

From the docs:

If a publisher has no connected subscribers, then it will simply drop all messages.

This means your buffer needs to be outside of zeromq's care. Your buffer could then be a list, or a database, or any other method of storage you choose, but you cannot use your publisher for doing that.

Now the next problem is dealing with how to detect that a subscriber has connected/disconnected. This is needed to tell us when we need to start reading from the buffer/filling the buffer.

I suggest using Socket.monitor and listening for the ZMQ_EVENT_CONNECTED and ZMQ_EVENT_DISCONNECTED, as these will tell you when a client has connected/disconnected and thus enable you to switching to filling your buffer of choice. Of course, there might be other ways of doing this that does not directly involve zeromq, but that's up to you to decide.

smac89
  • 39,374
  • 15
  • 132
  • 179
  • thanks, do you happen to know if zmq optimizes publishing on topics? like if you keep publishing on some 100char long topic rapidly, is it actually sending the topic every time or it maps to some int and sends the int subsequently...? (different question but hey maybe you know :) – vach Oct 18 '19 at 05:45
  • 1
    @vach It will always publish the topic. When I use the pub-sub pattern, I usually publish the topic first and then the actual message, so in the subscriber I just read the first frame and ignore it and then read the actual message – smac89 Oct 18 '19 at 05:48
  • @smac89 You might already know **this is not correct, ZeroMQ does not work this way** since many versions ago. Plus your choice of sending multi-frame message-payloads ( while legitimate, if performance and processing overheads are not your concern ) is too inefficient and way away from the initial design of how the smart topic-filtering was designed and implemented. It is absolutely unnecessary as it "doubles" the care of how the payload-topic-filtering actually works "under the hood" of the ZeroMQ **PUB/SUB** -archetype message-flow processing. – user3666197 Oct 18 '19 at 12:56
  • @user3666197 thanks for the correction. When you say that topic filtering does not work the way I described, do you mean that the subscriber does not get the topic frame and only the actual message is sent off? I use jzmq with zmq version 4.3 and the way I publish messages to subscribers is to do a `sendMore` with the topic, then `send` with the flatbuffer bytes I want to send. At the sub side, I do a `recv` which always gives the topic and the next `recv` with flag zero gives the flatbuffer. I guess my question is, what am I doing wrong here that is not the zmq way and how do I fix it? – smac89 Oct 18 '19 at 15:11
  • The ZeroMQ API documentation is crystal clear on how this works (+Martin SUSTRIK has lovely performance-related posts on 250bpm.com about making the filter smart and fast ) - May read this https://stackoverflow.com/a/58397285/3666197 and launch an experiment : **iterate** over i = 0..2^31 **make** PUB.send( str( i ) ) and **have four different SUB**-s, each .connect()-ed and subscribed to different **topic**-string, as demonstrated in the referred link and make each SUB print the .recv()-ed message on screen. You will see the difference - what each one of the SUB-s has and has not received – user3666197 Oct 18 '19 at 16:56
  • @user3666197 I find that this experiment does not answer my question. I'm aware that the filtering will efficiently send the messages to the right subscribers by looking at the message prefix to find the topic, but it will also send the topic along and this is what I answered in the initial question I was asked. I may have used the wrong terms to describe what I meant but I am not trying to say that one needs to send 2 messages for the filtering to work. What I am trying to describe is exactly what the last two code examples in [this blog](http://wiki.zeromq.org/blog:zero-copy) are doing. – smac89 Oct 18 '19 at 17:33