dotnet / orleans

Cloud Native application framework for .NET
https://docs.microsoft.com/dotnet/orleans
MIT License
9.94k stars 2.02k forks source link

Single Writer Multiple Readers Grain #447

Open nehmebilal opened 9 years ago

nehmebilal commented 9 years ago

We are currently working on adding support for the single writer multiple readers (SWMR) scenario in OrleansTemplates.

The grain interface code looks like the following:

    [SingleWriterMultipleReaders(ReadReplicaCount = 10)]
    public interface IHelloGrain : IGrainWithStringKey
    {
        [ReadOnly]
        Task<string> ReadSomething();

        Task WriteSomething(string something);
    }

Note: The following is out of date. This feature has been implemented and is now part of OrleansTemplates. Take a look at the code generation documentation 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:

    public interface IHelloGrainReader
    {
        Task<string> 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.

nehmebilal commented 8 years ago

@Plasma Very true! It is one of the use cases where this pattern might not be the best. But, this is also where you guys come in with good ideas or a pull request to improve this aspect :smile:

yevhen commented 8 years ago

@Plasma @nehmebilal that's easily fixed by applying event-sourcing. :smile:

What's interesting is that it could be done without any Roslyn-based black magic :wink:

dinavinter commented 6 years ago

Is there any news in that subject (SWMR)?