Ai Community Days di quest’anno abbiamo sproloquiato di scalabilità, una delle cose su cui ho cercato di insistere è che per poter parlare di scalabilità è necessario in primis essere in grado di misurare e definire quali sono i requisiti che devono essere rispettati.

Questo per un semplicissimo motivo: la scalabilità infinita è un mito, fine, è il requisito che ci deve far scegliere come scalare e non la semplice necessità di scalare.

Facciamo un esempio banale e un po’ astratto per capire:

Abbiamo un bel sito web di e-commerce, piccolo piccolo che fa un gran bene il suo sporco lavoro, basato sul più semplice degli stack, un’applicazione ASP.Net MVC e un database SQL Server con cui dialoghiamo grazie a EntityFramework.

Niente di trascendentale, funziona e fa quello che deve fare con nostra gran soddisfazione. Ad un certo punto cominciamo a ricevere qualche segnalazione dagli utenti che ogni tanto le pagine vanno in time-out e che in generale il sito è lentino.

Andiamo a guardare i log di IIS e del nostro gestore di statistiche preferito e ci rendiamo conto che le visite sono sensibilmente aumentate a tal punto da rendere un problema l’infrastruttura hardware su cui giriamo.

Abbiamo due opzioni:

  • pensare che siamo diventati di punto in bianco Amazon e quindi dire dobbiamo investire un sacco di soldi e rifare tutto, cambiando completamente approccio con tutti i vantaggi ma anche i problemi potenziali che ci portiamo a casa, oltre al dettaglio dei costi;
  • Comperare un SSD da 1TB e dare una nuova vita alla macchina che ospita il tutto;

Sempre iper-semplificando il “dev” sceglierebbe l’opzione 1 il “manager” l’opzione 2 (non polemizzate grazie, è così, e io nella vita faccio il “dev”), vi ricordo che ho scritto iper-semplificando, sto stigmatizzando due posizioni tipiche.

Non è detto che la scelta del “dev” sia quella sbagliata, se non altro tecnicamente, ma nell’esempietto di cui sopra la scelta non tiene conto del requisito che è banalmente aumentare la responsività di un’applicazione il cui problema è noto e non è di andare sulla luna e un investimento di 500€ circa è semplicemente la soluzione, fine.

Il processo di evasione di un ordine

Un esempio molto più interessante, e lascio alla vostra immaginazione calarlo in un contesto informatico basato su code e messaggi, è quello della gestione/evasione ordini in un sistema in cui la logistica è fondamentale. C’è uno spartiacque interessante che è generalmente definito non dalla quantità di ordini che si devono evadere in un’unità temporale (come sarebbe lecito pensare a prima vista) ma dalla numerosità di oggetti ordinabili e di conseguenza dalla dimensione fisica (o dalla logistica se volete vederla da un altro punto di vista) del magazzino.

Single picker per batch

Sotto una certa dimensione ha molto senso che ogni ordine sia evaso da un singolo operatore, in una sola volta, ergo:

  1. arriva l’ordine;
  2. l’operatore recupera dal magazzino tutti gli oggetti ordinati;
  3. l’ordine viene evaso;

Semplice, efficace ed efficiente.

Cambio di requisiti: aumentare la capacità di evasione

Se adesso prendiamo il nostro magazzino e lo raddoppiamo di dimensioni succede una cosa interessante: l’operatore fa un sacco di strada perché ovviamente gli oggetti in un ordine sono totalmente imprevedibili e di conseguenza lo stoccaggio in magazzino è spesso non adeguato all’ordine che si sta evadendo, quello che succede è che sulla carta il raddoppio dello spazio di stoccaggio che doveva aumentare la capacità di evasione degli ordini ha l’effetto invece di diminuirla perché ogni operatore investe un sacco di tempo in più in percorsi all’interno del magazzino.

Il processo di cui sopra resta semplice ed efficace (perché l’ordine viene comunque evaso) ma diventa decisamente inefficiente. In questo scenario aumentare il numero di operatori (il disco SSD dell’esempietto) non è un’opzione perché il costo non è sostenibile.

Siccome in un processo del genere l’efficienza è quasi tutto, diventa necessario cambiare approccio. Radicalmente.

Multiple picker per batch

Il cambio di approccio comporta però un cambio radicale di architettura che in questo caso è sostenuta dal requisito e dal “problema” incontrato, il cambio è talmente importante che comporta rifare tutto:

  1. Ad ogni operatore viene assegnata una zona operativa all’interno del magazzino, e potrà recuperare oggetti solo da quella zona che è quella che gli fa fare meno strada;
  2. Arriva l’ordine;
  3. c’è una fase di triage che “spacca” l’ordine in sotto-ordini base alla posizione degli oggetti in magazzino;
  4. i sotto-ordini vengono assegnati agli operatori;
  5. ogni operatore elabora la sua porzione di ordine portando gli oggetti in una zona di assemblaggio;
  6. una seconda passata, a questo punto fatta in una zona meno ampia, permette di ricomporre i sotto-ordini;
  7. l’ordine viene evaso;

Siamo passati da 3 passaggi a 7 ma l’efficienza complessiva è mostruosamente migliorata.

Dilemmi

La domanda a questo punto è: la seconda soluzione è migliore?

In termini assoluti assolutamente no, lo è in funzione dei requisiti. Oltre una certa soglia di dimensione del magazzino e un certo numero di ordini da evadere la seconda soluzione è migliore della prima, sotto quelle soglie la prima è migliore.

Conclusioni

Ogni soluzione ad un problema è una soluzione al problema specifico, anche un pattern architetturale (quindi una generalizzazione) è una soluzione ad un set finito di problemi non a tutti i problemi di quella tipologia, è quindi lecito aspettarsi che al cambiare dei requisiti la soluzione scelta in precedenza sia da cambiare ma non per questo la soluzione precedente è necessariamente sbagliata magari lo è stata la raccolta dei requisiti, o molto più semplicemente lo scenario è cambiato, mettiamoci l’anima in pace.

Come la scalabilità infinita è un mito anche la soluzione perfetta lo è, fatevene una ragione.

.m