News for hackers. Stuff that matters.
Re:You Cannot Have Exactly-Once Delivery by danpalmer on Wednesday March 01, 2023 @03:32PM (#34987889)
> then track those IDs in the receiver side

Now you need a database. Do you also need exactly-once delivery to the database? Now the service is no longer stateless too, which means scalability is a problem. Maybe you decide to make it just an in-process cache for de-duping, but that needs expiring and now the semantics are exactly-once within a given time period, and not across service restarts.

We can definitely solve this with higher level constructs, but they're not free, and they can introduce the same issues themselves.

> Isn't this basically what every "reliable" transport (TCP, HTTP3, message queues...) does?

TCP does this, to solve retries at the TCP layer. HTTP3 does this to solve issues at the HTTP3 layer. Message queues might solve this for the message queue, depends. But none of these solve the product level, or the user experience level, or other higher levels where these issues still crop up. They're issues you have to solve at every layer in some way.

Re:You Cannot Have Exactly-Once Delivery by sorokod on Wednesday March 01, 2023 @03:43PM (#34988049)
Do you also need exactly-once delivery to the database?

Yes, with uniqueness constraint.

the service is no longer stateless too, which means scalability is a problem

Do you have a specific problem in mind?

Re:You Cannot Have Exactly-Once Delivery by hinkley on Wednesday March 01, 2023 @04:46PM (#34988791)
It's probably more accurate to say that toy systems can maintain the illusion of exactly-once for a while, but it doesn't scale. You can't keep a record of every message ever seen in a message based system. Message passing systems exist to handle rates of traffic that cannot be achieved by rolling your own event queue as a thin wrapper around other tools like databases. It's not just the storage, it's the throughput.

The first time I encountered RabbitMQ it could only handle 60 messages per second with delivery guarantees. We already had our own bespoke system that used a database to handle a couple multiples of that. So we ended up limping along with what we had.

Re:You Cannot Have Exactly-Once Delivery by danpalmer on Wednesday March 01, 2023 @05:07PM (#34989015)
Yeah often the best way to tackle exactly-once delivery to a database is a uniqueness constraint, but that isn't free – there's the index cost, additional write cost, and the error needs to be handled when it's thrown back to the client on a collision (something many applications don't handle well).

Stateful services are far harder to scale than stateless ones. Typically a stateless service can be scaled out horizontally with relative ease, but when there's state storage involved this becomes harder. You can scale vertically, but only so far. You can scale horizontally, but then typically need to introduce some sort of sharding/consistent hashing/etc to ensure that the service has a consistent view of the world without needing to connect to every database instance.

Re:You Cannot Have Exactly-Once Delivery by sorokod on Wednesday March 01, 2023 @05:47PM (#34989414)
Not sure where the expectation of things being free comes from. If your stating point is stateless then you can consider the tradeoff of introducing state vs. processing the same request multiple times.
Re:You Cannot Have Exactly-Once Delivery by xg15 on Wednesday March 01, 2023 @03:53PM (#34988176)
Or I just use a monotonically increasing ID and track the highest ID I've received in order. I might have to buffer a certain number of packets/messages/whatever to deal with out-of-order arrivals, but the entire state fits into RAM. (Edit: Actually I don't even have to. It's probably a good thing to do for efficiency, but in principle I can just drop out-of-order messages and wait until they are redelivered, hopefully in the correct order)

But yes, even if I needed some stuff to archieve it, that doesn't make it impossible as the OP claimed.

> But none of these solve the product level, or the user experience level, or other higher levels where these issues still crop up.

I don't understand this point. Do you have some examples?

Re:You Cannot Have Exactly-Once Delivery by hinkley on Wednesday March 01, 2023 @04:40PM (#34988724)
If everything smells like shit, check your shoes.

How do you know what the last message you received is? You can crash in the middle of receiving a message. You can crash after you've written the ID to disk but before you've processed it, or you can crash after you've processed it but before you've written the ID to storage.

If you're a distribution box (which is quite, quite common in these message queue systems), you can get the message, and send it to a box that just powered off. You saw it, you recorded it, and now you're not sure if you forwarded it successfully or not.

Fun thing about power outages, not every box turns off at exactly the same nanosecond. PSUs are full of capacitors and inductors. Sometimes that's just enough to float through a brownout, too (and a bunch of machines booting can also cause a brownout)

Re:You Cannot Have Exactly-Once Delivery by danpalmer on Wednesday March 01, 2023 @05:01PM (#34988959)
> Or I just use a monotonically increasing ID and track the highest ID I've received in order.

This assumes you can generate monotonically increasing numbers. If you have many clients, now they all need to share a data source and may be performance bound by generating those numbers.

> Actually I don't even have to. It's probably a good thing to do for efficiency, but in principle I can just drop out-of-order messages and wait until they are redelivered, hopefully in the correct order

True (modulo first problem), but efficiency may be necessary here. With many clients, you may end up in a state where only a small fraction of messages get through successfully, and most traffic is unsuccessful, which is bad. This also makes performance commitments hard to maintain as it's perhaps just luck when a client manages to get a message through. Clients also now need more buffering, more state, etc.

>> But none of these solve the product level, or the user experience level, or other higher levels where these issues still crop up. >I don't understand this point. Do you have some examples?

Let's assume a simple client->server instant messaging app. As a user, if I send a message, I expect that to arrive exactly once. It's going over TCP which is "reliable", but it doesn't stop the HTTP request from failing and needing to be retried. It's using HTTP3, but that doesn't stop the server generating a 503 and needing to retry the POST request (or whatever). Maybe the server puts the message in a message queue, but that connection fails after sending a transaction commit, did it get committed?

Idempotency tokens or an equivalent mechanism do solve this, but there isn't one magic trick to solving it in some base layer technology like TCP, this needs to be solved again and again whenever you have distributed systems.

Also, this isn't just networking. Two processes on a single machine communicating via IPC may be effectively a distributed system! I've got some experience doing this on Android, and it's still hard.

Re:You Cannot Have Exactly-Once Delivery by rzzzt on Thursday March 02, 2023 @01:53AM (#34992867)
> This assumes you can generate monotonically increasing numbers. If you have many clients, now they all need to share a data source and may be performance bound by generating those numbers.

You can assign an ID to your nodes and let them generate increasing numbers on their own. Node ID decides on a tie, and if one node sees a larger counter value appear, it adjusts its own counter so that it doesn't stay behind:

- https://en.wikipedia.org/wiki/Lamport_timestamp

- https://en.wikipedia.org/wiki/Vector_clock

Re:You Cannot Have Exactly-Once Delivery by danpalmer on Thursday March 02, 2023 @05:56AM (#34994332)
This is a logical solution but not a full practical solution.

With this approach you'd still need to communicate the current clock number back to clients as otherwise one will get ahead and have all its traffic accepted, and others will fall behind and be unable to get traffic accepted. Even if an error causes a client to bump forwards to retry, by the time it has done that the number it is about to retry with may have been used.

Additionally, the aim is still to get exactly-once delivery, so clients need to be able to differentiate between an error caused by them reusing an ID that was rejected to enforce exactly-once delivery, and an error caused by another client getting that ID.

Basically, this issue is easy to solve with low traffic and reliable persistent storage everywhere, but hard to solve with high traffic, or the acceptance that all persistent storage brings additional reliability challenges.

Re:You Cannot Have Exactly-Once Delivery by mecsred on Wednesday March 01, 2023 @07:47PM (#34990489)
You don't "just" do anything when networking is involved. You can check out a course on TCP/IP if you want to see some reasons that is not enough. What you're describing is essentially a part of the TCP and isn't sufficient for a basic implementation of that.