6 ms·
I see a lot of event sourcing tutorials are based on ordering by timestamp. If events (state transitions) are generated concurrently that would lead to huge pro
by friendzis 9y ago
I see a lot of event sourcing tutorials are based on ordering by timestamp. If events (state transitions) are generated concurrently that would lead to huge problems reconstructing actual flow (and breaking state machines). For example when user has multiple sessions open and makes concurrent changes in both sessions.
Wouldn't it be better to store identifier of previous state and reconstruct the event tree using CONNECT BYs/WITH RECURSIVEs?
- damian-h 9y agoCorrect. Time is also non-discrete. That is one can only measure it to a certain degree of precision and thus provides weak ordering.
- piaste 9y agoIf your event payload contains the whole updated state of the system, then yes, you need to know the ID of the original state in order to detect concurrent updates to the same original state. But if you want to keep things smooth for the user you'd have to merge those concurrently updated states, which may well be a massive pain, depending on your data model. A better option is to only include the changes in the event payload, in which case you don't need to know the original state and you can simply project them all in order - and if one makes the next impossible, it's first come, first serve. Again, though, depending on your data model, generating an event payload that only includes the changed elements may also be a pain.
- friendzis 9y agoBut you do not necessarily need to merge events. Contrived example, but say you have some kind of transactional flow (e.g. accounting) that is in the end signed/sent/processed by a central authority/job. There are two concurrent transactions modifying the same object. If the flow is to commit the latest state, then user1 can accidentaly commit user2's edits. This can easily happen if accountants connect with company credentials. --- edit addition --- For example there is an error in submitted (not yet committed) invoice for green and blue pens - invoice is for 5 boxes of blue and 5 boxes of green pens, but the courier departed with 11 total boxes, because it was a quick call directly to warehousing. Accounting does not particularly care for pen colour and get request to modify the invoice to total to 11 boxes. Accountant 1 modifies green pens count to 6, accountant 2 modifies blue pens. There is now high chance to sign the invoice with 12 boxes. I understand that problem in example is solvable with checks and full state changesets, but it is a problem nevertheless. That's why I am proponent of history trees.
- piaste 9y agoIn your example, I think ending up with a 12-box invoice is absolutely the correct behaviour for the program.
- dfox 9y agoOrdering by timestamp is significantly faster (and even that is in some applications too slow and you want to cache the state somewhere), also there are applications where you don't know what the previous state is (eg. because of network partitions).
- karmajunkie 9y agoYou typically only see this method being used when the store is being backed by a database like postgres that comes down on the CP side of CAP. The timestamps get generated by the database, so they're a cheap way of providing a total ordering of the event stream. In addition, many if not most event stores/interfaces provide something like expectedVersion to enable optimistic concurrency—if the stream version doesn't match the event when its published the store kicks it out as an error. This is (I think) what you might be getting at when you say "identifier of previous state"... except events are not state—you build state from the events.
- friendzis 9y agoThe problem is not event ordering itself, which, as you described, is easily solved in a database, but event dependencies - some events logically can only fire when a certain chain of events has already fired. In the OP article it would be that email cannot be delivered if it was not sent. If possible event sequences are linear (or even a tree) then something intelligent can be done about it - maybe attempt to reconstruct dropped event, maybe hold or drop current event until the missing ones are in, depends. It's no candy, but doable. I see problems when possible event sequences form a graph. If you don't have metadata like "logically previous event" (which is not the same as "temporally previous event"), then eventually a situation will arise where you have two event sequences that are intermixed and have no way to tell them apart and then there is no way to apply projections and arrive at consistent state. Maybe it's the paranoid me talking, though.
- karmajunkie 9y agoWell, the OP is a somewhat contrived example of what I think you're talking about, which necessarily leads to bad discussion, but in that example if the timekeeper is a single point of integration (e.g. Postgres in my previous post) then its not really possible for that kind of inversion to happen unless you're publishing events related to process A from process B, and process B's knowledge of events from A is by inference. That said, I have put myself in situations with asynchronous processes where the projection interleaved two different streams and between streams temporality of the events became inverted. However, the solution in those situations is fairly straightforward in that you pass a causality token to the next command or attach it as metadata to the events it gives rise to, which allows you to resort the graph in terms of causes. Worth noting is that this situation can arise even when you're using logical clocks if you have complex relationships between asynchronous processes—one reason most ES proponents don't recommend going async until you really need to.
- anarazel 9y ago> I see a lot of event sourcing tutorials are based on ordering by timestamp. If events (state transitions) are generated concurrently that would lead to huge problems reconstructing actual flow (and breaking state machines). For example when user has multiple sessions open and makes concurrent changes in both sessions. Indeed. For postgres it's much better to use https://www.postgresql.org/docs/current/static/logicaldecoding.html https://www.postgresql.org/docs/current/static/logicaldecodi... to convert the WAL into a consistently ordered (commit time, with no issues w/ non-discrete or out of order clocks) stream of changes.