Dal recupero ai risultati strutturati: aggregazione e ORDER BY in Milvus 3.0

  • Engineering
August 07, 2026
Chun Han

Considera un flusso di ricerca prodotti familiare. Un acquirente carica la foto di un abito e la ricerca vettoriale recupera da un catalogo contenente decine di milioni di prodotti un insieme di candidati rilevanti.

La pagina, però, ha bisogno di più di un elenco ordinato. Ha bisogno di facet per marca. Ha bisogno di un ordinamento per prezzo. Il team di merchandising vuole sapere quali brand dominano questo insieme di risultati, l’intervallo di prezzo all’interno di ciascun brand e alcuni prodotti rappresentativi per ogni gruppo.

Prima di Milvus 3.0, le applicazioni gestivano comunemente da sole questo secondo passaggio: recuperavano righe da Milvus, le raggruppavano e ordinavano in pandas o in un livello di servizio, quindi assemblavano la risposta. Alcuni team mantenevano una pipeline di analytics separata solo per calcolare conteggi e distribuzioni su dati che erano già nel database vettoriale.

Il database vettoriale trovava i candidati; l’applicazione doveva trasformarli in un risultato strutturato.

Milvus 3.0 sposta una parte maggiore di questo lavoro nel motore di recupero. Aggiunge tre funzionalità correlate ma distinte:

  • Aggregazione delle query calcola count, sum, avg, min e max sulle righe filtrate e visibili, con campi GROUP BY opzionali.
  • Search Aggregation organizza i candidati di ricerca approximate nearest neighbor (ANN) mantenuti in bucket, calcola metriche per bucket, costruisce bucket annidati e restituisce hit rappresentative.
  • Lato server **ORDER BY** ordina i risultati delle query o i candidati ANN in base a uno o più campi scalari prima che l’applicazione li riceva.

La distinzione tra query e ricerca è importante:

FunzionalitàDati riepilogati o ordinatiForma principale del risultatoConfine di esattezza
Aggregazione delle queryTutte le righe visibili che corrispondono al filtroUna riga per gruppo, con valori aggregatiEsatta sull’insieme di righe visibili della query
Search AggregationCandidati mantenuti dalla ricerca ANN e dalla fase di raggruppamentoBucket, metriche, hit rappresentative e bucket figli opzionaliApprossimativa per progettazione
Query ORDER BYRighe visibili che corrispondono al filtroRighe ordinateEsatta sul risultato della query filtrata
Search ORDER BYCandidati ANNHit o gruppi di ricerca ordinatiNon espande il confine di recall ANN

Questo articolo spiega perché queste operazioni appartengono al database, come funziona l’aggregazione distribuita, in che modo Search Aggregation differisce da Grouping Search e dove si fermano le nuove semantiche.

Perché il post-processing lato applicazione non regge

Spostare aggregazione e ordinamento nell’applicazione può sembrare una piccola scelta implementativa. Su larga scala, crea tre problemi più grandi.

L’applicazione sposta molti più dati di quanti ne contenga la risposta

Supponiamo che una dashboard operativa abbia bisogno del conteggio dei prodotti e del prezzo medio per ogni categoria tra due milioni di righe di prodotti disponibili. Anche con un payload approssimativo di soli 100 byte per riga per categoria, prezzo, chiave primaria e overhead di serializzazione, l’applicazione deve ricevere circa 200 MB di dati prima di poter calcolare il risultato.

Se il catalogo ha 200 categorie, la risposta è composta solo da poche centinaia di chiavi e numeri, nell’ordine dei kilobyte. L’applicazione sposta diversi ordini di grandezza più dati di quanti ne restituisca, paga lo stesso costo a ogni aggiornamento e necessita di memoria client sufficiente per mantenere o trasmettere in streaming le righe intermedie.

Un’aggregazione nel motore cambia l’unità di movimento dei dati. Le righe grezze rimangono dove sono. Ciò che attraversa i nodi e alla fine esce da Milvus è l’insieme molto più piccolo degli stati di gruppo parziali e finali.

L’ordinamento locale alla pagina non è un ordinamento globale

Ordinare dopo la paginazione è un bug di correttezza, non semplicemente un’implementazione inefficiente.

Se un’applicazione recupera le righe dalla 11 alla 20 e ordina solo quelle righe per prezzo, ha prodotto l’ordine per prezzo all’interno di quella pagina, non le righe dalla 11 alla 20 del risultato ordinato globalmente per prezzo. Una pagina successiva può contenere prodotti più economici di ogni prodotto presente nella prima pagina.

Lo stesso confine è importante nella ricerca vettoriale. Recuperare un piccolo insieme Top-K e ordinarlo nell’applicazione può riordinare solo quei candidati. Non può recuperare candidati rilevanti che la fase ANN non ha restituito e spesso porta le applicazioni a effettuare over-fetch solo per rendere utile l’ordinamento lato client.

L’ordinamento lato server dà a Milvus il controllo sulla sequenza di ordinamento e paginazione. Per i carichi di lavoro di query, il motore ordina l’insieme di righe filtrato prima di applicare la finestra di pagina. Per i carichi di lavoro di ricerca, ordina entro il confine dei candidati ANN e mantiene esplicita tale limitazione.

Il client non può riprodurre la visibilità del database

L’aggregazione dipende anche da quali righe sono visibili al timestamp della query. Eliminazioni, entità scadute e scritture concorrenti sono governate dal controllo della concorrenza multiversione (MVCC) e dalle semantiche di consistenza di Milvus.

Una volta che le righe grezze lasciano il database, l’applicazione di solito presume che il batch ricevuto rappresenti lo snapshot corretto. Ricostruire le stesse regole di visibilità in un client è impraticabile, soprattutto mentre la collection riceve scritture ed eliminazioni.

La soluzione alternativa comune, un secondo motore di analytics alimentato da export ed ETL, aggiunge un’altra copia dei dati, un altro confine di consistenza e un’altra pipeline da gestire. Conteggi, metriche e ordinamento dovrebbero essere eseguiti dove esistono già sia i dati sia le relative regole di visibilità.

Ora diamo un’occhiata a ciò che offre Milvus 3.0.

Aggregazione delle query: statistiche esatte sulle righe visibili

L’aggregazione delle query risponde a domande come:

  • Quanti prodotti disponibili ci sono in ciascuna categoria?
  • Qual è il prezzo medio per brand?
  • Quali sono i timestamp minimi e massimi degli eventi per ciascun host?
  • Quanti record rimangono dopo l’applicazione di un filtro e della visibilità TTL?

L’API risulta familiare a chiunque abbia usato SQL: passa uno o più campi in group_by_fields, quindi inserisci le espressioni di aggregazione in output_fields.

res = client.query(
    collection_name="products",
    filter='status == "on_sale"',
    group_by_fields=["category"],
    output_fields=["category", "count(*)", "avg(price)"],
)

# [ # {"category": "books", "count(*)": 18734, "avg(price)": 45.3}, # … # ]

La sintassi è la parte semplice. Il modello di esecuzione è ciò che rende il risultato utile in un database vettoriale distribuito.

Gli stati locali ai segmenti sostituiscono il movimento delle righe grezze

Una collection Milvus può estendersi su centinaia o migliaia di segmenti distribuiti su diversi query node, con i dati scritti di recente ancora nel percorso di streaming. Nessun singolo nodo di esecuzione parte con tutte le righe visibili.

Milvus quindi spinge l’aggregazione verso il basso, fino ai segmenti:

  1. Ogni segmento applica localmente il filtro e le regole di visibilità MVCC.
  2. Il segmento emette uno stato parziale per gruppo invece delle righe corrispondenti.
  3. Gli stati parziali vengono fusi all’interno di un query node.
  4. Il proxy esegue la fusione finale tra nodi e restituisce i gruppi completati.

La quantità di dati intermedi ora scala con il numero di gruppi e di stati aggregati, anziché direttamente con il numero di righe corrispondenti.

L’operazione di merge dipende dall’aggregato:

AggregatoStato parzialeRegola di merge
countConteggio parzialeSomma i conteggi
sumSomma parzialeSomma le somme
minMinimo parzialePrendi il minimo
maxMassimo parzialePrendi il massimo
avgSomma e conteggio parzialiSomma entrambi gli stati, quindi dividi una sola volta nella fase finale

avg è il caso istruttivo. Fare la media di due medie parziali è errato quando le partizioni contengono numeri diversi di righe. Milvus mantiene sum e count separatamente e calcola la media finale solo dopo che entrambi sono stati fusi globalmente.

Questo è uno dei motivi per cui l’aggregazione appartiene al database: l’operazione non è semplicemente “esegui la stessa funzione su diversi batch”. Il motore deve preservare l’algebra di ciascun aggregato attraverso i confini di segmenti e nodi.

La visibilità viene applicata prima dell’aggregazione

Le righe eliminate e scadute vengono rimosse dagli stati parziali a livello di segmento in base al confine di visibilità della query. Non viaggiano verso l’alto per poi essere corrette nell’applicazione.

Il risultato quindi descrive le righe che Milvus considera visibili per quella richiesta, non una raccolta arbitraria di batch estratti in momenti leggermente diversi.

limit ora conta i gruppi

In una query normale, limit controlla quante righe di entità vengono restituite. In una query raggruppata, controlla quanti gruppi vengono restituiti. Poiché la cardinalità del risultato è determinata dai gruppi anziché dalle righe corrispondenti, un’aggregazione di query può anche omettere limit quando ha bisogno di ogni gruppo.

Questo sembra un piccolo dettaglio dell’API, ma riflette un modello di risultato diverso: l’output non è più una pagina di entità. È una relazione le cui righe rappresentano gruppi.

Search Aggregation: una vista a bucket dei candidati ANN

L’aggregazione delle query risponde a: “Che aspetto hanno le righe visibili che corrispondono a questo filtro?” Search Aggregation pone una domanda diversa: “Che aspetto ha l’insieme di candidati recuperato per questo vettore?”

Questa operazione non ha un equivalente SQL esatto. La ricerca ANN stabilisce prima un confine di candidati guidato dalla similarità. Milvus quindi organizza i candidati mantenuti per chiavi scalari e restituisce un albero di bucket invece di un normale elenco piatto di hit.

Un bucket può contenere:

  • una chiave come brand o una chiave composita come (brand, color);
  • un conteggio dei candidati mantenuti;
  • metriche tra cui count, sum, avg, min e max;
  • entità rappresentative selezionate con top_hits; e
  • una sub_aggregation annidata che crea bucket figli.

Per la pagina di ricerca prodotti, una richiesta può restituire bucket di brand, il prezzo medio all’interno di ciascun bucket e tre prodotti rappresentativi per brand:

from pymilvus import SearchAggregation, TopHits

aggregation = SearchAggregation( fields=[“brand”], size=10, # Return up to 10 brand buckets metrics={“avg_price”: {“avg”: “price”}}, order=[{“_count”: “desc”}], # Order by retained-candidate count top_hits=TopHits( size=3, sort=[{“_score”: “desc”}], # Use “asc” for L2 distance ), )

res = client.search( collection_name=“products”, data=[query_vector], anns_field=“embedding”, search_aggregation=aggregation, output_fields=[“title”, “brand”, “price”], )

buckets = res.agg_buckets[0]

Quando search_aggregation è impostato, il normale elenco di hit è vuoto. L’applicazione legge la risposta dei bucket da result.agg_buckets.

La specifica di aggregazione imposta due limiti diversi

Search Aggregation non esegue GROUP BY su ogni entità della collection e non si limita a prendere una normale risposta Top-K e ad aggregare quell’elenco piatto.

La sua esecuzione ha tre fasi:

  1. Milvus esegue la ricerca ANN per recuperare candidati vicini al vettore della query.
  2. La fase di raggruppamento mantiene un numero limitato di candidati per ciascuna chiave completa di bucket.
  3. Milvus costruisce i bucket, calcola le metriche sui candidati mantenuti, ordina i bucket e collega hit rappresentative o bucket figli.

Due parametri controllano parti diverse del risultato:

  • SearchAggregation.size limita quanti bucket vengono restituiti a quel livello di aggregazione.
  • Il valore più grande di TopHits.size ovunque nell’albero di aggregazione imposta il budget di candidati mantenuti per ciascuna chiave composita completa. Se la richiesta non contiene top_hits, il budget per chiave predefinito è uno.

Il limit della ricerca di livello superiore non controlla questa modalità e viene ignorato quando è presente search_aggregation.

Questa distinzione è essenziale quando si legge il count o le metriche di un bucket. Con TopHits(size=3), un bucket di brand può riepilogare al massimo tre candidati mantenuti per la sua chiave completa, anche se la collection contiene migliaia di prodotti rilevanti di quel brand. Aumentare TopHits.size amplia la finestra di metriche per chiave, ma non trasforma la ricerca ANN in una scansione esatta.

Se l’applicazione ha bisogno di statistiche esatte su ogni riga visibile che corrisponde a un filtro, dovrebbe usare l’aggregazione delle query. Search Aggregation serve a descrivere e confrontare i candidati prodotti dal recupero per similarità.

Search Aggregation e Grouping Search risolvono problemi diversi

Milvus supporta Grouping Search (group_by) da Milvus 2.4. È facile vedere la parola “grouping” in entrambe le funzionalità e presumere che siano due interfacce per la stessa operazione. I loro contratti di output sono diversi.

Grouping Search modifica quali entità compaiono in un elenco di risultati ordinato. Un pattern RAG comune memorizza i chunk come entità individuali, li raggruppa per doc_id e restituisce uno o pochi chunk da ciascun documento. L’output principale rimane costituito da normali hit di ricerca, ma con meno valori ripetuti dal campo di raggruppamento.

Search Aggregation restituisce una vista statistica. L’output principale è un albero di bucket contenente chiavi, conteggi, metriche, hit rappresentative e bucket figli opzionali.

Esigenza dell’applicazionePreferireConsumare
Un elenco di entità ordinato con maggiore diversità su un campoGrouping SearchHit di ricerca ordinarie
Conteggi di facet, metriche per gruppo, hit rappresentative o distribuzioni annidateSearch AggregationOggetti AggregationBucket in result.agg_buckets

Una regola pratica è partire dalla forma della risposta UI o API. Se l’applicazione renderizza un elenco, Grouping Search è di solito la primitiva giusta. Se renderizza facet, schede di distribuzione o una gerarchia di gruppi, usa Search Aggregation.

Le due modalità si escludono a vicenda in una richiesta perché definiscono forme di risultato principali diverse.

ORDER BY: spostare l’ordinamento prima del confine dell’applicazione

L’ordinamento è la funzionalità meno esotica di questa release e una delle più facili da implementare in modo errato fuori dal motore.

Milvus 3.0 espone l’ordinamento sia su query sia su search, ma i due percorsi usano parametri SDK diversi e operano su insiemi di input diversi.

L’ordinamento delle query ordina l’insieme di righe filtrato

La query PyMilvus usa order_by, espresso come elenco di stringhe "field:direction". Il motore applica il filtro, ordina le righe visibili e quindi applica limit e offset.

res = client.query(
    collection_name="products",
    filter='category == "books"',
    output_fields=["title", "price"],
    order_by=["price:desc", "title:asc"],
    limit=10,
    offset=10,  # Rows 11-20 in the filtered, price-sorted result
)

Questo rende la query utile per la navigazione ordinata secondo criteri di business: record ingeriti più di recente, prodotti con il prezzo più alto all’interno di un filtro, inventario più basso o valori estremi per l’ispezione dei dati. Senza ordinamento lato server, le applicazioni dovevano prima recuperare le righe e non potevano definire un ordine di business affidabile tra le pagine.

Per i campi di query nullable, l’ordine crescente posiziona i null alla fine e l’ordine decrescente li posiziona all’inizio. Un campo di ordinamento non deve necessariamente comparire in output_fields; includilo solo quando l’applicazione ha bisogno del valore nella risposta.

L’ordinamento della ricerca riordina l’insieme di candidati ANN

La search PyMilvus usa order_by_fields, dove ogni voce indica un campo scalare e una direzione:

res = client.search(
    collection_name="products",
    data=[query_vector],
    anns_field="embedding",
    limit=50,
    output_fields=["title", "price"],
    order_by_fields=[
        {"field": "price", "order": "asc"},
    ],
)

ANN determina ancora quali entità diventano candidati. order_by_fields cambia il modo in cui questi candidati vengono restituiti; non fa sì che la ricerca scansioni globalmente la collection per trovare i prodotti più economici.

Questo confine assegna ai due API compiti distinti:

  • Usa query più order_by quando l’ordine scalare stesso definisce il risultato, ad esempio i dieci prodotti disponibili più economici.
  • Usa search più order_by_fields quando la rilevanza semantica o vettoriale definisce l’insieme di candidati e un campo scalare determina come questi candidati dovrebbero essere presentati.

L’ordinamento multi-campo applica le chiavi nell’ordine dell’elenco. Quando i candidati di ricerca hanno gli stessi valori per ogni chiave scalare specificata, Milvus preserva il loro ordine originale basato sul punteggio di similarità.

L’ordinamento si compone anche con Grouping Search. Milvus ordina i gruppi in base al valore scalare configurato dell’entità principale di ciascun gruppo, mantenendo la forma del risultato raggruppato. Questo è utile quando l’applicazione vuole sia diversità su un campo sia un ordine dei gruppi rilevante per il business.

Cosa rendono possibili queste funzionalità

Le API sono primitive generali di database, ma diversi carichi di lavoro di recupero ne beneficiano immediatamente.

RAG e agenti: ispezionare la concentrazione del recupero

Un sistema RAG o agentico può suddividere i chunk recuperati in bucket per documento sorgente, linea di prodotto, tenant o tipo di contenuto. Un risultato concentrato in due documenti porta un segnale di copertura diverso rispetto a uno distribuito su decine di fonti.

Questa distribuzione non è una garanzia di qualità della risposta. È però una diagnostica di recupero utile che un’applicazione o un agente può combinare con punteggi, citazioni e altri controlli quando decide se ampliare la query, recuperare di nuovo o chiedere chiarimenti.

Grouping Search rimane la scelta giusta quando l’obiettivo è semplicemente diversificare i chunk restituiti. Search Aggregation è utile quando il sistema ha bisogno della distribuzione stessa.

La pagina di ricerca prodotti iniziale può ricevere da Milvus bucket di brand, metriche di prezzo, elementi rappresentativi e un elenco di candidati ordinato scalarmente. L’applicazione controlla ancora la presentazione e la logica di business, ma non deve più ricostruire le semantiche di base dei bucket da hit esportate.

Log e sicurezza: combinare la similarità con la distribuzione degli incidenti

La ricerca per similarità può trovare eventi correlati a una riga di log sospetta. Search Aggregation può quindi mostrare quali host dominano quei candidati, il timestamp minimo e massimo in ciascun bucket di host o come i candidati si dividono per severità e servizio.

Il risultato rimane una vista dei candidati recuperati anziché un conteggio globale esatto degli incidenti. Quando l’indagine richiede conteggi esatti su ogni evento che corrisponde a un filtro, l’aggregazione delle query fornisce quel secondo percorso.

Operazioni ed esplorazione dei dati: calcolare invece di esportare

Dashboard e strumenti amministrativi possono eseguire conteggi e medie esatte su righe filtrate, quindi esplorare le entità sottostanti in un ordine scalare definito. Questo elimina molte utility una tantum di tipo “esporta, calcola e ordina”, senza fingere che Milvus sia diventato un database analitico completo.

Confini: cosa non sostituiscono aggregazione e ORDER BY

Queste funzionalità estendono il motore di recupero; non trasformano Milvus in un sistema di online analytical processing (OLAP).

  • L’aggregazione delle query supporta il raggruppamento più count, sum, avg, min e max. Non aggiunge join, funzioni finestra o sottoquery complesse. I grandi job analitici offline appartengono ancora a sistemi come Spark, che possono lavorare con snapshot di Milvus 3.0 e percorsi di storage condivisi.
  • Le chiavi di gruppo delle query supportano campi interi, VARCHAR e TIMESTAMPTZ. Le chiavi di bucket di Search Aggregation supportano inoltre campi booleani. Valori in virgola mobile, vettoriali, JSON e array non sono chiavi di bucket.
  • Per Search Aggregation, count accetta "*" o una sorgente non JSON e non dinamica; sum e avg richiedono sorgenti numeriche; e min e max supportano anche sorgenti stringa e TIMESTAMPTZ. L’aggregazione delle query segue gli stessi confini di tipo aritmetici. Consulta la guida API prima di applicare un aggregato a un tipo di campo complesso.
  • L’aggregazione delle query può ordinare l’output raggruppato per chiavi di gruppo, mentre l’ordinamento per un aggregato calcolato come count(*) rimane un confine attuale. Senza un ordine esplicito, l’ordine dei gruppi non è garantito.
  • Search Aggregation attualmente non può essere combinata con Hybrid Search, Grouping Search, Search Iterators, un offset non nullo o l’evidenziazione nella stessa richiesta.
  • I conteggi e le metriche di Search Aggregation descrivono i candidati ANN mantenuti, non la collection completa e non ogni entità che potrebbe essere semanticamente rilevante.
  • Search ORDER BY cambia la presentazione dei candidati. Non ripara i candidati ANN mancati né converte il recupero per similarità in una query Top-N scalare esatta.

Il modo più pulito per scegliere tra le nuove primitive è partire dalla domanda:

  • Per statistiche esatte su righe visibili filtrate, usa l’aggregazione delle query.
  • Per una distribuzione sui candidati di recupero per similarità, usa Search Aggregation.
  • Per un elenco ordinato diversificato, usa Grouping Search.
  • Per un ordine scalare definito, usa query o search ORDER BY in base al percorso che ha stabilito l’insieme di risultati.

Dagli elenchi di candidati ai risultati strutturati

I database vettoriali hanno tradizionalmente ottimizzato una domanda: quali K entità sono più vicine a questo vettore?

I sistemi di recupero in produzione pongono subito domande successive. Quali gruppi dominano il risultato? Quali sono i loro conteggi e intervalli? Quali esempi rappresentano ciascun gruppo? In quale ordine di business l’applicazione dovrebbe presentare le righe o i candidati?

Milvus 3.0 porta queste operazioni nello stesso motore che possiede i dati, il confine dei candidati ANN e le semantiche di visibilità. L’aggregazione delle query esegue una riduzione distribuita esatta sulle righe visibili. Search Aggregation costruisce una vista a bucket sui candidati ANN mantenuti. ORDER BY fornisce ai percorsi di query e search un ordine scalare lato server senza chiedere all’applicazione di ricostruirlo pagina per pagina.

Il risultato non è un motore OLAP nascosto dentro un database vettoriale. È un motore di recupero che può restituire una parte maggiore della struttura di cui le applicazioni hanno effettivamente bisogno.

Prova aggregazione e ORDER BY in Milvus 3.0

Milvus 3.0 è ora disponibile. Usa la guida Query per l’aggregazione esatta e l’ordinamento delle query, la guida Search Aggregation per semantiche e limiti dei bucket, la guida Basic Vector Search per l’ordinamento della ricerca e la guida Grouping Search quando il tuo obiettivo principale è la diversità dei risultati.

Per la release più ampia, consulta il blog di lancio di Milvus 3.0, le note di rilascio di Milvus 3.0 e il repository milvus-io/milvus.

Se vuoi valutare le stesse API senza gestire il cluster autonomamente, provale su Zilliz Cloud. Gli attuali riferimento query di Zilliz Cloud e riferimento search descrivono disponibilità e parametri per i tipi di cluster gestiti.

Per discutere un carico di lavoro o un caso limite con il team, unisciti alla community Discord di Milvus o prenota una sessione Milvus Office Hours.

    Try Managed Milvus for Free

    Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

    Get Started

    Like the article? Spread the word

    Continua a Leggere