Event Synchronization with NATS #970
-
Hi, I am developing a kind of ordering system in which an order run through different states. In a certain state, an order must be processed by multiple consumers (independent subsystems). Afterwards it is required to wait that all consumers have been processed the order before moving on. With Kafka I basically would rely on its ordering guarantee related to is partition-key, thus each message related to an order would arrive on the same consumer (client), in which the synchronization can take place. As far as I know, nats does not support that. My first approach/idea: simply publish a synchronization event for each single order. Thus, each synchronization event will be picked by a random client based on its queued subscription. That client can hold on until each subsystem have signaled that their tasks have been finished. Regards |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 2 replies
-
As it happens NATS does now (as of nats-server v2.8) support this very feature of being able to deterministically partition streams of messages: i.e. the same functionality as Kafka's partitions, but done in a 'NATS way'! What makes this possible is actually additional functionality to an existing Core NATS functionality: subject mapping now allows you to, besides changing and re-ordering subject name tokens, also insert a partition number token with the partition number being automatically calculated for each message using a deterministic hashing of one or more of the subject's tokens. This allows you to scale many things: consumers requiring strict ordering (as in your case) is one but because this is a core NATS functionality it can actually also be used to scale Core NATS subscribers (deterministically, unlike queue groups), or even be used to speed up processing by leveraging local caching of the joined data on workers more efficiently. The functionality is the same but let me try to compare how NATS does it vs Kafka
Here is a preview of the upcoming update to the documentation regarding this new feature: Subject Mapping and Traffic ShapingSubject mapping is a very powerful feature of the NATS server, useful for canary deployments, A/B testing, chaos testing, and migrating to a new subject namespace. There are two places where you can apply subject mappings: each account has its own set of subject mappings, which will apply to any message published by client applications, and you can also use subject mappings as part of the imports and exports between accounts. When not using operator JWT security, you can define the subject mappings in server configuration files, and you simply need to send a signal for the nats-server process to reload the configuration whenever you change a mapping for the change to take effect. When using operator JWT security with the built-in resolver you define the mappings and the import/exports in the account JWT so after modifying them they will take effect as soon as you push the updated account JWT to the servers. Simple MappingThe example of Subject Token ReorderingWildcard tokens may be referenced by position number in the destination mapping using (only for versions 2.8.0 and above of You can also (for all versions of Example: with this mapping Deterministic Subject token PartitioningDeterministic token partitioning allows you to use subject based addressing to deterministically divide (partition) a flow of messages where one or more of the subject tokens make up the key upon which the partitioning will be based, into a number of smaller message flows. For example: new customer orders are published on This particular mapping means that any message published on
The mapping is deterministic because (as long as the number of partitions is 3) 'customerid1' will always map to the same partition number. The mapping is hash based, it's distribution is random but tending towards 'perfectly balanced' distribution (i.e. the more keys you map the more the number of keys for each partition will tend to converge to the same number). You can partition on more than one subject wildcard token at a time, e.g.:
What this deterministic partition mapping enables is the distribution of the messages that are subscribed to using a single subscriber (on When is deterministic partitioning neededthe core NATS queue-groups and JetStream durable consumer mechanisms to distribute messages amongst a number of subscribers are partition-less and non-deterministic, meaning that there is no guarantee that two sequential messages published on the same subject are going to be distributed to the same subscriber. While in most use cases a completely dynamic, demand-driven distribution is what you need, it does come at the cost of guaranteed ordering because if two subsequent messages can be sent to two different subscribers which would then both process those messages at the same time at different speeds (or the message has to be re-transmitted, or the network is slow, etc...) and that could result in potential 'out of order' message delivery. This means that if the application requires strictly ordered message processing, you need to limit distribution of messages to 'one at a time' (per consumer/queue-group, i.e. using the 'max acks pending' setting), which in turns hurts scalability because it means no matter how many workers you have subscribed only one at a time is doing any processing work. Being able to evenly split (i.e. partition) subjects in a deterministic manner (meaning that all the messages on a particular subject are always mapped to the same partition) allows you to distribute and scale the processing of messages in a subject stream while still maintaining strict ordering per subject. Another reason to need deterministic mapping is in the extreme message rates scenarios where you are reaching the limits of the throughput of incoming messages into a stream capturing messages using a wildcard subject. This limit can be ultimately reached at very high message rates due to the fact that a single nats-server process is acting as the RAFT leader (coordinator) for any given stream and can therefore become a limiting factor. In that case, distributing (i.e. partitioning) that stream into a number of smaller streams (each one with their own RAFT leader and therefore all these RAFT leaders are spread over all of the JetStream-enabled nats-servers in the cluster rather than a single one) in order to scale. Yet another use case where deterministic partitioning can help is if you want to leverage local data caching of data (context or potentially heavy historical data for example) that the subscribing process need to access as part of the processing of the messages. Weighted Mappings for A/B Testing or Canary ReleasesTraffic can be split by percentage from one subject to multiple subjects. Here's an example for canary deployments, starting with version 1 of your service. Applications would make requests of a service at
All requests to When version 2 comes along, you'll want to test it with a canary deployment. Version 2 would subscribe to Update the configuration file to redirect some portion of the requests made to For example the configuration below means 98% of the requests will be sent to version 1 and 2% to version 2.
Once you've determined Version 2 is stable you can switch 100% of the traffic over to it and you can then shutdown the version 1 instance of your service. Traffic Shaping in TestingTraffic shaping is also useful in testing. You might have a service that runs in QA that simulates failure scenarios which could receive 20% of the traffic to test the service requestor.
Artificial LossAlternatively, introduce loss into your system for chaos testing by mapping a percentage of traffic to the same subject. In this drastic example, 50% of the traffic published to
You can both split and introduce loss for testing. Here, 90% of requests would go to your service, 8% would go to a service simulating failure conditions, and the unaccounted for 2% would simulate message loss.
|
Beta Was this translation helpful? Give feedback.
-
Pretty awesome stuff! |
Beta Was this translation helpful? Give feedback.
-
Great! That's pretty much what I am looking for. @jnmoyne Does it mean that it requires a separate nats consumer per partition? or how would a typical setup would look like, that is able to scale out. |
Beta Was this translation helpful? Give feedback.
-
If you are trying to scale the strictly ordered consumption of the messages then you could simply use one stream for all the partitions (e.g. listens to If you have a very high message rate you want to capture in a stream and you are reaching the limit of performance a single stream can handle and you want to scale that stream then you would create a stream per partition (and one durable per partition stream). |
Beta Was this translation helpful? Give feedback.
As it happens NATS does now (as of nats-server v2.8) support this very feature of being able to deterministically partition streams of messages: i.e. the same functionality as Kafka's partitions, but done in a 'NATS way'!
What makes this possible is actually additional functionality to an existing Core NATS functionality: subject mapping now allows you to, besides changing and re-ordering subject name tokens, also insert a partition number token with the partition number being automatically calculated for each message using a deterministic hashing of one or more of the subject's tokens. This allows you to scale many things: consumers requiring strict ordering (as in your case) is one but …