
Oggi, l'alta disponibilità dei servizi è necessaria ovunque, non solo nei grandi e costosi progetti. Siti temporaneamente non disponibili con il messaggio "Scusate, manutenzione in corso" possono ancora comparire, ma di solito suscitano un sorriso benevolo. Aggiungiamo a questo la vita nel cloud, dove per avviare un server aggiuntivo è sufficiente una sola chiamata all'API, senza doversi preoccupare della gestione dell'hardware. Non ci sono più scuse per cui un sistema critico non possa essere realizzato in modo affidabile utilizzando tecnologie di clustering e di backup.
Vi racconteremo quali soluzioni abbiamo considerato per garantire l'affidabilità dei database nei nostri servizi e a quali conclusioni siamo giunti. Inoltre, presenteremo una demo con approfondimenti.
Legacy nell'architettura di alta disponibilità
Questo è ancora più evidente nel contesto dello sviluppo di diversi sistemi open source. Le soluzioni più vecchie hanno dovuto integrare tecnologie di alta disponibilità man mano che cresceva la domanda, con qualità variabile. Le soluzioni di nuova generazione pongono l'alta disponibilità alla base della loro architettura. Ad esempio, MongoDB posiziona il cluster come la principale modalità d'uso. Il cluster si scala orizzontalmente, rappresentando un forte vantaggio competitivo per questo DBMS.
Ritorniamo a PostgreSQL. Questo è uno dei progetti open source più antichi e popolari, il cui primo rilascio risale al 1995. Per molto tempo, il team del progetto non ha considerato l'alta disponibilità come una questione da affrontare a livello di sistema. Di conseguenza, la tecnologia di replicazione per la creazione di copie dei dati è stata integrata solo nella versione 8.2 nel 2006, ma era basata su file (log shipping). Nel 2010, con la versione 9.0, è stata introdotta la replicazione in streaming, che è alla base della creazione di vari cluster. Questo, in effetti, sorprende le persone che si avvicinano a PostgreSQL dopo aver utilizzato Enterprise SQL o moderne soluzioni NoSQL: la soluzione standard dalla comunità è semplicemente una coppia master-replica con replicazione sincrona o asincrona. In questo contesto, il passaggio del master avviene manualmente, e anche la questione del passaggio dei clienti è lasciata alla gestione autonoma.
Come abbiamo deciso di rendere PostgreSQL affidabile e quali scelte abbiamo fatto in merito
Tuttavia, PostgreSQL non sarebbe diventato così popolare se non fosse stato per l'enorme quantità di progetti e strumenti che aiutano a costruire una soluzione resistente, che non richiede attenzione costante. Nel cloud (MCS) sin dal lancio del DBaaS sono stati disponibili server PostgreSQL singoli e coppie master-replica con replica asincrona.
Naturalmente, volevamo semplificare la vita a tutti e rendere accessibile un'installazione di PostgreSQL che potesse fungere da base per servizi ad alta disponibilità, senza la necessità di monitoraggio costante e di svegliarsi di notte per effettuare il failover. In questo segmento esistono sia soluzioni consolidate che nuove utilità che utilizzano le tecnologie più recenti.
Oggi, il problema dell'alta disponibilità non riguarda solo il backup (questo è scontato), ma il consenso — l'algoritmo di elezione del leader (Leader election). Spesso, i grandi fallimenti non avvengono a causa della mancanza di server, ma a causa di problemi di consenso: non è stato scelto un nuovo leader, ci sono stati due leader in diversi data center, ecc. Un esempio è il fallimento del cluster MySQL di Github — hanno scritto .
La base matematica su questo tema è molto seria. Da un lato, esiste , che pone limiti teorici alle possibilità di costruzione di soluzioni HA, mentre dall'altra parte ci sono algoritmi matematicamente provati per la determinazione del consenso, come e . Su questa base esistono sistemi DCS (sistemi di consenso decentralizzati) piuttosto popolari — Zookeeper, etcd, Consul. Pertanto, se il sistema di decisione opera su un proprio algoritmo scritto autonomamente, è fondamentale trattarlo con grande cautela. Analizzando un'enorme quantità di sistemi, abbiamo scelto Patroni — un sistema open source, in gran parte sviluppato da Zalando.
Come nota di colore, posso dire che abbiamo anche considerato soluzioni multi-master, ovvero cluster che possono essere scalati orizzontalmente per la scrittura. Tuttavia, per due principali motivi, abbiamo deciso di non implementare un tale cluster. In primo luogo, tali soluzioni presentano un'elevata complessità e, di conseguenza, più punti vulnerabili. Sarà difficile creare una soluzione stabile per tutti i casi. In secondo luogo, in tal caso, PostgreSQL smette di essere puro (native), alcune funzionalità potrebbero non essere disponibili e potrebbero verificarsi bug nascosti in alcune applicazioni durante l'uso.
Patroni
Quindi, come funziona Patroni? Gli sviluppatori non hanno reinventato la ruota e hanno proposto di basarsi su una delle soluzioni DCS collaudate. Si occupa da solo di tutte le questioni legate alla sincronizzazione delle configurazioni, alla scelta del leader e al quorum. Abbiamo scelto di utilizzare etcd per questo.
Successivamente, Patroni si occupa dell'applicazione corretta di tutte le impostazioni su PostgreSQL e delle configurazioni di replica, nonché dell'esecuzione dei comandi di switchover e failover (cioè, il passaggio normale e anomalo del master). Specificamente nel cloud MCS è possibile creare un cluster composto da un master, una replica sincrona e una o più repliche asincrone. La presenza di una replica sincrona garantisce la sicurezza dei dati almeno su 2 server, e sarà proprio questa replica a diventare il principale "candidato al master".
Poiché etcd viene distribuito sugli stessi server, si raccomanda un numero di server pari a 3 o 5 per un valore ottimale di quorum. Tale cluster si scalda orizzontalmente per la lettura (ho già scritto sopra riguardo allo scaling per la scrittura). Tuttavia, è importante considerare che le repliche asincrone tendono a rimanere indietro, specialmente sotto carichi elevati.
L'uso di repliche in lettura (hot standby) è giustificato per compiti di reportistica o analisi, alleggerendo il server master.
Se desideri creare un cluster di questo tipo autonomamente, avrai bisogno di:
- preparare 3 o più server, configurare l'assegnazione degli indirizzi IP e le regole del firewall tra di essi;
- installare i pacchetti per i servizi etcd, Patroni, PostgreSQL;
- configurare il cluster etcd;
- configurare il servizio patroni per lavorare con PostgreSQL.
In totale, sarà necessario redigere correttamente una decina di file di configurazione senza fare errori. Per questo, è consigliabile utilizzare uno strumento di gestione della configurazione, come Ansible, ad esempio. Tuttavia, non è presente un bilanciatore TCP ad alta disponibilità. Crearlo è un lavoro a parte.
Per coloro che necessitano di un cluster pronto, ma non vogliono occuparsi di tutto ciò, abbiamo cercato di semplificare la vita creando un cluster pronto su Patroni nel nostro cloud, che può essere testato gratuitamente. Oltre al cluster stesso, abbiamo creato:
- un bilanciatore TCP; su diverse porte indica sempre il master attuale, la replica sincrona o asincrona, a seconda dei casi;
- un'API per cambiare il master attivo di Patroni.
Possono essere configurati sia tramite l'API di MCS Cloud che tramite la console web.
Demo
Per testare le capacità del cluster PostgreSQL nel cloud MCS, vediamo come si comporta un'applicazione reale in caso di problemi con il DBMS.
Di seguito è riportato il codice di un'applicazione che registrerà eventi artificiali e li mostrerà a schermo. In caso di errori, ne darà notifica e continuerà a funzionare in un ciclo finché non lo interromperemo con la combinazione Ctrl + C.
from __future__ import print_function
from datetime import datetime
from random import randint
from time import sleep
import psycopg2
def main():
try:
connection = psycopg2.connect(user = "admin",
password = "P@ssw0rd",
host = "89.208.87.38",
port = "5432",
database = "myproddb")
cursor = connection.cursor()
cursor.execute("SELECT version();")
record = cursor.fetchone()
print("Connection opened to", record[0])
cursor.execute(
"INSERT INTO log VALUES ({});".format(randint(1, 10000)))
connection.commit()
cursor.execute("SELECT COUNT(event_id) from log;")
record = cursor.fetchone()
print("Logged a value, overall count: {}".format(record[0]))
except Exception as error:
print ("Error while connecting to PostgreSQL", error)
finally:
if connection:
cursor.close()
connection.close()
print("Connection closed")
if __name__ == '__main__':
try:
while True:
try:
print(datetime.now())
main()
sleep(3)
except Exception as e:
print("Caught error:n", e)
sleep(1)
except KeyboardInterrupt:
print("exit")
L'applicazione richiede PostgreSQL. Creiamo un cluster nel cloud MCS utilizzando l'API. In un normale terminale, dove la variabile OS_TOKEN contiene il token per accedere all'API (può essere ottenuto con il comando openstack token issue), digitiamo i comandi:
Creiamo un cluster:
cat < pgc10.json
{"cluster":{"name":"postgres10","allow_remote_access":true,"datastore":{"type":"postgresql","version":"10"},"databases":[{"name":"myproddb"}],"users":[{"databases":[{"name":"myproddb"}],"name":"admin","password":"P@ssw0rd"}],"instances":[{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}},{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}},{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}}]}}
EOF
curl -s -H "X-Auth-Token: $OS_TOKEN"
-H 'Accept: application/json'
-H 'Content-Type: application/json'
-d @pgc10.json https://infra.mail.ru:8779/v1.0/ce2a41bbd1434013b85bdf0ba07c770f/clusters

Quando il cluster passerà allo stato ACTIVE, tutti i campi riceveranno valori aggiornati — il cluster è pronto.
In GUI:

Proviamo a connetterci e creare una tabella:
psql -h 89.208.87.38 -U admin -d myproddb
Password per l'utente admin:
psql (11.1, server 10.7)
Digitare "help" per aiuto.
myproddb=> CREATE TABLE log (event_id integer NOT NULL);
CREATE TABLE
myproddb=> INSERT INTO log VALUES (1),(2),(3);
INSERT 0 3
myproddb=> SELECT * FROM log;
event_id
----------
1
2
3
(3 righe)
myproddb=>

Nell'applicazione indicheremo le impostazioni attuali per la connessione a PostgreSQL. Indicheremo l'indirizzo del bilanciatore TCP, eliminando così la necessità di passare manualmente all'indirizzo principale. Avviamolo. Come si può vedere, gli eventi vengono registrati con successo nel database.

Switch di master programmato
Ora testeremo il funzionamento della nostra applicazione durante lo switch di master programmato:

Osserviamo l'applicazione. Possiamo notare che l'attività dell'applicazione effettivamente si interrompe, ma dura solo pochi secondi, in questo caso specifico, al massimo 9.

Guasto della macchina
Ora proviamo a simulare il guasto della macchina virtuale, attuale master. Si potrebbe semplicemente spegnere la macchina virtuale tramite l'interfaccia Horizon, ma questo sarebbe uno spegnimento corretto. Tale switch verrebbe gestito da tutti i servizi, incluso Patroni.
Abbiamo bisogno di uno spegnimento imprevisto. Pertanto, ho chiesto ai nostri amministratori di spegnere la macchina virtuale — attuale master — in modo anomalo per fini di test.

Nel frattempo, la nostra applicazione continuava a funzionare. Naturalmente, tale switch di master d'emergenza non può passare inosservato.
2019-03-29 10:45:56.071234
Connessione aperta a PostgreSQL 10.7 su x86_64-pc-linux-gnu, compilato da gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit
Valore registrato, conteggio totale: 453
Connessione chiusa
2019-03-29 10:45:59.205463
Connessione aperta a PostgreSQL 10.7 su x86_64-pc-linux-gnu, compilato da gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit
Valore registrato, conteggio totale: 454
Connessione chiusa
2019-03-29 10:46:02.661440
Errore durante la connessione al server PostgreSQL ha chiuso la connessione inaspettatamente
Questo probabilmente significa che il server è terminato in modo anomalo
prima o mentre elaborava la richiesta.
Errore catturato:
variabile locale 'connection' riferita prima dell'assegnazione
……………………………………………………….. - qui ci sono alcuni errori
2019-03-29 10:46:30.930445
Errore durante la connessione al server PostgreSQL ha chiuso la connessione inaspettatamente
Questo probabilmente significa che il server è terminato in modo anomalo
prima o mentre elaborava la richiesta.
Errore catturato:
variabile locale 'connection' riferita prima dell'assegnazione
2019-03-29 10:46:31.954399
Connessione aperta a PostgreSQL 10.7 su x86_64-pc-linux-gnu, compilato da gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit
Valore registrato, conteggio totale: 455
Connessione chiusa
2019-03-29 10:46:35.409800
Connessione aperta a PostgreSQL 10.7 su x86_64-pc-linux-gnu, compilato da gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit
Valore registrato, conteggio totale: 456
Connessione chiusa
^Cexit
Come si può vedere, l'applicazione è riuscita a riprendere la sua attività in meno di 30 secondi. Sì, un certo numero di utenti del servizio potrebbe notare i problemi. Tuttavia, si tratta di un grave guasto del server, che non accade molto spesso. Inoltre, una persona (l'amministratore) difficilmente sarebbe riuscita a reagire così rapidamente, a meno che non fosse seduto alla console con uno script di commutazione pronto.
Risultato
Ritengo che un tale cluster offra un'enorme vantaggio per gli amministratori. In sostanza, seri guasti e malfunzionamenti dei server DB non saranno percepibili per l'applicazione e, di conseguenza, per l'utente. Non sarà necessario riparare nulla in fretta o passare a configurazioni temporanee, server, ecc. E se tale soluzione viene utilizzata come servizio pronto nel cloud, non sarà necessario perdere tempo nella sua preparazione. Si potrà dedicare tempo a qualcosa di più interessante.
Fonte: habr.com
