Anatomie d’un système de gestion de base de données vectorielle cloud-native
Je suis honoré que notre dernier article, "Manu: A Cloud Native Vector Database Management System", ait été accepté par VLDB'22, une conférence internationale de premier plan dans la recherche sur les bases de données. Dans cet article, je présenterai la philosophie et les principes de conception clés qui sous-tendent Manu (nom de projet pour Milvus 2.0), une base de données cloud native spécialement conçue pour la gestion des données vectorielles. Vous pouvez consulter nos articles précédents et notre dépôt GitHub pour plus d’informations.
Contexte
Lorsque nous avons conçu Milvus 1.0, notre objectif principal était simplement de prendre en charge la gestion des vecteurs et d’optimiser les performances de recherche vectorielle. Mais à mesure que nous avons interagi avec davantage d’utilisateurs au fil des ans, nous avons découvert certaines exigences métier communes pour les bases de données vectorielles qu’il était difficile de satisfaire dans le cadre initial.
Ces besoins peuvent être regroupés dans les catégories suivantes : exigences en constante évolution, politique de cohérence plus flexible, élasticité au niveau des composants, et modèle de traitement des transactions plus simple et plus efficace.
Exigences en constante évolution
Les exigences métier ne sont toujours pas entièrement définies en matière de traitement des données vectorielles.
Aux tout débuts, la recherche des K plus proches voisins était la plus demandée. Mais ensuite, de plus en plus d’exigences sont apparues, notamment la recherche par plage, la prise en charge de diverses métriques de distance personnalisées, la recherche hybride, les requêtes multimodales et d’autres sémantiques de requête de plus en plus diverses.
Cela exige que l’architecture d’une base de données vectorielle soit suffisamment flexible pour prendre en charge rapidement et avec agilité de nouvelles exigences.
Une politique de cohérence plus flexible
Prenons la recommandation de contenu comme exemple : ce scénario présente des exigences élevées en matière de rapidité. Le nouveau contenu doit être recommandé aux utilisateurs en quelques minutes, voire quelques secondes, de sorte que le système ne peut pas prendre une journée ou plus pour mettre à jour ses recommandations. Dans ces scénarios, il est difficile de garantir les résultats métier en ne fournissant qu’une cohérence éventuelle, tandis qu’il y aura une surcharge système importante si nous insistons sur une cohérence forte.
Pour résoudre ce problème, nous proposons la solution suivante : selon les exigences métier, les utilisateurs peuvent spécifier le délai maximal pouvant être toléré avant que les données insérées puissent être interrogées. Le système ajuste alors certains mécanismes de traitement des données afin de garantir le résultat final pour l’activité.
Élasticité au niveau des composants
Les besoins en ressources et l’intensité de la charge varient fortement pour chaque composant d’une base de données vectorielle selon les applications. Par exemple, les composants de recherche vectorielle et de requête nécessitent d’importantes ressources de calcul et de mémoire pour garantir leurs performances, tandis que l’archivage des données et la gestion des métadonnées n’ont besoin que de peu de ressources pour fonctionner.
En ce qui concerne les applications, l’exigence la plus critique pour les systèmes de recommandation est la capacité à effectuer des requêtes concurrentes à grande échelle ; ainsi, dans ces systèmes, seul le composant de requête supporte une charge plus élevée. Les applications analytiques, en revanche, doivent souvent importer une grande quantité de données hors ligne, de sorte que la pression de charge repose sur l’insertion des données et la construction des index, deux composants interdépendants.
Afin d’améliorer l’utilisation des ressources, il est nécessaire que chaque module fonctionnel dispose d’une scalabilité indépendante et élastique, afin que l’allocation des ressources du système puisse correspondre plus étroitement aux besoins réels d’une application.
Un modèle de traitement des transactions plus simple et plus efficace
À proprement parler, le modèle de transaction est un espace d’optimisation qui peut être exploité dans la conception du système plutôt qu’une exigence métier.
À mesure que l’apprentissage automatique évolue dans sa puissance descriptive, les entreprises tendent à fusionner des données provenant de multiples dimensions d’une même entité afin de la représenter comme un vecteur unique et unifié. Par exemple, dans le profilage utilisateur, des informations telles que les profils personnels, les préférences et les relations sociales sont fusionnées ensemble. Par conséquent, les bases de données vectorielles peuvent être maintenues avec une seule table, sans avoir à implémenter des opérations de type JOIN courantes dans les bases de données traditionnelles. De cette manière, le système doit seulement prendre en charge l’ACID au niveau des lignes sur une seule table et peut se passer de transactions complexes impliquant plusieurs tables, laissant une grande marge pour le découplage des composants et l’optimisation des performances dans le système.
Objectifs
En tant que deuxième version majeure de Milvus, Manu est positionné comme un système de base de données vectorielle distribué et cloud-native.
Lorsque nous avons entrepris de concevoir Manu, nous avons pris en compte les différentes exigences métier mentionnées ci-dessus et les avons combinées aux exigences courantes d’un système distribué. Il en résulte cinq grands objectifs pour Manu : évolutivité à long terme, cohérence ajustable, bonne élasticité, haute disponibilité et hautes performances.
Évolutivité à long terme
Afin de contrôler la complexité du système à un niveau gérable tandis que les fonctionnalités évoluent, nous devons bien découpler le système pour garantir que les composants individuels puissent évoluer, être ajoutés ou être remplacés indépendamment, avec une interférence minimale avec les autres composants.
Cohérence ajustable
Le système doit prendre en charge la cohérence delta afin que les utilisateurs puissent spécifier le délai de visibilité des requêtes pour les données nouvellement insérées. La cohérence delta exige que toutes les requêtes puissent retourner toutes les données pertinentes au moins jusqu’à l’unité de temps delta, qui peut être spécifiée par l’application utilisateur en fonction des exigences métier.
Bonne élasticité
Afin d’améliorer l’efficacité de l’utilisation des ressources, le système doit atteindre une élasticité fine au niveau des composants, ainsi qu’une politique d’allocation des ressources qui tient compte des diverses dépendances matérielles des composants.
Haute disponibilité et performances
La haute disponibilité est l’exigence de base de toutes les bases de données cloud, ce qui nécessite qu’en cas de défaillance de quelques nœuds ou composants de service, les autres services ne soient pas affectés, et que le système soit capable d’une récupération efficace après défaillance.
Les hautes performances sont un cliché pour les bases de données vectorielles. Dans le processus de conception, nous devons contrôler strictement la surcharge générée au niveau du framework système afin de garantir de bonnes performances.
Architecture de Manu
Manu adopte une architecture à quatre couches qui permet le découplage de la lecture et de l’écriture, du sans état et de l’avec état, ainsi que du stockage et du calcul.
Comme le montre la figure ci-dessous, de haut en bas, Manu comporte quatre couches, à savoir la couche d’accès, la couche de coordination, la couche worker et la couche de stockage. Manu utilise également un système de journalisation comme colonne vertébrale, qui relie les composants découplés.
Architecture de Manu.
Couche d’accès
La couche d’accès se compose de proxies sans état qui servent de points d’accès utilisateur.
Ces proxies reçoivent les requêtes des clients, distribuent les requêtes aux composants correspondants et collectent les résultats avant de les renvoyer aux clients. En outre, les proxies mettent en cache une copie des métadonnées pour vérifier la légitimité des requêtes de recherche (par exemple, si la collection à rechercher existe).
Couche de coordination
La couche de coordination gère l’état du système, maintient les métadonnées et coordonne les composants du système pour le traitement des tâches.
Il existe quatre types de coordinateurs, chacun conçu indépendamment pour une fonctionnalité différente. De cette manière, les défaillances du système peuvent être isolées et les composants peuvent évoluer séparément. Pour des raisons de fiabilité, chaque coordinateur peut avoir plusieurs instances (par exemple, une principale et deux de secours).
Coordinateur racine
Le coordinateur racine gère les requêtes de définition des données, telles que la création/suppression de collections, et maintient les méta-informations des collections (p. ex., les propriétés des collections, le type de données de chaque propriété).
Coordinateur de données
Le coordinateur de données traite la persistance des données. Il coordonne les nœuds de données pour transformer les requêtes de mise à jour des données en binlogs et enregistre les informations détaillées des collections (p. ex., la liste des segments de chaque collection, le chemin de stockage de chaque segment).
Coordinateur d’index
Le coordinateur d’index gère l’indexation des données. Il coordonne les nœuds d’index pour les tâches d’indexation et enregistre les informations d’index de chaque collection (p. ex., type d’index, paramètres associés, chemin de stockage, etc.).
Coordinateur de requêtes
Le coordinateur de requêtes surveille l’état des nœuds de requêtes et ajuste l’affectation des segments (ainsi que des index associés) aux nœuds de requêtes pour l’équilibrage de charge.
Couche de travailleurs
La couche de travailleurs exécute les multiples tâches du système.
Tous les nœuds de travailleurs sont sans état - ils récupèrent des copies en lecture seule des données pour effectuer les tâches et n’ont pas besoin de se coordonner entre eux. Par conséquent, le nombre de nœuds de travailleurs peut être ajusté de manière flexible en fonction de la charge. En outre, Manu utilise différents nœuds de travailleurs pour différentes tâches, de sorte que chaque type de nœud puisse être mis à l’échelle indépendamment en fonction de la charge réelle et des exigences de QoS.
Couche de stockage
La couche de stockage stocke de manière persistante les informations d’état du système, les métadonnées, les collections et les index associés.
Manu utilise des magasins KV (clé-valeur) distribués à haute disponibilité, comme etcd, pour stocker les informations d’état du système et les métadonnées. Lorsque les métadonnées sont mises à jour, les données sont d’abord écrites dans le magasin KV, puis synchronisées avec les coordinateurs concernés. Les données de grand volume, comme celles des collections et des index, sont gérées avec des services de stockage d’objets comme AWS S3. La latence élevée qui accompagne le stockage d’objets ne constitue pas un goulot d’étranglement pour les performances, car les nœuds de travailleurs prennent des copies en lecture seule des données depuis le magasin d’objets et les mettent en cache localement avant de traiter les données, de sorte que la plupart du traitement des données est effectué localement.
Colonne vertébrale des logs
Colonne vertébrale des logs.
Pour mieux découpler les composants du système (p. ex., WAL, binlog, nœuds de données, nœuds d’index et nœuds de requêtes), afin que chacun puisse être mis à l’échelle et évoluer indépendamment, Manu suit le paradigme "log as data" et utilise un système de logs comme colonne vertébrale, qui relie les composants découplés du système. Dans Manu, les logs peuvent être abonnés de manière persistante par différents composants du système, qui sont donc appelés les abonnés des logs.
Les logs dans Manu peuvent être divisés en write-ahead log (WAL) et binlog. Le WAL est la partie incrémentale du log système, tandis que le binlog en est la partie de base. Ils se complètent en matière de délai, de capacité et de coût.
Comme indiqué dans la figure ci-dessus, les loggers sont les points d’entrée du système de logs, publiant des données dans le WAL. Les nœuds de données s’abonnent au WAL et convertissent les WAL basés sur les lignes en binlogs basés sur les colonnes. Tous les composants en lecture seule, tels que les nœuds d’index et les nœuds de requêtes, sont des abonnés indépendants au service de logs afin de se maintenir à jour.
Le système de logs sert également à transmettre des messages entre composants. Autrement dit, les composants peuvent diffuser des événements système via les logs. Par exemple, les nœuds de données peuvent informer les autres composants des segments qui ont été écrits dans le stockage d’objets, et les nœuds d’index peuvent informer tous les coordinateurs de requêtes dès que de nouveaux index ont été construits. De plus, différents types de messages sont organisés sur différents canaux. Chaque composant n’a besoin de s’abonner qu’à son canal correspondant au lieu d’écouter tous les logs diffusés.
Flux de traitement des données
Cette section détaille le flux de traitement des données à l’intérieur de Manu et présente le processus d’insertion des données, de construction d’index et d’exécution des requêtes.
Insertion des données
Flux de travail d’insertion de données.
La figure ci-dessus illustre le flux de travail de l’insertion de données dans Manu et les composants pertinents impliqués.
Après avoir été traitées par le proxy, les demandes d’insertion de données sont réparties dans plusieurs buckets sur la base d’algorithmes de hachage. En général, plusieurs loggers dans le système Manu gèrent les entités de chaque bucket de hachage sur la base d’un hachage cohérent. Les entités de chaque bucket de hachage sont écrites dans un canal de journal à écriture anticipée (WAL) qui correspond uniquement à ce bucket. Lorsqu’un logger reçoit une demande d’insertion de données, il attribue à cette demande un numéro de séquence de journal (LSN) globalement unique et l’écrit dans le canal WAL correspondant. Le LSN est généré par l’oracle de service temporel central (TSO). Chaque logger doit recevoir un LSN du TSO et l’enregistrer localement à intervalle régulier.
Pour garantir que la publication/l’abonnement aux journaux présente un faible délai et soit à granularité fine, les entités sont stockées de manière orientée lignes dans le WAL dans Manu, et chaque composant qui s’abonne au WAL y lit les données en streaming. Dans la plupart des cas, le WAL peut être mis en œuvre via une file de messages basée sur le cloud telle que Kafka ou Pulsar. Les nœuds de données s’abonnent aux WAL et convertissent les WAL orientés lignes en binlogs orientés colonnes. La nature orientée colonnes du binlog facilite la compression et l’accès aux données. Un exemple de cette efficacité concerne les nœuds d’index. Les nœuds d’index ne lisent que la colonne vectorielle requise depuis le binlog pour la construction de l’index et évitent ainsi les amplifications de lecture.
Construction d’index
Il existe deux scénarios de construction d’index dans Manu : l’indexation par lots et l’indexation en streaming. L’indexation par lots se produit lorsque l’utilisateur construit un index pour une collection entière (par exemple, lorsque tous les vecteurs sont mis à jour avec un nouveau modèle d’embedding). Dans ce cas, le coordinateur d’index obtient auprès du coordinateur de données les chemins de tous les segments de la collection et ordonne aux nœuds d’index de construire un index pour chaque segment. L’indexation en streaming a lieu lorsque les utilisateurs insèrent continuellement de nouvelles entités, et les index sont construits de manière asynchrone à la volée sans interrompre les services de recherche.
Lorsqu’un nœud de données écrit un nouveau segment dans le binlog, le coordinateur de données notifie le coordinateur d’index afin de créer une tâche pour qu’un nœud d’index construise un index sur le nouveau segment. Dans les scénarios d’indexation par lots comme d’indexation en streaming, une fois l’index requis construit pour un segment, le nœud d’index le persiste dans le stockage d’objets et envoie le chemin de stockage au coordinateur d’index, en notifiant le coordinateur de requêtes afin que les nœuds de requête puissent charger l’index pour traiter les requêtes.
Exécution des requêtes
Manu partitionne une collection en segments et distribue les segments entre les nœuds de requête afin d’exécuter les demandes de requête en parallèle. Les proxies mettent en cache une copie de la distribution des segments sur les nœuds de requête en interrogeant le coordinateur de requêtes, et transmettent les demandes de recherche aux nœuds de requête qui détiennent les segments de la collection recherchée. Les nœuds de requête effectuent des requêtes vectorielles sur leurs segments locaux, fusionnent les résultats et les renvoient au proxy. Le proxy agrège ensuite les résultats de chaque nœud de requête et renvoie les résultats finaux au client.
Les nœuds de requête obtiennent les données à partir de trois sources : le WAL, les fichiers d’index et le binlog. Pour les données historiques, les nœuds de requête lisent les binlogs ou fichiers d’index correspondants depuis le stockage d’objets. Pour les données incrémentales, en revanche, les nœuds de requête lisent directement depuis le WAL en streaming. Obtenir les données incrémentales depuis le binlog entraînera une latence dans la visibilité des données, ce qui est particulièrement vrai pour les grandes demandes de recherche. Autrement dit, les données nouvellement insérées ne seront disponibles pour les requêtes qu’après une longue période, ce qui ne répond pas au besoin de forte cohérence dans certains scénarios.
Comme mentionné précédemment, Manu adopte un modèle de cohérence delta afin de permettre aux utilisateurs d’ajuster les niveaux de cohérence avec plus de flexibilité. La cohérence delta garantit que les données mises à jour (y compris les données insérées et supprimées) peuvent être interrogées et recherchées jusqu’à delta unités de temps après la réception de la demande de mise à jour des données par Manu.
Manu assure la cohérence delta en ajoutant des LSN portant des horodatages à toutes les demandes d’insertion de données et de requête. Lors de l’exécution des demandes de requête, le nœud de requête vérifie l’horodatage de la demande (Lr) et l’horodatage de la dernière demande de mise à jour des données traitée par le nœud de requête (Ls). La demande de requête n’est exécutée que lorsque l’intervalle entre Lr et Ls est inférieur à delta. Sinon, la demande de requête attend d’être exécutée jusqu’à ce que les mises à jour de données enregistrées dans le WAL soient traitées. Cependant, s’il n’y a aucune mise à jour de données pendant une longue période, l’intervalle de temps entre Ls et l’heure système actuelle deviendra si petit que les requêtes seront bloquées. Pour éviter un tel problème, Manu insère régulièrement des informations de contrôle dans le WAL, forçant le nœud de requête à mettre à jour son horodatage.
Évaluation des performances
Dans l’article, nous avons également intégré Manu dans des applications réelles et mené une évaluation globale des performances du système. Voici une partie des résultats de l’évaluation.
Performances de requête de Manu et d’autres systèmes de recherche vectorielle.
La figure ci-dessus compare Manu à quatre autres systèmes de recherche vectorielle open-source anonymes en termes de performances de requête. Nous pouvons voir que Manu surpasse manifestement les autres systèmes de recherche vectorielle lors de l’exécution de requêtes sur les jeux de données SIFT et DEEP.
Performances de requête de Manu avec différents nombres de nœuds.
La figure ci-dessus montre les performances de requête de Manu lorsque le nombre de nœuds de requête varie. Nous pouvons voir que lors de l’interrogation de différents jeux de données avec différentes métriques de similarité, les performances de requête de Manu présentent une relation approximativement linéaire avec le nombre de nœuds de requête.
Performances de requête de Manu sous différents niveaux de cohérence.
Les figures ci-dessus démontrent les performances de requête de Manu sous différents niveaux de cohérence. Les coordonnées horizontales représentent les valeurs de delta comme dans la cohérence delta. Chaque figure reflète la fréquence des informations de contrôle envoyées au WAL qui forcent les nœuds de requête à synchroniser l’heure. Nous pouvons voir sur la figure que la latence de requête de Manu diminue drastiquement à mesure que la valeur de delta augmente. Par conséquent, les utilisateurs de Manu doivent choisir la valeur delta appropriée en fonction de leurs besoins en matière de performances et de cohérence.
Conclusion
Dans cet article, sur la base des exigences réelles pour les bases de données vectorielles, nous avons présenté les conceptions de Manu et les workflows de ses principales fonctionnalités. En bref, les deux principales caractéristiques de Manu sont les suivantes :
Manu utilise le log backbone pour connecter les composants du système, ce qui permet la mise à l’échelle et l’évolution indépendantes de chaque composant et facilite l’allocation des ressources et l’isolation des défaillances.
Grâce au système de journalisation et au LSN, Manu adopte un modèle de cohérence delta afin de permettre un compromis flexible entre cohérence, coût et performances.
En résumé, la principale contribution de notre VLDB paper réside dans l’introduction de la demande réelle de bases de données vectorielles et dans la conception de l’architecture de base d’une base de données vectorielle cloud-native. À l’heure actuelle, l’architecture est encore loin d’être parfaite et certaines de nos orientations futures incluent :
Comment récupérer des vecteurs extraits de contenu multimodal ;
Comment mieux tirer parti des services de stockage cloud, y compris les disques locaux, les lecteurs cloud et d’autres services de stockage, afin de rendre la récupération des données plus efficace ;
Comment maximiser les performances d’indexation et de recherche avec l’aide de nouveaux matériels de calcul, de stockage ou de communication comme FPGA、GPU、RDMA、NVM 、RDMA.
Note de fin
Il y a un an, j’ai assisté à ACM SIGMOD 2021 à Xi'an avec Charles Xie, CEO de Zilliz. L’idée d’écrire cet article m’est venue alors que nous étions sur le chemin du retour vers Shanghai pour la version GA de Milvus 2.0 (Manu). Charles et moi avons tous deux perçu que les bases de données cloud-native devenaient le nouveau sujet phare dans le monde universitaire. C’était une telle coïncidence que Manu soit précisément un système de base de données cloud-native et spécialement conçu pour les vecteurs massifs. En conséquence, nous en sommes venus à écrire cet article sur Manu et le système de gestion de bases de données cloud-native.
Nous espérons que notre article pourra apporter un éclairage et attirer davantage de chercheurs et de collègues de l’industrie à nous rejoindre dans l’exploration et la recherche sur les systèmes de gestion de bases de données vectorielles cloud-native.
Nous souhaitons également exprimer notre gratitude à l’Assistant Professor Bo Tang, au Research Assistant Professor Xiao Yan et à Long Xiang pour leur contribution. Cet article est rédigé conjointement par l’équipe Zilliz et le Database Group de la Southern University of Science and Technology.
Continuer à lire

3 Easiest Ways to Use Claude Code on Your Mobile Phone
Run Claude Code from your phone with Remote Control, Happy Coder, or SSH + Tailscale. Comparison table, setup steps, and tools for typing, memory, and parallel tasks.

How to Improve Retrieval Quality for Japanese Text with Sudachi, Milvus/Zilliz, and AWS Bedrock
Learn how Sudachi normalization and Milvus/Zilliz hybrid search improve Japanese RAG accuracy with BM25 + vector fusion, AWS Bedrock embeddings, and practical code examples.

How to Use Anthropic MCP Server with Milvus
MCP + Milvus: Streamline AI agent development with standardized data access, eliminating integration hassles while enhancing context and flexibility.



