web-dev-qa-db-fra.com

Comment la dénormalisation des données fonctionne-t-elle avec le modèle Microservice?

Je viens de lire un article sur Microservices et architecture PaaS . Dans cet article, à peu près au tiers de sa descente, l'auteur déclare (sous Denormalize like Crazy):

Refacturer les schémas de base de données et tout dénormaliser, pour permettre la séparation et le partitionnement complets des données. Autrement dit, n'utilisez pas de tables sous-jacentes servant plusieurs microservices. Il ne devrait y avoir aucun partage de tables sous-jacentes couvrant plusieurs microservices, ni aucun partage de données. Au lieu de cela, si plusieurs services ont besoin d'accéder aux mêmes données, celles-ci doivent être partagées via une API de service (telle qu'une interface publiée REST ou une interface de service de messagerie).

Bien que cette son) soit excellente en théorie, elle présente de sérieux obstacles à surmonter. Le plus important est que, souvent, les bases de données sont étroitement couplées et chaque table a une relation de {certains} _ clés étrangères avec au moins une autre table. De ce fait, il pourrait être impossible de partitionner une base de données en n sous-bases de données contrôlées par n microservices.

Je pose donc la question suivante: Étant donné une base de données entièrement constituée de tables liées, comment peut-on la dénormaliser en fragments plus petits (groupes de tables) afin que les fragments puissent être contrôlés par des microservices distincts?

Par exemple, étant donné la base de données suivante (plutôt petite, mais exemplaire):

[users] table
=============
user_id
user_first_name
user_last_name
user_email

[products] table
================
product_id
product_name
product_description
product_unit_price

[orders] table
==============
order_id
order_datetime
user_id

[products_x_orders] table (for line items in the order)
=======================================================
products_x_orders_id
product_id
order_id
quantity_ordered

Ne passez pas trop de temps à critiquer mon design, je l'ai fait à la volée. Le fait est que, pour moi, il est logique de scinder cette base de données en 3 microservices:

  1. UserService - pour les utilisateurs enregistrés dans le système; devrait en fin de compte gérer la table [users]; et
  2. ProductService - pour les produits incrustés dans le système; devrait en fin de compte gérer la table [products]; et
  3. OrderService - pour les commandes en attente dans le système; devrait en fin de compte gérer les tables [orders] et [products_x_orders]

Cependant, toutes ces tables ont des relations de clés étrangères entre elles. Si nous les dénormalisons et les traitons comme des monolithes, ils perdent tout leur sens sémantique:

[users] table
=============
user_id
user_first_name
user_last_name
user_email

[products] table
================
product_id
product_name
product_description
product_unit_price

[orders] table
==============
order_id
order_datetime

[products_x_orders] table (for line items in the order)
=======================================================
products_x_orders_id
quantity_ordered

Il est maintenant impossible de savoir qui a commandé quoi, en quelle quantité et quand.

Cet article est-il donc typique du monde académique, ou existe-t-il un aspect pratique du monde à cette approche de dénormalisation, et si oui, à quoi cela ressemble-t-il (points bonus pour utiliser mon exemple dans la réponse)?

66
smeeb

Il s’agit en effet d’un des problèmes clefs dans les microservices, omis assez couramment dans la plupart des articles. Heureusement, il existe des solutions pour cela. Comme base de discussion, établissons les tableaux que vous avez fournis dans la question .  enter image description here L'image ci-dessus montre à quoi ressembleront les tables dans monolith. Juste quelques tables avec des jointures.


Pour reformuler cela en microservices, nous pouvons utiliser quelques stratégies:

Api Join

Dans cette stratégie, les clés étrangères entre microservices sont rompues et microservice expose un point d'extrémité qui imite cette clé. Par exemple: Le microservice du produit exposera findProductById endpoint. Order microservice peut utiliser ce noeud final au lieu de rejoindre.

 enter image description here Il a un inconvénient évident. C'est plus lent.

Lecture seule des vues

Dans la deuxième solution, vous pouvez créer une copie de la table dans la deuxième base de données. La copie est en lecture seule. Chaque microservice peut utiliser des opérations mutables sur ses tables de lecture/écriture. Lorsqu'il s'agit de lire uniquement les tables copiées à partir d'autres bases de données, elles ne peuvent (évidemment) utiliser que des tables de lecture  enter image description here

Lecture haute performance

Il est possible d’atteindre des performances de lecture élevées en introduisant des solutions telles que redis/memcached au-dessus de la solution read only view. Les deux côtés de la jointure doivent être copiés sur une structure plate optimisée pour la lecture. Vous pouvez introduire un tout nouveau microservice sans état qui peut être utilisé pour lire à partir de ce stockage. Bien qu'il semble y avoir beaucoup de tracas, il est intéressant de noter que sa performance sera supérieure à celle d'une solution monolithique par rapport à une base de données relationnelle.


Il y a peu de solutions possibles. Ceux qui sont les plus simples dans la mise en œuvre ont des performances plus faibles La mise en œuvre de solutions hautes performances prendra quelques semaines.

9
Marcin Szymczak

Je me rends compte que ce n’est peut-être pas une bonne réponse mais bon sang. Votre question était:

Étant donné qu'une base de données est entièrement constituée de tables liées, comment on dénormalise cela en fragments plus petits (groupes de tables)

WRT la conception de la base de données Je dirais"vous ne pouvez pas sans supprimer les clés étrangères"

C'est-à-dire que les personnes poussant Microservices avec la règle stricte sans base de données partagée demandent aux concepteurs de bases de données de renoncer aux clés étrangères (et ce, de manière implicite ou explicite). Quand ils ne déclarent pas explicitement la perte de FK, on ​​se demande s'ils connaissent et reconnaissent réellement la valeur des clés étrangères (car cela n'est souvent pas mentionné du tout).

J'ai vu de gros systèmes divisés en groupes de tables. Dans ces cas, il peut y avoir soit A) aucun FK autorisé entre les groupes, soit B) un groupe spécial contenant des tables "de base" pouvant être référencées par FK avec des tables d'autres groupes.

... mais dans ces systèmes, les "groupes de tables" sont souvent plus de 50 tables, ce qui n’est pas assez petit pour une stricte conformité avec les microservices.

Pour moi, l’autre problème lié à la division du DB par l’approche Microservice est l’impact que cela a sur les rapports, la question de savoir comment toutes les données sont rassemblées pour être rapportées et/ou chargées dans un entrepôt de données. 

La tendance à ignorer les fonctionnalités de réplication de base de données intégrées au profit de la messagerie (et de l'impact de la réplication basée sur base de données du noyau partagé tables/noyau DDD) sur la base de données a également un impact sur la conception.

EDIT: (coût de la jointure via des appels REST)  

Lorsque nous séparons la base de données, comme suggéré par microservices, et supprimons les FK, nous perdons non seulement la règle de gestion déclarative appliquée (de la clé FK), mais également la possibilité pour la base de données d'exécuter la ou les jointures entre ces frontières.

Dans OLTP, les valeurs FK ne sont généralement pas "conviviales pour UX" et nous souhaitons souvent les rejoindre.

Dans l'exemple, si nous récupérons les 100 dernières commandes, nous ne souhaitons probablement pas afficher les valeurs d'identifiant client dans l'UX. Au lieu de cela, nous devons appeler un autre client pour obtenir son nom. Toutefois, si nous souhaitons également les lignes de commande, nous devons également appeler le service des produits pour afficher le nom du produit, le sku, etc., plutôt que l'identifiant du produit.

En général, nous pouvons constater que lorsque nous divisons la conception de la base de données de cette manière, nous devons effectuer de nombreux appels "JOIN via REST". Alors, quel est le coût relatif de cela?

Histoire réelle: Exemple de coûts pour 'JOIN via REST' vs DB Joins

Il existe 4 microservices et ils impliquent beaucoup de "JOIN via REST". Une charge de référence pour ces 4 services s’élève à ~ 15 minutes . Ces 4 microservices convertis en 1 service avec 4 modules contre un DB partagé (qui permet les jointures) exécute la même charge en ~ 20 secondes .

Malheureusement, ce n'est pas une comparaison directe entre pommes pour les jointures de base de données et "JOIN via REST", car dans ce cas, nous avons également changé de base de données NoSQL en Postgres.

Est-ce surprenant que "JOIN via REST" fonctionne relativement mal par rapport à une base de données dotée d'un optimiseur basé sur les coûts, etc.

Dans une certaine mesure, lorsque nous dissocions la base de données comme celle-ci, nous nous éloignons également de l'optimiseur basé sur les coûts et de tout ce qui entre dans la planification de l'exécution des requêtes pour créer notre propre logique de jointure (nous écrivons un peu notre propre plan d'exécution des requêtes non sophistiqué).

2
Rob Bygrave

Je verrais chaque microservice comme un objet et, comme tout ORM, vous utiliseriez ces objets pour extraire les données, puis créer des jointures au sein de votre collection de code et de requêtes, Microservices devrait être traité de la même manière. La différence uniquement ici sera que chaque microservice représentera un objet à la fois par rapport à une arborescence d'objets complète. Une couche API doit utiliser ces services et modéliser les données de manière à ce qu'elles soient présentées ou stockées.

Faire plusieurs appels aux services pour chaque transaction n'aura aucun impact car chaque service s'exécute dans un conteneur séparé et tous ces appels peuvent être exécutés en parallèle.

@ ccit-spence, j'ai aimé l'approche des services d'intersection, mais comment peut-elle être conçue et consommée par d'autres services? Je crois que cela créera une sorte de dépendance pour d'autres services.

Des commentaires s'il vous plaît?

0
user1294878