The “Orleans” runtime provides a build in support for persistence of the stat of a grain (actor). That means, developer can define a part of the grain to be persistible.
This is very important, because internally “Orleans” does not offer common object oriented command “new”, which can be used to create the grain.
Typically, you will execute something like
IMyGrain myGrain = new MyGrain();
In the world of “Orleans”, this is a “NO GO”! As object oriented guy, you might find this not very funny. But, remember, one of goals of Orleans is high performance across multiple silos and nodes.
That means if you have to take a care about grain instantiation, there is no framework which could help much.
Because of this creating (which is not real creating) of the grain is don as follows:
IMyGrain myGrain = MyGrainFactory.GetGrain(7);
When you call the GetGrain() method you will get the instance of the grain. Or more precisely, you will get the reference to it,
which is of type IAddressable. But you will never know if this instance has been created at the moment of invoking of MyGrainFactory.GetGrain() or the already existing instance has been returned.
In other words the instance is theoretically always existing. The picture below shows that the grain with Id=7 (7 is used in GetGrain(7)) already physically exist in the silo (silo contains a number of grains – G1,G2,..G7).
However if we would call now GetGrain(8) “Orleans” runtime would create the physical instance of Grain 7.
This grain would possible take a place of the “empty” grain shown on the picture above, or it would be created on a different machine.
We don’t know that and we should not know that. How long the grain remains physically in the memory we don’t know.
All we should know is that when we invoke factory.GetGrain(Id) we will get a reference to it.
If the grain is physically loaded in the memory (RAM) for the first time the method
public override Task ActivateAsync();
is invoked. This is the place where you will implement your code which is in OO typically implemented in constructor. Because the runtime strictly controls the life-cycle of the grain in memory, under some conditions the grain will be unloaded from memory. If you want to unload the grain explicitly you will have to invoke the method DeactivateOnIdle.
public abstract class GrainBase : IAddressable {
protected void DeactivateOnIdle();
}
Remember, every grain must implement the class GrainBase. For example, this is how MyGrain was implemented in this example:
public class MyGain: GrainBase, IMyGrain{
private double m_X();
}
That means DeactivateOnIdle is accessible form any of your grains. When the grain is unloaded the method DeactivateAsync is invoked:
public override Task DeactivateAsync();
But, this all story has an another side. Assume, if you have a member variable in your grain called m_X and you set this variable on some value.
In this case the variable will hold the value as long the instance of the grain is alive.
The grain is alive as long it is in RAM and as long it is not reallocated. “Orleans” runtime can decide to instantiate the grain on some other node. In this case the grain on the first node will be unloaded and the state of variable m_X will be forever lost.
After the grain is created on the node 2, the m_X will be set on 0.0.
This is where persistence comes into play. Till now, we had this feature enabled in frameworks like Workflow Foundation, AppFabric, Workflow Manager, BizTalk Server and Composite Applications (CTP – which was particularly conceptually moved into WinFabric in Azure).
The goal is that the state of the grain is saved on demand (when developer decides) in persistent storage and to load the state from persisted storage when the grain is activated again.
Following picture shows how this works.
Assuming that the grain was not existing (physically) in the RAM the runtime will create it and immediately invoke ActivateAsync. If you have override it, you can access the state in there (more about the later in this article).
The loading process was executed in steps 1a and 1b. Right now “ORLEANS” provide two storage providers:
1. AzureTable and
2. Memory
Assuming that AzureTable storage provider was used in step 1a, the “Orleans” will execute following storage request to load the state of the grain:
GET https://YOURaccount.table.core.windows.net/OrleansGrainState(PartitionKey='00000000000000000000000000000000030000004adbe5e3',RowKey=’MyGrain’)
As you assumed, AzureTable will persist the state in the Azure Table Storage. Memory will persist the state in the memory. In this case the state is obviously not durable.
If the wanted grain has never been created before this request will return with HTTP 404. In the step 1b “Orleans” will invoke ActivateStateAsync if overwritten.
Later on, you can in any of your grain-methods change the state. For example m_X = 25.71. If you do that and invoke WriteStateAsync the state of this value will be persisted.
In a case of Azure Table provider “Orleans” will send following request to persist the state.
POST https://frankfurt.table.core.windows.net/OrleansGrainState
How to implement persistence?
To illustrate this more practically, assume we have a class SensorData, which holds the state of the grain.
Note that the state class must be Serializable!
In a case of persistence we will not explicitly declare member variable as we do it typically in .NET:
public class MyGrain: GrainBase, IMyGrain{
private SensorState m_X();
}
In this case we first have to implement the interface which describes the state.
public interface IMySensorState : IGrainState{
SensorData Current { get; set; }
}
The idea of this interface is following. We want basically to have a member variable of SensorData type which will be persistable.
In other words we want to achive following. Inside of any code of your grain you can always access the state of the grain:
this.State = //
If the state is of type SensorDate then following would be possible:
this.State = new SensorData () { … };
To make this working we need to do few more steps. We will add the Storage Provider attribute on the grain class and derive from generic version of the GrainBase.
Following table shows on left the typical grain implementation of IMyGrain. The version on the right shows the same grain with state persistence capability:
public class MyGrain: GrainBase, IMyGrain { } | [StorageProvider(ProviderName = "AzureStore")] public class MyGrain : GrainBase<IMySensorState>, IMyGrain { } |
Following code shows how to access the state of the grain. In the method ActivateAsync we read the state. Note that at very first activation of the grain
the state must be initialized. In the method SetTemperature we will invoke WriteStateAsync to persist the state.
public override Task ActivateAsync() { if (this.State.SensorData == null) this.State.SensorData = new SensorData(); Debug.WriteLine("Activated grain {0}", this.GetPrimaryKeyLong()); return base.ActivateAsync(); } public override Task DeactivateAsync() { Debug.WriteLine("Deactivated grain {0}.", this.GetPrimaryKeyLong()); return base.DeactivateAsync(); } public Task SetTemperature(double temp) { var data = new SensorData() { Temperature = temp, Timestamp = DateTime.Now }; this.State= data; State.WriteStateAsync(); return TaskDone.Done; } |
Recap
One grain (every grain) always exist by definition. If the persistence is not used grain theoretically exist in the universe and physically is materialize when the first time factory.GetGrain(grainId) is called.
The grain can by runtime be unloaded (deactivated) and also loaded (activated) on some physically different place in RAM or even on the another node.
If the grain uses persistence as described in this article the grain theoretically also always exist in universe. But once the grain’s state is persisted and the grain is deactivated (unloaded for RAM)
the state of the grain i persisted in the storage. From that moment the grain physically exist in RAM, in storage in in both.
Remember, if we want to deactivate the grain (to unload it from RAM) we can invoke DeactivateOnIdle.
However if you want to physically remove the state from storage you can use following method
this.State.ClearStateAsync();
This call will remove the state data from storage. As you see the grain virtually always exist. If persistence is used the state of the grain moves (is copied) from storage to RAM and back.
Hope this article helped a bit to understand Grain activation, life-cycle and persistence.
Posted
Jun 17 2014, 07:29 AM
by
Damir Dobric