Microsoft Orleans – Introduction to the Actor model

Note: this is post is part of a serie about Microsoft Orleans and the Actor model

Some years ago I’ve had the opportunity to see Service Fabric in action in a Proof of Concept, and the results were very interesting. Good part of the benefits were coming from the Actor model, that – for me at that time – was a new concept. After a while, the interest on the Actor model went spread more and more, so i decided to spend more time on it, and to build a demo project.
I read many articles, blogs and the ufficial documentation, that but I found pretty basic. Many examples were only about the IoT (Internet of Things), chats, and games: that’s the perfect fit for the Actor model. But usually I’m working on differents kind of projets, so I raised to myself the quetion: which are the projects it can be used with success, and which are not?

The demo project I’m actually working on is based on Microsoft Orleans and its capabilities, beeing used in a SPA application and a classical N-tier-approach like.

N-tier architecture

My goal is to try to convert the multi-tier approach (SPA, WebApi, Sql Server) to an Actor-oriented one, and be able to understand its limits, where it fits very well, and where is not. For doing this I’m trying to implement patterns and best-practice in a simple way, to make easy to understand for everyone.

  • Most of today’s high scale properties are built as a composition of stateless n-tier services with most of the application logic residing in the middle tier.
  • While the model allows scaling out by adding more servers to the middle tier, it is constrained by the performance and scalability of the storage layer because most requests coming to the middle tier from the frontend web servers require one or more reads from storage.
  • Updates are even more complicated and prone to concurrency issues and conflicts due to lack of coordination among the middle tier servers. It often requires caching in the stateless layer to get acceptable performance, adding complexity and introducing cache consistency issues.
  • Consuming messages from a reliable queue require to use separated services.
  • Scheduled jobs, reminders, require external job processor (like Quartz).

I’m not an expert at all on the Actor model and Orleans, so feedbacks are welcome.

The Actor model

Actors were inspired by physics (and Petri nets) and defined in the 1973 paper by Carl Hewitt (MIT) but have been popularized by the Erlang language, and used for example at Ericsson with great success to build highly concurrent and reliable telecom systems. Relatively recently, their applicability to the challenges of modern computing systems has been recognized and proved to be effective.

Carl Hewitt
Carl Hewitt

Implementation of Orleans is based on the Actor Model, but unlike actors in more traditional actor systems (Erlang, Akka), Orleans grains are virtual actors. The biggest difference is that physical instantiations of grains are completely abstracted away and are automatically managed by the Orleans runtime.

The Virtual Actor Model is much more suitable for high-scale dynamic workloads like cloud services and is the major innovation of Orleans.

Suggested resource:

Other famous Actor model frameworks are Akka (and Akka.Net), Proto actor and Service Fabric.

What is Orleans

Orleans is a framework that provides a straightforward approach to building distributed high-scale computing applications without the need to learn and apply complex concurrency or other scaling patterns. It provides an intuitive way of building a stateful middle tier, where various business logic entities appear as sea of isolated globally addressable .NET objects (grains) of different application defined types distributed across a cluster of servers (silos). Orleans apps are distributable, easily horizontal scale to multiple machines.

It was created at Microsoft Research and designed for use in the cloud. Since 2011, it has been used extensively in the cloud and on premises by several Microsoft product groups, most notably by game studios, such as 343 Industries and The Coalition as a platform for cloud services behind Halo 4 and 5, and Gears of War 4, as well as by a number of other companies. Orleans was open-sourced in January 2015.

The virtual nature of grains allows Orleans to handle server failures mostly transparently to the application logic because grains that were executing on a failed server get automatically re-instantiated on other servers in the cluster once the failure is detected.

Compute is an atomic thing in space-time, for any compute to happen, data and code must be in the same place, at the same time. An actor model provides ease of design and performance because it keeps data close to the code and the code gives actors agency of their own. A virtual actor model makes many things a child’s play, if we are willing to change the way of thinking a bit.

Async/Await concepts

First of all, Orleans use heavily async/await concepts, so if you’re not yet familiar with async/await, it may be worth reading this documentation first: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

Orleans grain

A grain type is a simple .NET class that implements one or more application-defined grain interfaces. Individual grains are instances of application-defined grain classes that get automatically created by the Orleans runtime on servers on an as-needed basis to handle requests for those grains. Grains naturally map to most application entities, such as users, devices, sessions, inventories, and orders.

Orleans guarantees single-threaded execution of each individual grain, hence protecting the application logic from perils of concurrency and races. A grain can have persistent state in storage, in-memory state, or both. The Virtual actor model concurrency makes creating concurrent stateful apps really easy.

Actor lifecicle

The actor model provides an abstraction that allows you to think about your code in terms of communication, not unlike people in a large organization. The basic characteristic of actors is that they model the world as stateful entities communicating with each other by explicit message passing. It helps to follow the “single responsibility” principle and is (more) predictable.

Actor as fundamental unit of computation has to embody: processing, storage and communications. They have these characteristics:

  • They communicate with asynchronous messaging instead of method calls
  • They manage their own state, no shared state between Actors, Actors encapsulate state
  • Only way to interact with an actor is to send it a (immutable) message
  • Messages are processed sequentially, concurrent to the sender
  • When reacting/responding to a message, they can:
    • Create other (child) Actors
    • Send messages to other Actors
    • Designate how to handle the next message it receives
  • Actors create other actors to handle sub-tasks
  • Parent communicates with them via messages as usual
  • An actor can change its behavior for subsequent messages: this provides the basis for a finite state machine

The Actor model is NOT good at everything. But some patterns, while difficult to implement using typical n-tier architecture, become easy – or even trivial – to implement using an Actor model, while some common problems just disappear, like caching.

Orleans can remove the need for a caching layer outright – the grains are cache. You don’t need things like Redis because Orleans is basically your distributed database that keeps accessed data in memory for the duration you specify. Using grains as pass-through for storage operations removes database concurrency issues, as we can resolve concurrency in the grain itself.

Messaging and at-least-once garantee

Orleans messaging delivery guarantees are at-most-once (best effort): a message will either be delivered once or not at all. It will never be delivered twice. It doesn’t garantee that you will receive at-least-one message, because that will cost on terms of speed. If you really need that, you can use a Persistent Queue (like Azure Service Bus or RabbitMQ) and keep listening to the messages using a Observer pattern.

I’m using a Azure Service Bus Queue because the Basic pricing tier is close to free and you will pay for message. You could find useful ServiceBusExplorer.

Orleans functionality

Microsoft Research continues to partner with the Orleans team to bring new major features, such as:

  • geo-distribution,
  • Indexing,
  • (distributed) transactions

A list of some built-in functionality:

  • Store grain state to many storage providers: Sql server, Azure Table, Azure Blobs, Oracle, MongoDb, etc
  • You can write your own adapter to existing data structure: ulike a RDBMs – on-prem or legacy
  • Streams: in-memory (SMS) or reliable: Azure Event Grid, Azure Message bus, RabbitMQ, etc

Communication between grains – Serializers

To communicate between grains and between silos, the messages must be serialized to be transferred or to be persisted. So a serializer is needed when you need to comunicate between the Actor and external components: the state is serialized and then unserialized on the other side. Serialization is needed to save the state on disk to be able to recover it in future.

Orleans default serializer is Hagar, an high performance non version-tolerant serializer (but a version that is is work-in-progress), made by Reuben Bond, now working in Microsoft and part of the Orleans team.

PRO: It’s great for prototyping at the speed of light.

CONS: Non version-tolerant means that is not possible to change easily an object that is stored, if you change a model state, that will raise errors.  To avoid a lot of pain I think that is very important to understand very well the risk in the long-term of committing to a serializer, and made a decision at the beginning of the development. If you want, theorically you can use a different serializer for each model.

Once you commit to a model, it’s best to switch to a version tolerant serializer like Protobuf or Bond: those are supported out of the box.

I have struggled a lot to have something working. Basically for each model that you want to serialize, you need to add some decorators to tell the serializator the serialization strategy. As example:

  • how should I serialize a DateTime? there are plenty of different implementations… here you shouls decide what you want. Keep in mind that serialization and speed are not friends!
  • which position has this property in the serialized object?

More info about the main serializers:

  • Hagar, the default serializer is very fast and doesn’t require extra code to support the .NET object (like DateTimeOffset or Guid).
  • Microsoft Bond: no out-of-the-box support support for decimal, Guid neither DateTimeOffset.
  • Google protobuf: I was not able to use it, I was not able to found documentation to succeed to set the right attributes.
  • Protobuf.Net: it looks to me the best choice, just missing some types like DateTimeOffset that requires some custom code (DateTimeOffsetSurrogate), but it was the faster and easier to setup correcty, version-tolerant.
  • Json: it’s not a binary format, so it’s pretty inefficient, but can improve readability and version-tolerant deserialization.

Useful links

The Orleanse documentation is a good place to start, but as today it lacks a lot of knowledge that instead I have found arount in the web, or asking in their gitter channel.

I have collected many resources from many places around the web, that I’m happy to share and make your journey into Orleans easier. I think Orleans can be the future of the cloud and related technologies, that because it allow to scale very easily and it can achieve very fast response time (in the order of milliseconds).

 

5 Comments

Add a Comment

Your email address will not be published.