This post is about using a partition key while the rowkey is a GUID in Windows Azure Table Storage. You may read more on Windows Azure Tables partitioning in this MSDN article. Ce billet est %u00e0 propos de l’utilisation d’une clef de partition alors que la clef est un GUID lors de l’utilisation des tables non relationnelles de Windows Azure. On trouvera des informations compl%u00e9mentaires sur le partitionnement des tables Windows Azure dans cet article MSDN.
Here is some context on when this sample code comes from. Voici un peu de contexte sur l’origine de cet exemple de code.
I’m currently developing some sample code that works as a canvas application in Facebook. As this type of app runs in a iFrame, it restricts the use of cookies. Still, I wanted to keep some authentication token from page to page (the Facebook signed_request) without displaying it explicitly in the URL (it may also be sent to HTTP referer headers to external sites). So i decided to store the signed_request in a session. ASP.NET sessions can have their ID stored in the URL instead of cookies but ASP.NET pipelines does not provide the session yet while authenticating. So I created my own storage for the authentication and authorization session (Auth Session for short). I did it in Windows Azure tables so that it can easily scale out. Je d%u00e9veloppe actuellement un exemple d’application Facebook de type Canvas. Comme ces types d’applications s’ex%u00e9cutent dans une IFrame, les cookies sont difficilement utilisables. Cependant je devais tout de m%u00eame pouvoir garder un jeton d’authentification de page en page (le champ signed_request de Facebook) sans l’afficher explicitement dans l’URL (il pourrait entre autres %u00eatre envoy%u00e9 dans l’ent%u00eate referer HTTP vers des sites externes). J’ai donc choisi de stocker le signed_request dans la session. ASP.NET propose de stocker l’ID de session dans l’URL plut%u00f4t que dans des cookies mais l’ordre des %u00e9v%u00e9nements ASP.NET fait que lors de l’authentification la session n’est pas encore disponible. J’ai donc cr%u00e9%u00e9 mon propre stockage pour la session d’authentification et d’autorisation (en abr%u00e9g%u00e9: Auth Session). Je l’ai fait en m’appuyant sur les tables Windows Azure de fa%u00e7on %u00e0 b%u00e9n%u00e9ficier simplement de la mont%u00e9e en charge horizontale.
The functional key is a GUID (I don’t want user X to guess user Y’s authSessionKey). The key is passed from page to page as a query parameter (typically, app urls look like this: https://myhost/somepath?authSessionKey=3479D7A2-5D1A-41A8-B8FF-4F62EB1A07BB. La clef fonctionnelle est un GUID (je ne veux pas que l’utilisateur X puisse deviner la clef de session d’un utilisateur Y). Cette clef est pass%u00e9e de page en page sous la forme d’un param%u00e8tre de la requ%u00eate (typiquement les urls de l’app. ressemblent %u00e0 ceci: https://myhost/somepath?authSessionKey=3479D7A2-5D1A-41A8-B8FF-4F62EB1A07BB.
Still, in order to have this scaling horizontally I need to have a partition key. Here is the code I used: Il reste que pour pouvoir monter en charge horizontalement, il faut une clef de partition. Voici le code:

  

internal class AuthSessionDataSource
{
//...
        public const int nbPartitions = 15;
// ...

public static class AuthSessionState
{
//...
    private static string PartitionKeyForGuid(Guid guid)
    {
        int sumOfBytes = 0;
        foreach (var x in guid.ToByteArray())
        {
            sumOfBytes += x;
        }
        int partitionNumber = sumOfBytes % AuthSessionDataSource.nbPartitions;
        return partitionNumber.ToString();
    }
//...
The principle is to get the remainder of the sum of all bytes participating in the GUID divided by the number of partitions as the partition number. Le principe est de prendre comme num%u00e9ro de partition le reste de la division de la somme des octets qui consituent le GUID par le nombre de partitions.
In order to have a rough idea of what it provides, here is a small console application (code, then sample execution result). De fa%u00e7on %u00e0 avoir une id%u00e9e rapide de ce que cela donne, on peut mettre le code dans une application console et l’ex%u00e9cuter.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 36; i++)
            {
                Guid g = Guid.NewGuid();
                Console.WriteLine("{0}, {1}", g, PartitionKeyForGuid(g));
            }
            Console.ReadLine();


        }


        private static string PartitionKeyForGuid(Guid guid)
        {
            const int nbPartitions = 12;

            int sumOfBytes = 0;
            foreach (var x in guid.ToByteArray())
            {
                sumOfBytes += x;
            }
            int partitionNumber = sumOfBytes % nbPartitions;
            return partitionNumber.ToString();
        }

    }
}

 

The advantage is that the partition numbers should be distributed quite regularly and that you can get calculate the partition from the rowkey as long as the number of partitions doesn’t change. L’avantage est que les num%u00e9ros de partition devraient %u00eatre distribu%u00e9s assez r%u00e9guli%u00e8rement et que l’on peut calculer ce num%u00e9ro de partition %u00e0 partir de la clef (le GUID) tant que le nombre de partitions ne change pas.
Should I change and have more partitions as the number of users grow, I could store new users’ sessions to a new table where the number of partitions is higher while keeping already active users to the old table. Auth sessions don’t live very long so changing the number of partitions can be quite simple. Et si on devait changer le nombre de partitions parce que le nombre d’utilisateurs augmente? Dans mon cas, je peux stocker les session des nouveaux utilisateurs dans une nouvelle table avec un plus grand nombre de partitions tout en gardant les utilisateurs d%u00e9j%u00e0 actifs dans l’ancienne table. Les Auth Sessions n’ont de toutes fa%u00e7ons pas une dur%u00e9e de vie tr%u00e8s longue et changer le nombre de partitions devrait %u00eatre assez simple.

 

public const int nbDaysForOldSessions = 3;
//...
internal void RemoveOldValues()
{
    DateTime oldDate = DateTime.UtcNow.AddDays(-1 * nbDaysForOldSessions);

    for(int p=0; p<nbPartitions; p++)
    {
        string partitionKey = p.ToString();
        var query = (from c in context.AuthSessionStateTable
                     where c.PartitionKey == partitionKey
                     && c.Timestamp <= oldDate
                     select c)
                    .AsTableServiceQuery<AuthSessionStateEntity>();
        var result = query.Execute();
        int i = 0;
        foreach (var x in result)
        {
            i++;
            this.context.DeleteObject(x);
            if (i >= 100)
            {
                this.context.SaveChangesWithRetries();
                i = 0;
            }
        }
        this.context.SaveChangesWithRetries();
    }
}

 

Why not using the rowkey as a partition key? Well having several rows in the same partition allows batching which is also good for performance. For instance, I have to remove old sessions. As batch can only happen in a same partition and as no more than 100 rows can be batched together, here is the code to purge old Auth sessions: Pourquoi ne pas utiliser la clef (le GUID) en tant que clef de partition %u00e9galement? En fait, avoir plusieurs rang%u00e9es dans la m%u00eame partition permet de grouper des requ%u00eates ce qui am%u00e9liore aussi les performances. Par exemple, j’ai besoin de supprimer les vieilles sessions. Comme les regroupements ne peuvent se faire que dans la m%u00eame partition et que 100 enregistrements par 100 enregistrements voici le code de purge des vieilles sessions:
In my case, having this way of partitioning data, seems to be a good fit. Dans mon cas, partitionner les donn%u00e9es de cette fa%u00e7on semble assez adapt%u00e9.

 

Benjamin

Blog Post by: Benjamin GUINEBERTIERE