Come gestire i vincoli di chiave esterna durante la migrazione dal monolito ai microservizi?


18

Il mio team sta migrando da un'applicazione monolitica ASP.NET a .NET Core e Kubernetes. Le modifiche al codice sembrano andare per il meglio ma ci si può aspettare ma il mio team sta incontrando molta discordia nel database.

Al momento disponiamo di un database SQL Server piuttosto grande che ospita tutti i dati per l'intera azienda. Propongo di dividere il database in modo simile alla suddivisione del codice - dati di catalogo in un database (logico), dati di inventario in un altro, ordini in un altro, ecc. - e ogni microservizio sarebbe il gatekeeper per il suo database .

L'implicazione qui è che le chiavi esterne che attraversano i confini del microservizio dovrebbero essere rimosse e che gli sprocs e le viste che raggiungono oltre i confini sarebbero proibiti. Tutti i modelli di dati possono risiedere o meno nello stesso database fisico, ma anche se lo fanno, non dovrebbero interagire direttamente tra loro. Gli ordini potrebbero comunque fare riferimento agli articoli del catalogo in base all'ID, ma l'integrità dei dati non verrebbe applicata rigorosamente a livello di database e tali dati dovranno essere uniti nel codice anziché in SQL.

Vedo la perdita di questi come compromessi necessari per passare al microservizio e ottenere i vantaggi di scalabilità che ne derivano. Finché scegliamo saggiamente le nostre cuciture e ci sviluppiamo intorno a loro, allora dovrebbe essere OK. Gli altri membri del team sono fermamente convinti che tutto deve rimanere nello stesso database monolitico, quindi tutto può essere ACID e mantenere l'integrità referenziale ovunque.

Questo mi porta alla mia domanda. Innanzitutto, la mia posizione sui vincoli delle chiavi esterne e l'unione è plausibile? In tal caso, qualcuno è a conoscenza di materiale di lettura credibile che potrei offrire ai miei colleghi? La loro posizione è quasi religiosa e non sembrano essere influenzati da qualcosa che non sia Martin Fowler che dice loro che si sbagliano.


5
L'integrità referenziale è estremamente preziosa. La scala del database è davvero il collo di bottiglia qui? Hai davvero bisogno della scalabilità in stile microservizio? Sai meglio di me se quel cambiamento di architettura è appropriato per la tua organizzazione, ma ti preghiamo di considerare che semplicemente non è adatto per molti casi d'uso. Ci potrebbero essere altri modi per scalare con compromessi più interessanti. Ad esempio, se le query del database al secondo sono troppo elevate, forse è sufficiente la replica del database. E puoi ridimensionare i server Web in orizzontale senza dover utilizzare microservizi.
amon,

Punti buoni. Stiamo esaminando alcune di queste opzioni per guadagni a breve termine. Il passaggio ai microservizi è il gioco lungo, però. A mio avviso, ci consentirà di ridimensionare per anni anziché mesi.
Raymond Saltrelli,

3
Sono sicuro che i tuoi clienti saranno entusiasti che l'ordine che hanno effettuato 0,05 ms più velocemente venga annullato perché qualcun altro ha ordinato lo stesso prodotto quando ne era rimasto solo uno in magazzino.
Andy,

@amon Rendi questa una risposta e la voterò. Questa è una buona domanda e i pro ei contro devono essere rappresentati in modo equo.
mcottle,

@mcottle ok, fatto!
amon,

Risposte:


19

Non esiste una soluzione chiara perché dipende interamente dal contesto, in particolare dalle dimensioni che il sistema dovrebbe ridimensionare e dai problemi reali. Il database è davvero il tuo collo di bottiglia?

Questa risposta (purtroppo piuttosto lunga) leggerà un po 'come "i microservizi sono cattivi, i monoliti per la vita!", Ma non è questa la mia intenzione. Il mio punto è che i microservizi e i database distribuiti possono risolvere vari problemi, ma non senza problemi propri. Al fine di presentare una valida argomentazione per la tua architettura, devi dimostrare che questi problemi non si applicano, possono essere mitigati e che questa architettura è la scelta migliore per le tue esigenze aziendali.

I dati distribuiti sono difficili.

La stessa flessibilità che consente un miglior ridimensionamento è il rovescio della medaglia delle garanzie più deboli. In particolare, i sistemi distribuiti sono molto più difficili da ragionare.

Gli aggiornamenti atomici, le transazioni, la coerenza / integrità referenziale e la durata sono estremamente preziosi e non devono essere rinunciati in modo avventato. È inutile disporre di dati se sono incompleti, obsoleti o assolutamente errati. Quando hai ACID come requisito aziendale ma stai utilizzando una tecnologia di database che non è in grado di offrirlo immediatamente (ad es. Molti database NoSQL o un'architettura DB per microservizio), la tua applicazione deve colmare il vuoto e fornire tali garanzie.

  • Questo non è impossibile da fare, ma difficile da ottenere. Molto difficile. Soprattutto in un'impostazione distribuita in cui sono presenti più writer per ciascun database. Questa difficoltà si traduce in un'alta probabilità di bug, inclusi eventualmente dati persi, dati incoerenti e così via.

    Ad esempio, si consideri la lettura delle analisi Jepsen di noti sistemi di database distribuiti , forse a partire dall'analisi di Cassandra . Non capisco la metà di tale analisi, ma il TL; DR è che i sistemi distribuiti sono così difficili che persino i progetti leader del settore a volte li sbagliano, in modi che possono sembrare ovvi col senno di poi.

  • I sistemi distribuiti implicano anche un maggiore sforzo di sviluppo. In una certa misura, c'è un compromesso diretto tra i costi di sviluppo o la caduta di denaro su hardware più robusto.

Esempio: riferimenti penzolanti

In pratica, non dovresti guardare l'informatica ma i requisiti della tua azienda per vedere se e come ACID può essere rilassato. Ad esempio, molte relazioni di chiave esterna potrebbero non essere così importanti come sembrano. Considera una relazione prodotto - categoria n: m. In un RDBMS potremmo usare un vincolo di chiave esterna in modo che solo i prodotti esistenti e le categorie esistenti possano far parte di quella relazione. Cosa succede se introduciamo servizi di prodotti e categorie separati e un prodotto o una categoria vengono eliminati?

In questo caso, potrebbe non essere un grosso problema e possiamo scrivere la nostra applicazione in modo da filtrare tutti i prodotti o le categorie che non esistono più. Ma ci sono dei compromessi!

  • Si noti che ciò potrebbe richiedere un livello di applicazione JOINsu più database / microservizi, che sposta semplicemente l'elaborazione dal server di database all'applicazione. Ciò aumenta il carico totale e deve spostare dati extra attraverso la rete.

  • Questo può fare casino con l'impaginazione. Ad esempio, richiedi i prossimi 25 prodotti da una categoria e filtra i prodotti non disponibili da quella risposta. Ora l'applicazione visualizza 23 prodotti. In teoria, sarebbe anche possibile una pagina con zero prodotti!

  • Occasionalmente dovrai eseguire uno script che ripulisca i riferimenti penzolanti, dopo ogni modifica rilevante o ad intervalli regolari. Si noti che tali script sono piuttosto costosi perché devono richiedere ogni prodotto / categoria dal database di supporto / microservizio per vedere se esiste ancora.

  • Questo dovrebbe essere ovvio, ma per chiarezza: non riutilizzare gli ID. Gli ID in stile autoincremento possono o meno andare bene. I GUID o gli hash offrono maggiore flessibilità, ad esempio potendo assegnare un ID prima che l'elemento venga inserito in un database.

Esempio: ordini simultanei

Ora invece considera una relazione prodotto - ordine. Cosa succede a un ordine se un prodotto viene eliminato o modificato? Ok, possiamo semplicemente copiare i dati rilevanti del prodotto nella voce dell'ordine per tenerli disponibili - scambiando spazio su disco per semplicità. Ma cosa succede se il prezzo del prodotto cambia o il prodotto diventa non disponibile poco prima che venga effettuato un ordine per quel prodotto? In un sistema distribuito, gli effetti richiedono tempo per propagarsi e l'ordine probabilmente passerà attraverso dati obsoleti.

Ancora una volta, come affrontarlo dipende dalle esigenze della tua azienda. Forse l'ordine obsoleto è accettabile e puoi successivamente annullare l'ordine se non può essere eseguito.

Ma forse non è un'opzione, ad es. Per impostazioni altamente concorrenti. Considera 3000 persone che si affrettano ad acquistare i biglietti per i concerti entro i primi 10 secondi e supponiamo che un cambiamento nella disponibilità richieda 10ms per propagarsi. Qual è la probabilità di vendere l'ultimo biglietto a più persone? Dipende da come vengono gestite quelle collisioni, ma utilizzando una distribuzione di Poisson con λ = 3000 / (10s / 10ms) = 3abbiamo una P(k > 1) = 1 - P(k = 0) - P(k = 1) = 80%possibilità di collisione per intervallo di 10 ms. Se la vendita e la successiva cancellazione della maggior parte dei tuoi ordini è possibile senza commettere frodi, potresti condurre a un'interessante conversazione con il tuo ufficio legale.

Pragmatismo significa scegliere le migliori caratteristiche.

La buona notizia è che non è necessario passare a un modello di database distribuito, se non diversamente richiesto. Nessuno revocherà la tua iscrizione al Microservice Club se non esegui i microservizi "correttamente", perché non esiste un club del genere - e non esiste un vero modo per creare microservizi.

Il pragmatismo vince ogni volta, quindi mescola e abbina vari approcci mentre risolvono il tuo problema. Ciò potrebbe anche significare microservizi con un database centralizzato. Davvero, non passare attraverso il dolore dei database distribuiti se non è necessario.

È possibile ridimensionare senza microservizi.

I microservizi hanno due vantaggi principali:

  • Il vantaggio organizzativo che possono essere sviluppati e distribuiti in modo indipendente da team separati (che a sua volta richiede che i servizi offrano un'interfaccia stabile).
  • Il vantaggio operativo che ogni microservizio può essere ridimensionato in modo indipendente .

Se non è richiesto il ridimensionamento indipendente, i microservizi sono molto meno interessanti.

Un server di database è già un tipo di servizio che è possibile ridimensionare (in qualche modo) in modo indipendente, ad esempio aggiungendo repliche di lettura. Lei menziona le procedure memorizzate. La loro riduzione potrebbe avere un effetto così grande che qualsiasi altra discussione sulla scalabilità è controversa.

Ed è perfettamente possibile avere un monolite scalabile che include tutti i servizi come librerie. È quindi possibile ridimensionare avviando più istanze del monolito, che ovviamente richiede che ogni istanza sia apolide.

Questo tende a funzionare bene fino a quando il monolite è troppo grande per essere ragionevolmente distribuito, o se alcuni servizi hanno requisiti di risorse speciali in modo che tu possa voler ridimensionarli in modo indipendente. I domini problematici che coinvolgono risorse extra potrebbero non coinvolgere un modello di dati separato.

Hai un solido business case?

Sei a conoscenza delle esigenze aziendali della tua organizzazione e puoi quindi creare un argomento per un'architettura di database per microservizio, basata su un'analisi:

  • che è richiesta una certa scala e che questa architettura è l'approccio più conveniente per ottenere tale scalabilità, tenendo conto del maggiore sforzo di sviluppo per tale installazione e soluzioni alternative; e
  • che i requisiti aziendali consentano di allentare le garanzie ACID pertinenti, senza comportare vari problemi come quelli discussi sopra.

Al contrario, se non sei in grado di dimostrarlo, in particolare se l'attuale progettazione del database è in grado di supportare una scala sufficiente per il futuro (come sembrano credere i tuoi colleghi), allora hai anche la tua risposta.

C'è anche un grande componente YAGNI per la scalabilità. Di fronte all'incertezza, si tratta di una decisione aziendale strategica sulla costruzione della scalabilità ora (costi totali inferiori, ma comporta costi opportunità e potrebbe non essere necessario) rispetto a rinviare alcuni lavori sulla scalabilità (costi totali più elevati se necessario, ma si ha una migliore idea della scala reale). Questa non è principalmente una decisione tecnica.


Ottima risposta, grazie. Puoi approfondire questa affermazione? Vuoi dire che ridurre il numero di procedure potrebbe avere un grande effetto sulle prestazioni? Lei menziona le procedure memorizzate. Ridurli potrebbe avere un effetto così grande che qualsiasi altra discussione sulla scalabilità è controversa.
alan

1
@alan Le procedure memorizzate possono essere utilizzate per sempre, ma presentano due problemi di prestazioni: (1) Query più complesse sono più difficili da ottimizzare per il database. (2) Usare sprocs significa fare più lavoro sul server DB. OP vuole dividere il DB per ridimensionarlo ulteriormente, ma evitare complicati sproc potrebbe già fornire quel margine. Naturalmente, sprocs e query complesse possono anche essere utili per le prestazioni, ad esempio quando riducono al minimo la quantità di dati che devono essere trasferiti dal DB per una risposta alla query. La suddivisione del DB peggiorerebbe questo problema quando sono necessari JOIN tra server.
amon

0

Credo che entrambi gli approcci siano plausibili. È possibile scegliere di ottenere la scalabilità sacrificando i vantaggi dei database ACID e monolitici, nonché mantenere l'architettura attuale e sacrificare la scalabilità e l'agilità di un'architettura più distribuita. La decisione giusta verrà dall'attuale modello di business e dalla strategia buz per i prossimi anni. Solo dal punto di vista tecnologico, ci sono dolori che lo mantengono monolitico e si spostano verso un approccio più distribuito. Analizzerei il sistema e vedrei quali applicazioni / moduli / processi aziendali sono più critici per ridimensionare e valutare i rischi, i costi e i benefici per decidere quelli che dovrebbero attendere o continuare nell'architettura monolitica.


-1

La tua posizione è plausibile e corretta.

Come convincere tinti nei tossicodipendenti di lana db è però un'altra domanda. Direi che hai due opzioni.

  1. Trova un esempio concreto in cui il DB ha raggiunto i suoi limiti. Avete "tabelle di archivio" per esempio? Perché va bene? Qual è il numero massimo di ordini al secondo che puoi ricevere? ecc. Mostra che il DB non soddisfa i requisiti e che la tua soluzione li risolve.

  2. Assumi costosi appaltatori per presentarti la soluzione migliore. Perché sono spese e hanno blog tutti li crederanno


1
Non sono il -1 ma per essere una buona risposta perderei lo snark del punto 2 e mi espanderei quando sarebbe opportuno avere bisogno di più database. Non credo che le tabelle degli archivi siano necessariamente un antipattern nei database che non supportano il partizionamento. Il database che sto attualmente usando ha circa 130 GB di dati con 26 tabelle> 10 milioni di righe e le prestazioni non sono abbastanza lontane da un problema da dividere il database; quindi sono altamente scettico e mi piacerebbe sapere perché questa è una buona idea e quando deve essere fatta - questa risposta è la più vicina che abbia mai visto finora.
mcottle

bene. Cito le tabelle degli archivi perché eliminano i vincoli FK. è una crepa nell'armatura. la suddivisione per microservizio non è una dimensione di cosa db è una cosa separabile dai microservizi. Se non riesci a spegnerne uno e buttarlo via, non è davvero un microservizio. riguardo al punto 2. l'OP menziona MF, potrebbero letteralmente assumerlo / riflessioni per venire e dire loro di dividere il db
Ewan

"Se non riesci a spegnerne uno e buttarlo via, non è davvero un microservizio." Questo è vero per il servizio stesso, ma non è necessariamente un argomento per cui il servizio necessita del proprio database. In definitiva, il database è esso stesso un servizio utilizzato dal microservizio. Il microservizio non sa davvero se non importa se i dati che sta utilizzando si trovano in un database separato o in un database condiviso. Puoi girare su o giù le copie di questo microservizio e nulla cambia davvero.
Chris Pratt,

L'argomento migliore per il database per servizio sono i limiti di connessione. Non è insolito utilizzare il pool di connessioni, quindi ogni microservizio richiede già più connessioni all'istanza del database, quindi è possibile disporre di più istanze di ciascuno di questi microservizi, ognuna con i propri pool. Alla fine le cose potrebbero arrivare al limite, dove hai semplicemente esaurito la capacità del database di gestire tutte le connessioni che sta ottenendo.
Chris Pratt,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.