Storage nel cloud: Partizionamento


Mi sono assentato per in certo periodo in quanto aspettavo un buon momento per poter parlare di SDS (SQL Data Services). Purtroppo, non ne posso ancora parlare e quindi mi invento un'altro argomento, comunque fondamentale.

Una delle caratteristiche peculiari dello storage nel cloud e' quella di poter scalare, potenzialmente scalare indefinitivamente. Fino a qualche anno fa, scalare il database significava aggiungere dischi, e se la potenza di calcolo non era sufficiente, aggiungere nodi al cluster. Non vi e' dubbio che scalare con un cluster, oltre ad incrementare i costi esponenzialmente, presenta comunque un limite fisico oltre al quale non si puo' crescere.

Google, sin dalla sua prima comparsa, ha introdotto il concetto di scalabilita' usando un hardware di basso costo. SDS, cosi' come altre soluzioni, stanno cavalcando la stessa idea. Ma come fanno a scalare un database linearmente mantenendo i costi accettabili.

La soluzione si basa su due aspetti importanti:

  • Partizionamento dei dati
  • Replica

Immaginiamo che un singolo data server (SQL Server) sia in grado di gestire 10GB di dati. Bene, se dovete gestire 25GB di dati vi serviranno 3 data server e farete in modo di spalmare i dati fra i tre server, possibilmente in modo uniforme.

Avere un singolo server, nodo, ci espone di fronte ad un singolo punto di fallimento. Ecco che quindi entra in gioco la replica. In altre parole i dati potranno essere replicati su altri server in modo che se uno di questi fallisce, l'altro compensa.

Questa semplice tecnica permette servizi come Microsoft SDS, Amazon Simple DB ed altri, di poter fornire il servizio del database a basso costo e con grande livello di affidabilita'. Nel caso particolare di SDS, Microsoft garantisce 3 copie dei dati per ogni partizione.

Per ora mi fermo qui, in un prossimo futuro entrero' meglio nei dettagli del partizionamento in quanto ha un impatto diretto su come si scrivono le applicazioni.

author: Pierre Greborio | posted @ lunedì 1 giugno 2009 23.18 | Feedback (0)

Storage nel cloud: latenza, BEB


Nel post dedicato alla latenza, ho illustrato una delle tecniche per mitigare il piu' possibile i problemi legati al timeout. La tecnica si basa sostanzialmente sul riprovare.

L'algoritmo che ho implementato, NRetryPolicy, e' molto primitivo e presenta parecchie imperfezioni, oltre a non risolvere il problema e potenzialmente peggiorarlo. Il lettore attento infatti si sara' accorto che se riproviamo ad intervalli fissi potremmo avere un effetto a valanga generando, involontariamente, un attacco di DOS (Denial Of Service) al servizio stesso. Come?

Immaginiamo di avere un carico costante di chiamate di 100 RPS. Ad un certo punto il 20% di queste vanno in timeout. Queste riproveranno dopo un certo ammontare, che avete definito voi, di tempo. Scattato questo intervallo il servizio dovra' supportare il 20% in piu' di richieste, quindi 120 RPS. La probabilita' che queste vadano ancora in timeout aumenta, e quindi si ripropone il problema al prossimo intervallo. Immaginiamo ora che 40 di queste richieste siano andate in timeout. Al prossimo giro 140 RPS. Avento un limite di tentativi, diciamo 3, non dovremmo superare 160 RPS considerando un errore (timeout) costante del 20%.

Come risolvere il problema? Cambiando il pattern dei tentativi, passando da un sistema costante e statico ad un sistema casuale. Essendo un problema molto comune, anche nelle telecomunicazioni e nelle reti, ci appoggeremo ad un algoritmo ben conosciuto, denominato Binary Exponential Backoff.

L'algoritmo e' semplice, riprova randomicamente sulla base di un intervallo esponenziale (2n - 1). Immaginando che l'unita' di intervallo (delta backoff) sia 1 secondo, al primo fallimento riprova subito, al secondo un tempo random incluso fra 0 e 1 secondo, la seconda volta un tempo random fra 0 e 3 secondi, la terza volta un tempo rando fra 0 e 7 secondi e cosi' via.

L'implementazione e' abbastanza triviale:

 

public class ExponentialNRetryPolicy : RetryPolicy
{
    int numberOfRetries;
    TimeSpan minBackoff;
    TimeSpan maxBackoff;
    TimeSpan deltaBackoff;
    private readonly Random Random = new Random();

    /// <summary>
    /// Policy that retries a specified number of times with a randomized exponential backoff scheme
    /// </summary>
    /// <param name="numberOfRetries">The number of times to retry. Should be a non-negative number.</param>
    /// <param name="deltaBackoff">The multiplier in the exponential backoff scheme</param>
    /// <returns></returns>
    /// <remarks>For this retry policy, the minimum amount of milliseconds between retries is given by the
    /// StandardMinBackoff constant, and the maximum backoff is predefined by the StandardMaxBackoff constant.
    /// Otherwise, the backoff is calculated as random(2^currentRetry) * deltaBackoff.</remarks>
    public ExponentialNRetryPolicy(int numberOfRetries, TimeSpan deltaBackoff)
        : this (numberOfRetries, TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(30), deltaBackoff)
    {           
    }

    /// <summary>
    /// Policy that retries a specified number of times with a randomized exponential backoff scheme
    /// </summary>
    /// <param name="numberOfRetries">The number of times to retry. Should be a non-negative number</param>
    /// <param name="deltaBackoff">The multiplier in the exponential backoff scheme</param>
    /// <param name="minBackoff">The minimum backoff interval</param>
    /// <param name="maxBackoff">The maximum backoff interval</param>
    /// <returns></returns>
    /// <remarks>For this retry policy, the minimum amount of milliseconds between retries is given by the
    /// minBackoff parameter, and the maximum backoff is predefined by the maxBackoff parameter.
    /// Otherwise, the backoff is calculated as random(2^currentRetry) * deltaBackoff.</remarks>
    public ExponentialNRetryPolicy(int numberOfRetries, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff)
    {
        if (minBackoff > maxBackoff)
        {
            throw new ArgumentException("The minimum backoff must not be larger than the maximum backoff period.");
        }
        if (minBackoff < TimeSpan.Zero)
        {
            throw new ArgumentException("The minimum backoff period must not be negative.");
        }

        this.numberOfRetries = numberOfRetries;
        this.minBackoff = minBackoff;
        this.maxBackoff = maxBackoff;
        this.deltaBackoff = deltaBackoff;
    }

    public override void Retry(Action action)
    {
        int totalNumberOfRetries = numberOfRetries;
        int retryCount = numberOfRetries;
        TimeSpan backoff;

        do
        {
            try
            {
                action();
                break;
            }
            catch (RetryException e)
            {
                if (retryCount == 0)
                {
                    throw e.InnerException;
                }
                backoff = CalculateCurrentBackoff(minBackoff, maxBackoff, deltaBackoff, totalNumberOfRetries - retryCount);
                if (backoff > TimeSpan.Zero)
                {
                    Thread.Sleep(backoff);
                }
            }
        }
        while (retryCount-- > 0);
    }

    private TimeSpan CalculateCurrentBackoff(TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff, int curRetry)
    {
        long backoff;

        if (curRetry > 30)
        {
            backoff = maxBackoff.Ticks;
        }
        else
        {
            try
            {
                checked
                {
                    // only randomize the multiplier here
                    // it would be as correct to randomize the whole backoff result
                    backoff = Random.Next((1 << curRetry) + 1);
                    backoff *= deltaBackoff.Ticks;
                    backoff += minBackoff.Ticks;
                }
            }
            catch (OverflowException)
            {
                backoff = maxBackoff.Ticks;
            }
            if (backoff > maxBackoff.Ticks)
            {
                backoff = maxBackoff.Ticks;
            }
        }
        return TimeSpan.FromTicks(backoff);
    }
}

 

 

E con questo credo che abbiamo concluso l'argomento di come limitare gli effetti indesiderati della latenza.

author: Pierre Greborio | posted @ giovedì 12 marzo 2009 23.11 | Feedback (0)

Storage nel cloud: le soluzioni in casa Microsoft


Nel primo post dedicato all'argomento, ho elencato due servizi che verranno forniti da Microsoft nei prossimi mesi. Si tratta di Windows Azure Storage e SQL Server Data services (SSDS).

Che differenza c'e' fra questi servizi? Detto che saranno tutti in hosting presso i datacenter di Microsoft, possiamo dire che Windowas Azure Storage fornira' lo storage per blobs, queue e tables (non relazionali), mentre SSDS un database relazionale.

Chi ha avuto modo di guardare a SSDS in passato, si sara' reso conto che di relazionale ha ben poco a che fare, se non il legame fra authority, container ed entity. Bene, quella versione (comunque una CTP) di SSDS sparisce per fare spazio al database relazionale. E' infatti di ieri l'annuncio ufficiale sul blog di SSDS.

I dettagli mancano ancora, io comunque darei un acch'io al prossimo MIX (sessione MIX09-T06F) ;)

author: Pierre Greborio | posted @ mercoledì 11 marzo 2009 0.48 | Feedback (2)

Storage nel cloud: latenza


Immaginatevi una sera a casa, avete un problema con un servizio o un contratto e decidete di chiamare il call center. Un call center e' pensato e progettato per essere scalabile (ho detto scalabile, non cortese :-)), tutte le chiamate vengono accettate, accodate ed evase il prima possibile. Bene, chiamate e dopo un secondo inizia la musichetta che vi mette in attesa (coda). Se va male qualche cosa, cade la linea e riprovate. Se va bene iniziate a parlare con l'operatore. Se va bene finite la richiesta, avete la risposta e chiudete. se va male cade la linea con l'operatore e ripartite da capo.

Il mondo del cloud computing non e' poi cosi' tanto differente. Quando dovete salvare un record non fate null'altro che fare una HTTP POST (nel caso il servizio sia RESTish) con il record in formato XML e il provider tradurra' questo payload XML in un record (ad esempio trasformandolo in uno statement SQL che eseguira' su SQL Server).

Se stessimo parlando di effettuare la stessa operazione sul nostro database locale, la latenza sarebbe molto ridotta, in quanto legata al throughtput della rete locale (solitamente molto alto) e da quanto il nostro database engine e' occupato.

Con uno storage nel cloud il numero di variabili aumenta incredibilmente, ed oltre a quelle appena menzionate, possiamo ricordare le condizioni di rete fra noi ed il cloud provider, il server web che accetta la richiesta, la componente che transcodifica, e cosi' via.

Quindi, se con il nostro SQL Server locale il numero di timeout era un'eccezione, con lo storage nel cloud il timeout diventa la regola. Come mitigare questo problema?

Una delle soluzioni e' proprio quello di fare quello che avete fatto con il call center, ci riprovate. In altre parole, prima di rilanciare l'eccezzione al chiamante, ci riprovate qualche volta. Ma quale schema adottare?

Come tutte le cose e' bene iniziare con qualche cosa di semplice, ed eventualmente nel futuro si puo' raffinare. Iniziamo quindi con una classe base (il contratto) che definisca la nostra policy di retry (il codice e' ispirato dall'esempio StorageClient fornito con Windows Azure SDK):

public abstract class RetryPolicy
{
    public abstract void Retry(Action action);
}


Ovviamente non puo' mancare la policy che non faccia nulla:

public class NoRetryPolicy : RetryPolicy
{
    public override void Retry(Action action)
    {
        try
        {
            action();
        }
        catch (RetryException e)
        {
            throw e.InnerException;
        }
    }
}

Dato che questa policy risulta abbastanza inutile, possiamo implementarne una che riprovi per un certo numero di volte ad un determinato intervallo:

 

public class NRetryPolicy : RetryPolicy
{
    private int numberOfRetries;
    private TimeSpan intervalBetweenRetries;

    public NRetryPolicy(int numberOfRetries, TimeSpan intervalBetweenRetries)
    {
        this.numberOfRetries = numberOfRetries;
        this.intervalBetweenRetries = intervalBetweenRetries;
    }

    public override void Retry(Action action)
    {
        int retryCount = numberOfRetries;

        do
        {
            try
            {
                action();
                break;
            }
            catch (RetryException e)
            {
                if (retryCount == 0)
                {
                    throw e.InnerException;
                }
                if (intervalBetweenRetries > TimeSpan.Zero)
                {
                    Thread.Sleep(intervalBetweenRetries);
                }
            }
        }
        while (retryCount-- > 0);
    }
}

Ora, immaginiamo di voler riprovare a salvare il record 3 volte a distanza di 1 secondo, dovremo scrivere qualche cosa del tipo:


NRetryPolicy retryPolicy = new NRetryPolicy(3, TimeSpan.FromSeconds(1));
retryPolicy.Retry(() =>
{

    // Salvo il record
    if (<e' un timeout ?>)
        throw new RetryException("Throwing an exception from RetryTest");
});

In questo modo, il nostro codice riprovera' al massimo tre volte prima di fallire definitivamente. Ovviamente lo schema e' molto semplice e potrebbe essere ottimizzato ulteriormente, ma questo lo lascio ad un prossimo appuntamento. Per ora, abbiamo prodotto una tecnica semplice per mitigare un timeout ricorrente rendendolo meno ricorrente.

author: Pierre Greborio | posted @ lunedì 9 marzo 2009 1.40 | Feedback (2)

Storage nel cloud


Il cloud computing non e' un'idea nuova, anzi l'idea nasce negli anni 60 dal computer scientist John McCarthy. Che cosa lo ha reso cosi' popolare cosi' recentemente? I motivi sono veramente innumerevoli e per ora sorvolero' rimandando nel prossimo futura l'analisi piu' approfondita.

Di certo uno dei motivi principali della popolarita' oggi e' il costo. Con pochi dollari al giorno si possono gestire milioni di transazioni business senza dover fare investimenti infrastrutturali ingenti a priori. Ecco che quindi possiamo iniziare a pensare a usufruire di public utilities installandoci i nostri servizi e mettendoci i nostri dati, senza preoccuparci di investire a priori un euro (o dollaro) in infrastruttura. Se il nostro business va bene, pagheremo [volentieri] di piu', altrimenti pagheremo meno. Se abbiamo un piccho di utilizzo in un certo periodo dell'anno non dovremo preoccuparci di comprare 50 server che rimarranno a dormire 300 giorni all'anno, sara' un problema del cloud provider.

In questo post mi soffermero' su uno degli aspetti del cloud computing: storage. Supportare anche pochi milioni di dati dei clienti per un servizio online richiede un certo livello di infrastruttura, licenze e manutenzione. Sono costi up-front veramente ingenti (centinaia di migliaia di dollari) su un business basato interamente su delle proiezioni. La soluzione potrebbe essere quella di affidarsi ad un cloud provider.

Tanto per citare un esempio, ho fatto una stima dei costi per supportare un sistema di provisioning con 5 milioni di clienti, contanto lo storage e la banda (in e out). Il costo sarebbe di circa 21 dollari al mese (tutti i providers si equivalgono in termini di pricing)!

Negli USA i cloud providers piu' popolari sono:
  • Amazon S3, SompleDB e SQS
  • Windowws Azure Storage (Table, Blob e Queue) e SSDS
  • Google MegaStore
Praticamente tutti provvedono un set di API basato su protocolli ed architetture standard, REST (HTTP POX) e SOAP.

La domanda che ci dobbiamo porre quindi e', ma come ci arrivo li? Prendo il mio database e lo copio nel data center ed inizio a fornire il servizio? No! Bisogna fare i conti con le limitazioni, piu' o meno comuni a tutti i providers:
  1. Non e' un database relazionale
  2. Non c'e' supporto alle transazioni
  3. La latenza e' un dato di fatto percettibile
  4. Ci sono piu' possibilita' di race condition
  5. Non tutti supportano la data locality
  6. Il numero di timeout aumenta
  7. Le tipologie query sono decisamente limitate
  8. I data types sono limitati
Ora, molti si saranno detti:
  • Leggendo la prima parte del post "bellissimo, lo voglio!"
  • Leggendo la seconda parte del post "non se ne parla nemmeno!"
Ottimo, se siete arrivati sino a qui, allora avete resistito abbastanza. La prossima volta parlero' dei singoli punti e come mitigarli il piu' possibile.

author: Pierre Greborio | posted @ domenica 8 marzo 2009 3.40 | Feedback (5)

Il mondo mobile cambia tono


Dopo l'uscita dell'iPhone e l'arrivo di Palm Pre possiamo dire che i paradigmi del mondo mobile sono decisamente cambiati. I criteri di usabilita' sono decisamente piu' alti ed i competitors dovranno veramente darsi da fare per raggiungere Apple e Palm. In questo periodo sono andato a guardare e provare i modelli di programmazione di entrambe i devices, da un lato giochicchiando con l'iPhone SDK e xCode, dall'altro leggendo le recensioni del nuovo Palm. Sulla carte Palm e' avanti anni luce, con tanto di HTML, CSS, Javascript e JSON si puo' estendere le funzionalita del nuovo device. iPhone e' legato al mondo Objective-C, Cocoa, xCode ed il suo omonimo SDK. Sviluppare per iPhone e' all'apparenza banale, ma in realta' tutt'altro che banale. Molto spesso, se il problema e' nel suo framework e' bene abituarsi a saper legger l'assembler per debuggare. Chi e' poi abituato a Visual Studio si sentira' parecchio frustrato a passare a xCode. Eppure l'iPhone ha comunque catalizzato un certo numero di sviluppatori che han postato 15.000 applicazioni dall'uscita dell'SDK. Non male se consideriamo non piu' di due anni. Cosa faranno gli altri? Bh, stiamo a vedere...

author: Pierre Greborio | posted @ venerdì 23 gennaio 2009 9.05 | Feedback (2)

.NET Reflector passa di mano


Il futuro di .NET Reflector passa nelle mani di Red Gate! Bod Cramblitt ha postato nel suo blog alcuni dettagli del deal. La frase che interessa ai piu' e' condensata in: "Red Gate will continue to offer the tool for free to the community."

author: Pierre Greborio | posted @ martedì 26 agosto 2008 14.30 | Feedback (0)

Libri sul debugging


Quanto tempo dedicate durante lo sviluppo al debugging? Quante volte avete la necessita di debuggare un problema su un server di produzione?

Bene, nel mio caso durante lo sviluppo dedico circa il 40% di tempo al debugging ed ogni volta che vi sono problemi servi (hang, crash, memoria...) sul server di test, staging o produzione, mi debbo analizzare il mini dump.

Le tecniche di debugging possono essere estremamente sofisticate o piu' semplici, dipende dal contesto, dal codice e dall'accesso alle risorse. Un valido aiuto arriva sicuramente dai libri, i quali possono dare indicazioni chiare su come muoversi in questa difficile arte.

Cito quindi "Advanced Windows Debugging" per il debugging di codice nativo e "Debugging Microsoft .NET 2.0 applications" per il codice managed.

Ovviamente non deve mancare nel vostro RSS reader il blog della mitica Tess: http://blogs.msdn.com/tess/

 

author: Pierre Greborio | posted @ sabato 28 giugno 2008 0.41 | Feedback (0)

A volte l'eleganza del linguaggio fa la differenza


Poche ore fa stavo rivedendo una 'vecchia' classe che 'simula' il concetto di map. In pratica e' una sorta di dictionary che permette di avere duplicazioni di chiave. Niente di veramente speciale, di fatto usavo una List di KeyValuePair; si pou' fare di meglio, ma qui l'accorcio :-)

Per estrarre i valori data una chiave, ho usato il buon vecchio foreach che andava a riempire una lista di stringhe. In altre parole qualcosa del tipo

List<string> result = new List<string>();
foreach(KeyValuePair<string, string> parameter in parameters) {
  if(parameter.Key.Equals(param))
    result.Add(parameter.Value);
}
return result.ToArray();

Dato che il progetto e' migrato a FX 3.5, ho improvvisato una soluzione LINQ:

return parameters.FindAll(kv => kv.Key.Equals(param)).Select(kv => kv.Value).ToArray();

Devo dire che risulta decisamente piu' chiaro e compatto.

author: Pierre Greborio | posted @ martedì 20 maggio 2008 23.43 | Feedback (6)

[ROA] Contratto


Nel precedente post ho parlato della lacuna della definizione del contratto nel mondo ROA rispetto al mondo SOA. E' bene che faccia qualche precisazione, ma prima di parlare di questo fatemi fare un escursus sul concetto di contratto e come questo si applichi oggi nel mondo SOA (Service Oriented Architecture).

Se chiediamo ad un avvocato di scrivere un contratto, garantendo un certo introito :-), ci innondera' sicuramente di domande e probabilmente scrivera' 40/50 pagine di documento dove ogni singola parola ha un suo peso specifico. In sostanza la regola e', ogni aspetto non 'normato' o definito rappresenta un threat.

In ambito IT si tende ad essere piu' lassisti ed il tempo dedicato a definire il contratto risulta quasi sempre insufficiente. La domanda che ci dobbiamo porre inizialmente e', che cos'e' un service contract? Mi piace molto la definizione data in "SOA: Principles of Service Design" che dice: "A service contract establishes the terms of engagement, providing technical constraints and requirements as well as any semantic information the service owner whishes to make public.".

In termini pratici un contratto viene rappresentato attraverso un insieme di documenti, ognuno dei quali rappresenta un aspetto del servizio. I principali sono

1. Definitzione  del WSDL
2. Definizione dello schema XML
3. Descrizione della WS-Policy
4. SLA, Service Level Agreement

Il WSDL definisce quali funzionalita', in termini di operation e metodi, il nostro servizio espone. Lo schema XML definisce la struttura del payload fra consumer e provider. WS-Policy definisce le modalita' di accesso al servizio (es. sicurezza, crittografia, ecc.). Lo SLA definisce il livello del servizio che siamo in grado o vogliamo mantenere (es. 24x7, 99.99%).

Se noi sviluppassimo un Web Service (con ASP.NET o WCF) seguendo scrupolosamente questa procedura potremmo garantire di avere dei servizi molto solidi e scalabili nel tempo, almeno a livello contrattuale. Purtroppo questo processo oggi non avviene praticamente mai. Il processo che usiamo solitamente e' quello di partire dal codice, applicare gli attributi necessari e fare in modo che il framework esponde i contratti per noi. Il risultato e' che abbiamo contratti molto weak e poco durevoli. I principali motivi sono:

1. Si e' concentrati sul codice e non sul contratto, quindi le funzionalita' che il servizio deve veramente erogare
2. Il contratto e' impreciso in quanto molti frameworks non supportano pienamente le restrizioni degli schemi XML e WSDL
3. Diventa troppo 'facile' cambiare il contratto, edit and build
4. In molti frameworks non vi e' alcuna validazione a runtime dello schema
5. I deserializzatori del payload SOAP sono molto spessi troppo flessibili

Sebbene abbiamo un certo tipo di supporto dai tool di sviluppo per il mondo dei servizi basati su SOAP, meno evidente e' la definizione dei contratti nel mondo ROA (o REST). Sun ha pubblicato una proposta nel 2006 chiamata WADL, Web Application Description Language. Dopo due anni mi sembra sia rimasta abbastanza lettera morta. L'alternativa consiste in WSDL 2.0, nella quale vi e' una buona apertura al binding sul protocollo HTTP.

Quindi, se andiamo a stilare la stessaa lista usata per SOA in ROA, possiamo scrivere:

1. Definitzione  del WSDL 2.0
2. Definizione dello schema XML
3. Descrizione della policy su protocollo HTTP
4. SLA, Service Level Agreement

Che manca allora? Direi una buona coperyura da parte degli strumenti di sviluppo.

 

author: Pierre Greborio | posted @ domenica 11 maggio 2008 15.08 | Feedback (0)