Single Writer Multiple Readers Grain
- Dominant language
- C#
- Stars
- 10.9k
- Forks
- 2.1k
- Avg merge
- 15h 1m
- Merged PRs (30d)
- 345
Description
We are currently working on adding support for the single writer multiple readers (SWMR) scenario in [OrleansTemplates](https://github.com/OrleansContrib/OrleansTemplates).
The grain interface code looks like the following:
``` csharp
[SingleWriterMultipleReaders(ReadReplicaCount = 10)]
public interface IHelloGrain : IGrainWithStringKey
{
[ReadOnly]
Task ReadSomething();
Task WriteSomething(string something);
}
```
**Note**: The following is out of date. This feature has been implemented and is now part of [OrleansTemplates](https://github.com/OrleansContrib/OrleansTemplates). Take a look at the [code generation documentation](https://github.com/OrleansContrib/OrleansTemplates/blob/master/Documentation/CodeGenDocumentation.md#single-writer-multiple-readers-swmr) for more information about the SWMR pattern.
Our code generation (not done yet) will create an `IHelloGrainReader` and an `IHelloGrainWriter`. As you can guess, the `IHelloGrainReader` exposes the read interface and the `IHelloGrainWriter` exposes the write interface. To benefit from the SWMR scaling, client code must use the `IHelloGrainReader` and `IHelloGrainWriter` but not the `IHelloGrain` directly.
The `IHelloGrainReader` is a stateless worker that simply forwards read calls to read replicas. It uses a topology (consistent hash) to distribute the loads among read replicas. Because it's a stateless worker, it automatically scales.
The `IHelloGrainWriter` is a normal Grain (not a stateless worker). When it receives a write request, it forwards the call to **all** read replicas. We chose to execute the same write request on all read replicas rather than executing it once and then replicating the `State`. Please let us know if you have any concerns with that. One assumption we made here is that write requests are **deterministic** (i.e. they produce the same results when executed on multiple replicas).
To ensure that a client reads his own writes and to speed-up write requests, we require client code to pass in a session id. The `IHelloGrainReader` and `IHelloGrainWriter` look as follows:
``` csharp
public interface IHelloGrainReader
{
Task ReadSomething(string sessionId);
}
public interface IHelloGrainWriter
{
Task WriteSomething(string sessionId, string something);
}
```
When the `IHelloGrainWriter` receives a write request, it **initiates** the write request on **all** read replicas but **only awaits** the execution of the request on the replica corresponding to the given session Id. The topology guarantees that we always hit the same replica for a given session id.
The topology is immutable, it doesn't change (until we implement auto-scaling), and each activation of `IHelloGrainReader` and `IHelloGrainWriter` has a copy of it.
We did some initial testing and the speed-up was almost linear for randomly generated session ids. We of course assume that clients are going to use different session ids.
We would love to hear your feedback so we can adjust the design, API, implementation, etc.
Contributor guide
Assessment
This issue has not been assessed yet.