Premessa

Availability. Una parola che un architetto software dovrebbe sentire continuamente rimbombare nel proprio cranio. Scalability. Optimization. Performance. Security. Potrei star qui a indicarne ancora decine e non sarebbero mai abbastanza. Ma cosa vogliono dire, nella pratica, queste parole? Come si realizzano architetture che consentano ad un applicazione di rispondere a questi requisiti? E perchè non possiamo semplicemente fregarcene comprando magari un hosting in cloud a 25€ l’anno e piazzandoci sopra il nostro Super Software Stampa Soldi?

Partiamo dall’inizio. Cosa vuole il cliente che ci commissiona un’applicazione?

  • Che il suo software faccia quello che lui si aspetta che debba fare (se siete stati bravi avete fatto un preventivo con in mano delle specifiche tecniche concordate col cliente… se invece non l’avete fatto state invece probabilmente ancora litigando col cliente che via via aggiunge una “funzioncina da 5 minuti di lavoro”)
  • Che il software gli costi il giusto

Cosa il cliente non sa ancora di volere (ma lo pretenderà presto)?

  • Che la sua applicazione sia online 24 ore al giorno per tutti i giorni dell’anno ( => Availability)
  • Che la sua applicazione sia veloce a rispondere (=> Performance )
  • Che i server non gli costino un’occhio della testa (=> Optimization)
  • Che l’applicazione sia sicura (=> Security)

e per applicazioni che cambiano nel tempo (nel numero di dati che contengono o nel numero di utenti che la usano),

  • Che al mutare dei dati / utenti valgano tutte le suddette richieste (=> Scalability)

Si scrivono libri interi per ognuna di queste keyword, quindi lungi da me dal voler sviscerare il problema. Ho deciso di scrivere però qualche POST raccogliendo alcune idee che possono aiutare a non commettere errori di progettazione che garantiscono notti insonni sia al programmatore sia al committente. Oggi parleremo di…

Availability

Se affermiamo che la nostra applicazione garantisce High Availability, stiamo dicendo che da dovunque arrivino le richieste e in qualunque momento la nostra applicazione risponderà alle richieste in maniera pronta. Garantire il 100% di Availability è però impossibile per chiunque per cui solitamente la lotta tra competitor di Availabilty si fa a colpi di decimali che seguono il 99%.

Esistono availability pattern e antipattern e non staremo qui a vederli tutti uno per uno: il concetto che sta alla base, in questo caso è abbastanza semplice

Evitare come la peste i cosiddetti Single Point of Failure (SPOF)

Cosa vuol dire? Se mettiamo la nostra piattaforma su un singolo server, che magari ospita Webserver, Database, Interprete del linguaggio di scripting, magari Redis per la cache e qualche script in batch che esegue operazioni su dati/file e la mandiamo in produzione, siamo stati bravi?

Qualcuno si sta sbellicando dal ridere (eppure giuro, c’è ancora chi lo fa “Tanto accendo la cache!”).

Cosa succede se il server si pianta (se non vi è mai successo forse non avete fatto questo lavoro per un tempo sufficiente)?

Ma siamo meno drastici. Cosa succede se si pianta il database? O il Webserver? Semplice: la nostra applicazione smette di funzionare. Il nostro sistema è composto da una marea di SPOF.

Come eliminarli? Risposta breve: suddividiamo i servizi su più server ridondati messi dietro ad un Load Balancer.

Ma costa di piu!

… si e no. Costa leggermente di più in alcuni casi, ma non sempre. Se prendiamo una macchina in cloud che deve far tutto, probabilmente sarà inizialmente sovradimensionata (stiamo buttando soldi) e quando uno solo dei servizi che ci gira ha bisogno di più risorse, andremo a scalare verticalmente prendendo un server più grande che costerà il doppio (e magari i webserver non ne avevano bisogno). Al contrario possiamo prendere tante macchine più piccole dimensionandole precisamente per le esigenze dei servizi che ci girano e garantendoci anche la possibilità si scalare orizzontalmente la nostra architettura con costi molto inferiori.

Ma il Load Balancer diventa un SPOF!?

Vero… ma ci sono diversi modi per evitare che questo diventi un problema e ne vedremo alcuni.

Usando dei LB e suddividendo i servizi su istanze multiple, non solo potremmo costruire architetture ridondate e quindi con availability più alta, ma distribuiremo anche meglio il carico e le richieste tra tutte le macchine, garantendoci al contempo la possibilità di scalare orizzontalmente la nostra architettura di quel tanto che basta per offrire un servizio continuo senza spendere un occhio della testa.

ATTENZIONE: non sono un sistemista e non vi dirò COME configurare un LB su AWS. Un architetto sa quali sono le tecnologie, in che modo si usano e quali sono i vantaggi o gli svantaggi in base alle esigenze del proprio cliente. Per REALIZZARE le architetture, lasciamo fare ai sistemisti. Ad ognuno il suo lavoro.

Considerazioni preliminari

Se fino a ieri abbiamo installato il nostro framework preferito su una singola macchina ed oggi ci sentiamo pronti a fare il grande salto pensando che basti clonare un’ appServer perchè tutto continui a funzionare come prima, ovviamente ci sbagliamo di grosso.

Pensiamo per esempio a WordPress, che chiunque avrà installato nella propria vita. Quando andiamo a creare un POST nel blog e uploadiamo un’immagine, la stessa immagine verrà salvata sul server. Ma se i server sono due? o quattro? Abbiamo intuito quale sarà la prima problematica da affrontare:

come ci organizziamo con lo storage?

Per chi ha poi scritto un modulo di autenticazione o un carrello in PHP, sa che per ogni utente che utilizza la piattaforma, esiste un “file” di sessione sul server che contiene importanti informazioni sullo “stato” della navigazione dell’utente (è loggato? quanti prodotti ha nel carrello?). Non tutte le applicazioni web comunque sono stateful… le applicazioni possono essere scritte anche in modo da essere stateless. Se la nostra applicazione è stateful (fa uso di sessioni) dobbiamo fare molta attenzione perchè se l’utente viene indirizzato verso il server A, dove effettua il login, solo il server A possiederà questa informazione (comportamento di default). Se dopo 5 minuti il bilanciatore lo indirizza verso il server B, si ritroverà ad essere non loggato.

Quindi la cosa da tenere in considerazione al momento della scelta dell’architettura è:

la nostra applicazione è stateful o stateless?

Storage condiviso

Su AWS, abbiamo fondamentalmente 3 tipi di storage a disposizione: EBS, EFS ed S3 (tralasciamo Glacier perchè i suoi scopi non sono quelli di fornire funzioni di file system classiche). Alle macchine EC2 solitamente montiamo volumi EBS, che hanno un costo relativamente basso ma hanno lo svantaggio di poter essere montati solo su una EC2 per volta. S3 dal canto suo NON è un file system vero e proprio: si tratta piuttosto di un sistema di storage con accesso ai file basato su hash. Il suo costo per GB è basso, ma presenta alcune limitazioni che è necessario ponderare. E’ possibile montare con fuse un bucket S3 in locale su macchine EC2 (s3fs) ma Amazon non lo consiglia (in realtà non menziona mai neppure la possibilità che si possa fare) suggerendo invece l’uso delle proprie SDK per interagire con i bucket. EFS è una soluzione abbastanza nuova: si tratta di volumi di storage condiviso che possono essere montati su più macchine EC2 contemporaneamente ed hanno prestazioni (e costi) più alti. Vediamo in una tabella le principali differenze:

Caratteristiche ServizioAmazon S3Amazon EBSAmazon EFS
Dimensione dello StorageNessun limite al numero di oggettiMassimo 16TB per volumeNon ci sono limitazioni alle dimensioni del filesystem
Dimensione dei fileDa 0b a 5TB per ciascun fileNessuna limitazione sulla dimensione dei fileMassimo 52TB per file
Throughput dei dati e I/OSupporta multipart upload. Un singolo PUT deve essere MAX 5GB (quindi se il file è più grande siamo obbligati ad usare il multipart, raccomandato già da 100MB)E’ possibile scegliere tra SSD e HDD ed è raccomandato l’utilizzo del provisioned IOPSIl throughput di default è 3GB
Performance100 PUT/LIST/DELETE richieste al secondo di default (scalabili facilmente a 300 o 800 GET al secondo)I volumi possono essere scalati in dimensione senza spegnere l’istanza. In General Purpose 3 IOPS x GB, con il provisioned IOPS si hanno migliori performanceAltamente scalabile fino a 7000 operazioni al secondo su filesystem
Salvataggio dei datiI dati risiedono in un’unica regione. E’ possibile replicarli in un altra regione con apposito toolI dati risiedono nella Availability Zone. Le repliche sono all’interno della AZ.Dati e repliche stanno in una stessa regione
AccessoDiretto da internet (in base alle policy), con sdk dalle EC2 e montato con s3fs localmente su molteplici EC2 (non suggerito da AWS)Può essere montato su una EC2 per voltaSi può montare contemporaneamente su più EC2 (è un vero e proprio file system condiviso)
File system / permessiPuò essere montato come file system (non raccomandato). I permessi non si ereditano nelle sottocartelle. Può essere difficoltoso l’accesso diretto alle risorse da applicazioni mobile che non usino l’SDK standard (es. Nativescript + Cognito) Ext3 Ext4Può essere visto come un normale NFS, montabile anche dai server di una rete aziendale
Availability99.99%99.99%Alta (valore esatto non dichiarato)
ConsistenzaSono segnalati occasionali problemi di consistenza a causa di sovrascritture da PUT o DELETE in tutte le regioniNon ci sono problemi segnalatiNon ci sono problemi segnalati
CostiEsempio per regione EU Irlanda:
– Storage: circa 0,022USD per GB
– Richieste PUT, COPY, POST o LIST 0,005 USD per 1.000 richieste
– Trasferimento dati in uscita vs Internet (oltre 1GB/mese) circa 0,09 USD per GB
– Trasferimento dati tra zone diverse: 0,02USD
Costi altamente variabili. Con 10GB SSD provisioned (100I/O al secondo) si spenderebbero 8,5USD quindi 0,85USD per GB/mese. Non provisioned il costo si aggira sui 0,11USD per GB/meseCirca 0,33 USD per GB/mese + 6,6USD per MB/sec di banda richiesta

ObjectiveFs come alternativa

Fin’ora abbiamo elencato soluzioni direttamente fornite da AWS, anche se abbiamo menzionato S3Fs. Esiste un alternativa, che non ho mai testato sul campo, ma che sembra interessante per i progetti con un budget medio alto (il loro pricing plan più basso parte da 50$ al mese per un utilizzo su 3 istanze, max 5 TB + il costo vivo dello storage che viene calcolato su S3). Sul sito ufficiale è possibile trovare test comparativi e una descrizione dettagliata del suo funzionamento: si tratta di un FileSystem vero e proprio (e in questo è simile a NFS), montabile contemporaneamente da più istanze, che usa come endpoint un (vostro) bucket S3. A differenza di S3Fs però è totalmente POSIX compatibile, con un interfaccia standard, costruito on top di S3. S3fs invece è un’astrazione per vedere S3 come fosse un file system ma non supporta tutte le normali operazioni che un filesystem consente.   Sul sito ufficiale viene dato come molto più performante e sicuro di EFS, la soluzione di Storage condiviso offerta da AWS stessa. Personalmente gli darei una possibilità solo su progetti in cui il cliente può spendere >1000$ al mese per l’infrastruttura oppure con richieste specifiche di storage non esagerate: il piano base (50$ al mese) potrebbe andare bene per siti con un limite di 5TB di dati, con 2-3 server che montano ObjectiveFS, scalabili  max  a 10 servers (in quel caso si spenderebbero 120$/mese). Quindi se rimaniamo sotto queste soglie possiamo considerarla un’alternativa percorribile. Al di sopra di queste soglie (siti che hanno terabyte di dati video potrebbero starci nelle soglie sul numero di server ma non nei limiti di capienza) le cifre salgono…

Come possiamo vedere, quando decidiamo di passare ad una architettura condivisa, la scelta dello storage da usare diventa fondamentale ed è fortemente dipendente:

  • Dalla piattaforma che stiamo usando: se siamo noi a scrivere il framework possiamo usare tutti i tipi di storage visto che possiamo scrivere noi la logica di accesso ai file; se invece stiamo usando un CMS specifico è possibile che ci si debba orientare verso soluzioni previste dagli sviluppatori del CMS stesso o da plugin che siano stati già sviluppati
  • Dalle performance di cui necessitiamo: S3 costa molto poco, ma la velocità di accesso alle risorse e la gestione dei permessi sui file non è gestibile come su un file system vero e proprio
  • Dal budget / tempo di sviluppo a disposizione: EFS è molto performante ma ha costi neppure paragonabili con quelli di S3 o EBS

Ma quindi? Quale storage usiamo per migrare la nostra piattaforma da un’architettura mono-server ad un’architettura ridondata?

Dipende. Farò qui degli ESEMPI concreti di scenari reali (alcuni li ho affrontati direttamente in progetti passati, altri invece sono plausibili e riporto il ragionamento che farei nella scelta.

Scenario – Migrazione WordPress

Abbiamo una installazione WordPress (single site o multisite) che vogliamo rendere fault tolerant portandola su macchine EC2.

Abbiamo già quindi le cartelle contenenti i file media caricati sul sito con i relativi riferimenti nel database. A seconda del budget e delle performance richieste si possono fare le seguenti scelte:

  • Raccomandato da AWS: montare partizioni EFS nelle stesse posizioni in cui si aspetta di trovarle WP. Pro: prestazioni elevate, nessuna modifica al framework. Contro: costi mensili maggiori, servizio nuovo quindi soggetto a bugs/disservizi (alla data attuale si trovano diverse esperienze negative), la banda assegnata è proporzionale alla dimensione del volume, per cui è veloce solo se si hanno diversi GB di file (alcuni preferiscono copiare 20GB di file inutili, pagando più storage ma con un throughput dedicato piu alto)
  • Testato personalmente: Installare un plugin per l’utilizzo di bucket S3 per i media file. Pro: occorre installare solo un Plugin, costi mensili bassi. Contro: possibili conflitti con altri plugin (recentemente hanno inglobato AWS PHP SDK v2 con namespace unicizzato che migliora sensibilimente le cose). Fa affidamento su processi di background che possono comunque andare in errore o impiegare più tempo del previsto a terminare. In installazioni multisite non si possono separare i file in bucket differenti

Scenario – CMS custom realizzato da noi

Possiamo sempre utilizzare la soluzione EC2 + EFS come raccomandato da Amazon e sviluppare la nostra applicazione come se dovesse girare su mono-server. Se vogliamo rimanere sul sicuro e tenere i costi periodici bassi (con ovviamente qualche compromesso in termini di tempi di sviluppo e di performance assolute) utilizzerei l’AWS SDK (in PHP o in qualunque altro linguaggio ci occorra) per usare BUCKET S3 per i nostri file statici (tutti, compresi file CSS e Javascript) in modo da poter usare CloudFront. Questo vuol dire gestire a livello di interfaccia e di usabilità dei processi ASINCRONI di upload e pubblicazione dei contenuti. Teniamo presente che S3 ha una feature molto interessante: la creazione / cancellazione di file su un bucket può essere utilizzato come trigger per lanciare delle funzioni batch serverless (lambda functions) che possono occuparsi ad esempio di ridimensionamento e creazione di thumbnails, oltre che di aggiornamento di dati all’interno dei nostri database.

Nel prossimo articolo continueremo la discussione sulla migrazione di applicazioni su architetture ridondate partendo dalla questione “informazioni di sessione”.

Facebook Comments