Использование вашей векторной базы данных в качестве хранилища данных JSON (или реляционного)
TL;DR: Векторные базы данных поддерживают CRUD для «традиционных» форматов данных, таких как JSON. Если вы solo developer или небольшая команда и не хотите управлять множеством разных компонентов инфраструктуры данных, вы можете использовать Milvus или Zilliz Cloud (управляемый Milvus) как единственное хранилище данных и легко переносить коллекции без векторов в другие базы данных по мере масштабирования.
Благодаря популярности ChatGPT и других авторегрессионных языковых моделей vector search за последний год резко вырос в популярности. В результате мы увидели, как множество компаний и организаций вскочили на волну векторного поиска: от поставщиков NoSQL-баз данных, таких как MongoDB (через Atlas Vector Search), до традиционных реляционных баз данных, таких как Postgres (через pgvector). Общий посыл, который я слышу вокруг этих плагинов векторного поиска, в основном один и тот же и звучит примерно так: разработчикам следует оставаться с нами, поскольку у нас можно хранить таблицы/JSON в дополнение к векторам, поэтому нет необходимости управлять несколькими компонентами инфраструктуры!
Такое заявление всегда меня смешит, поскольку оно явно создано неискушёнными маркетинговыми командами. Мало того, что технология, лежащая в основе векторного поиска, значительно отличается от стратегий хранения и выполнения запросов в реляционных и NoSQL-базах данных, так ещё и теперь уже довольно хорошо известно, что векторные базы данных могут хранить связи, JSON-документы и другие структурированные источники данных. Первый момент трудно кратко проиллюстрировать без глубоких предварительных знаний о системах управления базами данных, но второй довольно легко показать с помощью нескольких коротких фрагментов кода. Именно этому и посвящён этот пост в блоге.
Настройка
Milvus хранит данные в единицах, известных как коллекции, аналогично таблицам в реляционных базах данных. Каждая коллекция может иметь собственную схему, а схемы имеют векторное поле фиксированной размерности, например 768 для векторных эмбеддингов на основе e5-base. Давайте создадим коллекцию для хранения JSON-документов, а не векторных данных. Чтобы лучше проиллюстрировать этот момент, я опустил некоторые из более ранних и последующих шагов, такие как вызов 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()
Здесь мы указали, что эта коллекция должна использовать возможности динамической схемы Milvus, позволяя коллекции принимать JSON-полезные нагрузки как «дополнительные» данные, связанные с каждой строкой.
Надеюсь, вы не ожидали большего, потому что это всё — всё действительно настолько просто.
CRUD-операции
Вы можете взаимодействовать с коллекцией, которую мы создали выше, так же, как и с любой другой базой данных. Например, давайте загрузим список действующих сенаторов США (в формате JSON) с 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
Мы можем сохранить эти документы напрямую в Milvus, лишь немного подготовив данные:
rows = [{"_unused": [0]} | d for d in data]
collection.insert(rows)
(insert count: 100, delete count: 0, upsert count: 0, ...
Отсюда мы можем выполнять запросы непосредственно к этим данным:
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}]
Не указывая никакого векторного поля, мы запросили в нашей базе данных первый результат, где поле party в сопутствующей JSON-нагрузке имеет префикс "Dem" (демократ).
Надеюсь, этот шаг демонстрирует возможность выполнять поиск по структурированным данным в Milvus, но это не особенно полезный запрос. Давайте найдём всех сенаторов из моего родного штата Орегон:
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}]
Хотя мы указали limit=10, было возвращено только два документа (поскольку у каждого штата только два сенатора). Давайте ещё больше сузим наш запрос, чтобы получить только старшего сенатора:
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}]
Возможно, я хотел бы обновить профиль сенатора Рона Уайдена, добавив немного больше информации. Мы легко можем сделать это, получив весь документ с помощью output_fields=["*"], обновив получившийся документ и вставив его обратно в нашу базу данных без старого первичного ключа:
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"]
(число вставок: 1, число удалений: 0, число upsert: 0, ...
collection.delete(expr)
collection.insert(res)
Давайте посмотрим, сработало ли это так, как ожидалось.
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}]
Данные действительно соответствуют внесённым нами обновлениям.
Полный скрипт
Вот весь скрипт от начала до конца для удобства, без лишних запросов. Я использовал наш Zilliz Cloud free tier вместо 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}]
Попробуйте сами!
pymongo -> milvusmongo
И последнее замечание перед завершением — я создал небольшой Python-пакет под названием milvusmongo, который реализует самую базовую CRUD-функциональность pymongo для коллекций. Он использует Milvus в качестве базовой базы данных, а не MongoDB. Как и pymongo, milvusmongo поддерживает доступ как в стиле словаря, так и в стиле атрибутов, и абстрагирует дополнительную логику, необходимую для CRUD-вызовов (т. е. insert_one, update_one и delete_one). Вы можете установить его с помощью pip install milvusmongo:
% pip install milvus
% pip install milvusmongo
После этого вы можете запустить встроенный экземпляр Milvus и использовать его вместе с milvusmongo для выполнения запросов к JSON-данным. Например:
from milvus import default_server
default_server.start()
from milvusmongo import MongoClient
client = MongoClient("127.0.0.1", 19530)
client.insert_one(my_document)
Обратите внимание, что эта библиотека предназначена для демонстрации гибкости Milvus в качестве хранилища данных, а не для использования в крупномасштабных производственных средах.
Заключительные слова
Milvus не призван заменить базы данных NoSQL или лексические поисковые системы по тексту; мы здесь, чтобы предоставить вам наилучший возможный опыт векторного/фильтрованного поиска. Что еще важнее, мы здесь, чтобы помочь ускорить внедрение векторного поиска как технологии — именно поэтому открытый исходный код является такой важной частью нашего духа.
Но это не означает, что мы не поддерживаем другие типы данных. Как самостоятельный разработчик или небольшой стартап, вы можете свободно использовать Milvus как свое единственное хранилище данных. Вы всегда сможете оптимизировать использование своей инфраструктуры позже, по мере роста. Milvus предоставит вам лучшие в своем классе возможности векторного поиска, тогда как другие базы данных предназначены для хранения, индексирования и поиска других форм данных. Когда ваше приложение начнет требовать более сложных рабочих нагрузок (таких как соединения или агрегации), именно тогда вам стоит задуматься об использовании разных хранилищ данных.
Читать далее

Vector Lakebase: End the AI Data Silo
Learn how Vector Lakebase unifies vector search, data lakes, and AI data operations so teams can serve RAG and agents without copy-and-sync pipelines.

VidTok: Rethinking Video Processing with Compact Tokenization
VidTok tokenizes videos to reduce redundancy while preserving spatial and temporal details for efficient processing.

DeepSeek Always Busy? Deploy It Locally with Milvus in Just 10 Minutes—No More Waiting!
Learn how to set up DeepSeek-R1 on your local machine using Ollama, AnythingLLM, and Milvus in just 10 minutes. Bypass busy servers and enhance AI responses with custom data.



