Une entité, plusieurs vecteurs : recherche au niveau entité et élément avec StructArray de Milvus 3.0
La plupart des schémas de bases de données vectorielles partent d'une hypothèse simple : une entité, un embedding. Un produit reçoit un vecteur, tout comme un document. Une requête utilisateur est vectorisée puis comparée à ces vecteurs via une recherche de plus proches voisins approximatifs (ANN). Ce modèle fonctionne pour la première génération de cas d'usage de recherche vectorielle, notamment le RAG, la recherche sémantique et les systèmes de recommandation.
Les données d'IA du monde réel, cependant, correspondent rarement à cette hypothèse. Une vidéo contient des clips, des plans ou des images clés, chacun avec son propre embedding, sa plage temporelle, sa légende, son étiquette de scène et son score de confiance. Un produit peut avoir plusieurs images et angles de vue. Un document long contient des passages ou des sections dont la signification locale importe plus qu'un embedding unique de l'ensemble du document. Les modèles à interaction tardive populaires exposent la même limite à une granularité encore plus fine : ColBERT produit un vecteur par jeton, tandis que ColPali produit un vecteur par patch visuel.
Dans chaque cas, l'entité parente reste l'unité que l'application stocke, affiche, sécurise et retourne. Pourtant, la pertinence, le filtrage et l'explication des résultats dépendent souvent d'éléments situés à l'intérieur de cette entité.
La nouvelle fonctionnalité StructArray donne à Milvus un modèle de données natif pour cette forme : une entité contient un tableau ordonné d'éléments Struct définis par le schéma, et chaque élément peut porter des métadonnées scalaires, des embeddings vectoriels, ou les deux. Milvus peut filtrer les champs appartenant au même élément, comparer deux listes d'embeddings au niveau de l'entité, ou rechercher des éléments individuels et retourner le décalage correspondant.
Cet article utilise un exemple de recherche vidéo pour expliquer le modèle de données, puis le suit à travers la conception du schéma, le filtrage, les granularités de recherche vectorielle, les stratégies d'indexation EmbeddingList, la fusion des résultats hybrides et la structure physique qui rend la fonctionnalité exécutable.
Pourquoi le modèle à un vecteur et une ligne plate ne suffit plus
Prenons l'exemple d'un utilisateur qui recherche dans un catalogue vidéo « une personne coupant des légumes dans une cuisine ». Le signal pertinent peut se trouver dans un clip de huit secondes, et non dans un embedding de la vidéo entière. Compresser chaque clip, objet et action dans un vecteur unique peut préserver le sujet général, mais cela peut estomper les détails locaux.
La même inadéquation apparaît dans d'autres charges de travail :
- La pertinence d'un produit peut provenir de l'une de ses plusieurs images ou angles.
- Un document peut correspondre grâce à un passage plutôt qu'à son sujet général.
- Une mémoire d'agent peut contenir plusieurs observations, dont une seule importe pour la tâche en cours.
- Un enregistrement ColBERT ou ColPali contient une liste de longueur variable de vecteurs de jetons ou de patches plutôt qu'un seul vecteur dense.
Une alternative consiste à diviser chaque clip, image ou passage en une ligne de base de données distincte. Cela permet la recherche locale, mais sépare également chaque fragment de son entité parente. Les métadonnées parentes peuvent être répétées sur plusieurs lignes, et la récupération au niveau de l'entité nécessite alors un regroupement, une déduplication et un reclassement après la recherche de fragments.
Le stockage imbriqué seul ne résout pas le problème des requêtes. JSON peut stocker des objets, mais il ne donne pas à Milvus un schéma de sous-champs prédéfini pour l'indexation vectorielle et scalaire. Les tableaux parallèles peuvent stocker les légendes, les étiquettes de scène et les valeurs de confiance, mais l'application doit maintenir l'alignement des décalages. La base de données ne peut pas déduire de manière fiable que scene_type[3] et label_confidence[3] décrivent le même clip, à moins que cette relation ne fasse partie du modèle de données.
StructArray encode cette relation directement. Il conserve les éléments locaux à l'intérieur de l'entité parente tout en exposant leurs sous-champs alignés à la validation du schéma, à l'indexation, au filtrage et à la recherche vectorielle.
Qu'est-ce que StructArray et son modèle de données ?
Un StructArray, également appelé tableau de structures, stocke un ensemble ordonné d'éléments Struct dans chaque entité. Un champ StructArray est un Array dont tous les éléments suivent un schéma Struct prédéfini. Pour une collection vidéo, la forme logique pourrait ressembler à ceci :
Plaintext
clips: ARRAY<STRUCT<
clip_embedding_list: FLOAT_VECTOR,
clip_embedding: FLOAT_VECTOR,
start_sec: DOUBLE,
end_sec: DOUBLE,
caption: VARCHAR,
scene_type: VARCHAR,
label_confidence: FLOAT
>>
Ici :
clipsest le champ StructArray parent.clip_embedding_list,clip_embedding,start_secet les autres attributs sont des sous-champs.clips[0]est le premier clip.- Chaque sous-champ au décalage
0appartient à ce même clip. - Chaque sous-champ au décalage
3appartient à un autre clip.
Les deux sous-champs vectoriels servent différents modes de recherche. clips[clip_embedding_list] est indexé avec une métrique MAX_SIM* pour la recherche EmbeddingList au niveau de l'entité, tandis que clips[clip_embedding] est indexé avec une métrique vectorielle régulière pour la recherche au niveau de l'élément. Comme un champ vectoriel ou un sous-champ vectoriel n'accepte qu'un seul index, une collection qui nécessite les deux modes doit définir et indexer les deux sous-champs séparément.
Ce modèle prend en charge trois sémantiques de requête distinctes.
1. La recherche EmbeddingList retourne les entités parentes
Les vecteurs dans clips[clip_embedding_list] forment une liste d'embeddings pour la vidéo. La requête est également un EmbeddingList. Milvus compare la liste de requête avec chaque liste stockée en utilisant une métrique MAX_SIM* et retourne un résultat au niveau de l'entité.
Plaintext
clips[clip_embedding_list] = [
embedding_0,
embedding_1,
embedding_2,
...
]
2. La famille MATCH_* filtre les entités parentes
MATCH_ANY, MATCH_ALL, MATCH_LEAST, MATCH_MOST et MATCH_EXACT évaluent un prédicat sur les éléments Struct, comptent combien d'éléments le satisfont et décident si l'entité parente passe le filtre.
Par exemple :
Plaintext
MATCH_ANY(clips, $[scene_type] == "kitchen" && $[label_confidence] > 0.8)
Les deux conditions scalaires doivent être vraies au même décalage de clip. Milvus ne combine pas une étiquette de cuisine d'un clip avec une valeur de confiance élevée d'un autre.
3. La recherche au niveau de l'élément retourne le décalage de l'élément correspondant
Un vecteur de requête régulier peut rechercher chaque vecteur dans clips[clip_embedding] indépendamment. Chaque correspondance identifie l'entité parente et le décalage basé sur zéro de l'élément Struct correspondant. Un element_filter peut restreindre quels éléments participent à cette recherche vectorielle.
Ces opérations partagent une prémisse : Milvus sait quelles valeurs vectorielles et scalaires appartiennent au même élément, et quels éléments appartiennent à la même entité.
StructArray n'est pas un système d'imbrication arbitraire à usage général. Son modèle actuel est un Array d'éléments Struct avec des sous-champs scalaires et vectoriels pris en charge. Cette limite rend l'indexation des sous-champs et l'exécution consciente des éléments réalisables.
Construire le schéma, les index et le chemin d'insertion
L'exemple PyMilvus simplifié suivant crée une collection vidéo avec un vecteur de niveau supérieur et un StructArray pour les clips. Il utilise des sous-champs vectoriels de clip séparés afin que la même collection puisse démontrer les deux modes de recherche.
from pymilvus import DataType, MilvusClient
client = MilvusClient(uri=“http://localhost:19530”)
schema = client.create_schema(auto_id=False, enable_dynamic_field=False)
schema.add_field(“id”, DataType.INT64, is_primary=True)
schema.add_field(“title”, DataType.VARCHAR, max_length=512)
schema.add_field(“video_embedding”, DataType.FLOAT_VECTOR, dim=768)
# Define the Struct schema explicitly.
clip_schema = client.create_struct_field_schema()
clip_schema.add_field(“clip_embedding_list”, DataType.FLOAT_VECTOR, dim=768)
clip_schema.add_field(“clip_embedding”, DataType.FLOAT_VECTOR, dim=768)
clip_schema.add_field(“start_sec”, DataType.DOUBLE)
clip_schema.add_field(“end_sec”, DataType.DOUBLE)
clip_schema.add_field(“caption”, DataType.VARCHAR, max_length=2048)
clip_schema.add_field(“scene_type”, DataType.VARCHAR, max_length=128)
clip_schema.add_field(“label_confidence”, DataType.FLOAT)
schema.add_field(
“clips”,
datatype=DataType.ARRAY,
element_type=DataType.STRUCT,
struct_schema=clip_schema,
max_capacity=1024,
)
client.create_collection(“videos”, schema=schema)
Les sous-champs vectoriels doivent être indexés avant la recherche. Comme la famille de métriques détermine le mode de recherche, chaque sous-champ vectoriel reçoit son propre index :
index_params = client.prepare_index_params()
# EmbeddingList search.
index_params.add_index(
field_name="clips[clip_embedding_list]",
index_type=“HNSW”,
metric_type=“MAX_SIM_COSINE”,
index_name=“clips_clip_embedding_list_maxsim_idx”,
params={“M”: 16, “efConstruction”: 200},
)
# Element-level search.
index_params.add_index(
field_name="clips[clip_embedding]",
index_type=“HNSW”,
metric_type=“COSINE”,
index_name=“clips_clip_embedding_cosine_idx”,
params={“M”: 16, “efConstruction”: 200},
)
client.create_index(“videos”, index_params=index_params)
Les index scalaires sont optionnels, mais les sous-champs qui apparaissent fréquemment dans les filtres à grande échelle devraient utiliser un index scalaire compatible. Par exemple, clips[scene_type] peut utiliser un index inversé, tandis qu'un sous-champ numérique tel que clips[label_confidence] peut utiliser un index adapté au filtrage numérique.
Insérez les données dans leur forme d'entité naturelle : une ligne vidéo avec un tableau d'objets clip. Pour garder l'exemple compact, il écrit le même vecteur de clip dans les deux sous-champs vectoriels.
rows = [
{
"id": 1,
"title": "cooking tutorial",
"video_embedding": video_vec,
"clips": [
{
"clip_embedding_list": clip_vec_1,
"clip_embedding": clip_vec_1,
"start_sec": 0.0,
"end_sec": 8.0,
"caption": "A person washes vegetables.",
"scene_type": "kitchen",
"label_confidence": 0.92,
},
{
"clip_embedding_list": clip_vec_2,
"clip_embedding": clip_vec_2,
"start_sec": 8.0,
"end_sec": 16.0,
"caption": "A person cuts carrots on a board.",
"scene_type": "kitchen",
"label_confidence": 0.96,
},
],
}
]
client.insert(“videos”, rows)
client.flush(“videos”)
client.load_collection(“videos”)
À la frontière de l'API, clips reste un tableau d'objets structurés. À l'intérieur de Milvus, chaque sous-champ suit le chemin typé requis pour son propre index, filtre et comportement de sortie. Cette distinction est transparente au moment de l'insertion mais fondamentale pour tout ce qui suit.
Le filtrage au sein du même élément est la différence entre structure et tableaux parallèles
Le principal avantage du filtrage n'est pas une syntaxe plus courte pour les champs imbriqués. C'est une corrélation correcte entre les sous-champs scalaires.
Supposons que l'application ait besoin de vidéos contenant un clip de cuisine avec un score de confiance d'étiquette supérieur à 0.8. Il ne suffit pas qu'une vidéo contienne un clip de cuisine et un clip à haute confiance ; le même clip doit satisfaire les deux conditions.
La famille MATCH_* de StructArray exprime cela directement :
Plaintext
MATCH_ANY(clips, $[scene_type] == "kitchen" && $[label_confidence] > 0.8)
MATCH_ALL(clips, $[label_confidence] > 0.5)
MATCH_LEAST(clips, $[scene_type] == "sports", threshold=3)
MATCH_MOST(clips, $[label_confidence] < 0.2, threshold=1)
MATCH_EXACT(clips, $[scene_type] == "intro", threshold=1)
Milvus évalue le prédicat à chaque décalage d'élément, puis applique le quantificateur de l'opérateur pour décider si l'entité parente passe :
MATCH_ANY: Au moins un élément correspond.MATCH_ALL: Chaque élément correspond.MATCH_LEAST: Au moinsthresholdéléments correspondent.MATCH_MOST: Au plusthresholdéléments correspondent.MATCH_EXACT: Exactementthresholdéléments correspondent.
Si les mêmes données étaient stockées sous forme de deux tableaux indépendants, l'expression suivante ne préserverait pas cette corrélation :
Plaintext
array_contains(clips[scene_type], "kitchen")
AND
array_contains(clips[label_confidence], 0.9)
Les deux valeurs pourraient se produire à des décalages différents. Cela peut être valable pour des attributs sans rapport, mais c'est incorrect lorsque les deux conditions décrivent le même clip, la même image de produit ou le même passage de document.
StructArray fait de l'identité d'élément une partie du prédicat de la base de données plutôt qu'une convention que l'application doit appliquer.
Deux granularités de recherche vectorielle, deux identités de résultat
Une fois qu'une entité stocke plusieurs vecteurs, la récupération doit régler une question de modélisation avant que la recherche ANN ne commence :
Les vecteurs doivent-ils être scorés ensemble comme une représentation unique de l'entité parente, ou chaque vecteur d'élément doit-il concourir indépendamment ?
StructArray prend en charge les deux modèles, mais ils utilisent des formes de requête, des familles de métriques, des sous-champs vectoriels et des identités de résultat différents.
Recherche EmbeddingList : une liste de vecteurs de requête trouve une entité
Une requête EmbeddingList contient plusieurs vecteurs. Une vidéo de requête peut être divisée en plusieurs clips ; une requête produit peut contenir plusieurs images de référence ; une requête ColBERT contient un vecteur par jeton de requête.
Pour chaque entité, Milvus compare la liste de requête avec la liste d'embeddings stockée de l'entité. Avec le scoring de type MaxSim, chaque vecteur de requête sélectionne sa meilleure correspondance dans la liste de l'entité, et Milvus agrège ces scores de meilleure correspondance en un score d'entité. La correspondance finale représente l'entité parente, et non un élément Struct particulier.
from pymilvus.client.embedding_list import EmbeddingList
query = EmbeddingList()
query.add(query_clip_vec_1)
query.add(query_clip_vec_2)
client.search(
collection_name=“videos”,
data=[query],
anns_field="clips[clip_embedding_list]",
search_params={“metric_type”: “MAX_SIM_COSINE”},
limit=10,
)
Cette recherche répond à la question : Quelles vidéos sont la meilleure correspondance globale pour cet ensemble de clips de requête ?
Elle convient à la récupération vidéo-à-vidéo, à la recherche produit multi-images, à la récupération de type ColBERT et ColPali, et à d'autres cas où la requête et l'entité stockée sont toutes deux représentées par plusieurs vecteurs.
Recherche au niveau de l'élément : un vecteur de requête trouve un clip dans une entité
La recherche au niveau de l'élément utilise un vecteur de requête régulier. Chaque vecteur dans clips[clip_embedding] participe à la recherche ANN comme candidat indépendant. Chaque correspondance identifie l'entité parente et le décalage de l'élément correspondant.
client.search(
collection_name="videos",
data=[query_vec],
anns_field="clips[clip_embedding]",
search_params={"metric_type": "COSINE"},
limit=10,
output_fields=["id", "title", "clips"],
)
Pour ne rechercher que certains clips, attachez un element_filter dont les conditions scalaires s'appliquent au même clip :
client.search(
collection_name="videos",
data=[query_vec],
anns_field="clips[clip_embedding]",
search_params={"metric_type": "COSINE"},
filter='element_filter(clips, $[scene_type] == "kitchen" && $[label_confidence] > 0.8)',
limit=10,
output_fields=["id", "title", "clips"],
)
Le filtre ne sélectionne pas d'abord un clip de cuisine puis ne recherche pas un clip à haute confiance différent. Les deux prédicats et le candidat vectoriel se réfèrent au même élément Struct.
Une réponse non groupée peut ressembler à ceci :
Plaintext
id = 1, offset = 1, distance = 0.91
id = 8, offset = 4, distance = 0.88
id = 1, offset = 3, distance = 0.84
La même entité peut apparaître plusieurs fois car plusieurs clips peuvent correspondre. Cela est utile lorsque l'application doit montrer non seulement quelle vidéo ou quel document est pertinent, mais aussi quel clip ou quel passage a produit la correspondance.
| Aspect | Recherche EmbeddingList | Recherche au niveau de l'élément |
|---|---|---|
| Entrée de requête | Un ou plusieurs vecteurs de requête dans un EmbeddingList | Un vecteur de requête régulier |
| Exemple de cible | clips[clip_embedding_list] | clips[clip_embedding] |
| Famille de métriques | MAX_SIM* | Métriques régulières telles que COSINE, IP ou L2 |
| Unité candidate ANN | La liste d'embeddings de l'entité parente | Chaque vecteur d'élément Struct |
| Identité du résultat | Entité parente | Entité parente plus décalage d'élément |
| Cas d'usage typique | Correspondre une requête multi-vecteurs à une entité multi-vecteurs | Trouver le clip, l'image, le passage, le patch ou le fait le plus pertinent |
Pour prendre en charge les deux modes dans une seule collection, définissez et indexez des sous-champs vectoriels séparés. La forme de la requête, la famille de métriques et l'index cible doivent être cohérents.
L'indexation EmbeddingList est une décision qualité-coût
Avec un embedding par entité, un index ANN trouve les entités proches d'un vecteur de requête. La recherche EmbeddingList est plus coûteuse car la pertinence dépend d'interactions par paires entre deux listes de vecteurs.
Calculer le MaxSim exact contre chaque vecteur de chaque entité produit le classement de référence le plus propre, mais une analyse complète est généralement trop coûteuse pour la récupération en ligne. Milvus utilise donc un modèle en deux étapes :
- Une stratégie approximative récupère les entités parentes candidates.
- Lorsque
emb_list_rerankest activé, Milvus recalcule le MaxSim sur ces candidats pour produire le classement final.
Récupérer plus de candidats de première étape améliore généralement les chances que les vrais meilleurs résultats atteignent le reclassement, mais cela augmente également la latence et le calcul. Les trois stratégies diffèrent principalement dans la manière dont elles produisent cet ensemble de candidats.
| Stratégie | Représentation des candidats de première étape | Bon point de départ lorsque | Compromis principal |
|---|---|---|---|
| TokenANN | Indexe chaque vecteur de chaque liste d'embeddings. Les vecteurs de requête exécutent des ANN indépendants ; les correspondances sont agrégées vers les entités parentes avant le reclassement MaxSim. | La qualité est la priorité, les listes sont courtes ou moyennes, et les vecteurs individuels sont discriminants. | La taille de l'index et le travail de recherche de première étape croissent avec la longueur des listes et le nombre de vecteurs de requête. |
| MUVERA | Encode chaque liste d'embeddings en un vecteur de dimension fixe via des projections aléatoires, puis exécute un ANN ordinaire. | TokenANN est trop lourd et une compression sans pipeline d'entraînement est préférée. | L'encodage perd des informations ; des réglages de projection plus forts augmentent la dimensionnalité encodée et le coût ANN. |
| LEMUR | Entraîne un modèle qui mappe une liste d'embeddings vers un vecteur d'entité parente de dimension fixe. | Les embeddings sont moins discriminants, les listes sont grandes, ou la charge de travail est visuelle ou multimodale. | Cela nécessite un entraînement et peut être sensible à la distribution du corpus et au biais de longueur de document. |
Aucune stratégie unique n'est la meilleure pour chaque charge de travail. Commencez par les données cibles et la distribution des requêtes :
- Utilisez TokenANN comme référence qualité d'abord lorsque la taille du jeu de données le permet.
- Essayez MUVERA lorsque l'index ou la récupération de candidats de TokenANN devient trop coûteuse à mesure que la longueur des listes augmente, et que vous souhaitez éviter un pipeline d'entraînement.
- Évaluez LEMUR lorsque l'espace d'embeddings est bruité ou faiblement discriminant, ou lorsque la charge de travail est visuelle ou multimodale.
- Mesurez le rappel ou le nDCG en parallèle de la latence et de la taille de l'index. Une stratégie qui fonctionne pour des textes courts peut se comporter différemment avec des longueurs de document à longue traîne ou des milliers de patches visuels.
StructArray résout un problème : comment représenter des éléments alignés, filtrables et porteurs de vecteurs à l'intérieur d'une seule entité. La stratégie EmbeddingList en résout un autre : comment approximer le MaxSim à un coût acceptable pour un modèle et un corpus particuliers.
La recherche hybride rend l'identité du résultat explicite
La récupération en production suit rarement un seul chemin vectoriel. Une requête vidéo peut combiner un embedding vidéo de niveau supérieur, un ou plusieurs embeddings au niveau du clip, un signal de légende ou de transcription, et un reclassement.
Une fois que les candidats au niveau de l'élément entrent dans ce pipeline, le moteur doit décider ce qui identifie un candidat final.
| Composition de la requête hybride | Portée du candidat final | Identité du résultat |
|---|---|---|
| Toutes les sous-recherches sont au niveau de l'élément et ciblent des sous-champs vectoriels sous le même StructArray | Niveau de l'élément | Clé primaire plus champ StructArray plus décalage d'élément |
| Un champ vectoriel de niveau supérieur est inclus | Niveau de l'entité | Clé primaire |
| Une requête EmbeddingList est incluse | Niveau de l'entité | Clé primaire |
| Les requêtes au niveau de l'élément ciblent différents champs StructArray | Niveau de l'entité | Clé primaire |
La première configuration préserve l'identité de l'élément car le décalage 3 se réfère au même élément Struct pour chaque sous-recherche sous un StructArray parent donné. Cela convient à une application qui souhaite retourner le clip ou le passage le plus pertinent après avoir fusionné plusieurs signaux au niveau de l'élément.
Les autres configurations mélangent les granularités des candidats ou les espaces de noms des éléments. Une correspondance d'élément doit donc être réduite en un score au niveau de l'entité avant le reclassement final. Milvus prend en charge plusieurs stratégies de réduction :
| Stratégie de réduction | Score d'entité à partir des correspondances d'éléments retournées | Condition importante |
|---|---|---|
max | Meilleur score d'élément | Fonctionne avec les métriques vectorielles régulières prises en charge |
sum | Somme de tous les scores d'éléments retournés | À utiliser avec des métriques à corrélation positive telles que IP ou COSINE |
avg | Moyenne des scores d'éléments retournés | Fonctionne avec les métriques vectorielles régulières prises en charge |
topk_sum | Somme des meilleurs K scores d'éléments retournés | Nécessite un topk positif ; à utiliser avec IP ou COSINE |
topk_avg | Moyenne des meilleurs K scores d'éléments retournés | Nécessite un topk positif |
La réduction opère uniquement sur les correspondances d'éléments retournées par cette sous-recherche ANN ; elle ne scanne pas chaque élément de l'entité après la récupération. La limit de la requête contrôle donc quelles correspondances d'éléments sont disponibles pour la fonction de réduction.
Ce choix façonne la sémantique de la récupération, et non pas seulement le formatage de la sortie. Si l'application présente un clip ou un passage, préserver le décalage à travers la fusion est naturel. Si elle présente une vidéo, un produit ou un document, la réduction au niveau de l'entité est naturelle. Lorsque les signaux opèrent à différentes granularités, le système a besoin d'une règle explicite de scoring élément-vers-entité.
StructArray déplace ce problème d'identité-et-réduction du post-traitement ad hoc vers le modèle d'exécution de la recherche.
Comment Milvus exécute StructArray sans le traiter comme un blob
Le modèle côté utilisateur est ARRAY<STRUCT>. Stocker la valeur entière comme un blob opaque, cependant, rendrait les index de sous-champs, les filtres et la sortie sélective inefficaces.
Milvus utilise une conception parent-logique, colonnes-enfant-physiques.
Au niveau du schéma, clips est le champ parent logique. Il définit des propriétés telles que le schéma Struct, la capacité maximale et la possibilité de valeur nulle. Ses sous-champs sont normalisés en chemins tels que clips[clip_embedding_list], clips[clip_embedding], clips[scene_type] et clips[label_confidence].
Les sous-champs scalaires suivent des chemins de stockage de tableaux scalaires par entité, tandis que les sous-champs vectoriels suivent des chemins de tableaux vectoriels. Chaque sous-champ peut alors utiliser le chemin de données approprié à son type : filtrage scalaire et index scalaires pour les métadonnées, et index vectoriels et recherche ANN pour les embeddings.
À l'ingestion, le Proxy déplie la liste Struct imbriquée en colonnes enfant typées. Pendant l'exécution, Milvus maintient la relation entre chaque élément physique et son entité parente. Conceptuellement, cette relation ressemble à ceci :
Plaintext
entity 0 -> elements [0, 1, 2]
entity 1 -> elements [3]
entity 2 -> elements []
entity 3 -> elements [4, 5, 6, 7]
Lorsque la recherche au niveau de l'élément retourne un ID d'élément physique, Milvus le fait correspondre à l'entité parente et au décalage d'élément. Lorsque element_filter produit un bitmap au niveau de l'élément, le moteur l'aligne avec la visibilité de l'entité parente, les suppressions et les autres filtres.
Lors du retour des résultats, Milvus utilise le schéma logique et les décalages partagés pour reconstruire la forme StructArray que l'application a insérée. Le système peut exécuter sur des colonnes enfant typées pendant que l'utilisateur continue de lire et d'écrire des objets imbriqués naturels. Cette structure physique rend StructArray plus qu'un JSON typé : la relation imbriquée participe au modèle d'index et d'exécution.
Où StructArray convient, et où il ne convient pas
StructArray est un excellent choix lorsque toutes les conditions suivantes sont réunies :
- L'application a une entité parente significative, telle qu'une vidéo, un produit, un document, une page visuelle ou un enregistrement de mémoire.
- Chaque parent contient un ensemble ordonné de longueur variable d'éléments locaux.
- Ces éléments nécessitent leurs propres métadonnées scalaires, vecteurs, ou les deux.
- La recherche ou le filtrage doit préserver la relation entre les sous-champs au même décalage d'élément.
- L'application a besoin d'une récupération multi-vecteurs au niveau de l'entité, de correspondances au niveau de l'élément, ou des deux.
StructArray n'est pas automatiquement meilleur pour chaque collection. Un document court ou une requête simple peut être bien servi par un embedding dense unique. L'indexation multi-vecteurs ajoute des coûts de stockage et de recherche, donc la représentation supplémentaire doit gagner sa place par une meilleure qualité de récupération ou une granularité de résultats plus utile.
Les limites actuelles du schéma et de l'exécution comptent également :
Structest pris en charge comme type d'élément d'unArray, et non comme champ de collection de niveau supérieur.- Tous les éléments d'un même StructArray partagent un schéma prédéfini unique.
max_capacityest obligatoire et limite le nombre d'éléments par entité.- Les sous-champs
Struct,Array,ArrayOfStructetJSONimbriqués ne sont pas pris en charge à l'intérieur d'un StructArray. - Un sous-champ vectoriel accepte un seul index. Utilisez des sous-champs vectoriels séparés pour la recherche EmbeddingList et la recherche au niveau de l'élément lorsque les deux sont nécessaires.
- Les sous-champs vectoriels doivent être indexés avant la recherche. Les sous-champs scalaires fortement utilisés dans les filtres doivent être indexés de manière appropriée.
- Le schéma des sous-champs est fixé après la création du champ StructArray, donc planifiez les attributs des éléments avant le déploiement en production.
Ces contraintes rendent le modèle plus étroit que l'imbrication arbitraire d'une base de données documentaire, mais elles donnent aussi à Milvus suffisamment de structure pour raisonner sur l'identité des éléments, indexer chaque sous-champ et exécuter à deux granularités de recherche.
StructArray maintient la preuve locale de première classe sans perdre l'entité
StructArray donne à Milvus un objet de récupération que les schémas plats ont du mal à représenter : une entité parente avec un ensemble ordonné d'éléments structurés. Les relations entre ces éléments participent au filtrage, à l'indexation et à la recherche plutôt que d'exister uniquement dans le stockage.
Chaque élément conserve ses propres métadonnées et embeddings. Les éléments peuvent satisfaire des prédicats scalaires au sein du même élément, participer ensemble à la recherche EmbeddingList au niveau de l'entité, ou concourir indépendamment dans la recherche au niveau de l'élément. En même temps, ils restent attachés à l'entité parente dont les métadonnées, les permissions et l'identité applicative leur donnent du contexte.
Pour les clips vidéo, les images de produit, les passages de documents, les patches visuels et les fragments de mémoire, la preuve locale peut être recherchée et filtrée sans perdre l'entité à laquelle elle appartient. Les choix de conception restants sont explicites : sélectionnez la granularité de recherche, donnez à chaque sous-champ vectoriel la métrique et l'index correspondants, et décidez si les résultats hybrides doivent préserver les décalages d'éléments ou se réduire aux entités.
Essayez StructArray dans Milvus 3.0
StructArray est disponible dans Milvus 3.0. Commencez par la vue d'ensemble de StructArray. Si vous évaluez la récupération multi-vecteurs au niveau de l'entité, lisez le guide des stratégies EmbeddingList. Pour la granularité des résultats et le comportement de réduction, consultez Hybrid Search with StructArray.
Pour le contexte plus large de la version, voir le blog de lancement de Milvus 3.0, les notes de version et le dépôt milvus-io/milvus.
Zilliz Cloud prend également en charge StructArray et la recherche EmbeddingList pour les déploiements gérés. Consultez le guide Zilliz Cloud StructArray pour les limites spécifiques au service. Dans Zilliz Cloud, les opérateurs scalaires sur StructArray sont actuellement documentés pour les clusters On-Demand.
Pour discuter d'un schéma ou d'une conception de récupération avec l'équipe, rejoignez la communauté Discord Milvus ou réservez une session Milvus Office Hours.
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word



