dotnet / orleans

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

What would be the recommended approach to persist grain state in relational database in order to query across multiple actors? #5027

Closed Kimserey closed 6 years ago

Kimserey commented 6 years ago

I had been following the indexing of actor state and read the paper published on it but there doesn't seem to be much movement on it.

What would be the recommended approach to persist grain state in relational database in order to query across multiple actors?

For the moment, I have implemented it as such:

philbe commented 6 years ago

@Kimserey, did you consider directly storing grain state in a relational database, rather than storing grain state elsewhere and then post-processing events to persist the grains in a relational DB?

The Orleans indexing project is now here. Its goal is to maintain indexes that are exposed as Orleans collections that can be accessed via the Orleans API. While it would certainly be worthwhile to extend it to support queries over multiple classes, we don't plan to work on adding that functionality anytime soon.

DarkCow commented 6 years ago

@Kimserey I store grain state in SQL using either entity framework, or the repository pattern. I don't use the built-in orleans grain state methods at all.

From there grains kinda-sorta turn into a read-through/write-through cache of what is in SQL. You can query SQL directly, but it is eventually consistent with active grains.

However querying SQL for filtering/indexing should be okay and fit within your business requirements. E.G. I would query SQL for a list of student names attending a class. once I got the names, then I query all the grains for their current state. I would not read the current state out of SQL... Only query the grain key's.

SebastianStehle commented 6 years ago

For simple things and relatively small sets of data I create special index grains, which care about this aspect only.

ifle commented 6 years ago

There is interesting library YesSql created by @sebastienros from asp.net team, used by OrchardCore CMS that can be relevant for sql store provider

YesSql is a .NET Core document database interface over relational databases which allows you to define documents and indexes using plain old CLR objects. The main difference with document databases is that it uses any RDBMS to store them, which gives you all the power of SQL databases like transactions, replication, reporting, ... But the main advantage might be that there is no magic involved, it's pure SQL

Storage provider based on this library can create indexes automatically and give interface for querying

Kimserey commented 6 years ago

Hey guys thanks a lot for the reply,

@philbe, I had been following the repository and read the paper you published, but there doesn't seem to be a stable release scheduled any time soon (I might be wrong). I did think of storing grain state in relational database as @DarkCow mentioned and I believe this is what I am looking for.

@DarkCow, are you using a custom grain storage? Last time I checked, the state of the grains were stored as blob in the table rather than relational/table-column which defeated the purpose of the relational db for me.

Kimserey commented 6 years ago

@SebastianStehle , would you have a sample to share?

My current problem is that I am using Orleans as a writing mechanism for purchases. The model can be seen as accounts with purchases getting added to. accounts do not hold a list of purchases instead, it emits a message purchaseAdded and a implicit stream picks it up and store the purchase in a relational database.

I intentionally did not hold a list of purchases inside the account as I am afraid that too many account activations would use up too much memory.

I could probably switch the relational database for a index grain, would that be what you would do?

DarkCow commented 6 years ago

@kimserey I would design your code this way.

  1. Don't use the built in grain state stuffs.

  2. Use the standard c# repository pattern, or ef core to read/write/update state using standard SQL stuffs...

  3. Write helper functions like queryPurchaseIdsForAccount. This will help you the grain IDs to get grain references

Kimserey commented 6 years ago

@DarkCow , that's quite interesting, I actually never considered that option.

So in this scenario, you would just drop the grain state and store everything in SQL?

I don't quite understand 3) what do you mean by This will help you [with] the grain IDs to get grain references? Would you be able to give me an example?

DarkCow commented 6 years ago
public class AccountGrain : Grain {
    private readonly EfContext _context;
    private readonly AccountModel _model

    public AccountGrain(EfContext context) {
        _context = context;
    }

    public async Task onActivateAsync() {
        _model = await _context.Accounts.Single(a => a.AccountId == this.GetPrimaryKeyLong( ));
    }

    public async Task BusinessLogicThingy() {
        // do stuff with entity framework here!
        await _context.SaveAsync();
    }
}

// For use elsewhere in the code
var accountForPurchase = _context.Account
    .Where(a => a.Purchases.Contains(purchaseId))
    .Select(a => a.accountId);  // Only want to select accountId.

var accountGrain = _clusterClient.GetGrain<IAccountGrain>(accountForPurchase);
await accountGrain.BusinessLogicThingy();
Kimserey commented 6 years ago

Thanks! @DarkCow, that's really an interesting idea. I'll try that and see if it fits in the context of my application.