233 karma · joined January 13, 2014
In short, these are complimentary things. Think about it like this: you use aspire to define the pieces which your app is comprised of (services, databases, etc), and that includes your Orleans silos & clients. Aspire lets you configure clustering once and then pass it to the services which consume it - rather than having to configure clustering identically in every silo & client app, for example. Here's an example of the work-in-progress: https://x.com/reubenbond/status/1724479093247549470?s=20
I think workflows (durable async/await) are more useful than Reminders v2 alone (in some sense, workflows are Reminders v2), but an enhanced reminders system is likely part of that, as is the new log-structured storage system. The log structured storage issue discusses this: https://github.com/dotnet/orleans/issues/7691. We've been experimenting with a programming model for workflows and intend to share that more broadly soon. Currently, we are planning for .NET 9, so feedback is welcome (best provided via GitHub rather than here). Aspire will make it easier to build and deploy Orleans apps, which is one of the harder points for people getting started with Orleans currently.
Would you mind elaborating on this? I'd be happy for others to chime in with their experiences/opinions, too.
EPVS is used in Microsoft FASTER, a KV storage engine that uses a hybrid log as its primary data structure: https://dl.acm.org/doi/10.1145/3183713.3196898, which is OSS and used by Microsoft (where I work), with implementations in C++ and C#: https://microsoft.github.io/FASTER/
> If you just synchronously run A then B, the overall time would be shorter (higher throughput) because of less context switching overhead.
There are no context switches: async/await isn't threads. The compiler generates state machines which are scheduled on a thread pool. Basically, each time an event happens (eg, database request completes or times out, or a new request arrives), that state machine is scheduled again so that it can observe that event. This doesn't involve context switching: you can have 1 thread or N threads happily working away on many concurrent tasks without needing to context switch between them.
The minimum here is contingent on a few things. The API can accept a TimeSpan which can express durations as low as 100ns (10M ticks per second: https://docs.microsoft.com/dotnet/api/system.timespan.ticksp...). The actual delay is subject to the timer frequency, which can be as high as 16ms and depends on the OS configuration (eg, see https://stackoverflow.com/a/22862989/635314). However, I'm not sure how any of this relates to "go[ing] as fast as possible", since surely you would simply not use a Task.Delay in that case.
> There is a shitload of context switching and other barbarism that occurs when you employ async/await.
Async/await reduces context switching over the alternative of having one thread per request (i.e, many more OS threads than cores) and it (async/await) exhibits the same amount of context switching as Goroutines in Go and other M:N schedulers. If there is work enqueued to be processed on the thread pool, then that work will be processed without yielding back to the OS. The .NET Thread Pool dynamically sizes itself depending on the workload in an attempt to maximize throughput. If your code is not blocking threads during IO, you would ideally end up with 1 thread per core (you can configure that if you want).
Async/await can introduce overhead, though, so if you're writing very high-performance systems, then you may want to consider when to use it versus when to use other approaches as well as the relevant optimizations which can be implemented. I'd recommend people take the simple approach of using async/await at the application layer and only change that approach if profiling demonstrates that it's becoming a performance bottleneck.
If you're in the .NET ecosystem or interested in distributed systems in general, you may like Orleans (https://github.com/dotnet/orleans), which I work on at Microsoft. Orleans contributes the Virtual Actor model which other modern actor frameworks are starting to adopt since it is well suited for the hectic, failure-prone environment of distributed systems (which those so-called Cloud Native Apps live in). The Ray paper linked from the article (https://www.usenix.org/system/files/osdi18-moritz.pdf) discusses some similarities. Slight correction on the paper: it states that "For message delivery, Orleans provides at-least-once [...] semantics". It's at-most-once. At-least-once messaging semantics (usually implemented via automatic retries) aren't ideal for these kinds of systems, in my opinion.
Was that my Orleans Deep Dive video? https://youtu.be/R0ODfwU6MzQ
I started referring to Grains as Cloud Native Objects as a way to better convey them to people who aren't familiar already. My view is that Grains are different enough from Erlang/Akka style actors that lumping them under the Actor terminology can cause confusion. In the research publications, the term Virtual Actor is coined, which better describes them.
That video doesn't discuss it, but Orleans also supports distributed cross-actor ACID transactions, largely thanks to Phil Bernstein: https://www.microsoft.com/en-us/research/publication/transac... and more recently https://www.microsoft.com/en-us/research/publication/resurre....
Service Fabric has something called Reliable Actors which are heavily inspired by Orleans.
Source: I'm the project lead for Orleans
There is no experimental fix, just improvements. Things which can make life easier for developers running on Kubernetes by automating some things (setting addresses), and taking advantage of information that's available in a Kubernetes cluster (whether or not a pod has been deleted) and feeding that into the cluster membership system.
The reason the latter is useful is that it addresses something which can occur during initial dev/test, but which does not come up in production cases: when an entire cluster is deleted and redeployed with the same identity, the new instances try to contact defunct instances for a few minutes as a safety measure. The enhancement is to query Kubernetes to determine if it's worth trying to contact those nodes, or whether they're almost certainly dead.
- why Microsoft doesn't write and release an Operator & Helm chart, instead asking the community?
Helm charts aren't something we see requested often. Perhaps because Orleans is a framework which is embedded into the developer's application and not a service which gets deployed and stands alone (compared to, for example, a database). There are no separate Orleans pods, just the user's application pods. Internal users have been building applications on Kubernetes with their own Helm charts. Microsoft is not asking anybody to create those things, or anything, unless they want them for themselves.
If you want to discuss, feel free to message me on Twitter (https://twitter.com/reubenbond) or Gitter (https://gitter.im/dotnet/orleans).
Ordering has a performance cost. It needs to either be maintained at all levels, or reconstructed from unordered messages at a later point. Even something simple like an m:n thread pool scheduler can ruin ordering guarantees. My view (Orleans core developer) is this: if you want ordering, await your calls. That way, you are guaranteed ordering regardless of any message reordering that can occur in the scheduling or networking layers, or due to failure and recovery of a host. So you can choose when to pay that cost and when to reap the performance benefits of not paying it (by firing off multiple calls in parallel).
> A stable and properly configured Akka Shard cluster will never have more than one of the same entity actor alive at a time.
Likewise for Orleans, but the caveat of a "stable cluster" doesn't do much for users. "Stable cluster" falls apart frequently in real scenarios, which can be as simple as a single machine being abruptly restarted. Developers must account for the error scenarios.
> Akka and Erlang have the concept of Supervision.
Orleans does not have supervision, since each grain stands on its own (no hierarchy) and have an eternal nature (managed lifecycle). Grains are not destroyed when a method throws an exception: the exception is propagated back to the caller and the caller can use try/catch to handle the exception. This is similar to what .NET developers are used to, since regular objects are also not destroyed when a method throws an exception, and the caller is able to handle the exception. My belief is that this kind of exception handling is usually appropriate, since the caller has context which can be useful in handling the error. The developer can also write per-grain or global call filters which can operate on all calls and handle any exception, so if that's preferred, then it's available.
References to grains are represented by interfaces and if the machine you're communicating with fails, the grain will be re-activated on a surviving host the next time you need to call it. In other words, they are location transparent and the application won't get stuck in some failure state when a machine crashes.