Basically, a multi-step event handling system, where the data could be spread across multiple databases/systems, so there's no way to rollback a transaction atomically across all the databases. Instead, you explicitly codify "compensations" to undo your previous commits so that you eventually end up in a consistent state.
Temporal is the orchestrator/framework/library to implement the above in an easier way.
In a past job, they tried to implement a similar thing on much lower scale with bidirectional database migrations.
Fortunately, they were mostly ran in one direction.
Honestly I never did come to a satisfying conclusion on whether I thought it was worth it. The teams I worked on that used it found it neither simple nor easy to use, and I was never sure whether we were really reaping the benefits of correctness in the face of failure at the scale we were running. We eventually migrated everything off of it, and everyone was happier, but perhaps (probably) we had more lurking bugs in distributed failure cases. But to my knowledge we never tracked down an incident to the kind of problem that temporal solves.
I guess I'd say that I like durable execution (or at least temporal specifically, it's the only system like this that I've used) in theory, but not really in practice.
The overhead of doing something in this way is quite large, but, as this handled literal money, it was definitely worth it.
You can scale the workflows up and down to as complex or simple as you want for whatever your business is. The example above is one I’ve seen, but it works on a smaller scale as well. But you have to write the implementation code in very specific ways to get the benefits, and the overhead ends up being very difficult to plan and reason about, and a lot of business logic actually gets hidden amongst the weeds of the tool’s overhead.
In Temporal, you use their SDK to mark which code is either:
- Deterministic without external dependencies on network, disk, clock, etc.
- Non-deterministic (e.g. accesses a filesystem, dependent on clock time, talks over the network)
You can write the code without handling flakiness or retries and the Temporal control plane handles all the progress tracking and retries for you. Progress is tracked at the individual line of code for deterministic code, or for non-deterministic code, tracked at function boundaries defined by the programmer. You buy into more complexity upfront, but it makes the application code way simpler and easier to manage overall.
They do a lot of other cool stuff on top of all this, and their Temporal Worker Controller architecture is particularly well suited to running massive scale processing/AI workloads on Kubernetes (handles a lot of stuff like autoscaling without interrupting work, rainbow version rollout, etc.)
You need to reserve some inventory, charge a card, eventually email a customer and steps can go wrong. So you have queues, and retries and ways to back things out, undo changes.
To my understanding, Temporal's idea is to factor that part out, the queues and retries, and offsetting actions, so you write the logic and not the workflow orchestration.
Also having a unified control plane is handy even at a pretty small scale if your alternative is going full “cloud native” on hyperscaler microservices. The ability to see what’s happening when and where across a single workflow run is a dream compared to all the traditional logging approaches I’ve seen.
It's used by OpenAI.
temporal centralizes all the error handling, retry handling, ... some web pages to manage failed, pending tasks.
it's been good for us, but a real step function in complexity of the app.
Functionally, it's a "workflow" runner (ex - you can mostly treat it like a queue, where you've got workers that are picking up work to do).
But it wraps a couple of pretty handy features on top like:
- It preserves most arguments to actions, and it makes system details deterministic for retries (ex you can re-run a workflow at a later time, and temporal will make sure the code sees details like date and time as though it were the original run, and will yell at you if you try to write code that won't be deterministic on retries)
- It supports very long waits easily. (ex - very easy to have a workflow do a couple things, wait a week, then do some more things).
- It has decent profiling and UI tools
- It can "restart" a failed workflow deterministically from the step at which it failed (using details from the original point)
---
Basically - it's a background worker service that's put a lot of time and thought into ways to handle failures better.
It absolutely still has some considerable pain points though, and I find it difficult to use for larger tasks (ex - their message gRPC size limit of 4mb is a b*&^% to work around, since it often breaks a lot of the utility they provide, and the history cap at 50mb is also really painful in certain situations.)
Really - I think it was just the right tool at the right time to make calling LLMs with long waits relatively easy and somewhat foolproof.
Yes...with eventual consistency.
If your process OOMs, if the machine restarts, etc.
Plus some other distributed execution advantages.
Durable Execution offers three key benefits:
So... like exception handling? or something like erlang's let it crash mantra?Why is it such a big deal? Genuine question, not trying to be snarky
[1] https://temporal.io/blog/what-is-durable-execution