Как обрабатываются данные в векторной базе данных?
Изображение на обложке
Эта статья написана Чжэньшань Цао и переработана Анжелой Ни.
В предыдущих двух постах этой серии мы уже рассмотрели системную архитектуру Milvus, самой передовой в мире векторной базы данных, а также ее SDK и API на языке Python.
Цель этого поста - помочь вам понять, как обрабатываются данные в Milvus, углубившись в систему Milvus и рассмотрев взаимодействие между компонентами обработки данных.
Ниже перечислены некоторые полезные ресурсы, необходимые для начала работы. Мы рекомендуем сначала прочитать их, чтобы лучше понять тему этого поста.
- Глубокое погружение в архитектуру Milvus
- Модель данных Milvus
- Роль и функции каждого компонента Milvus
- Обработка данных в Milvus
Интерфейс MsgStream
Интерфейс MsgStream имеет решающее значение для обработки данных в Milvus. Когда вызывается Start()
, короутин в фоновом режиме записывает данные в лог-брокер или считывает их оттуда. Когда вызывается Close()
, корутина останавливается.
Интерфейс MsgStream
MsgStream может выступать в роли производителя и потребителя. Интерфейс AsProducer(channels []string)
определяет MsgStream как производителя, а AsConsumer(channels []string, subNamestring)
- как потребителя. Параметр channels
является общим для обоих интерфейсов и используется для определения того, в какие (физические) каналы записывать или считывать данные.
Количество шардов в коллекции может быть задано при ее создании. Каждый шард соответствует виртуальному каналу (vchannel). Поэтому коллекция может иметь несколько виртуальных каналов. Milvus назначает каждому виртуальному каналу в лог-брокере физический канал (pchannel).
Каждому виртуальному каналу/шарду соответствует физический канал.
Produce()
в интерфейсе MsgStream, отвечающем за запись данных в pchannels в log broker. Данные могут быть записаны двумя способами:
- Одиночная запись: сущности записываются в разные шарды (vchannel) по хэш-значениям первичных ключей. Затем эти сущности поступают в соответствующие pchannels в log broker.
- Широковещательная запись: сущности записываются во все pchannels, указанные параметром
channels
.
Consume()
является разновидностью блокирующего API. Если в указанном pchannel нет данных, то при вызове Consume()
в интерфейсе MsgStream корутина будет заблокирована. С другой стороны, Chan()
является неблокирующим API, что означает, что корутина читает и обрабатывает данные только в том случае, если в указанном pchannel есть существующие данные. В противном случае корутина может обрабатывать другие задачи и не будет заблокирована при отсутствии данных.
Seek()
метод восстановления после сбоев. При запуске нового узла можно получить запись о потреблении данных и возобновить потребление данных с того места, где оно было прервано, вызвав Seek()
.
Запись данных
Данные, записываемые в различные vchannels (shards), могут быть либо сообщениями вставки, либо сообщениями удаления. Эти vchannels также могут называться DmChannels (каналы манипулирования данными).
Различные коллекции могут использовать одни и те же каналы в брокере журналов. Одна коллекция может иметь несколько шардов и, следовательно, несколько соответствующих vchannels. Сущности в одной коллекции, следовательно, поступают в несколько соответствующих pchannels в брокере журналов. В результате выгода от совместного использования pchannels заключается в увеличении пропускной способности, обеспечиваемой высокой параллельностью брокера журналов.
При создании коллекции указывается не только количество шардов, но и определяется сопоставление между vchannels и pchannels в лог-брокере.
Путь записи в Milvus
Как показано на рисунке выше, при записи прокси-серверы записывают данные в лог-брокер через интерфейс AsProducer()
MsgStream. Затем узлы данных потребляют эти данные, после чего преобразуют и сохраняют потребленные данные в объектном хранилище. Путь хранения - это тип метаинформации, которая будет записываться в etcd координаторами данных.
Flowgraph
Поскольку разные коллекции могут использовать одни и те же pchannels в лог-брокере, при потреблении данных узлам данных или узлам запросов необходимо определить, к какой коллекции принадлежат данные в pchannel. Чтобы решить эту проблему, мы ввели в Milvus функцию flowgraph. В основном он отвечает за фильтрацию данных в общем pchannel по идентификаторам коллекций. Таким образом, можно сказать, что каждый flowgraph обрабатывает поток данных в соответствующем шарде (vchannel) в коллекции.
Флоуграф на пути записи
Создание MsgStream
При записи данных объект MsgStream создается в следующих двух сценариях:
- Когда прокси получает запрос на вставку данных, он сначала пытается получить отображение между vchannels и pchannels через корневой координатор (root coord). Затем прокси создает объект MsgStream.
Сценарий 1
- Когда узел данных запускается и считывает метаинформацию каналов в etcd, создается объект MsgStream.
Сценарий 2
Чтение данных
Путь чтения в Milvus
Общий процесс чтения данных показан на рисунке выше. Запросы транслируются по каналу DqRequestChannel на узлы запросов. Узлы запросов параллельно выполняют задачи запроса. Результаты запросов от узлов запросов проходят через gRPC, прокси агрегирует результаты и возвращает их клиенту.
Чтобы рассмотреть процесс чтения данных более подробно, мы видим, что прокси записывает запросы в канал DqRequestChannel. Затем узлы запросов потребляют сообщения, подписываясь на DqRequestChannel. Каждое сообщение в канале DqRequestChannel транслируется, чтобы все подписанные узлы запроса могли его получить.
Когда узлы запроса получают запросы, они выполняют локальный запрос как к пакетным данным, хранящимся в запечатанных сегментах, так и к потоковым данным, которые динамически вставляются в Milvus и хранятся в растущих сегментах. После этого узлы запроса должны агрегировать результаты запроса как в закрытых, так и в растущих сегментах. Эти агрегированные результаты передаются прокси-серверу через gRPC.
Прокси собирает все результаты от нескольких узлов запроса и затем агрегирует их для получения окончательных результатов. Затем прокси возвращает окончательные результаты запроса клиенту. Поскольку каждый запрос и соответствующие ему результаты запроса обозначаются одним и тем же уникальным requestID, прокси может определить, какие результаты запроса соответствуют какому запросу.
Flowgraph
Flowgraph в пути чтения
Подобно пути записи, флоуграфы также представлены в пути чтения. В Milvus реализована унифицированная архитектура Lambda, которая объединяет обработку инкрементных и исторических данных. Поэтому узлы запросов должны получать и потоковые данные в реальном времени. Аналогично, потоковые графы на пути чтения фильтруют и дифференцируют данные из разных коллекций.
Создание MsgStream
Создание объекта MsgStream в пути чтения
При чтении данных объект MsgStream создается в следующем сценарии:
- В Milvus данные не могут быть прочитаны, пока они не загружены. Когда прокси получает запрос на загрузку данных, он отправляет запрос координатору запросов, который решает, как назначить шарды различным узлам запросов. Информация о назначении (т. е. имена vchannels и отображение между vchannels и соответствующими им pchannels) отправляется узлам запросов через вызов метода или RPC (удаленный вызов процедуры). Впоследствии узлы запросов создают соответствующие объекты MsgStream для потребления данных.
Операции DDL
DDL означает язык определения данных. Операции DDL над метаданными можно разделить на запросы на запись и запросы на чтение. Однако при обработке метаданных эти два типа запросов обрабатываются одинаково.
К запросам на чтение метаданных относятся:
- схема коллекции запросов
- Информация об индексировании запросов и многое другое.
Запросы на запись включают:
- Создать коллекцию
- Удалить коллекцию
- Создать индекс
- Удалить индекс и многое другое.
DDL-запросы отправляются прокси от клиента, и далее прокси передает эти запросы в полученном порядке корневому коорду, который назначает временную метку для каждого DDL-запроса и проводит динамические проверки запросов. Прокси обрабатывает каждый запрос последовательно, то есть по одному DDL-запросу за раз. Прокси не будет обрабатывать следующий запрос, пока не завершит обработку предыдущего и не получит результаты от корневого коорда.
Операции DDL.
Как показано на рисунке выше, в очереди задач Root coord находится K
DDL-запросов. DDL-запросы в очереди задач располагаются в порядке их поступления в корневой коорд. Так, ddl1
- первый, отправленный в корневой коорд, а ddlK
- последний в этой партии. Корневой коорд обрабатывает запросы один за другим в порядке очереди.
В распределенной системе связь между прокси и корневым коордом обеспечивается с помощью gRPC. Корневой коорд ведет учет максимального значения временной метки выполняемых задач, чтобы гарантировать, что все DDL-запросы будут обработаны в порядке очереди.
Предположим, есть два независимых прокси, прокси 1 и прокси 2. Они оба отправляют DDL-запросы на один и тот же корневой коорд. Однако одна из проблем заключается в том, что более ранние запросы не обязательно отправляются в корневой коорд раньше, чем запросы, полученные другим прокси позже. Например, на изображении выше, когда DDL_K-1
отправляется в корневой коорд от прокси 1, DDL_K
от прокси 2 уже был принят и выполнен корневым коордом. Как записал корневой коорд, максимальное значение временной метки выполненных задач в этот момент составляет K
. Поэтому, чтобы не нарушать временной порядок, запрос DDL_K-1
будет отклонен очередью задач корневого координатора. Однако, если прокси 2 отправит запрос DDL_K+5
корневому координатору в этот момент, запрос будет принят в очередь задач и будет выполнен позже в соответствии с его значением временной метки.
Индексирование
Построение индекса
Получая от клиента запросы на построение индекса, прокси сначала выполняет статическую проверку запросов и отправляет их в корневой коорд. Затем корневой коорд сохраняет эти запросы на построение индекса в метахранилище (etcd) и отправляет запросы координатору индекса (index coord).
Построение индекса.
Как показано выше, когда индексный координатор получает запросы на построение индекса от корневого координатора, он сначала сохраняет задачу в etcd для метахранилища. Начальный статус задачи построения индекса - Unissued
. Индексный коорд ведет учет загрузки задач каждого индексного узла и отправляет входящие задачи на менее загруженный индексный узел. По завершении задачи индексный узел записывает статус задачи, либо Finished
, либо Failed
, в метахранилище, которым в Milvus является etcd. Затем индексный узел поймет, успешна или нет задача построения индекса, посмотрев в etcd. Если задача провалилась из-за нехватки системных ресурсов или падения индексного узла, индексный коорд повторно запустит весь процесс и назначит ту же задачу другому индексному узлу.
Отказ от индекса
Кроме того, index coord отвечает за запросы на удаление индексов.
Сбрасывание индекса.
Когда корневой узел получает от клиента запрос на сброс индекса, он сначала помечает индекс как "сброшенный" и возвращает результат клиенту, уведомляя об этом индексный узел. Затем индексный коорд фильтрует все задачи индексирования по адресу IndexID
, и те задачи, которые соответствуют условию, отбрасываются.
Фоновая коротина index coord постепенно удаляет все задачи индексирования, помеченные как "dropped", из хранилища объектов (MinIO и S3). В этом процессе задействован интерфейс recycleIndexFiles. Когда все соответствующие индексные файлы будут удалены, метаинформация удаленных задач индексирования будет удалена из метахранилища (etcd).
О серии "Глубокое погружение
После официального объявления об общей доступности Milvus 2.0 мы организовали эту серию блогов Milvus Deep Dive, чтобы предоставить углубленную интерпретацию архитектуры и исходного кода Milvus. В этой серии блогов рассматриваются следующие темы:
- Интерфейс MsgStream
- Запись данных
- Чтение данных
- Операции DDL
- Индексирование
- О серии "Глубокое погружение
On This Page
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word