🚀 Попробуйте Zilliz Cloud, полностью управляемый Milvus, бесплатно — ощутите 10-кратное увеличение производительности! Попробовать сейчас>

milvus-logo
LFAI

Использование

  • Engineering
February 07, 2022
Lichen Wang

Благодаря унифицированной пакетной и потоковой обработке и облачной нативной архитектуре Milvus 2.0 представляет собой более сложную задачу, чем его предшественник при разработке функции DELETE. Благодаря усовершенствованной конструкции дезагрегации хранения и вычислений и гибкому механизму публикации/подписки мы с гордостью можем заявить, что справились с этой задачей. В Milvus 2.0 вы можете удалить сущность в данной коллекции с помощью ее первичного ключа, и тогда удаленная сущность больше не будет фигурировать в результатах поиска или запроса.

Обратите внимание, что операция DELETE в Milvus относится к логическому удалению, в то время как физическая очистка данных происходит во время Data Compaction. Логическое удаление не только значительно повышает производительность поиска, ограниченную скоростью ввода-вывода, но и облегчает восстановление данных. Логически удаленные данные все еще можно восстановить с помощью функции "Путешествие во времени".

Использование

Давайте сначала опробуем функцию DELETE в Milvus 2.0. (В следующем примере используется PyMilvus 2.0.0 на Milvus 2.0.0).

from pymilvus import connections, utility, Collection, DataType, FieldSchema, CollectionSchema
# Connect to Milvus
connections.connect(
    alias="default", 
    host='x.x.x.x', 
    port='19530'
)
# Create a collection with Strong Consistency level
pk_field = FieldSchema(
    name="id", 
    dtype=DataType.INT64, 
    is_primary=True, 
)
vector_field = FieldSchema(
    name="vector", 
    dtype=DataType.FLOAT_VECTOR, 
    dim=2
)
schema = CollectionSchema(
    fields=[pk_field, vector_field], 
    description="Test delete"
)
collection_name = "test_delete"
collection = Collection(
    name=collection_name, 
    schema=schema, 
    using='default', 
    shards_num=2,
    consistency_level="Strong"
)
# Insert randomly generated vectors
import random
data = [
    [i for i in range(100)],
    [[random.random() for _ in range(2)] for _ in range(100)],
]
collection.insert(data)
# Query to make sure the entities to delete exist
collection.load()
expr = "id in [2,4,6,8,10]"
pre_del_res = collection.query(
    expr,
    output_fields = ["id", "vector"]
)
print(pre_del_res)
# Delete the entities with the previous expression
collection.delete(expr)
# Query again to check if the deleted entities exist
post_del_res = collection.query(
    expr,
    output_fields = ["id", "vector"]
)
print(post_del_res)

Реализация

В экземпляре Milvus узел данных в основном отвечает за упаковку потоковых данных (журналов в лог-брокере) в исторические данные (снимки журналов) и их автоматическую промывку в объектное хранилище. Узел запросов выполняет поисковые запросы по полным данным, то есть как по потоковым, так и по историческим данным.

Чтобы максимально использовать возможности параллельных узлов кластера по записи данных, Milvus использует стратегию шардинга на основе хеширования первичных ключей для равномерного распределения операций записи между различными рабочими узлами. Иными словами, прокси будет направлять сообщения (т. е. запросы) сущности на языке манипулирования данными (DML) на один и тот же узел данных и узел запросов. Эти сообщения публикуются через DML-канал и потребляются узлом данных и узлом запросов по отдельности для совместного предоставления услуг поиска и запросов.

Узел данных

Получив сообщения INSERT данных, узел данных вставляет данные в растущий сегмент, который представляет собой новый сегмент, созданный для приема потоковых данных в памяти. Если количество строк данных или продолжительность растущего сегмента достигает порогового значения, узел данных запечатывает его, чтобы предотвратить поступление данных. Затем узел данных сбрасывает запечатанный сегмент, содержащий исторические данные, в объектное хранилище. Тем временем узел данных генерирует фильтр цветения на основе первичных ключей новых данных и сбрасывает его в хранилище объектов вместе с запечатанным сегментом, сохраняя фильтр цветения как часть двоичного журнала статистики (binlog), который содержит статистическую информацию о сегменте.

Фильтр цветения - это вероятностная структура данных, состоящая из длинного двоичного вектора и ряда функций случайного отображения. Она может быть использована для проверки того, является ли элемент членом множества, но может возвращать ложноположительные совпадения. -- Википедия

Когда приходят сообщения DELETE, узел данных буферизирует все фильтры bloom в соответствующем шарде и сопоставляет их с первичными ключами, указанными в сообщениях, чтобы получить все сегменты (как растущие, так и закрытые), которые могут включать сущности, подлежащие удалению. Выявив соответствующие сегменты, узел данных буферизирует их в памяти, чтобы создать бинлоги Delta для записи операций удаления, а затем сбрасывает эти бинлоги вместе с сегментами обратно в хранилище объектов.

Data Node Узел данных

Поскольку одному шарду назначается только один DML-канал, дополнительные узлы запросов, добавленные в кластер, не смогут подписаться на этот DML-канал. Чтобы гарантировать, что все узлы запросов смогут получать сообщения DELETE, узлы данных фильтруют сообщения DELETE из DML-канала и пересылают их в Delta-канал, чтобы уведомить все узлы запросов об операциях удаления.

Узел запроса

При загрузке коллекции из хранилища объектов узел запроса сначала получает контрольную точку каждого шарда, в которой отмечены операции DML с момента последней операции flush. Основываясь на контрольной точке, узел запроса загружает все запечатанные сегменты вместе с их дельта-бинлогом и фильтрами bloom. После загрузки всех данных узел запроса подписывается на DML-Channel, Delta-Channel и Query-Channel.

Если после загрузки коллекции в память приходят сообщения INSERT с дополнительными данными, узел запроса сначала определяет растущие сегменты в соответствии с этими сообщениями и обновляет соответствующие фильтры цветения в памяти только для целей запроса. Эти фильтры цветности, предназначенные для запросов, не будут удаляться в хранилище объектов после завершения запроса.

Query Node Узел запросов

Как упоминалось выше, только определенное количество узлов запросов может получать сообщения DELETE от DML-канала, а значит, только они могут выполнять запросы DELETE в растущих сегментах. Те узлы запросов, которые подписались на DML-канал, сначала фильтруют DELETE-сообщения в растущих сегментах, находят сущности, сопоставляя предоставленные первичные ключи с выделенными запросом фильтрами цветения растущих сегментов, а затем записывают операции удаления в соответствующие сегменты.

Узлы запросов, которые не могут подписаться на DML-канал, могут обрабатывать запросы поиска или запросов только в сегментах с уплотнениями, поскольку они могут подписаться только на Delta-канал и получать сообщения DELETE, пересылаемые узлами данных. Собрав все сообщения DELETE в закрытых сегментах из Delta-Channel, узлы запросов находят сущности, сопоставляя предоставленные первичные ключи с фильтрами bloom в закрытых сегментах, а затем записывают операции удаления в соответствующие сегменты.

В конечном итоге при поиске или запросе узлы запроса формируют набор битов на основе записей об удалении, чтобы исключить удаленные сущности, и осуществляют поиск среди оставшихся сущностей из всех сегментов, независимо от статуса сегмента. И последнее, но не менее важное: уровень согласованности влияет на видимость удаленных данных. При сильном уровне согласованности (как показано в предыдущем примере кода) удаленные сущности сразу же становятся невидимыми после удаления. При использовании уровня Bounded Consistency Level пройдет несколько секунд, прежде чем удаленные сущности станут невидимыми.

Что дальше?

В серии блогов о новых возможностях версии 2.0 мы постараемся объяснить дизайн новых возможностей. Читайте больше в этой серии блогов!

Try Managed Milvus for Free

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

Get Started

Like the article? Spread the word

Продолжить чтение