Ridimensionare la tua applicazione web: passaggi di base

Pubblicato: 2022-09-13

Non basta creare applicazioni per il tuo business, devi ottimizzarle. Un modo efficace è quello di scalare. In questo articolo imparerai l'ottimizzazione del codice, l'ottimizzazione dell'architettura e come creare applicazioni Web scalabili in generale.

Ottimizzazione

Gearheart suggerisce di porsi le seguenti domande:

  • le query del database sono ottimali (analisi EXPLAIN, utilizzo di indici)?
  • i dati sono archiviati correttamente (SQL vs NoSQL)?
  • viene utilizzata la memorizzazione nella cache?
  • nessuna richiesta non necessaria alle FS o al database?
  • gli algoritmi di elaborazione dati sono ottimali?
  • le impostazioni dell'ambiente sono ottimali: Apache/Nginx, MySQL/PostgreSQL, PHP/Python?

Ciascuna di queste domande potrebbe essere trattata in un articolo separato, quindi una considerazione dettagliata di esse nell'ambito di questo articolo è chiaramente eccessiva. È importante capire che prima di iniziare a ridimensionare un'applicazione, è altamente auspicabile ottimizzarne il più possibile il lavoro, infatti è possibile che non sia necessario alcun ridimensionamento.

Ridimensionamento

Supponiamo di aver già ottimizzato la tua applicazione, ma non è ancora in grado di gestire il carico. In questo caso, la soluzione ovvia è distribuire l'applicazione su più host per aumentare le prestazioni complessive dell'applicazione aumentando le risorse disponibili. Questo approccio è ufficialmente chiamato "ridimensionamento" dell'applicazione. Più precisamente, la scalabilità è la capacità di un sistema di aumentare le proprie prestazioni aumentando la quantità di risorse a sua disposizione.

Esistono due tipi di scalabilità: verticale e orizzontale. La scalabilità verticale implica l'aumento delle prestazioni dell'applicazione aggiungendo risorse (CPU, memoria, disco) all'interno di un nodo (host). Il ridimensionamento orizzontale è tipico per le applicazioni distribuite e implica l'aumento delle prestazioni dell'applicazione aggiungendo un altro nodo.

È chiaro che il modo più semplice è un semplice aggiornamento dell'hardware (processore, memoria, disco), ovvero il ridimensionamento verticale. Inoltre, questo approccio non richiede alcuna modifica all'applicazione. Tuttavia, il ridimensionamento verticale raggiunge rapidamente il suo limite, dopodiché lo sviluppatore e l'amministratore non hanno altra scelta che passare al ridimensionamento orizzontale dell'applicazione.

Architettura dell'applicazione

La maggior parte delle applicazioni web sono distribuite a priori, perché la loro architettura può essere suddivisa in almeno tre livelli: web-server, business logic (applicazione), dati (database, statico).

Ciascuno di questi livelli può essere ridimensionato. Quindi, se il tuo sistema ha un'applicazione e un database che risiedono sullo stesso host, il primo passo dovrebbe essere sicuramente quello di separarli su host diversi.

Il collo di bottiglia

Procedendo allo scaling del sistema, la prima cosa da fare è determinare quale dei layer è il “collo di bottiglia”, cioè più lento del resto del sistema. Per cominciare, puoi utilizzare utilità banali come top (htop) per valutare il consumo di CPU/memoria e df, iostat per valutare il consumo del disco. Tuttavia, è desiderabile fornire un host separato con un'emulazione del carico di battaglia (usando AB o JMeter), su cui è possibile profilare l'applicazione utilizzando utilità come xdebug, oprofile e così via. Puoi usare utilità come pgFouine per identificare query di database ristrette (ovviamente, è meglio farlo in base ai registri del server di battaglia).

Di solito dipende dall'architettura dell'applicazione, ma in generale i candidati più probabili per un collo di bottiglia sono il database e il codice. Se l'applicazione gestisce molti dati utente, è probabile che il collo di bottiglia sia l'archiviazione statica.

Ridimensionamento del database

Come accennato in precedenza, il collo di bottiglia nelle applicazioni moderne è spesso il database. I problemi con esso sono generalmente divisi in due classi: prestazioni e necessità di archiviare una grande quantità di dati.

È possibile ridurre il carico sul database suddividendolo in più host. C'è un'acuta difficoltà di sincronizzazione tra di loro, che può essere risolta implementando lo schema master/slave con replica sincrona o asincrona. Per PostgreSQL, puoi usare Slony-I per la replica sincrona e PgPool-II o WAL (9.0) per la replica asincrona. Per risolvere il problema della suddivisione delle richieste di lettura e scrittura, oltre a bilanciare il carico tra gli slave, è possibile configurare uno speciale livello di accesso al database (PgPool-II).

La preoccupazione di archiviare grandi quantità di dati in caso di database relazionali può essere risolta mediante il partizionamento ("partizionamento" in PostgreSQL) o distribuendo il database su un database distribuito come Hadoop DFS.

Puoi leggere entrambe le soluzioni nell'eccellente libro sulla configurazione di PostgreSQL.

1.Tuttavia, per archiviare grandi quantità di dati, la soluzione migliore è lo sharding, che è un vantaggio intrinseco della maggior parte dei database NoSQL (ad es. MongoDB).

2. Inoltre, i database NoSQL in generale funzionano più velocemente dei loro fratelli SQL a causa della mancanza di sovraccarico per l'analisi/ottimizzazione della query, il controllo dell'integrità della struttura dei dati, ecc. Anche l'argomento del confronto dei database relazionali e NoSQL è piuttosto ampio e merita un articolo separato.

3.Separatamente degna di nota è l'esperienza di Facebook, che utilizza MySQL senza selezioni JOIN. Questa strategia consente loro di ridimensionare il database molto più facilmente, trasferendo il carico dal database al codice, che, come verrà descritto di seguito, si ridimensiona più facilmente del database.

Ridimensionamento del codice

  • Le complessità del codice di ridimensionamento dipendono dal numero di risorse condivise necessarie ai tuoi host per eseguire la tua applicazione. Saranno solo sessioni o dovrai condividere cache e file? In ogni caso, la prima cosa da fare è eseguire copie dell'applicazione su più host con lo stesso ambiente.
  • Successivamente, è necessario impostare il bilanciamento del carico/richiesta tra questi host. Puoi farlo sia su TCP (HAProxy), HTTP (nginx) o DNS.
  • Il passaggio successivo, menzionato da Gearheart, è rendere disponibili i file statici, la cache e le sessioni dell'applicazione Web su ciascun host. Per le sessioni, puoi utilizzare un server che funziona in rete (ad esempio, Memcached). Come server cache, ha senso utilizzare lo stesso Memcached, ma su un host diverso, ovviamente.
  • I file statici possono essere montati da alcuni archivi di file condivisi tramite NFS/CIFS o utilizzando FS distribuito (HDFS, GlusterFS, Ceph).

E' anche possibile archiviare file in un database (es. Mongo GridFS), risolvendo così il problema della disponibilità e della scalabilità (tenendo conto che per il database NoSQL il problema della scalabilità viene risolto tramite sharding).

Separatamente degno di nota, il problema della distribuzione su più host. Come assicurarsi che l'utente facendo clic su "Aggiorna" non veda versioni diverse dell'applicazione? La soluzione più semplice, a mio avviso, sarebbe quella di escludere dal config load balancer (web-server) gli host che non vengono aggiornati e accenderli in sequenza man mano che vengono effettuati gli aggiornamenti. Puoi anche associare utenti a host specifici tramite cookie o IP. Se l'aggiornamento richiede modifiche significative al database, il modo più semplice è chiudere temporaneamente il progetto.