Benjamin Guinebertière

This blog is about Microsoft Azure. Older stuff include architecture, SOA, BizTalk, ...

Windows Azure tables: partitioning guid row keys | Windows Azure tables: partionnement de clefs de type GUID

Windows Azure tables: partitioning guid row keys | Windows Azure tables: partionnement de clefs de type GUID

  • Comments 1

 

 

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 à 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émentaires 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éveloppe actuellement un exemple d’application Facebook de type Canvas. Comme ces types d’applications s’exécutent dans une IFrame, les cookies sont difficilement utilisables. Cependant je devais tout de même 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 être envoyé dans l’entête 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ôt que dans des cookies mais l’ordre des événements ASP.NET fait que lors de l’authentification la session n’est pas encore disponible. J’ai donc créé mon propre stockage pour la session d’authentification et d’autorisation (en abrégé: Auth Session). Je l’ai fait en m’appuyant sur les tables Windows Azure de façon à bénéficier simplement de la montée 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ée de page en page sous la forme d’un paramètre de la requête (typiquement les urls de l’app. ressemblent à 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éro 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çon à avoir une idée rapide de ce que cela donne, on peut mettre le code dans une application console et l’exécuter.
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();
        }

    }
}

image

 

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éros de partition devraient être distribués assez régulièrement et que l’on peut calculer ce numéro de partition à 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éjà actifs dans l’ancienne table. Les Auth Sessions n’ont de toutes façons pas une durée de vie très longue et changer le nombre de partitions devrait être 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 également? En fait, avoir plusieurs rangées dans la même partition permet de grouper des requêtes ce qui améliore aussi les performances. Par exemple, j’ai besoin de supprimer les vieilles sessions. Comme les regroupements ne peuvent se faire que dans la même 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ées de cette façon semble assez adapté.

 

Smile

Benjamin

  • Facebook will always return the signed_request for each page when you redirect to the page correctly.

    Rather then redirecting in your iframe like this https://myhost/somepath?authSessionKey=3479D7A2-5D1A-41A8-B8FF-4F62EB1A07BB. redirect to apps.facebook.com/.../somepath and make sure you are setting the target="_top"  for links, this will also easy sharing of link and make it bookmarkable.

    Implementing hash-ring might solve be better so it does not take hard dependency on the number of partitions.

Page 1 of 1 (1 items)
Leave a Comment
  • Please add 5 and 2 and type the answer here:
  • Post