Ne payez plus pour des données froides : Réduction des coûts de 80 % grâce au chargement de données chaudes et froides à la demande dans le système de stockage hiérarchisé de Milvus
Combien d'entre vous paient encore des factures d'infrastructure élevées pour des données que votre système touche à peine ? Honnêtement, c'est le cas de la plupart des équipes.
Si vous exécutez une recherche vectorielle en production, vous l'avez probablement constaté de première main. Vous prévoyez de grandes quantités de mémoire et de disques SSD pour que tout soit prêt pour les requêtes, même si seule une petite partie de votre ensemble de données est réellement active. Et vous n'êtes pas le seul. Nous avons vu beaucoup de cas similaires :
Plateformes SaaS multi-locataires : Des centaines de locataires inscrits, mais seulement 10 à 15 % d'entre eux sont actifs chaque jour. Les autres restent inactifs mais continuent d'occuper des ressources.
Systèmes de recommandation pour le commerce électronique : Un million d'UGS, mais les 8 % de produits les plus vendus génèrent la plupart des recommandations et du trafic de recherche.
Recherche en IA : De vastes archives d'encastrements, même si 90 % des requêtes des utilisateurs portent sur des articles datant de la semaine dernière.
C'est la même chose dans tous les secteurs : moins de 10 % de vos données sont interrogées fréquemment, mais elles consomment souvent 80 % de votre espace de stockage et de votre mémoire. Tout le monde sait que ce déséquilibre existe, mais jusqu'à récemment, il n'existait pas de solution architecturale propre pour y remédier.
Cela change avec Milvus 2.6.
Avant cette version, Milvus (comme la plupart des bases de données vectorielles) dépendait d'un modèle de chargement complet: si les données devaient être consultables, elles devaient être chargées sur les nœuds locaux. Peu importe que ces données soient consultées un millier de fois par minute ou une fois par trimestre, elles devaient rester chaudes. Ce choix de conception garantissait des performances prévisibles, mais il impliquait également de surdimensionner les clusters et de payer pour des ressources que les données froides ne méritaient tout simplement pas.
Lestockage hiérarchisé est notre réponse.
Milvus 2.6 introduit une nouvelle architecture de stockage hiérarchisé avec un véritable chargement à la demande, permettant au système de différencier automatiquement les données chaudes des données froides :
les segments chauds restent mis en cache à proximité de l'ordinateur
Les segments froids sont stockés à moindre coût dans le stockage d'objets distants.
Les données ne sont transférées vers les nœuds locaux que lorsqu'une requête en a réellement besoin.
La structure des coûts passe ainsi de "la quantité de données dont vous disposez" à "la quantité de données que vous utilisez réellement". Dans les premiers déploiements en production, ce simple changement permet de réduire de 80 % les coûts de stockage et de mémoire.
Dans la suite de ce billet, nous allons voir comment fonctionne le stockage hiérarchisé, partager des résultats de performance réels et montrer où ce changement a le plus d'impact.
Pourquoi le chargement complet s'effondre à grande échelle
Avant de plonger dans la solution, il convient d'examiner de plus près pourquoi le mode de chargement complet utilisé dans Milvus 2.5 et les versions antérieures est devenu un facteur limitant à mesure que les charges de travail évoluaient.
Dans Milvus 2.5 et les versions antérieures, lorsqu'un utilisateur émettait une requête Collection.load(), chaque QueryNode mettait en cache l'ensemble de la collection localement, y compris les métadonnées, les données de champ et les index. Ces composants sont téléchargés à partir du stockage d'objets et stockés soit entièrement en mémoire, soit en mémoire mappée (mmap) sur le disque local. Ce n'est que lorsque toutes ces données sont disponibles localement que la collection est considérée comme chargée et prête à répondre aux requêtes.
En d'autres termes, la collection n'est pas interrogeable tant que l'ensemble des données - chaudes ou froides - n'est pas présent sur le nœud.
Remarque : Pour les types d'index qui intègrent des données vectorielles brutes, Milvus ne charge que les fichiers d'index, et non le champ vectoriel séparément. Malgré cela, l'index doit être entièrement chargé pour répondre aux requêtes, quelle que soit la quantité de données réellement accédée.
Pour comprendre pourquoi cela devient problématique, prenons un exemple concret :
Supposons que vous disposiez d'un ensemble de données vectorielles de taille moyenne comprenant
100 millions de vecteurs
768 dimensions (BERT embeddings)
précisionfloat32 (4 octets par dimension)
Un index HNSW
Dans cette configuration, l'index HNSW seul - y compris les vecteurs bruts intégrés - occupe environ 430 Go de mémoire. Après l'ajout de champs scalaires communs tels que les identifiants des utilisateurs, les horodatages ou les étiquettes de catégorie, l'utilisation totale des ressources locales dépasse facilement les 500 Go.
Cela signifie que même si 80 % des données sont rarement ou jamais interrogées, le système doit toujours provisionner et conserver plus de 500 Go de mémoire locale ou de disque simplement pour maintenir la collection en ligne.
Pour certaines charges de travail, ce comportement est acceptable :
Si presque toutes les données sont fréquemment consultées, le chargement complet de toutes les données permet d'obtenir la latence de requête la plus faible possible, au coût le plus élevé.
Si les données peuvent être divisées en sous-ensembles chauds et tièdes, le mappage en mémoire des données tièdes sur le disque peut réduire partiellement la pression de la mémoire.
Toutefois, dans les charges de travail où 80 % ou plus des données se trouvent dans la longue traîne, les inconvénients du chargement complet apparaissent rapidement, tant au niveau des performances que des coûts.
Goulets d'étranglement des performances
Dans la pratique, le chargement complet n'affecte pas seulement les performances des requêtes et ralentit souvent les processus opérationnels de routine :
Des mises à niveau plus longues : Dans les grands clusters, les mises à niveau en continu peuvent prendre des heures, voire une journée entière, car chaque nœud doit recharger l'ensemble des données avant de redevenir disponible.
Récupération plus lente après les pannes : Lorsqu'un QueryNode redémarre, il ne peut pas servir le trafic tant que toutes les données n'ont pas été rechargées, ce qui prolonge considérablement le temps de reprise et amplifie l'impact des pannes de nœuds.
Ralentissement de l'itération et de l'expérimentation : Le chargement complet ralentit les flux de développement, obligeant les équipes d'intelligence artificielle à attendre des heures pour que les données se chargent lorsqu'elles testent de nouveaux ensembles de données ou de nouvelles configurations d'index.
Inefficacité des coûts
Le chargement complet fait également grimper les coûts d'infrastructure. Par exemple, sur les instances optimisées pour la mémoire du cloud grand public, le stockage de 1 To de données en local coûte environ
Considérons maintenant un modèle d'accès plus réaliste, où 80 % de ces données sont froides et pourraient être stockées dans le stockage objet à la place (à environ 0,023 $ / Go / mois) :
200 Go de données chaudes × 5,68
800 Go de données froides × 0,023
Coût annuel : (200×5,68+800×0,023)×12≈$14 000
Il s'agit d'une réduction de 80 % du coût total du stockage, sans sacrifier les performances là où elles sont réellement importantes.
Qu'est-ce que le stockage hiérarchisé et comment fonctionne-t-il ?
Pour supprimer ce compromis, Milvus 2.6 a introduit le stockage hiérarchisé, qui équilibre les performances et les coûts en traitant le stockage local comme un cache plutôt que comme un conteneur pour l'ensemble des données.
Dans ce modèle, les QueryNodes ne chargent que des métadonnées légères au démarrage. Les données de champ et les index sont récupérés à la demande à partir du stockage d'objets distant lorsqu'une requête les requiert, et mis en cache localement si l'on y accède fréquemment. Les données inactives peuvent être expulsées pour libérer de l'espace.
Ainsi, les données chaudes restent à proximité de la couche de calcul pour les requêtes à faible latence, tandis que les données froides restent dans le stockage d'objets jusqu'à ce qu'elles soient nécessaires. Cela réduit le temps de chargement, améliore l'efficacité des ressources et permet aux QueryNodes d'interroger des ensembles de données bien plus importants que la capacité de leur mémoire locale ou de leur disque.
En pratique, le stockage hiérarchisé fonctionne comme suit :
Garder les données chaudes au niveau local : Environ 20 % des données fréquemment consultées restent résidentes sur les nœuds locaux, ce qui garantit une faible latence pour les 80 % de requêtes les plus importantes.
Chargement des données froides à la demande : Les 80 % restants de données rarement consultées ne sont récupérées qu'en cas de besoin, ce qui libère la majeure partie de la mémoire locale et des ressources du disque.
S'adapter dynamiquement grâce à l'éviction basée sur la méthode LRU : Milvus utilise une stratégie d'éviction LRU (Least Recently Used) pour ajuster en permanence les données considérées comme chaudes ou froides. Les données inactives sont automatiquement éliminées pour faire de la place aux données nouvellement accédées.
Grâce à cette conception, Milvus n'est plus contraint par la capacité fixe de la mémoire locale et du disque. Au lieu de cela, les ressources locales fonctionnent comme un cache géré dynamiquement, où l'espace est continuellement récupéré des données inactives et réattribué aux charges de travail actives.
Sous le capot, ce comportement est rendu possible par trois mécanismes techniques fondamentaux :
1. Chargement paresseux
Lors de l'initialisation, Milvus ne charge que les métadonnées minimales au niveau du segment, ce qui permet aux collections d'être interrogeables presque immédiatement après le démarrage. Les données de champ et les fichiers d'index restent dans le stockage distant et sont récupérés à la demande pendant l'exécution de la requête, ce qui permet de limiter l'utilisation de la mémoire locale et du disque.
Fonctionnement du chargement des collections dans Milvus 2.5
Fonctionnement du chargement paresseux dans Milvus 2.6 et versions ultérieures
Les métadonnées chargées lors de l'initialisation se répartissent en quatre catégories principales :
Statistiques de segment (informations de base telles que le nombre de lignes, la taille du segment et les métadonnées de schéma)
Horodatage (utilisé pour prendre en charge les requêtes temporelles)
Insertion et suppression d'enregistrements (nécessaires pour maintenir la cohérence des données pendant l'exécution de la requête)
Filtres de Bloom (utilisés pour un pré-filtrage rapide afin d'éliminer rapidement les segments non pertinents)
2. Chargement partiel
Alors que le chargement paresseux contrôle le moment où les données sont chargées, le chargement partiel contrôle la quantité de données chargées. Lorsque les requêtes ou les recherches commencent, le QueryNode effectue un chargement partiel, en récupérant uniquement les morceaux de données ou les fichiers d'index nécessaires dans le stockage d'objets.
Index vectoriels : Chargement en fonction du locataire
L'une des fonctionnalités les plus importantes introduites dans Milvus 2.6+ est le chargement des index vectoriels en fonction du locataire, conçu spécifiquement pour les charges de travail multi-locataires.
Lorsqu'une requête accède aux données d'un seul locataire, Milvus charge uniquement la partie de l'index vectoriel appartenant à ce locataire, en ignorant les données d'index pour tous les autres locataires. Les ressources locales restent ainsi concentrées sur les locataires actifs.
Cette conception présente plusieurs avantages :
Les index vectoriels des locataires inactifs ne consomment pas de mémoire locale ni de disque.
Les données d'index pour les locataires actifs restent en cache pour un accès à faible latence.
Une politique d'éviction LRU au niveau du locataire garantit une utilisation équitable du cache entre les locataires.
Champs scalaires : Chargement partiel au niveau des colonnes
Le chargement partiel s'applique également aux champs scalaires, ce qui permet à Milvus de charger uniquement les colonnes explicitement référencées par une requête.
Considérons une collection comportant 50 champs de schéma, tels que id, vector, title, description, category, price, stock, et tags, et vous n'avez besoin de renvoyer que trois champs :id, title, et price.
Dans Milvus 2.5, les 50 champs scalaires sont chargés indépendamment des exigences de la requête.
Dans Milvus 2.6+, seuls les trois champs demandés sont chargés. Les 47 champs restants ne sont pas chargés et ne sont récupérés paresseusement que s'ils sont consultés ultérieurement.
Les économies de ressources peuvent être substantielles. Si chaque champ scalaire occupe 20 Go, le chargement de tous les champs nécessite 1 000 Go :
le chargement de tous les champs nécessite 1 000 Go (50 × 20 Go)
Le chargement des trois champs requis seulement utilise 60 Go
Cela représente une réduction de 94 % du chargement des données scalaires, sans affecter l'exactitude de la requête ou les résultats.
Remarque : le chargement partiel des champs scalaires et des index vectoriels en fonction du locataire sera officiellement introduit dans une prochaine version. Une fois disponible, il permettra de réduire davantage la latence de chargement et d'améliorer les performances des requêtes à froid dans les grands déploiements multi-locataires.
3. Eviction du cache basée sur LRU
Le chargement paresseux et le chargement partiel réduisent considérablement la quantité de données introduites dans la mémoire locale et sur le disque. Toutefois, dans les systèmes fonctionnant depuis longtemps, le cache continue de s'agrandir au fur et à mesure que de nouvelles données sont consultées. Lorsque la capacité locale est atteinte, l'éviction du cache basée sur la méthode LRU prend effet.
L'éviction LRU (Least Recently Used) suit une règle simple : les données auxquelles on n'a pas accédé récemment sont évincées en premier. Cela permet de libérer de l'espace local pour les données nouvellement accédées tout en conservant les données fréquemment utilisées dans la mémoire cache.
Évaluation des performances : Stockage hiérarchisé vs. chargement complet
Pour évaluer l'impact réel du stockage hiérarchisé, nous avons mis en place un environnement de test qui reproduit fidèlement les charges de travail de production. Nous avons comparé Milvus avec et sans stockage hiérarchisé sur cinq dimensions : le temps de chargement, l'utilisation des ressources, les performances des requêtes, la capacité effective et la rentabilité.
Configuration expérimentale
Jeu de données
100 millions de vecteurs avec 768 dimensions (BERT embeddings)
Taille de l'index vectoriel : environ 430 Go
10 champs scalaires, dont l'ID, l'horodatage et la catégorie
Configuration matérielle
1 QueryNode avec 4 vCPU, 32 Go de mémoire et 1 TB NVMe SSD
Réseau 10 Gbps
Cluster de stockage d'objets MinIO en tant que backend de stockage distant
Modèle d'accès
Les requêtes suivent une distribution réaliste d'accès chaud-froid :
80 % des requêtes ciblent les données des 30 derniers jours (≈20 % des données totales)
15 % des requêtes portent sur des données datant de 30 à 90 jours (≈30 % du total des données)
5 % ciblent des données datant de plus de 90 jours (≈50 % du total des données).
Principaux résultats
1. Temps de chargement 33 fois plus rapide
| Phase | Milvus 2.5 | Milvus 2.6+ (Tiered Storage) | Accélération |
|---|---|---|---|
| Téléchargement des données | 22 minutes | 28 secondes | 47× |
| Chargement de l'index | 3 minutes | 17 secondes | 10.5× |
| Total | 25 minutes | 45 secondes | 33× |
Dans Milvus 2.5, le chargement de la collection prenait 25 minutes. Avec le stockage hiérarchisé dans Milvus 2.6+, la même charge de travail s'exécute en 45 secondes seulement, ce qui représente une amélioration considérable de l'efficacité de la charge.
2. Réduction de 80 % de l'utilisation des ressources locales
| Phase | Milvus 2.5 | Milvus 2.6+ (stockage hiérarchisé) | Réduction |
|---|---|---|---|
| Après chargement | 430 GO | 12 GO | -97% |
| Après 1 heure | 430 GO | 68 GO | -84% |
| Après 24 heures | 430 GO | 85 GO | -80% |
| État stable | 430 GO | 85-95 GB | ~80% |
Dans Milvus 2.5, l'utilisation des ressources locales reste constante à 430 Go, quelle que soit la charge de travail ou la durée d'exécution. En revanche, Milvus 2.6+ démarre avec seulement 12 Go immédiatement après le chargement.
Au fur et à mesure de l'exécution des requêtes, les données fréquemment consultées sont mises en cache localement et l'utilisation des ressources augmente progressivement. Après environ 24 heures, le système se stabilise à 85-95 Go, reflétant l'ensemble des données chaudes. Sur le long terme, cela se traduit par une réduction d'environ 80 % de la mémoire locale et de l'utilisation du disque, sans sacrifier la disponibilité des requêtes.
3. Impact quasi nul sur les performances des données chaudes
| Type de requête | Milvus 2.5 P99 latence | Milvus 2.6+ P99 latency | Changement |
|---|---|---|---|
| Requêtes de données chaudes | 15 ms | 16 ms | +6.7% |
| Requêtes de données chaudes | 15 ms | 28 ms | +86% |
| Requêtes de données froides (premier accès) | 15 ms | 120 ms | +700% |
| Requêtes de données froides (en cache) | 15 ms | 18 ms | +20% |
Pour les données chaudes, qui représentent environ 80 % de toutes les requêtes, la latence P99 n'augmente que de 6,7 %, ce qui n'a pratiquement aucun impact perceptible en production.
Les requêtes de données froides présentent une latence plus élevée lors du premier accès en raison du chargement à la demande à partir du stockage d'objets. Cependant, une fois mises en cache, leur latence n'augmente que de 20 %. Étant donné la faible fréquence d'accès aux données froides, ce compromis est généralement acceptable pour la plupart des charges de travail réelles.
4. Une capacité effective 4,3 fois supérieure
Avec le même budget matériel - huit serveurs dotés de 64 Go de mémoire chacun (512 Go au total) - Milvus 2.5 peut charger au maximum 512 Go de données, ce qui équivaut à environ 136 millions de vecteurs.
Avec le stockage hiérarchisé activé dans Milvus 2.6+, le même matériel peut prendre en charge 2,2 To de données, soit environ 590 millions de vecteurs. Cela représente une augmentation de 4,3 fois de la capacité effective, ce qui permet de servir des ensembles de données beaucoup plus importants sans augmenter la mémoire locale.
5. Réduction des coûts de 80,1
En prenant pour exemple un ensemble de données vectorielles de 2 To dans un environnement AWS, et en supposant que 20 % des données sont chaudes (400 Go), la comparaison des coûts est la suivante :
| Article | Milvus 2.5 | Milvus 2.6+ (Stockage hiérarchisé) | Économies |
|---|---|---|---|
| Coût mensuel | $11,802 | $2,343 | $9,459 |
| Coût annuel | $141,624 | $28,116 | $113,508 |
| Taux d'économie | - | - | 80.1% |
Résumé de l'analyse comparative
Dans tous les tests, le stockage hiérarchisé apporte des améliorations constantes et mesurables :
Temps de chargement 33 fois plus rapides : Le temps de chargement des collections est passé de 25 minutes à 45 secondes.
Réduction de 80 % de l'utilisation des ressources locales : En fonctionnement continu, l'utilisation de la mémoire et du disque local diminue d'environ 80 %.
Impact quasi nul sur les performances des données chaudes : La latence P99 pour les données chaudes augmente de moins de 10 %, ce qui préserve les performances des requêtes à faible latence.
Latence contrôlée pour les données froides : Les données froides subissent une latence plus élevée lors du premier accès, mais cela est acceptable compte tenu de leur faible fréquence d'accès.
Capacité effective 4,3 fois plus élevée : Le même matériel peut servir 4 à 5 fois plus de données sans mémoire supplémentaire.
Réduction des coûts de plus de 80 % : Les coûts annuels d'infrastructure sont réduits de plus de 80 %.
Quand utiliser le stockage hiérarchisé dans Milvus ?
Sur la base des résultats de l'analyse comparative et des cas de production réels, nous regroupons les cas d'utilisation du stockage hiérarchisé en trois catégories pour vous aider à décider s'il convient à votre charge de travail.
Cas d'utilisation les mieux adaptés
1. Plates-formes de recherche vectorielle multi-locataires
Caractéristiques : Grand nombre de locataires avec une activité très inégale ; la recherche vectorielle est la charge de travail principale.
Modèle d'accès : Moins de 20 % des locataires génèrent plus de 80 % des requêtes vectorielles.
Avantages escomptés : Réduction des coûts de 70 à 80 % ; augmentation de la capacité de 3 à 5 fois.
2. Systèmes de recommandation pour le commerce électronique (charges de travail de recherche vectorielle)
Caractéristiques : Forte asymétrie de popularité entre les produits phares et la longue traîne.
Modèle d'accès : Les 10 % de produits les plus populaires représentent ~80 % du trafic de recherche vectorielle.
Avantages attendus : Pas de besoin de capacité supplémentaire pendant les périodes de pointe ; réduction des coûts de 60 à 70 %.
3. Ensembles de données à grande échelle avec une séparation claire entre le chaud et le froid (dominance vectorielle)
Caractéristiques : Ensembles de données à l'échelle du TB ou plus, avec un accès fortement biaisé vers les données récentes.
Schéma d'accès : Une distribution classique 80/20 : 20 % des données répondent à 80 % des requêtes.
Avantages escomptés : Réduction des coûts de 75 à 85
Cas d'utilisation appropriés
1. Charges de travail sensibles aux coûts
Caractéristiques : Budgets serrés avec une certaine tolérance pour des compromis mineurs en matière de performances.
Modèle d'accès : Les requêtes vectorielles sont relativement concentrées.
Avantages attendus : Réduction des coûts de 50 à 70 % ; les données froides peuvent entraîner une latence d'environ 500 ms lors du premier accès, ce qui doit être évalué en fonction des exigences de l'accord de niveau de service (SLA).
2. Conservation des données historiques et recherche dans les archives
Caractéristiques : Gros volumes de vecteurs historiques avec une très faible fréquence d'interrogation.
Modèle d'accès : Environ 90 % des requêtes portent sur des données récentes.
Avantages escomptés : Conservation de l'ensemble des données historiques ; coûts d'infrastructure prévisibles et contrôlés.
Cas d'utilisation mal adaptés
1. Charges de travail avec des données uniformément chaudes
Caractéristiques : Toutes les données sont consultées à une fréquence similaire, sans distinction claire entre chaud et froid.
Raisons de l'inadéquation : Avantages limités pour le cache ; complexité accrue du système sans gains significatifs.
2. Charges de travail à très faible latence
Caractéristiques : Systèmes extrêmement sensibles à la latence, tels que les transactions financières ou les enchères en temps réel.
Raisons de l'inadaptation : Les variations de latence, même minimes, sont inacceptables ; le chargement complet offre des performances plus prévisibles.
Démarrage rapide : Essayez le stockage hiérarchisé dans Milvus 2.6+.
# Download Milvus 2.6.1+
$ wget https://github.com/milvus-io/milvus/releases/latest
# Configure Tiered Storage
$ vi milvus.yaml
queryNode.segcore.tieredStorage:
warmup:
scalarField: disable
scalarIndex: disable
vectorField: disable
vectorIndex: disable
evictionEnabled: true
# Launch Milvus
$ docker-compose up -d
Conclusion
Le stockage hiérarchisé dans Milvus 2.6 répond à une inadéquation courante entre la manière dont les données vectorielles sont stockées et la manière dont on y accède réellement. Dans la plupart des systèmes de production, seule une petite fraction des données est interrogée fréquemment, alors que les modèles de chargement traditionnels traitent toutes les données comme étant aussi chaudes les unes que les autres. En passant au chargement à la demande et en gérant la mémoire locale et le disque comme un cache, Milvus aligne la consommation des ressources sur le comportement réel des requêtes plutôt que sur les hypothèses les plus pessimistes.
Cette approche permet aux systèmes d'évoluer vers des ensembles de données plus importants sans augmentation proportionnelle des ressources locales, tout en maintenant les performances des requêtes à chaud largement inchangées. Les données froides restent accessibles en cas de besoin, avec une latence prévisible et limitée, ce qui rend le compromis explicite et contrôlable. À mesure que la recherche vectorielle s'implante dans les environnements de production sensibles aux coûts, multi-locataires et à long terme, le stockage hiérarchisé constitue une base pratique pour un fonctionnement efficace à grande échelle.
Pour plus d'informations sur le stockage hiérarchisé, consultez la documentation ci-dessous :
Vous avez des questions ou souhaitez approfondir une fonctionnalité de la dernière version de Milvus ? Rejoignez notre canal Discord ou déposez des questions sur GitHub. Vous pouvez également réserver une session individuelle de 20 minutes pour obtenir des informations, des conseils et des réponses à vos questions dans le cadre des Milvus Office Hours.
En savoir plus sur les fonctionnalités de Milvus 2.6
Présentation de Milvus 2.6 : recherche vectorielle abordable à l'échelle du milliard
Déchiquetage JSON dans Milvus : filtrage JSON 88,9 fois plus rapide et flexible
La compression vectorielle à l'extrême : comment Milvus répond à 3× plus de requêtes avec RaBitQ
Les benchmarks mentent - les bases de données vectorielles méritent un vrai test
Nous avons remplacé Kafka/Pulsar par un Woodpecker pour Milvus
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word



