Эволюция облачной масштабируемой векторной базы данных Milvus
В этой статье мы расскажем о том, как мы разрабатывали новую архитектуру кластера баз данных Milvus.
Цели векторной базы данных Milvus
Когда идея создания векторной базы данных Milvus впервые пришла нам в голову, мы хотели создать инфраструктуру данных, которая помогла бы людям ускорить внедрение ИИ в их организациях.
Для выполнения этой задачи мы поставили перед проектом Milvus две важнейшие цели.
Простота использования
AI/ML - это развивающаяся область, в которой постоянно появляются новые технологии. Большинство разработчиков не совсем знакомы с быстро развивающимися технологиями и инструментами ИИ. Разработчики уже потратили большую часть своей энергии на поиск, обучение и настройку моделей. Им трудно тратить дополнительные усилия на обработку большого количества векторов встраивания, генерируемых моделями. Не говоря уже о том, что работа с большим объемом данных - это всегда очень сложная задача.
Поэтому мы придаем "простоте использования" очень высокий приоритет, так как это может значительно снизить стоимость разработки.
Низкие эксплуатационные расходы
Одним из главных препятствий на пути внедрения ИИ в производство является обоснование возврата инвестиций. У нас будет больше возможностей внедрить наши приложения ИИ в производство с более низкими эксплуатационными расходами. А это будет способствовать увеличению потенциальной выгоды.
Принципы проектирования Milvus 2.0
В Milvus 1.0 мы уже начали двигаться к этим целям. Но этого далеко не достаточно, особенно в плане масштабируемости и доступности. Тогда мы начали разработку Milvus 2.0, чтобы улучшить эти показатели. Принципы, которые мы заложили в эту новую версию, включают:
- Стремление к высокой масштабируемости и доступности
- Опора на развитую облачную инфраструктуру и практику
- Минимальный компромисс производительности в облаке
Другими словами, мы хотим сделать кластер баз данных Milvus "облачным".
Эволюция кластеров баз данных
Векторная база данных - это новый вид баз данных, поскольку она работает с новыми типами данных (векторами). Однако перед ней стоят те же задачи, что и перед другими базами данных, с некоторыми собственными требованиями. В оставшейся части этой статьи я расскажу о том, что мы узнали из существующих реализаций кластеров баз данных, и о том, как мы разрабатывали новую архитектуру группы Milvus.
Если вас интересуют детали реализации групповых компонентов Milvus, пожалуйста, следите за документацией Milvus. Мы будем постоянно публиковать технические статьи в репозитории Milvus GitHub, на сайте Milvus и в блоге Milvus.
Идеальный кластер баз данных
"Целься в малое, промахнись в малое".
Давайте сначала перечислим важнейшие возможности, которыми должен обладать идеальный кластер баз данных.
- Взаимодействие и отсутствие единой точки отказа: пользователи, подключенные к разным членам группы, могут одновременно иметь доступ на чтение/запись к одному и тому же фрагменту данных.
- Согласованность: разные члены группы должны видеть одни и те же данные.
- Масштабируемость: мы можем добавлять или удалять членов группы на ходу.
Честно говоря, все эти возможности трудно получить вместе. В современных реализациях кластеров баз данных людям приходится идти на компромисс с некоторыми из этих возможностей. Люди не ожидают идеального кластера баз данных, пока он может вписаться в пользовательские сценарии. Однако кластер "разделяй все" когда-то был очень близок к идеальному кластеру баз данных. Если мы хотим чему-то научиться, нам следует начать именно с этого.
Ключевые аспекты кластера баз данных
Кластер shared-everything имеет более длинную историю по сравнению с другими современными реализациями. Db2 data sharing group и Oracle RAC являются типичными кластерами shared-everything. Многие думают, что shared-everything означает совместное использование дисков. Это гораздо больше.
В кластере shared-everything есть только один вид базы данных, входящий в группу. Пользователи могут подключаться к любому из этих симметричных членов для доступа к любым данным. Что же такое "все", которое должно быть общим для того, чтобы это работало?
Последовательность событий в группе
Во-первых, последовательность событий в группе очень важна для разрешения потенциальных конфликтов, вызванных одновременным доступом разных членов группы. Для обозначения последовательности событий мы обычно используем порядковый номер записи журнала базы данных. В то же время порядковый номер записи журнала обычно генерируется из временной метки.
Таким образом, потребность в последовательности групповых событий равна потребности в глобальном таймере. Если бы мы могли иметь атомные часы для группы, это было бы просто замечательно. Однако Milvus - это проект программного обеспечения с открытым исходным кодом, а значит, мы должны полагаться на общедоступные ресурсы. На сегодняшний день атомные часы - это все еще опция премиум-класса для крупных компаний.
Мы реализовали компонент синхронизации времени в кластере баз данных Milvus 2.0. Ссылку на него вы можете найти в приложении.
Глобальная блокировка
В базе данных есть механизм блокировки для разрешения конфликтов одновременного доступа, будь то оптимистическая или пессимистическая блокировка. Аналогично, нам нужна глобальная блокировка для разрешения конфликтов одновременного доступа между разными членами группы.
Глобальная блокировка означает, что разные члены группы должны общаться друг с другом для согласования запросов на блокировку. На эффективность процесса согласования глобальной блокировки влияет несколько важных факторов:
- Скорость межсистемных соединений
- Количество членов группы, которые должны участвовать в процессе переговоров
- Частота конфликтов в группе.
Типичный размер группы не превышает 100 человек. Например, Db2 DSG - 32, Oracle RAC - 100. Члены группы размещаются в одной серверной комнате, соединенной оптическим волокном для минимизации задержки передачи данных. Поэтому такой кластер иногда называют централизованным. Из-за ограничения размера группы для кластеров с общим доступом выбирают серверы высокого класса (мейнфреймы или миникомпьютеры, которые имеют гораздо большую емкость процессора, памяти, каналов ввода-вывода и т. д.).
В современной облачной среде это предположение об аппаратном обеспечении кардинально изменилось. Сегодня облачные центры обработки данных представляют собой высокоплотные серверные комнаты, заполненные тысячами (тысячами) товарных X86-серверов с TCP/IP-соединениями. Если мы будем опираться на эти серверы X86 для построения кластера баз данных, размер группы должен увеличиться до сотен (даже тысяч) машин. И в некоторых бизнес-сценариях мы захотим, чтобы эти сотни машин X86 были разбросаны по разным регионам. Таким образом, внедрение глобальной блокировки может оказаться нецелесообразным, поскольку производительность глобальной блокировки будет недостаточно высокой.
В Milvus 2.0 мы не будем реализовывать глобальную блокировку. С одной стороны, не будет обновления векторных данных. (Люди предпочитают удалять-вставлять вместо обновления.) Поэтому нам не нужно беспокоиться о конфликтах нескольких писателей на одном и том же фрагменте данных в группе Milvus с чередованием. В то же время мы можем использовать MVCC (multi-version concurrency control, метод управления параллелизмом, предотвращающий блокировку) для разрешения конфликтов между читателями и писателями.
С другой стороны, обработка векторных данных занимает гораздо больше памяти, чем обработка структурированных данных. Поэтому векторные базы данных требуют более высокой масштабируемости.
Общий кэш данных в памяти
Вкратце можно разделить механизм базы данных на две части: механизм хранения и механизм вычислений. Механизм хранения отвечает за две важнейшие задачи:
- Запись данных в постоянное хранилище для обеспечения их долговечности.
- Загрузка данных из постоянного хранилища в кэш данных in-memory (он же буферный пул); это единственное место, где вычислительный механизм получает доступ к данным.
В сценарии кластера баз данных что, если участник A обновил данные, кэшированные в участнике B? Как участник B может узнать, что срок хранения данных в памяти истек? Для решения этой проблемы в классическом кластере с общим доступом предусмотрен механизм аннулирования перекрестных буферов. Механизм перекрестного аннулирования буферов будет работать аналогично глобальной блокировке, если мы поддерживаем сильную согласованность между членами группы. Как уже говорилось, в современной облачной среде это нецелесообразно. Поэтому мы решили снизить уровень согласованности в группе Milvus, масштабируемой в облаке, до уровня возможной согласованности. Таким образом, механизм аннулирования перекрестных буферов в Milvus 2.0 может стать асинхронным процессом.
Совместное хранение
Совместное хранение данных - это, вероятно, первое, о чем думают при обсуждении кластера баз данных.
За последние годы развития облачных хранилищ варианты хранения данных также значительно изменились. Сеть хранения данных (SAN) была (и остается) основой хранения данных в группе "все общее". Но в облачной среде SAN не существует. Базе данных приходится использовать локальный диск, подключенный к виртуальным машинам облака. Использование локального диска создает проблему согласованности данных между членами группы. Кроме того, нам приходится беспокоиться о высокой доступности членов группы.
Затем Snowflake стал отличной ролевой моделью для облачных баз данных, использующих облачное хранилище общего доступа (S3 storage). Это вдохновляет и Milvus 2.0. Как уже говорилось, мы намерены опираться на развитую облачную инфраструктуру. Но прежде чем мы сможем использовать облачное хранилище, нам нужно подумать о нескольких вещах.
Во-первых, хранилище S3 дешево и надежно, но оно не предназначено для мгновенного доступа к данным, как в сценариях с базами данных. Нам нужно создать компоненты данных (которые мы называем узлами данных в Milvus 2.0), чтобы соединить локальную память/диск и хранилище S3. Есть несколько примеров (например, Alluxio, JuiceFS и т. д.), которые мы можем изучить. Причина, по которой мы не можем интегрировать эти проекты напрямую, заключается в том, что мы ориентируемся на разную гранулярность данных. Alluxio и JuiceFS предназначены для наборов данных или POSIX-файлов, в то время как мы ориентируемся на уровень записей данных (векторов).
Когда векторные данные будут размещены в хранилище S3, ответ для метаданных будет прост: хранить их в ETCD. А как быть с данными журнала? В классических реализациях хранилище журналов также основано на SAN. Файлы журнала одного члена группы баз данных совместно используются в кластере баз данных для восстановления после сбоев. Поэтому это не было проблемой до тех пор, пока мы не перешли в облачную среду.
В статье Spanner компания Google показала, как она реализовала глобально распределенную базу данных (группу) с алгоритмом консенсуса Paxos. Вам нужно запрограммировать кластер базы данных как группу репликации машины состояний. Редо-журнал обычно является "состоянием", которое будет реплицироваться по всей группе.
Репликация redo-log с помощью алгоритмов консенсуса - мощный инструмент, и в некоторых бизнес-сценариях он имеет существенные преимущества. Но для векторной базы данных Milvus мы не нашли достаточных стимулов для создания группы репликации машины состояний в целом. Мы решили использовать облачную очередь/платформу обмена сообщениями (Apache Pulsar, Apache Kafka и т. д.) в качестве альтернативного облачного хранилища журналов. Делегировав хранение журналов платформе обмена сообщениями, мы получили следующие преимущества.
- Группа в большей степени ориентирована на события, а значит, многие процессы могут быть асинхронными. Это улучшает масштабируемость.
- Компоненты более слабо связаны друг с другом, что значительно упрощает выполнение скользящих обновлений в режиме онлайн. Повышается доступность и работоспособность.
Мы вернемся к этой теме в следующем разделе.
Итак, мы рассмотрели важнейшие аспекты кластера баз данных. Прежде чем мы перейдем к обсуждению архитектуры Milvus 2.0, позвольте мне сначала объяснить, как мы управляем векторами в Milvus.
Управление данными и предсказуемость производительности
В Milvus векторы хранятся в коллекциях. Коллекция" - это логическая концепция, эквивалентная "таблице" в базах данных SQL. В "коллекции" может быть несколько физических файлов для хранения векторов. Физический файл - это "сегмент". Сегмент" - это физическое понятие, подобное файлу табличного пространства в базах данных SQL. Когда объем данных невелик, мы можем сохранить все в одном сегменте/физическом файле. Но сегодня мы постоянно сталкиваемся с большими данными. Когда имеется несколько сегментов/физических файлов, как распределить данные по разным разделам данных?
Хотя на первом месте стоят данные, а не индексы, мы должны хранить данные так, как предпочитает алгоритм индексации, чтобы в большинстве случаев обеспечить эффективный доступ к данным. Часто используемой стратегией в базах данных SQL является разделение по диапазону значений ключа разделения. Обычно люди создают кластеризованный индекс для применения ключа разделения. В целом, это достойный подход для баз данных SQL. Данные хранятся в хорошем виде, оптимизированы для ввода-вывода (prefetch). Но недостатки все же есть.
- Перекос данных. В некоторых разделах может быть гораздо больше данных, чем в других. Распределение реальных данных не так просто, как числовой диапазон.
- Горячие точки доступа. На некоторые разделы данных может приходиться больше нагрузки.
Представьте, что больше нагрузки приходится на разделы с большим количеством данных. При возникновении таких ситуаций нам необходимо перераспределить данные между разделами. (Это утомительная повседневная жизнь DBA).
Кластеризованный индекс для векторов
Мы также можем создать кластеризованный индекс для векторов (индекс инвертированного списка). Но это не тот же случай, что и в базах данных SQL. Если в базах данных SQL индекс построен, то доступ к данным через него будет очень эффективным, с меньшими вычислениями и операциями ввода-вывода. Но для векторных данных даже при наличии индекса потребуется гораздо больше вычислений и операций ввода-вывода. Поэтому упомянутые выше недостатки будут иметь более серьезное влияние на кластеры векторных баз данных. Кроме того, стоимость ребалансировки векторов в разных сегментах очень высока из-за объема данных и сложности вычислений.
В Milvus мы используем стратегию разделения по росту. Когда мы вносим данные в коллекцию векторов, Milvus добавляет новые векторы в последний сегмент коллекции. Milvus закроет сегмент, как только его размер станет достаточно большим (порог настраивается), и построит индекс для закрытого сегмента. Тем временем будет создан новый сегмент для хранения новых данных. Эта простая стратегия более сбалансирована для векторной обработки.
Векторный запрос - это процесс поиска наиболее похожих кандидатов в коллекции векторов. Это типичная процедура MapReduce. Например, мы хотим найти 20 наиболее похожих результатов из коллекции векторов с десятью сегментами. Мы можем найти 20 лучших результатов в каждом из сегментов, а затем объединить результаты 20 * 10 в итоговые 20 результатов. Поскольку в каждом сегменте одинаковое количество векторов и одинаковый индекс, время обработки каждого сегмента практически одинаково. Это дает нам преимущество предсказуемости производительности, что очень важно при планировании масштаба кластеров баз данных.
Новые парадигмы в Milvus 2.0
В Milvus 1.0 мы реализовали шардинг с разделением на чтение и запись, как в большинстве баз данных SQL. Это была хорошая попытка масштабирования кластера баз данных Milvus. Но проблемы тоже вполне очевидны.
База данных Milvus 1.0
В Milvus 1.0 узел R/W должен полностью заботиться о последнем сегменте, включая векторное добавление, поиск в этом неиндексированном сегменте, построение индекса и т. д. Поскольку у каждой коллекции есть только один писатель, он очень занят, если данные непрерывно поступают в систему. Также проблемой является производительность обмена данными между узлом R/W и узлами чтения. Кроме того, для совместного хранения данных мы должны полагаться либо на NFS (нестабильно), либо на премиальные облачные хранилища (слишком дорого).
Эти существующие проблемы трудно решить в архитектуре Milvus 1.0. Поэтому мы внедрили новые парадигмы в дизайн Milvus 2.0, чтобы решить эти проблемы.
Архитектура Milvus
Акторная модель
Существует две модели программирования параллельных вычислительных систем.
- Общая память, которая подразумевает контроль параллелизма (блокировку) и синхронную обработку.
- Модель акторов (она же модель передачи сообщений) означает управление сообщениями и асинхронную обработку.
Эти две модели мы можем применить и в кластерах распределенных баз данных.
Как уже говорилось, большинство известных распределенных баз данных используют один и тот же метод: репликацию redo-log по алгоритмам консенсуса. Это синхронная обработка с использованием алгоритмов консенсуса для создания распределенной общей памяти для записей redo-log. Различные компании и венчурные капиталы вложили в эту технологию миллиарды долларов. Я не хотел комментировать это, пока мы не начали работать над Milvus 2.0. Многие считают эту технологию единственным способом реализации распределенных систем баз данных. Это раздражает. Если я промолчу, люди могут понять, что мы были безрассудны в проектировании распределенных баз данных.
В последние годы репликация Redo-log с помощью алгоритмов консенсуса была самой переоцененной технологией баз данных. Здесь есть две ключевые проблемы.
- Предположение о том, что репликация redo-log лучше, является хрупким.
- Поставщики вводят людей в заблуждение относительно возможностей алгоритмов консенсуса.
Допустим, у нас есть два узла базы данных, исходный и целевой. В самом начале у них есть точные копии данных. Мы выполняем некоторые операции изменения (I/U/D SQL-операторы) на узле-источнике и хотим, чтобы целевой узел обновлялся. Что мы должны сделать? Самый простой способ - воспроизвести операции на целевом узле. Но это не самый эффективный способ.
Размышляя о стоимости выполнения оператора I/U/D, мы можем разделить ее на подготовку к выполнению и физическую работу. Подготовка к выполнению включает в себя работу SQL-парсера, SQL-оптимизатора и т. д. Независимо от того, сколько записей данных будет затронуто, это фиксированная стоимость. Стоимость физической части работы зависит от того, сколько записей данных будет затронуто; это плавающая стоимость. Идея репликации redo-log заключается в экономии фиксированных затрат на целевом узле; на целевом узле мы воспроизводим только redo-log (физическую работу).
Процент экономии зависит от количества записей redo-log. Если одна операция затрагивает только одну запись, я должен увидеть значительную экономию от репликации redo-log. А если речь идет о 10 000 записей? Тогда стоит задуматься о надежности сети. Что надежнее - отправка одной операции или 10 000 записей redo-log? А как насчет миллиона записей? Репликация redo-log - это супер в таких сценариях, как платежные системы, системы метаданных и т. д. В этих сценариях каждая операция ввода/вывода из базы данных затрагивает лишь небольшое количество записей (1 или 2). Но с интенсивными нагрузками ввода-вывода, такими как пакетные задания, работать сложно.
Поставщики всегда утверждают, что алгоритмы консенсуса могут обеспечить сильную согласованность в кластерах баз данных. Но люди используют алгоритмы консенсуса только для репликации записей redo-log. Записи redo-log согласованы на разных узлах, но это не означает, что представления данных на других узлах тоже согласованы. Мы должны объединить записи redo-log в реальные записи таблицы. Поэтому даже при такой синхронной обработке мы все равно можем получить только конечную согласованность представлений данных.
Мы должны использовать репликацию redo-log по алгоритмам консенсуса в соответствующих местах. В системе метаданных (ETCD) и платформе обмена сообщениями (например, Apache Pulsar), используемых в Milvus 2.0, реализованы алгоритмы консенсуса. Но, как я уже говорил, "для векторной базы данных Milvus мы не находим достаточных стимулов для того, чтобы быть группой репликации государственной машины в целом".
В Milvus 2.0 мы используем модель акторов для организации рабочих узлов. Рабочие узлы одиноки. Они общаются только с платформой обмена сообщениями, получая команды и отправляя результаты. Это звучит скучно.
"Каков наш девиз?" - "Скука всегда лучше" - The Hitman's Bodyguard (2017).
Акторная модель является асинхронной. Она подходит для масштабируемости и доступности. Поскольку рабочие узлы не знают друг друга, присоединение или удаление некоторых рабочих узлов не оказывает влияния на другие рабочие узлы.
Разделение доступности и долговечности
В Milvus 2.0 мы делаем повтор операций, а не повтор журналов, потому что в векторной базе данных нет большой разницы между повтором операций и повтором журналов. У нас нет ни функции Update, ни функции Insert with Select. Кроме того, воспроизведение операций гораздо проще осуществлять с помощью модели акторов.
Таким образом, несколько рабочих узлов могут выполнять одну и ту же операцию из платформы обмена сообщениями в соответствии со своими обязанностями. Я уже упоминал, что мы решили использовать облачное хранилище S3 в качестве общего уровня хранения кластера баз данных Milvus. Хранилище S3 очень надежно. Тогда есть ли необходимость в том, чтобы разные рабочие узлы записывали одни и те же данные в общее хранилище?
Поэтому мы разработали три роли для рабочих узлов.
- Узел запросов поддерживает представление данных в памяти в соответствии с заданием. Работа узла запроса включает в себя выполнение векторного поиска и поддержание данных в памяти в актуальном состоянии. При этом ему не нужно ничего записывать в хранилище S3. Это самый чувствительный к памяти узел в группе.
- Узел данных отвечает за запись новых данных в хранилище S3. Узлу данных не нужно поддерживать представление данных в памяти, поэтому аппаратная конфигурация узла данных существенно отличается от узла запросов.
- Индексный узел строит индексы для сегментов, закрытых узлом данных, когда размер сегментов достигает порогового значения. Это самая ресурсоемкая работа в группе.
Эти три типа узлов представляют различные виды рабочей нагрузки. Они могут масштабироваться независимо друг от друга. Мы называем это разделением доступности и долговечности, о котором узнали из облачной базы данных Microsoft Socrates.
Конец, но и начало
В этой статье мы рассмотрели несколько проектных решений векторной базы данных Milvus 2.0. Давайте быстро подведем итоги.
- Мы выбрали конечную согласованность для кластера Milvus 2.0.
- Мы интегрировали зрелые облачные компоненты в Milvus 2.0 настолько, насколько это было возможно. Мы контролировали внедрение новых компонентов Milvus 2.0 в производственные среды пользователей.
- Благодаря модели акторов и разделению доступности и долговечности Milvus 2.0 легко масштабируется в облачной среде.
На данный момент мы сформировали основу базы данных Milvus 2.0, масштабируемой в облаке, но в нашем бэклоге есть множество требований от сообщества Milvus, которые необходимо удовлетворить. Если вы придерживаетесь той же миссии ("Создавать больше инфраструктурного программного обеспечения с открытым исходным кодом для ускорения трансформации ИИ"), добро пожаловать в сообщество Milvus.
Milvus - это выпускной проект фонда LF AI & Data. Вам НЕ нужно подписывать никаких CLA для Milvus!
Приложение
Дизайн-документ Milvus
https://github.com/milvus-io/milvus/tree/master/docs/design_docs
Реализация Raft на C++
Если вас все еще интересует алгоритм консенсуса, я предлагаю вам ознакомиться с проектом с открытым исходным кодом Gringofts от eBay. Это реализация на C++ алгоритма консенсуса Raft (вариант семейства Paxos). Мои друзья Джеки и Элвис (мои бывшие коллеги по Morgan Stanley) создали его для системы онлайн-платежей eBay, которая как раз является одним из наиболее подходящих сценариев для этой технологии.
- Цели векторной базы данных Milvus
- Эволюция кластеров баз данных
- Конец, но и начало
- Приложение
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