 There it is. All right, round two. All right. So today I'm going to talk about NATS. I won't introduce it right this second. But yeah, really the scope of this is sort of to make the argument that NATS is a great substrate connective technology to enable reactive systems. So very briefly, and he already mentioned this, but I spent 14 years in pediatric research, building tools and technologies for researchers. You actually did the research, not myself. So I had a lot of experience learning there. I recently joined Senedy at the end of July this year. So I'm pretty fresh. But I was also a NATS user for six plus years at my previous employer. So I had a lot of experience and understanding of the community and how NATS worked. In the past year, I created the NATS weekly newsletter and a NATS by example website to sort of give back to the community and improve the available documentation and sort of get that going. So I was happy to join Senedia to further improve the content and the docs and so on and so forth. All right, so what is NATS? If you haven't seen Derek Collison's talk from two years ago at Reactive Summit, you should check that out if you haven't seen that. He goes a little bit deeper into what NATS is. He is the creator of NATS back in 2010. He created as part of Cloud Foundry at the time. So he was the lead architect for Cloud Foundry. And NATS started out as a very straightforward sort of messaging pub sub type of system. So he wanted messaging in Cloud Foundry and that was sort of the basis of that. So some of the other kind of details that I think are worth calling out just because of the scope of what NATS is designed to do. It's a small binary. It was originally written in Ruby at the time, but it was rewritten in Go once Go got some legs. It only has six external dependencies. And those are cryptography libraries and compression libraries and things like that. So everything is sort of really tight-knit, intentionally developed in the source code. It supports a handful of different architectures and operating systems. It really is designed to get deployed on not only cloud instances and things like that, but all the way down to devices. Down onto your, obviously your phones are very powerful, so that's not even a good argument. But smaller devices that can be deployed on factory floors, can be deployed in farms, on farm fields and things like that as part of sensors. And then we have a handful of client libraries that talk to NATS' server, some of which we have, for example, a C client and a Rust client that can obviously be integrated into more embedded systems as well. So it's really sort of like the ethos of NATS is really simplicity. We want it to be able to run everywhere where possible and connect all the things. So just keep that in the back of your head. So sort of the first element of this, and I mentioned it started out as a humble publish-subscribe messaging system. It still is rock solid with that, and that is kind of its core use case, I would say, but I want to kind of talk about more so the past 18 months, but this is sort of delaying the foundation. So we have your typical end-to-end publish-subscribe. It uses subject-based messaging, which means that once your clients connect into NATS, you send messages using names. You're not talking IP addresses, DNS, any of that stuff. You're talking names. So you can say this is the subject on my message, go publish, and any subscriber that is subscribed to that subject will receive that message. It supports wildcards as part of the subject, so a subject is composed of a series of tokens, as it's called, delimited by dots, periods. And you can say something like NATS sub reactive, I got a little pointer here, NATS sub reactive dot star, and then anything that matches reactive dot, and then any token, you can subscribe to that, and then you receive all the messages that way. So you sort of have this nice little pattern matching built in, and you also have this greater than sign, which says any number of tokens at the end of a subject, please give me those messages too. So this is just plain and simple pub sub. You start a server, no arguments required, no configuration required. You have this NAT CLI that you can use, pub and sub, and you can get started to start playing around with it. And the CLI is super powerful, has everything built into it, so it's very easy to get started. Some other basic things, you got headers, that was added a little bit later in the life cycle, but everyone likes headers for metadata and things like that, so you don't have to bake that into the payload. It's payload agnostic, you can use whatever you want, it's just a byte stream from NATS perspective. And a really neat one is the queue groups, which allows you to say, define a group of subscribers that are all subscribed to the same subject using the same group name, and then NATS will basically distribute messages to any one of those instances, depending if they're online or not, and you can freely add and remove these members of this group at any time. So it's sort of a story for high availability, it's very easy to do, it's trivial, and so you sort of have, and also distribution of workload if you have heavier processing. So you get a lot of flexibility out of the box for this, and the API is super simple to use. So what are some properties of this sort of messaging layer at this stage? You have your at most once guarantee, so this means that I sort of refer to it as like stateless messaging in the sense that all the clients, everything has to be connected in order for a message to be received. So it's not yet persistence, we'll get there. So you have a subscriber online, it'll receive the message, but if it comes online too late, the message is gonna get dropped. They're not gonna see it. However, that means that for use cases that fit this model, you can achieve very incredibly low latency and high throughput because NATS is optimized for this. This is just, I'm not a huge fan of microbenchmarks or anything like that, but this is just running on my laptop. One publisher, one subscriber, and you're pumping through 360 megs a second of throughput. Okay, so, and then it supports fan out, fan in, all of those different patterns. But this is just to say, NATS is designed to make the fast path as fast as it can go. And then things built on top of that. So a couple other things, there's really great monitoring end points. This is not HTTP or anything like that. This is a text-based protocol on TCP directly. So there's sort of ways of monitoring NATS in a different way than what most people are maybe used to. But all of the popular tooling and everything, Prometheus, Grafana, Open Telemetry, all of that stuff still works with this. You can still instrument and get all of your necessary stats from that. So let's talk about connectivity now. So this is another facet of NATS that I think is differentiated from a lot of other solutions out there in terms of messaging. So typical term cluster. So cluster is typically deployed in a region. Multiple availability zones is your typical setup in a cloud environment. Again, this can be deployed on edge devices as well, but I'll get to that in a second. So you typically have a three-node cluster, you deploy it, and there's sort of a full mesh of connectivity that is observed between those nodes. And clients can connect to any one of those nodes, and all of the message routing happens transparently. And that's not as impressive with a cluster, but we'll get to the next slide in a second to showcase this. So servers can of course be added and removed dynamically in this case, which makes it very flexible to scale up if you need to. But usually for a three-node cluster in one region, it's sufficient to be able to handle most loads kind of out of the box. And that is just simply the configuration, which is again very simple. So each one of the nodes would have a config file that basically declares the name of the cluster, what host and port you're listening on, and then you basically define the seed, and these are just seed routes, because I said you can always add servers after the fact, as long as you seed a server with at least one of these routes, it will discover all of the other nodes in the cluster automatically. So it does all discovery. Similarly, a cluster will, when a client connects, will share all of the other server IP addresses to the client. So if they get disconnected, if the server they're connected goes down, it'll automatically reconnect to another server automatically without dropping any messages. So that's another nice little feature. So again, an emphasis on connectivity, trying to be resilient to network interruptions, failures, nodes going down, things like that, is all part of the design of NATs. So sort of the next extension to this, so you have a regional cluster, and you might wanna have a bunch of regions, you wanna cover a bunch of regions, so now you can define what is known as called gateway connections between these clusters. And so there's a nuance why can't you just keep adding servers perpetually across all regions. And it really comes down to you're just gonna have a ton of excess traffic of just gossiping all over the place, so you sort of wanna manage that well and be deliberate with how you wanna place the clusters in different regions, and then how you wanna kind of gateway those things together. So this is sort of an answer to say, when I know that there's interest in subjects in one cluster that a client is trying to, let's say, subscribe to in another cluster, then as it's referred to, the interest graph of that subject will propagate across gateway connections. And so this extends this location transparency even further. You can have clusters across multiple regions, clients can connect from any point anywhere, and messages will just flow across all the gateways transparently. So NATS optimizes how to propagate messages and how to basically allow other cluster to discover what subjects are of interest as messages are flowing. So it's a pretty magical thing when you actually see it in action. So here's the, I would say, even more of a differentiator. This is kind of the most fun thing to show people, which is the leaf node. A leaf node is sort of, you can think of it as someone on our team refers to it as like your local router in your house, where you can, it's like your own little network in your little space, but it's connected back up to the internet. And so what this allows you to do is create this hub and spoke model. You can have this main cluster, you can have multiple clusters gateway together, and then you can have all these little leaf nodes. And a leaf node is just another NAT server. It's not a different process or anything. It's just another NAT server with different configuration. And that's what you can deploy on devices. And so you can have your kind of cloud deployment. You can have your leaf nodes on any number of devices. They can all talk back to the hub, but the hub can't talk back directly into the leaf node, if that makes sense. So you can basically broker and have a, both a messaging and a security boundary between the leaf node and the cloud. So what you do here is you basically say, yes, I allow leaf node connections. And then on the client side, on the leaf, leaf node itself, you say these are the leaf nodes that I wanna be able to connect to. And then you can establish credentials and things like that as well, or TLS support as well. So this is important for the next, the next half handful of slides. So this is what sort of visually what this could look like. So you can have different regions. Of course this can span across other continents as well, for example. But then you have leaf nodes. You have the clusters of the little N red ones, N1, N2, N3. Those are your regional clusters. And then those are gatewayed together. And then all the clients are connecting around that. So we're just not depicted. All right, so I talked about, I said what is the state of NATs? So in the last 18 months, the team has introduced persistence to NATs. So building on this really great foundation of at most once delivery. Now you have at least once delivery and support for it exactly once semantics. And that is a very nuanced term. So I'm not gonna belabor that here. So it supports both in memory and file based storage for streams. We use the stream notion of stream instead of just a raw subject. How you define a stream is pretty, I think, pretty clever. So you define a stream, you just give it a name here, and then you bind one or more subjects to that stream. And so conceptually you can think of a stream as a server-side service. It's embedded in the server. It's a service. And when someone publishes a message on a subject that is bound to that stream, it's gonna receive it, store it, and act back to the client. And so it's basically just saying, yep, got your message, storing it. So that's sort of the streaming side. It uses RAF for consensus, for synchronous consensus around the cluster, so streams are cluster local. You have a few different interesting retentions. You can do limit-based retention where you can say the max age of a message once reaches that age and it has to go away. It could be time limit, excuse me, it could be memory size of the stream itself. Interest is very interesting. You can say, given however many consumers you define on that stream, once all of them act any given message, now delete it from the stream. So don't delete it until everyone says it's okay to, but once they do, then remove it. And then you have a work queue stream, which is your classic first in, first out type of thing. So a message comes in once it is processed, it gets act and then it goes away. So it's just a persistent buffer, effectively. It also supports optimistic concurrency control. You do that by, as a client, you can specify message ID. Excuse me, that's for the second one. I'll talk about deduplication first. Supports deduplication. You can specify a message ID and if you happen to resend that message because you didn't get an act back or your program crashed and it has to restart, it'll actually, as long as that message ID is unique for that message, Nats will say, okay, great, I'm just gonna tell you that I acknowledged it, but I'm gonna tell you it's also a duplicate. So we're not gonna store it twice. So there's, and that happens within a defined window, deduplication window, by default is two minutes. So you can expand that. You have to realize that you're gonna be increasing the amount of memory on the server because it kind of keeps track of it that way. But for most use cases, that's really robust in terms of handling network retries and things like that. And now optimistic concurrency control is neat. So once a message gets act back, it has a sequence ID that is of course unique, which is an integer. And you can basically say on the next publish, you can specify a sequence ID either at a stream level or a subject level. And if you specify that ID, Nats will say, sorry, another concurrent writer already put a message in here, so I'm gonna reject you, so retry again. So that is, again, a fairly unique thing for streaming like this. And I wrote a couple blog posts actually of how you can build a whole event store for event sourcing use cases on top of that with this one little feature among a couple other things. But being able to ensure that you can get sort of sequential writes without concurrent access is really, really nice. So on the flip side of a stream, you have consumers. A consumer is, you define it on a stream, so I had an order stream, and now I have a consumer that is trying to filter down food orders, let's say. These are arbitrary examples, but bear with me. So recall that I had orders.star.star as the subject that the stream could accept messages for. With a consumer, you can also define a filter on the consumer itself. So you're not limited to saying, okay, this is a stream, these are all the messages in the stream, now I have to basically accept everything and then just drop whatever I don't need on the floor. You can do server-side filtering here based on, again, the pattern matching of tokens. And you can have any number of consumers. There's no concept of a native partition in a stream. So you can have tens, 20s, hundreds of consumers if you want on the same stream. You can have a consumer group, similar to a queue group that I mentioned for messaging. You can have a consumer group that basically says here's a bunch of subscribers that are all getting messages delivered from this consumer. So again, you can think of consumer as a server-side construct that is basically just managing the state of acknowledgments and dealing with delivery. So given these primitives, you can build some more fun, interesting things on top. So one that has been very popular so far has been a key value store abstraction on top of a stream. So recall that you can have any number of subjects mapped to a stream. So sort of what you can get out of this is you can treat every subject as a key. And so now you can just start saying I have this key value store and I say kv.put and I specify a subject and that gets mapped as the subject, or sorry, you have the key and that gets mapped to the subject and that actually gets written as a message. Whatever the value is, is the message value and then you have, you're basically publishing on that subject, which is the key. And so what you get out of this, a key value store is just a stream under the hood and you have basically just key value semantics on top but you get all the same benefits of replication. You get all the same benefits of being able to do optimistic concurrency control. So you can still say only put this new value of this key, assuming the sequence ID is what I expect it to be. So you can get that. There's also a watch command. So you can basically say I wanna watch this kv store for any updates that come in because it's just a stream under the hood so you can get that. And then another one, of course, with the stream you can have historical values. So you can say I wanna keep the last five values for any given key. You can do that, it's just a stream. So it's been pretty interesting and a lot of people have been for simple use cases of Redis because I love Redis, Redis is awesome and it has tons of data structures but for simple use cases of Redis, people are just swapping that out for this because it's baked into NATs, it has all the nice replication benefits built out of the box. Another interesting abstraction is object storage. And you're probably thinking this is a terrible idea. Why would you throw big objects in a stream? So again, this uses chunking so it doesn't actually store obviously a massive object as a value in a single message, it actually transparently chunks the message or chunks the object up into many, many different messages. And again, you get the benefits of transparent replication. You can have a TTL as well. You can define, of course, max bucket size and whatnot but you're able to sort of get all the benefits of distribution and location transparency because you still have that nice topology of clusters and gateways and leaf nodes that these object stores and key value stores can just get mirrored on any of these things which kind of gets to that next point. So you have these primitives, you have streams, you have KVs built on top, you have object stores built on top. I mentioned they all support synchronous replication using RAFT for obviously consistency and high availability within its cluster but because we want to really satisfy edge use cases and we wanna really emphasize data locality, you can create mirrors of streams and key values and object stores anywhere. They're not bound to the same cluster that the origin stream is located. So a common use case is that you actually have edge devices that have a leaf node deployed and you actually have the origin stream on the device, on the leaf node and the nice thing about that is that you sort of have your local processing and whatnot and if it gets disconnected from the cloud, it just keeps going, it doesn't care and then when it reconnects, if you define a mirror in the cloud, it just re-synchronizes everything up and so the use case there is that you can have a bunch of devices, you have mirrors in the cloud, you can aggregate all those mirrors, there's another type of stream called a sourcing stream and you're able to define one or more streams that basically fan in to this bigger aggregate stream and then you can do things with that. It has its own retention policy, has its own file storage, every stream is kind of independent in that sense. Even with mirrors, you can define a different set of replicas, you can define the location where they actually get deployed, for example and so you get a lot of flexibility with being able to move data around very easily or define where you want the data to exist in the first place. So what it ends up looking like just kind of superimposing, now you have these like E's and K's, these little circles that just sort of represent streams or mirrors that exist in these different places and you can literally just drop them wherever you need them to be based on where the traffic is coming from, where you want to replicate it to for offline analytics or whatever your needs are but you get really a lot of flexibility with mirrors in this case. So sort of tying in some of the principles and patterns, reactive principles and patterns here too, I think NATS provides a lot of simplicity. It's designed to be inherently responsive, resilient and elastic just because of the types of use cases that we want to be able to solve with sort of cloud, near-edge, far-edge type of use cases, things like that. You of course decouple space, that's with that whole location transparency, that's what NATS messaging did from day one. Now with persistence, you're also decoupling time because you don't have to have both publisher and subscriber on at the same time. Partitioning and localizing state, you can define any number of streams that map to any number of subjects. You can localize state by creating the origin stream on an edge device and then mirroring somewhere else or you can rely on mirrors to be able to localize state somewhere else. So you have flexibility on how you prefer to kind of place these streams. And similarly, tailoring consistency, you have optimistic concurrency control, you can choose to consume messages in parallel. If that makes sense, you don't need strict ordering. There's other ways to achieve that, but if you don't need strict ordering out of the box, you just say, I just need to work through all of these messages, you can do that. And then promoting autonomy and isolation. And again, this goes back to really, really wanting to satisfy the edge use case, really wanting to satisfy the data locality use case where you can run a NAT server on a very, very small device and you can have local subscribers and publishers and streams and things like that and it can work as its own little unit and it can communicate out or mirror out messages or streams as it needs to. So if this sounded interesting to you, there was no demos today, but there is, we're hosting, Cincinnati is hosting a free virtual event on November 10th. You can sign up, there's a link at the bottom and we're gonna be going through a whole bunch of good demos, showcasing a lot of these things and then a handful of little lightning talks to show some more interesting future advancements that we might be getting into as well. And a whole bunch of resources. So there's a bunch of NATS links. There's Sinadia's website, Twitter account and then a couple personal things. So that's it, thank you very much.