Comment la gestion des données est effectuée dans Milvus
L'auteur : Yihua Mo
Date : 2019-11-08
Comment la gestion des données est effectuée dans Milvus
Tout d'abord, quelques concepts de base de Milvus :
- Table : La table est un ensemble de données de vecteurs, chaque vecteur ayant un identifiant unique. Chaque vecteur et son ID représentent une ligne de la table. Tous les vecteurs d'un tableau doivent avoir les mêmes dimensions. Voici un exemple de tableau avec des vecteurs à 10 dimensions :
tableau
- Index : La construction d'un index est le processus de regroupement des vecteurs par un certain algorithme, ce qui nécessite de l'espace disque supplémentaire. Certains types d'index nécessitent moins d'espace car ils simplifient et compriment les vecteurs, tandis que d'autres types nécessitent plus d'espace que les vecteurs bruts.
Dans Milvus, les utilisateurs peuvent effectuer des tâches telles que la création d'une table, l'insertion de vecteurs, la construction d'index, la recherche de vecteurs, l'extraction d'informations de table, l'abandon de tables, la suppression de données partielles dans une table et la suppression d'index, etc.
Supposons que nous ayons 100 millions de vecteurs à 512 dimensions et que nous devions les insérer et les gérer dans Milvus pour une recherche vectorielle efficace.
(1) Insertion de vecteurs
Voyons comment les vecteurs sont insérés dans Milvus.
Chaque vecteur occupant 2 Ko d'espace, l'espace de stockage minimum pour 100 millions de vecteurs est d'environ 200 Go, ce qui rend irréaliste l'insertion en une seule fois de tous ces vecteurs. Il faut donc plusieurs fichiers de données au lieu d'un seul. La performance d'insertion est l'un des principaux indicateurs de performance. Milvus prend en charge l'insertion unique de centaines, voire de dizaines de milliers de vecteurs. Par exemple, l'insertion en une seule fois de 30 000 vecteurs à 512 dimensions ne prend généralement qu'une seconde.
insertion
Toutes les insertions de vecteurs ne sont pas chargées sur le disque. Milvus réserve un tampon mutable dans la mémoire de l'unité centrale pour chaque table créée, où les données insérées peuvent être rapidement écrites. Lorsque les données du tampon mutable atteignent une certaine taille, cet espace est considéré comme immuable. Dans l'intervalle, un nouveau tampon mutable sera réservé. Les données de la mémoire tampon immuable sont écrites régulièrement sur le disque et la mémoire correspondante de l'unité centrale est libérée. Le mécanisme d'écriture régulière sur le disque est similaire à celui utilisé dans Elasticsearch, qui écrit les données en mémoire tampon sur le disque toutes les secondes. En outre, les utilisateurs qui connaissent LevelDB/RocksDB peuvent y voir une certaine ressemblance avec MemTable.
Les objectifs du mécanisme d'insertion de données sont les suivants
- L'insertion de données doit être efficace.
- Les données insérées peuvent être utilisées instantanément.
- Les fichiers de données ne doivent pas être trop fragmentés.
(2) Fichier de données brutes
Lorsque les vecteurs sont écrits sur le disque, ils sont sauvegardés dans un fichier de données brutes contenant les vecteurs bruts. Comme indiqué précédemment, les vecteurs à grande échelle doivent être enregistrés et gérés dans plusieurs fichiers de données. La taille des données insérées varie : les utilisateurs peuvent insérer 10 vecteurs ou 1 million de vecteurs à la fois. Cependant, l'opération d'écriture sur le disque est exécutée une fois toutes les secondes. Des fichiers de données de tailles différentes sont donc générés.
Les fichiers de données fragmentés ne sont ni pratiques à gérer, ni faciles d'accès pour la recherche de vecteurs. Milvus fusionne constamment ces petits fichiers de données jusqu'à ce que la taille du fichier fusionné atteigne une taille donnée, par exemple 1 Go. Cette taille particulière peut être configurée dans le paramètre API index_file_size
lors de la création de la table. Par conséquent, 100 millions de vecteurs à 512 dimensions seront distribués et enregistrés dans environ 200 fichiers de données.
Compte tenu des scénarios de calcul incrémentiel, dans lesquels les vecteurs sont insérés et recherchés simultanément, nous devons nous assurer qu'une fois les vecteurs écrits sur le disque, ils sont disponibles pour la recherche. Ainsi, avant que les petits fichiers de données ne soient fusionnés, il est possible d'y accéder et d'y effectuer des recherches. Une fois la fusion terminée, les petits fichiers de données seront supprimés et les nouveaux fichiers fusionnés seront utilisés pour la recherche.
Voici à quoi ressemblent les fichiers interrogés avant la fusion :
rawdata1
Fichiers interrogés après la fusion :
rawdata2
(3) Fichier d'index
La recherche basée sur le fichier de données brutes est une recherche par force brute qui compare les distances entre les vecteurs de la requête et les vecteurs d'origine, et calcule les k vecteurs les plus proches. La recherche par force brute est inefficace. L'efficacité de la recherche peut être grandement améliorée si la recherche est basée sur le fichier d'index où les vecteurs sont indexés. La création d'un index nécessite de l'espace disque supplémentaire et prend généralement beaucoup de temps.
Quelles sont donc les différences entre les fichiers de données brutes et les fichiers d'index ? Pour simplifier, le fichier de données brutes enregistre chaque vecteur avec son identifiant unique, tandis que le fichier d'index enregistre les résultats du regroupement des vecteurs, tels que le type d'index, les centroïdes des regroupements et les vecteurs de chaque regroupement.
fichier d'index
En général, le fichier d'index contient plus d'informations que le fichier de données brutes, mais la taille des fichiers est beaucoup plus petite car les vecteurs sont simplifiés et quantifiés pendant le processus de construction de l'index (pour certains types d'index).
Les tables nouvellement créées font par défaut l'objet d'une recherche par calcul brut. Une fois l'index créé dans le système, Milvus construit automatiquement l'index pour les fichiers fusionnés dont la taille atteint 1 Go dans un thread autonome. Lorsque la construction de l'index est terminée, un nouveau fichier d'index est généré. Les fichiers de données brutes seront archivés pour la construction d'index basée sur d'autres types d'index.
Milvus construit automatiquement un index pour les fichiers qui atteignent 1 Go :
buildindex
La construction de l'index est terminée :
indexcomplete
L'index ne sera pas construit automatiquement pour les fichiers de données brutes qui n'atteignent pas 1 Go, ce qui peut ralentir la vitesse de recherche. Pour éviter cette situation, vous devez forcer manuellement la construction de l'index pour cette table.
forcer la construction
Après la construction forcée de l'index pour le fichier, les performances de recherche sont grandement améliorées.
indexfinal
(4) Métadonnées
Comme indiqué précédemment, 100 millions de vecteurs à 512 dimensions sont enregistrés dans 200 fichiers disque. Lorsque l'index est construit pour ces vecteurs, il y a 200 fichiers d'index supplémentaires, ce qui porte le nombre total de fichiers à 400 (y compris les fichiers de disque et les fichiers d'index). Un mécanisme efficace est nécessaire pour gérer les métadonnées (état des fichiers et autres informations) de ces fichiers afin de vérifier l'état des fichiers, de les supprimer ou de les créer.
L'utilisation de bases de données OLTP pour gérer ces informations est un bon choix. Milvus autonome utilise SQLite pour gérer les métadonnées, tandis que dans le cadre d'un déploiement distribué, Milvus utilise MySQL. Lorsque le serveur Milvus démarre, deux tables (à savoir "Tables" et "TableFiles") sont créées respectivement dans SQLite/MySQL. Tables" enregistre les informations relatives aux tables et "TableFiles" enregistre les informations relatives aux fichiers de données et aux fichiers d'index.
Comme le montre l'organigramme ci-dessous, "Tables" contient des métadonnées telles que le nom de la table (table_id), la dimension du vecteur (dimension), la date de création de la table (created_on), le statut de la table (state), le type d'index (engine_type), le nombre de grappes de vecteurs (nlist) et la méthode de calcul de la distance (metric_type).
TableFiles" contient le nom de la table à laquelle le fichier appartient (table_id), le type d'index du fichier (engine_type), le nom du fichier (file_id), le type de fichier (file_type), la taille du fichier (file_size), le nombre de lignes (row_count) et la date de création du fichier (created_on).
métadonnées
Ces métadonnées permettent d'effectuer diverses opérations. Voici quelques exemples :
- Pour créer une table, Meta Manager n'a besoin que d'exécuter une instruction SQL :
INSERT INTO TABLES VALUES(1, 'table_2, 512, xxx, xxx, ...)
. - Pour exécuter une recherche vectorielle sur la table_2, Meta Manager exécutera une requête dans SQLite/MySQL, qui est une instruction SQL de facto :
SELECT * FROM TableFiles WHERE table_id='table_2'
pour récupérer les informations sur les fichiers de la table_2. Ces fichiers seront ensuite chargés en mémoire par le planificateur de requêtes pour le calcul de la recherche. - Il n'est pas permis de supprimer instantanément une table car des requêtes peuvent être exécutées sur celle-ci. C'est la raison pour laquelle il existe des fonctions de suppression douce et de suppression dure pour une table. Lorsque vous supprimez une table, elle est étiquetée comme "soft-delete" et ne peut plus faire l'objet de requêtes ou de modifications. Cependant, les requêtes qui étaient en cours avant la suppression sont toujours en cours. Ce n'est que lorsque toutes ces requêtes préalables à la suppression seront terminées que la table, ainsi que ses métadonnées et ses fichiers connexes, seront définitivement supprimés.
(5) Planificateur de requêtes
Le graphique ci-dessous illustre le processus de recherche vectorielle dans le CPU et le GPU en interrogeant les fichiers (fichiers de données brutes et fichiers d'index) qui sont copiés et sauvegardés sur le disque, dans la mémoire du CPU et dans celle du GPU pour trouver les k vecteurs les plus similaires.
topkrésultat
L'algorithme de planification des requêtes améliore considérablement les performances du système. La philosophie de base de la conception est d'obtenir les meilleures performances de recherche en utilisant au maximum les ressources matérielles. Voici une brève description de l'algorithme d'ordonnancement des requêtes, qui fera l'objet d'un article spécifique à l'avenir.
Nous appelons la première requête sur une table donnée la requête "froide", et les requêtes suivantes la requête "chaude". Lors de la première interrogation d'une table donnée, Milvus effectue un travail considérable pour charger les données dans la mémoire de l'unité centrale et certaines données dans la mémoire du processeur graphique, ce qui prend beaucoup de temps. Pour les requêtes suivantes, la recherche est beaucoup plus rapide car une partie ou la totalité des données se trouvent déjà dans la mémoire de l'unité centrale, ce qui permet d'économiser le temps de lecture à partir du disque.
Pour raccourcir le temps de recherche de la première requête, Milvus propose la configuration Preload Table (preload_table
) qui permet le préchargement automatique des tables dans la mémoire de l'unité centrale au démarrage du serveur. Pour une table contenant 100 millions de vecteurs à 512 dimensions, soit 200 Go, la vitesse de recherche est la plus rapide si la mémoire de l'unité centrale est suffisante pour stocker toutes ces données. Toutefois, si la table contient des vecteurs à l'échelle du milliard, il est parfois inévitable de libérer de la mémoire CPU/GPU pour ajouter de nouvelles données qui ne sont pas interrogées. Actuellement, nous utilisons la stratégie de remplacement des données LRU (Latest Recently Used).
Comme le montre le graphique ci-dessous, supposons qu'une table comporte 6 fichiers d'index stockés sur le disque. La mémoire de l'unité centrale ne peut stocker que 3 fichiers d'index, et la mémoire de l'unité de calcul que 1 fichier d'index.
Lorsque la recherche commence, 3 fichiers d'index sont chargés dans la mémoire de l'unité centrale pour la requête. Le premier fichier est libéré de la mémoire de l'unité centrale immédiatement après avoir été interrogé. Pendant ce temps, le quatrième fichier est chargé dans la mémoire de l'unité centrale. De la même manière, lorsqu'un fichier est interrogé dans la mémoire du GPU, il est instantanément libéré et remplacé par un nouveau fichier.
Le planificateur de requêtes gère principalement deux ensembles de files d'attente, l'une pour le chargement des données et l'autre pour l'exécution de la recherche.
planificateur de requêtes
(6) Réducteur de résultats
Il existe deux paramètres clés liés à la recherche vectorielle : l'un est "n", qui signifie n nombre de vecteurs cibles ; l'autre est "k", qui signifie les k vecteurs les plus similaires. Les résultats de la recherche sont en fait n ensembles de KVP (paires clé-valeur), chacun comportant k paires clé-valeur. Comme les requêtes doivent être exécutées pour chaque fichier individuel, qu'il s'agisse d'un fichier de données brutes ou d'un fichier d'index, n ensembles d'ensembles de résultats top-k seront récupérés pour chaque fichier. Tous ces ensembles de résultats sont fusionnés pour obtenir les ensembles de résultats top-k de la table.
L'exemple ci-dessous montre comment les ensembles de résultats sont fusionnés et réduits pour la recherche vectorielle dans un tableau comportant 4 fichiers d'index (n=2, k=3). Notez que chaque ensemble de résultats comporte 2 colonnes. La colonne de gauche représente l'identifiant du vecteur et la colonne de droite représente la distance euclidienne.
résultat
(7) Optimisation future
Voici quelques réflexions sur les optimisations possibles de la gestion des données.
- Et si les données de la mémoire tampon immuable ou même de la mémoire tampon mutable pouvaient également être interrogées instantanément ? Actuellement, les données de la mémoire tampon immuable ne peuvent pas être interrogées, tant qu'elles n'ont pas été écrites sur le disque. Certains utilisateurs sont plus intéressés par l'accès instantané aux données après leur insertion.
- Fournir une fonctionnalité de partitionnement des tables qui permette à l'utilisateur de diviser de très grandes tables en partitions plus petites et d'exécuter une recherche vectorielle sur une partition donnée.
- Ajouter aux vecteurs certains attributs qui peuvent être filtrés. Par exemple, certains utilisateurs ne veulent rechercher que parmi les vecteurs ayant certains attributs. Il est nécessaire de récupérer les attributs des vecteurs et même les vecteurs bruts. Une approche possible consiste à utiliser une base de données KV telle que RocksDB.
- Fournir une fonctionnalité de migration des données qui permette la migration automatique des données obsolètes vers d'autres espaces de stockage. Dans certains scénarios où les données affluent en permanence, les données peuvent vieillir. Comme certains utilisateurs ne s'intéressent qu'aux données du mois le plus récent et n'effectuent des recherches que sur celles-ci, les données plus anciennes perdent de leur utilité tout en consommant beaucoup d'espace disque. Un mécanisme de migration des données permet de libérer de l'espace disque pour les nouvelles données.
Résumé de l'article
Cet article présente principalement la stratégie de gestion des données dans Milvus. D'autres articles sur le déploiement distribué de Milvus, la sélection des méthodes d'indexation vectorielle et le planificateur de requêtes seront publiés prochainement. Restez à l'écoute !
Blogs associés
- Résumé de l'article
- Blogs associés
On This Page
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word