Usar tu base de datos vectorial como un almacén de datos JSON (o relacional)
TL;DR: Las bases de datos vectoriales admiten CRUD sobre formatos de datos "tradicionales" como JSON. Si eres un desarrollador en solitario o un equipo pequeño y no quieres gestionar muchas piezas diferentes de infraestructura de datos, puedes usar Milvus o Zilliz Cloud (el Milvus gestionado) como tu único almacén de datos y migrar fácilmente colecciones sin vectores a diferentes bases de datos a medida que escalas.
Impulsada por la popularidad de ChatGPT y otros modelos de lenguaje autorregresivos, la búsqueda vectorial ha explotado en popularidad durante el último año. Como resultado, hemos visto a muchas empresas y organizaciones subirse al tren de la búsqueda vectorial, desde proveedores de bases de datos NoSQL como MongoDB (a través de Atlas Vector Search) hasta bases de datos relacionales tradicionales como Postgres (a través de pgvector). El mensaje general que escucho en torno a estos plugins de búsqueda vectorial es en gran medida el mismo y va más o menos así: los desarrolladores deberían quedarse con nosotros, ya que pueden almacenar tablas/JSON además de vectores, ¡así que no hay necesidad de gestionar múltiples piezas de infraestructura!
Este tipo de declaración siempre me hace reír, ya que claramente está elaborada por equipos de marketing poco sofisticados. No solo la tecnología detrás de la búsqueda vectorial es enormemente diferente de las estrategias de almacenamiento y consulta en bases de datos relacionales y NoSQL, sino que ahora es bastante sabido que las bases de datos vectoriales pueden almacenar relaciones, documentos JSON y otras fuentes de datos estructurados. El primer punto es difícil de ilustrar de forma concisa sin un conocimiento previo profundo de los sistemas de gestión de bases de datos, pero el segundo punto es bastante fácil de mostrar mediante algunos fragmentos breves de código de ejemplo. A eso está dedicada esta publicación de blog.
Configuración
Milvus almacena datos en unidades conocidas como colecciones, análogas a las tablas en bases de datos relacionales. Cada colección puede tener su propio esquema, y los esquemas tienen un campo vectorial de dimensionalidad fija, por ejemplo 768 para embeddings vectoriales basados en e5-base. Creemos una colección para almacenar documentos JSON en lugar de datos vectoriales. Para ilustrar mejor este punto, he omitido algunos de los pasos anteriores y posteriores, como llamar a collections.connect:
from pymilvus import Collection, CollectionSchema, DataType, FieldSchema
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True, max_length=100),
FieldSchema(name="_unused", dtype=DataType.FLOAT_VECTOR, dim=1)
]
schema = CollectionSchema(fields, "NoSQL data", enable_dynamic_field=True)
collection = Collection("json_data", schema)
collection.load()
Aquí, hemos especificado que esta colección use las capacidades de esquema dinámico de Milvus, lo que permite que la colección acepte cargas útiles JSON como datos "extra" asociados con cada fila.
Espero que no esperaras más, porque eso es todo: realmente es así de simple.
Operaciones CRUD
Puedes interactuar con la colección que hemos creado arriba como lo harías con cualquier otra base de datos. Como ejemplo, descarguemos la lista de senadores actuales de EE. UU. (en formato JSON) desde govtrack:
import requests
r = requests.get("https://www.govtrack.us/api/v2/role?current=true&role_type=senator")
data = r.json()["objects"]
len(data)
100
Podemos almacenar estos documentos directamente en Milvus con solo un poquito de preparación de datos:
rows = [{"_unused": [0]} | d for d in data]
collection.insert(rows)
(insert count: 100, delete count: 0, upsert count: 0, ...
A partir de aquí, podemos realizar consultas directamente sobre esos datos:
collection.query(
expr="party like 'Dem%'",
limit=1,
output_fields=["person"]
)
[{'person': {'bioguideid': 'C000127',
'birthday': '1958-10-13',
'cspanid': 26137,
'fediverse_webfinger': None,
'firstname': 'Maria',
'gender': 'female',
'gender_label': 'Female',
'lastname': 'Cantwell',
'link': 'https://www.govtrack.us/congress/members/maria_cantwell/300018',
'middlename': '',
'name': 'Sen. Maria Cantwell [D-WA]',
'namemod': '',
'nickname': '',
'osid': 'N00007836',
'pvsid': None,
'sortname': 'Cantwell, Maria (Sen.) [D-WA]',
'twitterid': 'SenatorCantwell',
'youtubeid': 'SenatorCantwell'},
'id': 447376465724036249}]
Sin especificar ningún campo vectorial, hemos consultado nuestra base de datos para obtener el primer resultado donde el campo party en la carga JSON adjunta tiene "Dem" (demócrata) como prefijo.
Con suerte, este paso demuestra la capacidad de realizar búsquedas de datos estructurados en Milvus, pero no es una consulta particularmente útil. Busquemos todos los senadores de mi estado natal, Oregón:
collection.query(
expr="state in ['OR']",
limit=10,
output_fields=["person"]
)
[{'person': {'bioguideid': 'M001176',
'birthday': '1956-10-24',
'cspanid': 1029842,
'fediverse_webfinger': None,
'firstname': 'Jeff',
'gender': 'male',
'gender_label': 'Male',
'lastname': 'Merkley',
'link': 'https://www.govtrack.us/congress/members/jeff_merkley/412325',
'middlename': '',
'name': 'Sen. Jeff Merkley [D-OR]',
'namemod': '',
'nickname': '',
'osid': 'N00029303',
'pvsid': None,
'sortname': 'Merkley, Jeff (Sen.) [D-OR]',
'twitterid': 'SenJeffMerkley',
'youtubeid': 'SenatorJeffMerkley'},
'id': 447376465724036286},
{'person': {'bioguideid': 'W000779',
'birthday': '1949-05-03',
'cspanid': 1962,
'fediverse_webfinger': None,
'firstname': 'Ron',
'gender': 'male',
'gender_label': 'Male',
'lastname': 'Wyden',
'link': 'https://www.govtrack.us/congress/members/ron_wyden/300100',
'middlename': '',
'name': 'Sen. Ron Wyden [D-OR]',
'namemod': '',
'nickname': '',
'osid': 'N00007724',
'pvsid': None,
'sortname': 'Wyden, Ron (Sen.) [D-OR]',
'twitterid': 'RonWyden',
'youtubeid': 'senronwyden'},
'id': 447376465724036331}]
Aunque especificamos limit=10, solo se devolvieron dos documentos (ya que cada estado tiene solo dos senadores). Afinemos aún más nuestra consulta para obtener solo al senador sénior:
collection.query(
expr="state in ['OR'] and senator_rank in ['senior']",
limit=10,
output_fields=["person"]
)
[{'person': {'bioguideid': 'W000779',
'birthday': '1949-05-03',
'cspanid': 1962,
'fediverse_webfinger': None,
'firstname': 'Ron',
'gender': 'male',
'gender_label': 'Male',
'lastname': 'Wyden',
'link': 'https://www.govtrack.us/congress/members/ron_wyden/300100',
'middlename': '',
'name': 'Sen. Ron Wyden [D-OR]',
'namemod': '',
'nickname': '',
'osid': 'N00007724',
'pvsid': None,
'sortname': 'Wyden, Ron (Sen.) [D-OR]',
'twitterid': 'RonWyden',
'youtubeid': 'senronwyden'},
'id': 447376465724036331}]
Quizás me gustaría actualizar el perfil del senador Ron Wyden con un poco más de información. Podemos hacerlo fácilmente recuperando el documento completo con output_fields=["*"], actualizando el documento resultante e insertándolo de nuevo en nuestra base de datos sin la antigua clave primaria:
expr = "state in ['OR'] and senator_rank in ['senior']"
res = collection.query(
expr=expr,
limit=10,
output_fields=["*"]
)
res[0].update({
"elected_in": "1996",
"college": "Stanford University",
"college_major": "Political Science"
})
del res[0]["id"]
(insert count: 1, delete count: 0, upsert count: 0, ...
collection.delete(expr)
collection.insert(res)
Veamos si esto funcionó como se esperaba.
collection.query(
expr=expr,
limit=10,
output_fields=["elected_in", "college", "college_major"]
)
[{'elected_in': '1996',
'college': 'Stanford University',
'college_major': 'Political Science',
'id': 447376465724036353}]
Los datos efectivamente coinciden con las actualizaciones que hicimos.
El script completo
Aquí está todo el script de principio a fin para mayor comodidad, sin las consultas innecesarias. Usé nuestro nivel gratuito de Zilliz Cloud en lugar de milvus-lite:
from milvus import default_server
from pymilvus import connections
from pymilvus import Collection, CollectionSchema, DataType, FieldSchema
import requests
# Uncomment this if you're using `milvus-lite`
#default_server.start()
#connections.connect(host="127.0.0.1", port=default_server.listen_port)
# Uncomment this if you're using Zilliz Cloud
connections.connect(
uri=os.environ["ZILLIZ_URI"],
token=os.environ["ZILLIZ_TOKEN"]
)
# Create the schema for our new collection. We set turn on Milvus' dynamic
# schema capability in order to store arbitratily large (or small) JSON blogs
# in each row.
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True, max_length=100),
FieldSchema(name="_unused", dtype=DataType.FLOAT_VECTOR, dim=1)
]
schema = CollectionSchema(fields, "Milvus as a JSON datastore", enable_dynamic_field=True)
# Now let's creat the collection.
collection = Collection("json_data", schema)
index_params = {
"index_type": "AUTOINDEX",
"metric_type": "L2",
"params": {}
}
collection.create_index(
field_name="_unused",
index_params=index_params
)
collection.load()
# Insert US senator JSON data into our newly formed collection.
r = requests.get("https://www.govtrack.us/api/v2/role?current=true&role_type=senator")
data = r.json()["objects"]
rows = [{"_unused": [0]} | d for d in data]
collection.insert(rows)
# Fetch the first Democrat in the database
top_k = 1
collection.query(
expr="state in ['OR'] and senator_rank in ['senior']",
limit=top_k,
output_fields=["person"]
)
[{'person': {'bioguideid': 'W000779',
'birthday': '1949-05-03',
'cspanid': 1962,
'fediverse_webfinger': None,
'firstname': 'Ron',
'gender': 'male',
'gender_label': 'Male',
'lastname': 'Wyden',
'link': 'https://www.govtrack.us/congress/members/ron_wyden/300100',
'middlename': '',
'name': 'Sen. Ron Wyden [D-OR]',
'namemod': '',
'nickname': '',
'osid': 'N00007724',
'pvsid': None,
'sortname': 'Wyden, Ron (Sen.) [D-OR]',
'twitterid': 'RonWyden',
'youtubeid': 'senronwyden'},
'id': 447376465724036331}]
¡Pruébalo por ti mismo!
pymongo -> milvusmongo
Una última nota antes de terminar: he creado un pequeño paquete de Python llamado milvusmongo que implementa la funcionalidad CRUD más básica de pymongo en colecciones. Usa Milvus como base de datos subyacente en lugar de MongoDB. Al igual que pymongo, milvusmongo admite tanto el acceso tipo diccionario como el acceso de estilo atributo, y abstrae la lógica adicional necesaria para las llamadas CRUD (es decir, insert_one, update_one y delete_one). Puedes instalarlo con pip install milvusmongo:
% pip install milvus
% pip install milvusmongo
Una vez hecho esto, puedes iniciar una instancia integrada de Milvus y usarla junto con milvusmongo para realizar consultas sobre datos JSON. Por ejemplo:
from milvus import default_server
default_server.start()
from milvusmongo import MongoClient
client = MongoClient("127.0.0.1", 19530)
client.insert_one(my_document)
Tenga en cuenta que esta biblioteca está pensada para demostrar la flexibilidad de Milvus como almacén de datos, más que para servir como algo que debería usar en entornos de producción a gran escala.
Palabras finales
Milvus no está aquí para reemplazar las bases de datos NoSQL ni los motores de búsqueda de texto léxico; estamos aquí para ofrecerle la mejor experiencia posible de búsqueda vectorial/filtrada. Más importante aún, estamos aquí para ayudar a acelerar la adopción de la búsqueda vectorial como tecnología: por eso el código abierto es una parte tan fundamental de nuestro espíritu.
Pero eso no significa que no admitamos otros tipos de datos. Como desarrollador independiente o una pequeña startup, es libre de usar Milvus como su único almacén de datos. Siempre puede optimizar el uso de su infraestructura más adelante a medida que crezca. Milvus le proporcionará capacidades de búsqueda vectorial de primer nivel, mientras que otras bases de datos están ahí para almacenar, indexar y buscar otras formas de datos. Una vez que su aplicación empiece a requerir cargas de trabajo más complejas (como joins o aggregations), será entonces cuando querrá considerar el uso de diferentes almacenes de datos.
Sigue leyendo

Why Teams Are Migrating from Weaviate to Zilliz Cloud — and How to Do It Seamlessly
Explore how Milvus scales for large datasets and complex queries with advanced features, and discover how to migrate from Weaviate to Zilliz Cloud.

Zilliz Cloud Update: Tiered Storage, Business Critical Plan, Cross-Region Backup, and Pricing Changes
This release offers a rebuilt tiered storage with lower costs, a new Business Critical plan for enhanced security, and pricing updates, among other features.

Zilliz Cloud Update: Smarter Autoscaling for Cost Savings, Stronger Compliance with Audit Logs, and More
What's new in Zilliz Cloud? Smarter autoscaling with scale-down, audit logs GA, enhanced SSO, and Milvus 2.6 in Private Preview.



