dotnet / dotnet/orleans

Single Writer Multiple Readers Grain

Open
#447 53 comments 0 reactions 0 assignees View on GitHub
area-grains enhancement Needs: attention :wave: Needs: design Needs: documentation Status: In PR
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.