Smettere di pagare per i dati freddi: Riduzione dell'80% dei costi con il caricamento dei dati caldo-freddo su richiesta nello storage a livelli Milvus
Quanti di voi stanno ancora pagando bollette di infrastrutture premium per dati che il vostro sistema tocca a malapena? Siate onesti: la maggior parte dei team lo fa.
Se gestite la ricerca vettoriale in produzione, probabilmente lo avete visto di persona. Si forniscono grandi quantità di memoria e di unità SSD in modo che tutto sia "pronto per le query", anche se solo una piccola parte del set di dati è effettivamente attiva. E non siete i soli. Abbiamo visto molti casi simili:
Piattaforme SaaS multi-tenant: Centinaia di tenant iscritti, ma solo il 10-15% attivo in un determinato giorno. Gli altri rimangono fermi ma occupano comunque risorse.
Sistemi di raccomandazione per il commercio elettronico: Un milione di SKU, ma il primo 8% dei prodotti genera la maggior parte delle raccomandazioni e del traffico di ricerca.
Ricerca AI: Vasti archivi di embeddings, anche se il 90% delle interrogazioni degli utenti riguarda articoli della settimana scorsa.
È la stessa storia in tutti i settori: meno del 10% dei dati viene interrogato frequentemente, ma spesso consuma l'80% dello storage e della memoria. Tutti sanno che questo squilibrio esiste, ma fino a poco tempo fa non esisteva un'architettura pulita per risolverlo.
Le cose cambiano con Milvus 2.6.
Prima di questa versione, Milvus (come la maggior parte dei database vettoriali) dipendeva da un modello full-load: se i dati dovevano essere ricercabili, dovevano essere caricati sui nodi locali. Non importava che i dati venissero caricati un migliaio di volte al minuto o una volta al trimestre: dovevano rimanere caldi. Questa scelta progettuale garantiva prestazioni prevedibili, ma comportava anche il sovradimensionamento dei cluster e il pagamento di risorse che i dati freddi semplicemente non meritavano.
L 'archiviazione a livelli è la nostra risposta.
Milvus 2.6 introduce una nuova architettura di archiviazione a livelli con un vero caricamento on-demand, che consente al sistema di differenziare automaticamente i dati caldi da quelli freddi:
I segmenti caldi rimangono nella cache vicino all'elaboratore.
I segmenti freddi vivono a basso costo in uno storage a oggetti remoto.
I dati vengono caricati nei nodi locali solo quando una query ne ha effettivamente bisogno.
Questo sposta la struttura dei costi da "quanti dati si hanno" a "quanti dati si usano effettivamente". Nelle prime implementazioni di produzione, questo semplice cambiamento consente di ridurre fino all'80% i costi di storage e memoria.
Nel resto del post spiegheremo come funziona il Tiered Storage, condivideremo i risultati delle prestazioni reali e mostreremo dove questo cambiamento ha un impatto maggiore.
Perché il caricamento completo si interrompe su larga scala
Prima di immergerci nella soluzione, vale la pena di esaminare più da vicino perché la modalità full-load utilizzata in Milvus 2.5 e nelle versioni precedenti è diventata un fattore limitante con la scalabilità dei carichi di lavoro.
In Milvus 2.5 e nelle versioni precedenti, quando un utente inviava una richiesta a Collection.load(), ogni QueryNode memorizzava nella cache l'intera collezione a livello locale, compresi i metadati, i dati dei campi e gli indici. Questi componenti vengono scaricati dalla memoria degli oggetti e memorizzati completamente in memoria o mappati in memoria (mmap) sul disco locale. Solo dopo che tutti questi dati sono disponibili localmente, la collezione viene contrassegnata come caricata e pronta per servire le query.
In altre parole, l'insieme non è interrogabile finché il set di dati completo, caldo o freddo, non è presente sul nodo.
Nota: per i tipi di indice che incorporano dati vettoriali grezzi, Milvus carica solo i file dell'indice, non il campo vettoriale separatamente. Anche in questo caso, l'indice deve essere completamente caricato per servire le query, indipendentemente dalla quantità di dati effettivamente accessibili.
Per capire perché questo diventa problematico, si consideri un esempio concreto:
Supponiamo di avere un set di dati vettoriali di medie dimensioni con:
100 milioni di vettori
768 dimensioni (incorporazioni BERT)
precisionefloat32 (4 byte per dimensione)
Un indice HNSW
In questa configurazione, il solo indice HNSW, compresi i vettori grezzi incorporati, occupa circa 430 GB di memoria. Se si aggiungono i campi scalari più comuni, come gli ID utente, i timestamp o le etichette di categoria, l'utilizzo totale delle risorse locali supera facilmente i 500 GB.
Ciò significa che anche se l'80% dei dati viene interrogato raramente o mai, il sistema deve comunque fornire e mantenere più di 500 GB di memoria locale o di disco solo per mantenere la raccolta online.
Per alcuni carichi di lavoro, questo comportamento è accettabile:
Se quasi tutti i dati vengono consultati di frequente, il caricamento completo di tutti i dati offre la più bassa latenza di interrogazione possibile, al costo più elevato.
Se i dati possono essere suddivisi in sottoinsiemi caldi e caldi, il memory-mapping dei dati caldi su disco può ridurre parzialmente la pressione della memoria.
Tuttavia, nei carichi di lavoro in cui l'80% o più dei dati si trova nella coda lunga, gli svantaggi del caricamento completo emergono rapidamente, sia in termini di prestazioni che di costi.
Colli di bottiglia delle prestazioni
In pratica, il caricamento completo non influisce solo sulle prestazioni delle query e spesso rallenta i flussi di lavoro operativi di routine:
Aggiornamenti periodici più lunghi: Nei cluster di grandi dimensioni, gli aggiornamenti periodici possono richiedere ore o addirittura un giorno intero, poiché ogni nodo deve ricaricare l'intero set di dati prima di renderlo nuovamente disponibile.
Recupero più lento dopo i guasti: Quando un QueryNode si riavvia, non può servire il traffico fino a quando tutti i dati non sono stati ricaricati, prolungando in modo significativo il tempo di recupero e amplificando l'impatto dei guasti ai nodi.
Rallentamento dell'iterazione e della sperimentazione: Il caricamento completo rallenta i flussi di lavoro di sviluppo, costringendo i team di intelligenza artificiale ad attendere ore per il caricamento dei dati quando testano nuovi set di dati o configurazioni di indici.
Inefficienze dei costi
Il caricamento completo fa aumentare anche i costi dell'infrastruttura. Ad esempio, sulle istanze ottimizzate per la memoria del cloud mainstream, l'archiviazione di 1 TB di dati in locale costa all'incirca
Consideriamo ora un modello di accesso più realistico, in cui l'80% dei dati è freddo e potrebbe essere archiviato nello storage a oggetti (a circa $0,023 / GB / mese):
200 GB di dati caldi × 5,68 dollari
800 GB di dati freddi × 0,023 dollari
Costo annuale: (200×5,68+800×0,023)×12≈$14.000
Si tratta di una riduzione dell'80% del costo totale dello storage, senza sacrificare le prestazioni dove sono effettivamente importanti.
Cos'è lo storage a livelli e come funziona?
Per eliminare il compromesso, Milvus 2.6 ha introdotto l'archiviazione a livelli, che bilancia prestazioni e costi trattando l'archiviazione locale come una cache piuttosto che come un contenitore per l'intero set di dati.
In questo modello, i QueryNode caricano solo metadati leggeri all'avvio. I dati di campo e gli indici vengono recuperati su richiesta dallo storage remoto degli oggetti quando una query li richiede e vengono memorizzati nella cache locale se vengono consultati di frequente. I dati inattivi possono essere eliminati per liberare spazio.
Di conseguenza, i dati caldi rimangono vicino al livello di elaborazione per le query a bassa latenza, mentre i dati freddi rimangono nell'archiviazione degli oggetti fino a quando non sono necessari. In questo modo si riducono i tempi di caricamento, si migliora l'efficienza delle risorse e si consente ai QueryNode di interrogare insiemi di dati molto più grandi della loro memoria locale o della capacità del disco.
In pratica, l'archiviazione a livelli funziona come segue:
Mantenere i dati caldi in locale: Circa il 20% dei dati a cui si accede di frequente rimane nei nodi locali, garantendo una bassa latenza per l'80% delle interrogazioni più importanti.
Caricare i dati freddi su richiesta: Il restante 80% dei dati ad accesso raro viene recuperato solo quando necessario, liberando la maggior parte delle risorse di memoria e disco locali.
Adattamento dinamico con lo sfratto basato su LRU: Milvus utilizza una strategia di evasione LRU (Least Recently Used) per regolare continuamente i dati considerati caldi o freddi. I dati inattivi vengono automaticamente eliminati per fare spazio ai dati di recente accesso.
Con questo design, Milvus non è più vincolato dalla capacità fissa della memoria locale e del disco. Al contrario, le risorse locali funzionano come una cache gestita dinamicamente, dove lo spazio viene continuamente recuperato dai dati inattivi e riassegnato ai carichi di lavoro attivi.
Questo comportamento è reso possibile da tre meccanismi tecnici fondamentali:
1. Carico pigro
All'inizializzazione, Milvus carica solo i metadati minimi a livello di segmento, consentendo alle collezioni di diventare interrogabili quasi immediatamente dopo l'avvio. I dati di campo e i file di indice rimangono nello storage remoto e vengono recuperati su richiesta durante l'esecuzione delle query, mantenendo basso l'utilizzo della memoria locale e del disco.
Come funziona il caricamento delle collezioni in Milvus 2.5
Come funziona il caricamento pigro in Milvus 2.6 e successivi
I metadati caricati durante l'inizializzazione rientrano in quattro categorie principali:
Statistiche del segmento (informazioni di base come il conteggio delle righe, la dimensione del segmento e i metadati dello schema)
Timestamp (usati per supportare le query di viaggio nel tempo)
Record di inserimento e cancellazione (necessari per mantenere la coerenza dei dati durante l'esecuzione delle query)
Filtri Bloom (utilizzati per un pre-filtraggio veloce per eliminare rapidamente i segmenti irrilevanti).
2. Caricamento parziale
Mentre il caricamento pigro controlla quando vengono caricati i dati, il caricamento parziale controlla la quantità di dati caricati. Una volta iniziate le query o le ricerche, il QueryNode esegue un caricamento parziale, recuperando solo i pezzi di dati o i file di indice necessari dallo storage degli oggetti.
Indici vettoriali: Caricamento consapevole degli inquilini
Una delle funzionalità di maggior impatto introdotte in Milvus 2.6+ è il caricamento tenant-aware degli indici vettoriali, progettato specificamente per i carichi di lavoro multi-tenant.
Quando una query accede ai dati di un singolo tenant, Milvus carica solo la parte dell'indice vettoriale che appartiene a quel tenant, saltando i dati dell'indice per tutti gli altri tenant. In questo modo le risorse locali si concentrano sui tenant attivi.
Questo design offre diversi vantaggi:
Gli indici vettoriali per i tenant inattivi non consumano memoria o disco locale.
I dati degli indici per i tenant attivi rimangono nella cache per un accesso a bassa latenza.
Una politica di sfratto LRU a livello di tenant garantisce un uso equo della cache tra i tenant.
Campi scalari: Caricamento parziale a livello di colonna
Il caricamento parziale si applica anche ai campi scalari, consentendo a Milvus di caricare solo le colonne esplicitamente referenziate da una query.
Consideriamo una collezione con 50 campi dello schema, come id, vector, title, description, category, price, stock, e tags, e abbiamo bisogno di restituire solo tre campi:id, title, e price.
In Milvus 2.5, tutti i 50 campi scalari vengono caricati indipendentemente dai requisiti della query.
In Milvus 2.6+, vengono caricati solo i tre campi richiesti. I restanti 47 campi non vengono caricati e vengono recuperati solo se vengono consultati in seguito.
Il risparmio di risorse può essere notevole. Se ogni campo scalare occupa 20 GB:
il caricamento di tutti i campi richiede 1.000 GB (50 × 20 GB)
Il caricamento dei soli tre campi richiesti richiede 60 GB
Ciò rappresenta una riduzione del 94% nel caricamento dei dati scalari, senza influire sulla correttezza delle query o sui risultati.
Nota: il caricamento parziale tenant-aware per i campi scalari e gli indici vettoriali sarà introdotto ufficialmente in una prossima release. Una volta disponibile, ridurrà ulteriormente la latenza di carico e migliorerà le prestazioni delle cold-query in grandi implementazioni multi-tenant.
3. Evacuazione della cache basata su LRU
Il caricamento pigro e il caricamento parziale riducono in modo significativo la quantità di dati da portare in memoria locale e su disco. Tuttavia, nei sistemi di lunga durata, la cache continua a crescere con l'accesso a nuovi dati nel tempo. Quando la capacità locale viene raggiunta, entra in vigore l'evasione della cache basata su LRU.
L'evasione LRU (Least Recently Used) segue una semplice regola: i dati che non sono stati consultati di recente vengono evasi per primi. In questo modo si libera spazio locale per i dati di recente accesso, mantenendo nella cache i dati utilizzati di frequente.
Valutazione delle prestazioni: Archiviazione a livelli rispetto al caricamento completo
Per valutare l'impatto reale del Tiered Storage, abbiamo creato un ambiente di prova che rispecchia fedelmente i carichi di lavoro di produzione. Abbiamo confrontato Milvus con e senza Tiered Storage su cinque dimensioni: tempo di caricamento, utilizzo delle risorse, prestazioni delle query, capacità effettiva ed efficienza dei costi.
Configurazione sperimentale
Set di dati
100 milioni di vettori con 768 dimensioni (embeddings BERT)
Dimensione dell'indice vettoriale: circa 430 GB
10 campi scalari, tra cui ID, timestamp e categoria
Configurazione hardware
1 QueryNode con 4 vCPU, 32 GB di memoria e 1 TB di SSD NVMe
Rete a 10 Gbps
Cluster di object storage MinIO come backend di storage remoto
Modello di accesso
Le query seguono una distribuzione realistica degli accessi caldo-freddo:
L'80% delle interrogazioni riguarda i dati degli ultimi 30 giorni (≈20% dei dati totali).
Il 15% si rivolge a dati di 30-90 giorni (≈30% dei dati totali)
Il 5% si rivolge a dati più vecchi di 90 giorni (≈50% dei dati totali).
Risultati principali
1. Tempo di caricamento 33 volte più veloce
| Fase | Milvus 2.5 | Milvus 2.6+ (archiviazione a livelli) | Accelerazione |
|---|---|---|---|
| Download dei dati | 22 minuti | 28 secondi | 47× |
| Caricamento dell'indice | 3 minuti | 17 secondi | 10.5× |
| Totale | 25 minuti | 45 secondi | 33× |
In Milvus 2.5, il caricamento della raccolta richiedeva 25 minuti. Con Tiered Storage in Milvus 2.6+, lo stesso carico di lavoro viene completato in soli 45 secondi, il che rappresenta un miglioramento significativo dell'efficienza di carico.
2. Riduzione dell'80% dell'utilizzo delle risorse locali
| Fase | Milvus 2.5 | Milvus 2.6+ (Tiered Storage) | Riduzione |
|---|---|---|---|
| Dopo il carico | 430 GB | 12 GB | -97% |
| Dopo 1 ora | 430 GB | 68 GB | -84% |
| Dopo 24 ore | 430 GB | 85 GB | -80% |
| Stato stazionario | 430 GB | 85-95 GB | ~80% |
In Milvus 2.5, l'utilizzo delle risorse locali rimane costante a 430 GB, indipendentemente dal carico di lavoro o dal tempo di esecuzione. Al contrario, Milvus 2.6+ inizia con soli 12 GB subito dopo il caricamento.
Con l'esecuzione delle query, i dati di accesso frequente vengono memorizzati nella cache locale e l'utilizzo delle risorse aumenta gradualmente. Dopo circa 24 ore, il sistema si stabilizza a 85-95 GB, riflettendo l'insieme dei dati caldi. A lungo termine, si ottiene una riduzione di circa l'80% dell'utilizzo della memoria locale e del disco, senza sacrificare la disponibilità delle query.
3. Impatto quasi nullo sulle prestazioni dei dati a caldo
| Tipo di query | Latenza Milvus 2.5 P99 | Milvus 2.6+ latenza P99 | Cambiamento |
|---|---|---|---|
| Interrogazione di dati a caldo | 15 ms | 16 ms | +6.7% |
| Interrogazioni di dati a caldo | 15 ms | 28 ms | +86% |
| Interrogazione di dati a freddo (primo accesso) | 15 ms | 120 ms | +700% |
| Interrogazione di dati freddi (cache) | 15 ms | 18 ms | +20% |
Per i dati caldi, che rappresentano circa l'80% di tutte le query, la latenza di P99 aumenta solo del 6,7%, con un impatto praticamente impercettibile in produzione.
Le query sui dati freddi presentano una latenza più elevata al primo accesso, a causa del caricamento on-demand dallo storage degli oggetti. Tuttavia, una volta memorizzate nella cache, la loro latenza aumenta solo del 20%. Data la bassa frequenza di accesso dei dati freddi, questo compromesso è generalmente accettabile per la maggior parte dei carichi di lavoro reali.
4. Capacità effettiva 4,3 volte superiore
Con lo stesso budget hardware - otto server con 64 GB di memoria ciascuno (512 GB in totale) - Milvus 2.5 può caricare al massimo 512 GB di dati, equivalenti a circa 136 milioni di vettori.
Con il Tiered Storage abilitato in Milvus 2.6+, lo stesso hardware può supportare 2,2 TB di dati, ovvero circa 590 milioni di vettori. Ciò rappresenta un aumento di 4,3 volte della capacità effettiva, consentendo di servire insiemi di dati significativamente più grandi senza espandere la memoria locale.
5. Riduzione dell'80,1% dei costi
Prendendo come esempio un set di dati vettoriali da 2 TB in un ambiente AWS e ipotizzando che il 20% dei dati sia caldo (400 GB), il confronto dei costi è il seguente:
| Articolo | Milvus 2.5 | Milvus 2.6+ (archiviazione a livelli) | Risparmio |
|---|---|---|---|
| Costo mensile | $11,802 | $2,343 | $9,459 |
| Costo annuale | $141,624 | $28,116 | $113,508 |
| Tasso di risparmio | - | - | 80.1% |
Riepilogo dei benchmark
In tutti i test, lo storage a livelli offre miglioramenti costanti e misurabili:
Tempi di caricamento 33 volte più veloci: Il tempo di caricamento della raccolta si riduce da 25 minuti a 45 secondi.
80% in meno di utilizzo delle risorse locali: Nel funzionamento a regime, l'utilizzo della memoria e del disco locale si riduce di circa l'80%.
Impatto quasi nullo sulle prestazioni dei dati a caldo: La latenza di P99 per i dati a caldo aumenta di meno del 10%, preservando le prestazioni delle query a bassa latenza.
Latenza controllata per i dati freddi: I dati freddi comportano una latenza più elevata al primo accesso, ma è accettabile data la loro bassa frequenza di accesso.
Capacità effettiva 4,3 volte superiore: Lo stesso hardware può servire 4-5 volte più dati senza memoria aggiuntiva.
Riduzione dei costi di oltre l'80%: I costi annuali dell'infrastruttura si riducono di oltre l'80%.
Quando utilizzare lo storage a livelli in Milvus
Sulla base dei risultati dei benchmark e dei casi di produzione reali, abbiamo raggruppato i casi d'uso del Tiered Storage in tre categorie per aiutarvi a decidere se è adatto al vostro carico di lavoro.
Casi d'uso più adatti
1. Piattaforme di ricerca vettoriale multi-tenant
Caratteristiche: Un gran numero di tenant con un'attività altamente disomogenea; la ricerca vettoriale è il carico di lavoro principale.
Modello di accesso: Meno del 20% dei locatari genera oltre l'80% delle interrogazioni vettoriali.
Benefici attesi: Riduzione dei costi del 70-80%; espansione della capacità di 3-5 volte.
2. Sistemi di raccomandazione per il commercio elettronico (carichi di lavoro di ricerca vettoriale)
Caratteristiche: Forte sbandamento della popolarità tra i prodotti di punta e la coda lunga.
Modello di accesso: Il 10% dei prodotti principali rappresenta l'80% del traffico di ricerca vettoriale.
Benefici attesi: Nessuna necessità di capacità aggiuntiva durante gli eventi di punta; riduzione dei costi del 60-70%.
3. Insiemi di dati su larga scala con una netta separazione caldo-freddo (a prevalenza vettoriale).
Caratteristiche: Dataset di dimensioni TB o superiori, con accesso fortemente orientato ai dati recenti.
Modello di accesso: Una classica distribuzione 80/20: il 20% dei dati serve l'80% delle interrogazioni.
Benefici attesi: Riduzione dei costi del 75-85%.
Casi d'uso adatti
1. Carichi di lavoro sensibili ai costi
Caratteristiche: Budget ristretti con una certa tolleranza per piccoli compromessi sulle prestazioni.
Modello di accesso: Le interrogazioni vettoriali sono relativamente concentrate.
Benefici attesi: Riduzione dei costi del 50-70%; i dati freddi possono incorrere in una latenza di ~500 ms al primo accesso, che deve essere valutata in base ai requisiti SLA.
2. Conservazione dei dati storici e ricerca d'archivio
Caratteristiche: Grandi volumi di vettori storici con una frequenza di interrogazione molto bassa.
Modello di accesso: Circa il 90% delle interrogazioni riguarda dati recenti.
Vantaggi attesi: Conservare i set di dati storici completi; mantenere i costi dell'infrastruttura prevedibili e controllati.
Casi d'uso poco adatti
1. Carichi di lavoro di dati uniformemente caldi
Caratteristiche: Tutti i dati vengono acceduti con una frequenza simile, senza una chiara distinzione tra caldo e freddo.
Perché non adatto: Benefici limitati per la cache; aggiunta di complessità al sistema senza vantaggi significativi.
2. Carichi di lavoro a bassissima latenza
Caratteristiche: Sistemi estremamente sensibili alla latenza, come il trading finanziario o le offerte in tempo reale.
Perché non è adatto: Anche piccole variazioni di latenza sono inaccettabili; il caricamento completo fornisce prestazioni più prevedibili.
Avvio rapido: Prova l'archiviazione a livelli in 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
Conclusione
Il sistema di archiviazione a livelli di Milvus 2.6 risolve una comune discrepanza tra il modo in cui i dati vettoriali vengono archiviati e il modo in cui vengono effettivamente consultati. Nella maggior parte dei sistemi di produzione, solo una piccola parte dei dati viene interrogata frequentemente, ma i modelli di caricamento tradizionali trattano tutti i dati come ugualmente caldi. Passando al caricamento su richiesta e gestendo la memoria locale e il disco come una cache, Milvus allinea il consumo di risorse al comportamento reale delle query piuttosto che alle ipotesi peggiori.
Questo approccio consente ai sistemi di scalare verso insiemi di dati più grandi senza aumenti proporzionali delle risorse locali, mantenendo le prestazioni delle query a caldo sostanzialmente invariate. I dati freddi rimangono accessibili quando servono, con una latenza prevedibile e limitata, rendendo il compromesso esplicito e controllabile. Poiché la ricerca vettoriale si sposta sempre più in ambienti di produzione sensibili ai costi, multi-tenant e di lunga durata, il Tiered Storage fornisce una base pratica per operare in modo efficiente su scala.
Per ulteriori informazioni sul Tiered Storage, consultate la documentazione qui sotto:
Avete domande o volete un approfondimento su una qualsiasi funzionalità dell'ultima versione di Milvus? Unitevi al nostro canale Discord o inviate problemi su GitHub. È anche possibile prenotare una sessione individuale di 20 minuti per ottenere approfondimenti, indicazioni e risposte alle vostre domande tramite Milvus Office Hours.
Per saperne di più sulle caratteristiche di Milvus 2.6
Presentazione di Milvus 2.6: ricerca vettoriale accessibile su scala miliardaria
Triturazione JSON in Milvus: filtraggio JSON 88,9 volte più veloce e flessibile
Il vero recupero a livello di entità: Nuove funzionalità di Array-of-Structs e MAX_SIM in Milvus
Filtraggio geospaziale e ricerca vettoriale insieme a campi geometrici e RTREE in Milvus 2.6
MinHash LSH in Milvus: l'arma segreta per combattere i duplicati nei dati di allenamento LLM
Portare la compressione vettoriale all'estremo: come Milvus serve 3 volte di più le query con RaBitQ
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word



