Guaranteed eventual consistency #7662
-
What are recommended ways to achieve guaranteed eventual consistency in Orleans? I am talking about the following scenario:
For the discussion, I don't think it matters much whether our mental model is that of an event with handlers or simply one where the originating grain needs to make some followup requests. To further illustrate the scenario, here is one way in which we might traditionally solve the problem:
Such a system allows us guarantee that we can keep distributed systems in-sync eventually, or in the very worst case, get an alert when we fail to. Can you recommend a way to achieve the described constraints with Orleans? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
TLDR; This is what sagas are for, but there are simpler ways to go about it, such as plain queues. Note that any approach we use requires some "thing" to act as a coordinator for eventual consistency. The responsibility to ensure everything eventually happens, rain or shine, needs to lie somewhere. In a transactional case, for example, it lies with whatever is acting as a transaction coordinator, if it's capable of self-retrying. With a job retry scheme, the responsibility lies with whatever code is retrying in the first place and keeping tabs on completed steps. In a queue case, it's the queue itself, by making the item available again if the consumer did not confirm receipt within a timeout. Something to keep in mind when looking at options. And here are a couple:
(if you still want to use Orleans):
These are just from the top of my head. |
Beta Was this translation helpful? Give feedback.
-
TLDR; Option 3.
It's possible, if both persistence and reminders providers support distributed transactions using a context (e.g. with Orleans does support managed transactions, but this doesn't apply to reminders.
When a reminder is no longer needed, we can, and should, clear it. Same goes for grain state and the grain activation itself. So, if well managed, grains will self-regulate over time and work fine.
Of the three options, I suggest this one first.
Yes, make sure to In general I suggest making grain methods internally idempotent, so the caller can safely retry as many times as needed, until the call fully succeeds. Making them idempotent, by virtue of ordering internal steps correctly, lets us avoid the performance drop of transactions, and still handle the minimal cases where calls fail. So in your case, I suggest option 3, plus adding some simple retry logic on the caller - we can do this in a few lines of Polly code anyway. And if the caller itself fails to retry until success, and leaves the grain with a reminder and no state, then the reminder can handle that, and remove itself when it ticks. |
Beta Was this translation helpful? Give feedback.
TLDR; Option 3.
It's possible, if both persistence and reminders providers support distributed transactions using a context (e.g. with
TransactionScope
), but implementing this breaks the abstraction that Orleans provides, as you're relying on them supporting this. Performance will also take a hit even on the happy path, if this hack works at all, so I don't recommend this.Orleans does support managed transactions, but this doesn't apply to reminders.
When a reminder is no longer needed, we can, and sh…