As a part of enabling custom partition keys on the cosmosDb provider I wanted to get a better understanding of the id used in the documents stored in CosmosDB, so I decided I could just as well do a write up of it as a part of the learning experience.
Here's an example of an id
b780de47-a7d2-44c5-8a8f-7eea29e9561f__GrainReference=4fb7889c000475dcf1a50290320ead970600000000000000+13f58135-de2b-4ec7-8f5b-ae609c6d8cbb
After a bit of digging it is not as bad as it looks, the pattern is {ClusterServiceId}__{GrainReference.KeyString}
. The GrainReference.KeyString has four different formats
- Observer reference =
GrainReference={GrainId} ObserverId={ObserverId}
- System target =
GrainReference={GrainId} SystemTarget={Silo Address}
- Generic argument =
GrainReference={GrainId} GenericArguments={genericArguments}
- Grain reference =
GrainReference={GrainId}
In our case we're looking at grain references. The GrainId is a string created by calling GrainId.GrainToParsableString
which in turn calls on UniqueKey.ToHexString()
So this seems straight forward, right. Well just almost. The id 4fb7889c000475dcf1a50290320ead970600000000000000+13f58135-de2b-4ec7-8f5b-ae609c6d8cbb
is from a grain created calling GetGrain(new Guid("000475dc-889c-4fb7-97ad-0e329002a5f1"), "13f58135-de2b-4ec7-8f5b-ae609c6d8cbb");
Last part first, when using composite grain keys the last part is preserved as is, after a + sign. The first part is a bit more of a puzzle. To get to this we have to look at the implementation in UniqueKey. Provided we're after a grain reference the call chain from GrainReference is GrainReference.ToKeyString -> GrainId.ToParsableString -> UniqueKey.ToHexString
. The string before the +
sign is built with the following format s.AppendFormat("{0:x16}{1:x16}{2:x16}", N0, N1, TypeCodeData);
(x16 formats to hexadecimal string of 16 digits.).
Starting from the end again, typecode is used to identify the Grain implementation with any generic parameters (it can be overridden by TypeCodeOverrideAttribute). The typecode is a SHA256 hash based on the full name of the class with generic arguments.
All we're left with is the first two longs, which for our case is easy enough. When the UniqueKey is initialized the Guid it is converted to 2 int64's
var n0 = BitConverter.ToUInt64(guidBytes, 0);
var n1 = BitConverter.ToUInt64(guidBytes, 8);
And that's it.
There's some discussions on this when searching Gitter, and there's some proposals to change how this works https://github.com/dotnet/orleans/issues/1123 and https://github.com/dotnet/orleans/issues/3049 and https://github.com/dotnet/orleans/issues/1121#issuecomment-303951424.