Разбираемся с Vespa. Часть 1

от автора

Содержание

Эта статья открывает серию из трёх материалов, посвящённых работе с поисковой системой хранения данных Vespa.

Из этой статьи вы узнаете:

  • Как запустить сервер конфигурации Vespa в Docker.

  • Как настроить конфигурацию Vespa.

  • Как выглядит структура схемы данных.

  • Как выполнить фильтрацию полей в результатах поиска.

  • Как отключить валидацию схемы данных и файла конфигурации для локальной отладки.

В следующих частях мы обсудим, как устроен поиск, ранжирование и группировка в Vespa, а также сравним скорость выполнения CRUD-операций в ElasticSearch и Vespa.

Поисковые системы

На данный момент существует множество различных поисковых систем, которые используются для хранения и поиска данных с релевантной выдачей. Согласно рейтингу компании solid IT, лидирующее положение занимает ElasticSearch, который значительно опережает ближайших конкурентов.

Полный список поисковых систем и информация о расчете рейтинга доступны по ссылке.

Если познакомиться с ElasticSearch ближе, становится очевидно, почему эта система так популярна:

  • Открытый исходный код.

  • Мощное API — готовые клиенты доступны на множестве популярных языков программирования, в том числе на Java и Python.

  • Хорошая горизонтальная масштабируемость.

  • Гибкая модель данных, которая позволяет не использовать схемы и обрабатывать документы с различными полями и структурами. Хотя возможность создания схем (маппинга) остается.

  • Поиск данных осуществляется при помощи широко используемого формата — JSON.

  • Репликация данных позволяет повысить отказоустойчивость систем.

Но у ElasticSearch есть один существенный недостаток: все документы являются неизменными, и при попытке частичного обновления происходит полная переиндексация всего документа. Это существенно замедляет загрузку большого количества данных.

Одной из альтернатив ElasticSearch является Vespa. Vespa — это система для высоконагруженных систем полнотекстового поиска с фильтрацией и ранжированием исходного результата. Vespa использует строгую структурированную модель данных, которая описывается в схеме.

Vespa устраняет основной недостаток ElasticSearch, обеспечивая возможность обработки больших данных с низкой задержкой и частичного обновления документов.

Стоит уточнить, что Vespa — это система компромиссов, и у нее есть ряд недостатков:

  • К сожалению, на момент написания статьи Vespa не имеет полноценного поискового клиента на современных языках, таких как Java и Python. Разработчики Vespa рекомендуют использовать любые доступные HTTP-клиенты для поиска.

  • Сложное двухэтапное развёртывание.

  • Из-за того, что документация недостаточно полная, а накопленных знаний о практических аспектах работы не так много, возникают трудности при попытке разобраться в деталях работы Vespa (эта статья частично это исправит). Например, найти информацию про работу ключевого слова from disk — это целый квест.

  • Схема данных хранится в специальном формате .sd (schema definition).

Тестовый проект

Для тестового проекта мы возьмём за основу базу данных, содержащую информацию о товарах в магазине обуви. На первом этапе в базе данных будут представлены две категории обуви: кроссовки и ботинки. Также будет присутствовать сущность, которая отражает информацию о доступности каждой из этих категорий в каждом магазине.

Характеристики товаров были выбраны максимально простые и понятные для читателей статьи.

Опишем наши продукты на схеме данных:

Схема данных

Схема данных

Vespa CLI

Перед началом работы с Vespa необходимо установить Vespa CLI. Ниже актуальная ссылка на релизную версию:

Для работы с Windows нужно скачать архив и добавить переменную среды на папку с исполняемым файлом. Если всё сделано правильно, в командной строке появится возможность использовать команду «vespa [cmd]».

Демопроект

По ссылке ниже можно склонировать демопроект хранилища продуктов:

Vespa в Docker

Чтобы развернуть Vespa в Docker, достаточно загрузить образ с конфигурационным сервером Vespa. Пример настройки можно найти в файле docker-compose:

docker-compose.yml

version: "3.8" name: vespa services:   vespa:     container_name: vespa     image: vespaengine/vespa     ports:       - "8080:8080"       - "19071:19071"

Настройка Vespa

У Vespa есть несколько способов развернуть настройки, но, по моему мнению, самый удобный способ — использование maven-плагина. Для этого создаем пустой проект. Затем добавляем в него плагин для сборки и библиотеку для работы с клиентом Java:

vespa-config/pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">      <modelVersion>4.0.0</modelVersion>      <parent>         <groupId>ru.sportmaster</groupId>         <artifactId>vespa</artifactId>         <version>1.0-SNAPSHOT</version>     </parent>      <artifactId>vespa-config</artifactId>     <!-- Специальная упаковка пакета для Vespa -->     <packaging>container-plugin</packaging>      <dependencies>         <!-- Библиотека для работы с Vespa -->         <dependency>             <groupId>com.yahoo.vespa</groupId>             <artifactId>container</artifactId>             <version>${vespa.version}</version>             <scope>provided</scope>         </dependency>     </dependencies>      <build>         <plugins>             <!-- Плагин используется для сборки и упаковки компонентов Vespa в контейнер -->             <plugin>                 <groupId>com.yahoo.vespa</groupId>                 <artifactId>bundle-plugin</artifactId>                 <version>${vespa.version}</version>                 <extensions>true</extensions>                 <configuration>                     <!-- В случае наличия предупреждений, будет ошибка сборки -->                     <failOnWarnings>true</failOnWarnings>                 </configuration>             </plugin>             <!-- Архивирует компоненты Vespa -->             <plugin>                 <groupId>com.yahoo.vespa</groupId>                 <artifactId>vespa-application-maven-plugin</artifactId>                 <version>${vespa.version}</version>                 <executions>                     <execution>                         <goals>                             <goal>packageApplication</goal>                         </goals>                     </execution>                 </executions>             </plugin>         </plugins>     </build>  </project>

Конфигурирование сервера Vespa происходит при помощи файла services.xml. Пример простой конфигурации для пользовательского документа:

src/main/application/services.xml

<?xml version="1.0" encoding="utf-8" ?> <services version="1.0">      <container version="1.0" id="default">         <!-- Включает поисковую часть контейнера, без него не будет работать поиск -->         <search/>         <!-- Включает API для работы с документами -->         <document-api/>     </container>      <!-- Создает кластер содержимого который хранит и индексирует документы -->     <content id="product" version="1.0">         <!-- Определяет какие типы документов должны быть направлены в этот кластер -->         <documents>             <document mode="index" type="sneakers" />             <document mode="index" type="boots" />         </documents>         <!-- Количество реплик документа в кластере -->         <redundancy>2</redundancy>         <!-- Определяет набор узлов в кластере -->         <nodes>             <!-- distribution-key - идентификатор узла для алгоритма распределения данных -->             <node hostalias="node-1" distribution-key="0"/>             <node hostalias="node-2" distribution-key="1"/>             <node hostalias="node-3" distribution-key="2"/>         </nodes>     </content>    </services>

Данная конфигурация создаст 3 узла внутри кластера. Важно отметить, что свойство redundancy определяет количество копий данных, хранимых на каждом узле. В данном случае документ и его копия будут храниться на 2 из 3 доступных узлов.

Пример кластера данных с redundancy = 1

Пример кластера данных с redundancy = 1

В атрибуте document.mode указывается режим хранения данных, всего доступно три варианта:

  • index — режим индексирования, документ будет доступен для поиска.

  • store-only — режим обычного хранения, поиск и индексация недоступны.

  • streaming — потоковый режим позволяет искать информацию по необработанным данным, если поиск осуществляется на небольшом объеме информации. В этом случае индексация может быть не очень эффективной, так как требует дополнительных ресурсов для создания и поддержки индексов. Потоковый режим позволяет найти информацию быстро и без необходимости создания индексов.

Атрибут document.type должен указывать на документ из схемы данных (см. ниже).

Схема данных

Как я уже упомянул ранее, схема данных хранится в специальном формате .sd.

Для примера возьмем схему данных абстрактного документа, который описывает общие свойства обуви:

src/main/application/schemas/shoes.sd

# Схема обуви schema shoes inherits product {     # Документ описывающий общие поля для любой обуви     document shoes inherits product {         # Сезон         field season type string {             indexing: summary | index         }         # Материал изготовления         field material type map<string, int> {             indexing: summary             struct-field key {                 indexing: attribute             }             struct-field value {                 indexing: attribute             }         }         # Пол         field gender type string {             indexing: summary | index         }     } }

Стоит заметить, что мы не можем сохранить этот абстрактный документ в Vespa, так как он не указан в content.documents конфигурации сервера — services.xml.

При помощи ключевого слова inherits мы наследуем базовые поля документа product.

Поля в схеме поддерживают такие типы данных, как:

  • bool, byte, double, float, int, long — простые типы. Не поддерживают индексацию.

  • array<type> — массив простых типов или структура данных. Каждый элемент этого типа индексируется отдельно.

  • map<key, value> — ассоциативный массив. Не поддерживает индексацию.

  • position — координаты по широте и долготе. Не поддерживает индексацию.

  • predicate — поле с набором логических ограничений. Индексируется в бинарном формате.

  • raw — двоичные данные. Не поддерживает индексацию.

  • reference<document-type> — поле с ссылкой на глобальный документ. Запрещает индексацию на уровне развертывания приложения.

  • annotationreference<annotation-type> — поле с ссылкой на аннотацию.

  • string — строка. Индексируется.

  • struct — типом поля может быть любая структура данных. Не поддерживает индексацию.

  • tensor — поле типа тензор. Индексируется.

  • uri — поле для сопоставления с URL. Поддерживает индексацию с разбором адреса на составляющие.

  • weightedset<element-type> — поле, где каждому значению добавляется вес. Индексируется.

Отдельно стоит обсудить свойство indexing, которое задает тип индексирования. Всего на выбор три варианта:

  • index — для неструктурированного текста. Он создает текстовый индекс и сохраняет в него разобранную строку — токены. Это позволяет осуществлять поиск по токенам. По умолчанию имя индекса совпадает с именем поля.

  • attribute — для структурированных данных. Делает поле доступным для сортировки, группировки и ранжирования. Позволяет осуществлять поиск по полному совпадению.

  • summary — добавляет поле в сводку документов (см. ниже).

  • set_language — возможность задать язык для строкового анализатора. По умолчанию для токенизации используется OpenNLP, который поддерживает несколько языков: английский, немецкий, французский, испанский и итальянский. Однако есть возможность использовать Lucene Linguistics. Более подробно обсудим во второй статье, посвященной поиску.

Самое интересное, что эти типы можно объединить с помощью символа |. Если задать сразу все: summary | index | attribute, то будет использован тип index.

Сводка документа

Поговорим немного о сводке документов. По сути, это просто информация о том, какие документы в каком виде должны быть представлены в результате поиска. По умолчанию доступна сводка default, которую можно включить при помощи параметра HTTP-запроса presentation.summary. В ней будут отображаться все поля, у которых в типах индексирования присутствует summary. Но также возможно добавить описание собственных сводок в схеме данных:

src/main/application/schemas/sneakers.sd

# Сводка документов, для использования нужно добавить к запросу - "presentation.summary": "demo-summary" document-summary demo-summary {     # Переименовывает поле pavement в pavement_demo_rename     summary pavement_demo_rename {         source: pavement     }     from-disk }

Таким образом, можно, например, изменить результирующую информацию поиска кроссовок:

POST /search/ HTTP/1.1 Host: localhost:8080 Content-Type: application/json Content-Length: 97  {     "yql": "select * from sneakers where true",     "presentation.summary": "demo-summary" }

В ответе будут отображены только поле с псевдонимом pavement_demo_rename, остальные поля будут скрыты:

{     "root": {         "id": "toplevel",         "relevance": 1.0,         "fields": {             "totalCount": 1         },         "coverage": {             "coverage": 100,             "documents": 1,             "full": true,             "nodes": 3,             "results": 1,             "resultsFull": 1         },         "children": [             {                 "id": "index:product/2/c4ca42387a14dc6e295d3d9d",                 "relevance": 0.0,                 "source": "product",                 "fields": {                     "sddocname": "sneakers",                     "pavement_demo_rename": "ASPHALT"                 }             }         ]     } }

Валидация настроек при развертывании

Допустим, мы описали конфигурацию сервера и схемы данных продуктов. Затем мы добавили несколько продуктов, и в одной из схем нам потребовалось изменить тип поля:

src/main/application/schemas/boots.sd

Было:      field moisture type bool {     indexing: summary | attribute   }  Стало:      field moisture type string {     indexing: summary | index   }

В таком случае Vespa не сможет корректно применить настройки, так как мы изменили не только тип элемента, но и тип индексации. Vespa не знает, как обработать уже сохраненные и проиндексированные элементы. Это может повлиять на целостность данных, по этой причине по умолчанию Vespa выдаст ошибку:

Error: invalid application package (400 Bad Request)
Invalid application:
indexing-change:
Document type ‘boots’:
Field ‘moisture’ changed:
add index aspect, matching:
‘word’ -> ‘text’, stemming:
‘none’ -> ‘best’, normalizing:
‘LOWERCASE’ -> ‘ACCENT’, summary field ‘moisture’ transform:
‘attribute’ -> ‘none’

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

src/main/application/validation-overrides.xml

<validation-overrides>     <!-- Изменение типа индексирования полей в схеме данных -->     <allow until="2024-03-07" comment="Для локальной работы">indexing-change</allow>     <!-- Изменение режима индексирования (services.xml) -->     <allow until="2024-03-07" comment="Для локальной работы">indexing-mode-change</allow>     <!-- Изменение типа данных в схеме данных -->     <allow until="2024-03-07" comment="Для локальной работы">field-type-change</allow>     <!-- Изменение типа тензора -->     <allow until="2024-03-07" comment="Для локальной работы">tensor-type-change</allow>     <!-- Значительное уменьшение (>50%) ресурсов узла -->     <allow until="2024-03-07" comment="Для локальной работы">resources-reduction</allow>     <!-- Удаление кластера данных или изменение его идентификатора (services.xml) -->     <allow until="2024-03-07" comment="Для локальной работы">content-cluster-removal</allow>     <!-- Изменение глобального атрибута в кластере данных -->     <allow until="2024-03-07" comment="Для локальной работы">global-document-change</allow>     <!-- Изменение глобальной точки входа -->     <allow until="2024-03-07" comment="Для локальной работы">global-endpoint-change</allow>     <!-- Увеличение избыточности данных -->     <allow until="2024-03-07" comment="Для локальной работы">redundancy-increase</allow>     <!-- Избыточность, равная одному, недопустима -->     <allow until="2024-03-07" comment="Для локальной работы">redundancy-one</allow>     <!-- Удаление сертификата -->     <allow until="2024-03-07" comment="Для локальной работы">certificate-removal</allow> </validation-overrides>

Атрибут allow.until обозначает последний день, когда действует данное правило. Максимальный срок действия правила составляет 30 дней.

Заключение

На этом настройка и сборка самого простого сервера с Vespa завершены. С текущими настройками мы можем использовать Vespa для хранения и чтения документов.

В следующей статье мы обсудим:

  • Чем отличаются и какие задачи выполняют DocumentProcessor и QueryProcessor.

  • Как работает токенизатор текста.

  • Как сделать ранжирование, группировку и поиск по заданным условиям.

  • Сравним скорость поиска по полям-атрибутам с быстрым поиском и без.

  • И многое другое.


ссылка на оригинал статьи https://habr.com/ru/articles/827460/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *