Working with Partitions in Service Fabric

Damir Dobric Posts

Next talks:

 

    

Follow me on Twitter: #ddobric



 

 

Archives


Based on our experiences and article written by Boris partitioning seems to be very complex topic. For this reason I decided provide a more information and examples by using of the same sample (Voting Service), which Boris has used.
To setup partition configuration we will have to open configuration of the Stateful Service in application manifest. There we can set type of partition (Singleton, Named and Uniform) and required parameters.
For example in a case of uniform partition we can defined a number of partition keys. Partition key is a value defined by your application. It can be defined by any meaningful rule. Defined number of partition keys is shared across physical partitions. If you define 5 partitions (instances of your statefull service) and number of partition keys is 15, then every instance will host 3 partition keys.

To define number of keys we will use LowKey and HighKey. In this case we will set it on 0 and 10. To do that open ApplicationManifest.xml file.

clip_image002

This is the configuration in manifest file:

<Service Name="VotingService">
   <StatefulService ServiceTypeName="VotingServiceType"   
      TargetReplicaSetSize="[VotingService_TargetReplicaSetSize]"  
       MinReplicaSetSize="[VotingService_MinReplicaSetSize]">

            <UniformInt64Partition PartitionCount="[VotingService_PartitionCount]" LowKey="0" HighKey="10" />

</StatefulService>

</Service>

To demonstrate using of partitions, I will create a very simple console application, which must run in the SF-cluster.

using System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading.Tasks;
using
Microsoft.ServiceFabric.Services.Client;
using
System.Threading;
using
Newtonsoft.Json;
using
Newtonsoft.Json.Linq;
using
System.Fabric;

namespace
ConsoleApplication
{
   
class Program

    {
       
static void Main(string[] args)
        {
            traceAll();
           
//getPartitionUrlTest();


           
Console.ReadLine();
        }

       
private static void
getPartitionUrlTest()
        {
           
while (true
)
            {
               
ServicePartitionInformation
inf;

               
var url = getPartitionUrl(0, out
inf);

               
Console
.WriteLine(url);

               
Thread
.Sleep(2500);
            }
        }


       
///

       
private static void traceAll()
        {
           
/*
              Note that APplicationManifest.Xml must have UniformPartition type with keys 0-10!

              <StatefulService ServiceTypeName="VotingServiceType" TargetReplicaSetSize="[VotingService_TargetReplicaSetSize]" MinReplicaSetSize="[VotingService_MinReplicaSetSize]">
                    <UniformInt64Partition PartitionCount="[VotingService_PartitionCount]" LowKey="0" HighKey="100" />          
                </StatefulService>
            */


           
for (int i = 0; i < 10; i++)
            {
               
ServicePartitionInformation
inf;

               
var url = getPartitionUrl(i, out
inf);

               
Console.WriteLine($"partitionId={inf.Id} Url:{url}"
);
            }
        }

       
private static string getPartitionUrl(long partitionKey,
       
out ServicePartitionInformation
info)
        {
           
CancellationTokenSource src = new CancellationTokenSource
();

           
var resolver = ServicePartitionResolver
.GetDefault();

           
var partKey = new ServicePartitionKey
(partitionKey);

           
var partition = resolver.ResolveAsync(new Uri
             (
"fabric:/Voting/VotingService"
), partKey, src.Token).Result;

           
var
pEndpoint = partition.GetEndpoint();

           
var primaryEndpoint =
            partition.Endpoints.FirstOrDefault(p => p.Role ==
            System.Fabric.
ServiceEndpointRole
.StatefulPrimary);
            info = partition.Info;
           
if (primaryEndpoint != null
)
            {
               
JObject addresses = JObject
.Parse(primaryEndpoint.Address);

               
var p = addresses["Endpoints"
].First();

               
string primaryReplicaAddress = p.First().Value<string
>();

               
return
primaryReplicaAddress;
            }
           
else

               
return ":("
;
        }
    }
}

Add following nugget packages:

<package id="Microsoft.ServiceFabric" version="5.0.217"
targetFramework="net452" />

<package id="Microsoft.ServiceFabric.Data" version="2.0.217"
targetFramework="net461" />

<package id="Microsoft.ServiceFabric.Services" version="2.0.217"
targetFramework="net461" />

<package id="Newtonsoft.Json" version="9.0.1-beta1"
targetFramework="net461" />

If you start application by entering partition key 0-10 in following statement

new ServicePartitionKey(partitionKey);

You will get the URL of the of the service in partition, where this key is hosted. As long the service is running in in on partition all partition-keys will be hosted in that partition. If you execute the test method testAll() you will get following result:

            for (int i = 0; i < 10; i++)
            {
               
ServicePartitionInformation
inf;

               
var url = getPartitionUrl(i, out
inf);

               
Console.WriteLine($"partitionId={inf.Id} Url:{url}"
);
            }

clip_image004
If VotingService_PartitionCount is set to 1 all URLs of all keys are same, because all partition keys are assigned to a single existing partition.

To change number of partitions go to file Local.xml under ApplicationParameters

clip_image006

As you see in following code-snippet, number of partition is set to 1.

<Parameters>
   <Parameter Name="VotingService_PartitionCount" Value="1" />
   <Parameter Name="VotingService_MinReplicaSetSize" Value="2" />
  <Parameter Name="VotingService_TargetReplicaSetSize" Value="3" />
</Parameters>

To see physical meaning of partitions open a task manager and see 3 instances of the service.

clip_image008

clip_image010

                                          On left taskmanager on right Service Fabric Explorer

Every instance corresponds to one of replicas. According to parameter VotingService_TargetReplicaSetSize, we have 3 replicas, which explains, why we have 3 processes. Note, that only one process is hosted on some URL. Other two replicas are ActiveSecondary replicas, which hold the copy of the state, but do not actively observe requests.

Now, let’s set number of partitions to for example 3 and deploy the application:

<Parameters>
    <Parameter Name="VotingService_PartitionCount" Value="3" />
    <Parameter Name="VotingService_MinReplicaSetSize" Value="2" />
    <Parameter Name="VotingService_TargetReplicaSetSize" Value="3" />
</Parameters>

As next delete the application instance.

clip_image012

clip_image014

Before deploy, please be sure that you are not doing “Update”. Update of running application is not allowed if you change number of partitions.

clip_image016

After successful update you will see following:

clip_image018

clip_image020

Now, VotingService is running in 5 processes (on each node one process). That means, that some of 3*3 = 9 replicas will be assigned to same process on some nodes. You can see these assignments in SF-Explorer:

clip_image022

Finally start the ConsoleApplication and execute method traceAll(), which will list URLs for all 10 partitions. As you see at the picture below, partition keys are shared across all 3 partitions.

clip_image023

That means, the application, which is using VotingService, in this case ConsoleApplication, is responsible to make a decision, which service in this partition will be used. By using of this principal, we can automate load-balancing across partitions based on partition-key. For example, console application could route all requests with names starting with A, B and C to partition with key 1, then names starting with D, E and F to partition 2 etc. Assuming that statistically number of requests (names) starting with some latter are statistically shared across all letters, out system would be symmetrically balanced across all requests.

In this case we used alphabetic letters as a rule, but we could use for example tenant identifies (name) to route all tenant requests to one partition and all other tenant requests to other partition.

As you see, we simply need to define a rule for key sharing based on any business decision and Service Fabric will do the rest.

Last, but not least, If you try to resolve partition with none-existing key (in this case -1), you will get following error:

{"Invalid partition key/ID '{0}' for selector {1}"}

clip_image024

This means that we have used key, which is not defined to be used in partition configuration. Remember we have used LowKey=0 and HighKey=10. Provided key ‘-1’ is obviously not allowed.


Posted Jun 03 2016, 01:58 PM by Damir Dobric

Comments

izwmypwelq wrote re: Working with Partitions in Service Fabric
on 07-27-2016 0:52
developers.de is a .Net Community Blog powered by daenet GmbH.